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**
|
**LISTING 1.1 L1-1.C**
|
||||||
|
|
||||||
/*
|
```c
|
||||||
* Program to calculate the 16-bit checksum of all bytes in the
|
/*
|
||||||
* specified file. Obtains the bytes one at a time via read(),
|
* Program to calculate the 16-bit checksum of all bytes in the
|
||||||
* letting DOS perform all data buffering.
|
* specified file. Obtains the bytes one at a time via read(),
|
||||||
*/
|
* letting DOS perform all data buffering.
|
||||||
#include <stdio.h>
|
*/
|
||||||
#include <fcntl.h>
|
#include <stdio.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
main(int argc, char *argv[]) {
|
main(int argc, char *argv[]) {
|
||||||
int Handle;
|
int Handle;
|
||||||
unsigned char Byte;
|
unsigned char Byte;
|
||||||
unsigned int Checksum;
|
unsigned int Checksum;
|
||||||
int ReadLength;
|
int ReadLength;
|
||||||
|
|
||||||
if ( argc != 2 ) {
|
if ( argc != 2 ) {
|
||||||
printf("usage: checksum filename\n");
|
printf("usage: checksum filename\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||||
printf("Can't open file: %s\n", argv[1]);
|
printf("Can't open file: %s\n", argv[1]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize the checksum accumulator */
|
/* Initialize the checksum accumulator */
|
||||||
Checksum = 0;
|
Checksum = 0;
|
||||||
|
|
||||||
/* Add each byte in turn into the checksum accumulator */
|
/* Add each byte in turn into the checksum accumulator */
|
||||||
while ( (ReadLength = read(Handle, &Byte, sizeof(Byte))) > 0 ) {
|
while ( (ReadLength = read(Handle, &Byte, sizeof(Byte))) > 0 ) {
|
||||||
Checksum += (unsigned int) Byte;
|
Checksum += (unsigned int) Byte;
|
||||||
}
|
}
|
||||||
if ( ReadLength == -1 ) {
|
if ( ReadLength == -1 ) {
|
||||||
printf("Error reading file %s\n", argv[1]);
|
printf("Error reading file %s\n", argv[1]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Report the result */
|
/* Report the result */
|
||||||
printf("The checksum is: %u\n", Checksum);
|
printf("The checksum is: %u\n", Checksum);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Table 1.1 shows the time taken for Listing 1.1 to generate a checksum of
|
Table 1.1 shows the time taken for Listing 1.1 to generate a checksum of
|
||||||
the WordPerfect version 4.2 thesaurus file, TH.WP (362,293 bytes in
|
the WordPerfect version 4.2 thesaurus file, TH.WP (362,293 bytes in
|
||||||
|
|
|
||||||
188
01-03.md
188
01-03.md
|
|
@ -30,105 +30,109 @@ disk caching turned off.
|
||||||
|
|
||||||
**LISTING 1.2 L1-2.C**
|
**LISTING 1.2 L1-2.C**
|
||||||
|
|
||||||
/*
|
```c
|
||||||
* Program to calculate the 16-bit checksum of the stream of bytes
|
/*
|
||||||
* from the specified file. Obtains the bytes one at a time in
|
* Program to calculate the 16-bit checksum of the stream of bytes
|
||||||
* assembler, via direct calls to DOS.
|
* from the specified file. Obtains the bytes one at a time in
|
||||||
*/
|
* assembler, via direct calls to DOS.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
main(int argc, char *argv[]) {
|
main(int argc, char *argv[]) {
|
||||||
int Handle;
|
int Handle;
|
||||||
unsigned char Byte;
|
unsigned char Byte;
|
||||||
unsigned int Checksum;
|
unsigned int Checksum;
|
||||||
int ReadLength;
|
int ReadLength;
|
||||||
|
|
||||||
if ( argc != 2 ) {
|
if ( argc != 2 ) {
|
||||||
printf("usage: checksum filename\n");
|
printf("usage: checksum filename\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||||
printf("Can't open file: %s\n", argv[1]);
|
printf("Can't open file: %s\n", argv[1]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if ( !ChecksumFile(Handle, &Checksum) ) {
|
if ( !ChecksumFile(Handle, &Checksum) ) {
|
||||||
printf("Error reading file %s\n", argv[1]);
|
printf("Error reading file %s\n", argv[1]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Report the result */
|
/* Report the result */
|
||||||
printf("The checksum is: %u\n", Checksum);
|
printf("The checksum is: %u\n", Checksum);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
**LISTING 1.3 L1-3.ASM**
|
**LISTING 1.3 L1-3.ASM**
|
||||||
|
|
||||||
; Assembler subroutine to perform a 16-bit checksum on the file
|
```nasm
|
||||||
; opened on the passed-in handle. Stores the result in the
|
; Assembler subroutine to perform a 16-bit checksum on the file
|
||||||
; passed-in checksum variable. Returns 1 for success, 0 for error.
|
; opened on the passed-in handle. Stores the result in the
|
||||||
;
|
; passed-in checksum variable. Returns 1 for success, 0 for error.
|
||||||
; Call as:
|
;
|
||||||
; int ChecksumFile(unsigned int Handle, unsigned int *Checksum);
|
; Call as:
|
||||||
;
|
; int ChecksumFile(unsigned int Handle, unsigned int *Checksum);
|
||||||
; where:
|
;
|
||||||
; Handle = handle # under which file to checksum is open
|
; where:
|
||||||
; Checksum = pointer to unsigned int variable checksum is
|
; Handle = handle # under which file to checksum is open
|
||||||
; to be stored in
|
; Checksum = pointer to unsigned int variable checksum is
|
||||||
;
|
; to be stored in
|
||||||
; Parameter structure:
|
;
|
||||||
;
|
; Parameter structure:
|
||||||
Parms struc
|
;
|
||||||
dw ? ;pushed BP
|
Parms struc
|
||||||
dw ? ;return address
|
dw ? ;pushed BP
|
||||||
Handle dw ?
|
dw ? ;return address
|
||||||
Checksum dw ?
|
Handle dw ?
|
||||||
Parms ends
|
Checksum dw ?
|
||||||
;
|
Parms ends
|
||||||
.model small
|
;
|
||||||
.data
|
.model small
|
||||||
TempWord label word
|
.data
|
||||||
TempByte db ? ;each byte read by DOS will be stored here
|
TempWord label word
|
||||||
db 0 ;high byte of TempWord is always 0
|
TempByte db ? ;each byte read by DOS will be stored here
|
||||||
;for 16-bit adds
|
db 0 ;high byte of TempWord is always 0
|
||||||
;
|
;for 16-bit adds
|
||||||
.code
|
;
|
||||||
public _ChecksumFile
|
.code
|
||||||
_ChecksumFile proc near
|
public _ChecksumFile
|
||||||
push bp
|
_ChecksumFile proc near
|
||||||
mov bp,sp
|
push bp
|
||||||
push si ;save C's register variable
|
mov bp,sp
|
||||||
;
|
push si ;save C's register variable
|
||||||
mov bx,[bp+Handle] ;get file handle
|
;
|
||||||
sub si,si ;zero the checksum ;accumulator
|
mov bx,[bp+Handle] ;get file handle
|
||||||
mov cx,1 ;request one byte on each ;read
|
sub si,si ;zero the checksum ;accumulator
|
||||||
mov dx,offset TempByte ;point DX to the byte in
|
mov cx,1 ;request one byte on each ;read
|
||||||
;which DOS should store
|
mov dx,offset TempByte ;point DX to the byte in
|
||||||
;each byte read
|
;which DOS should store
|
||||||
ChecksumLoop:
|
;each byte read
|
||||||
mov ah,3fh ;DOS read file function #
|
ChecksumLoop:
|
||||||
int 21h ;read the byte
|
mov ah,3fh ;DOS read file function #
|
||||||
jcErrorEnd;an error occurred
|
int 21h ;read the byte
|
||||||
and ax,ax ;any bytes read?
|
jcErrorEnd;an error occurred
|
||||||
jz Success ;no-end of file reached-we're done
|
and ax,ax ;any bytes read?
|
||||||
add si,[TempWord] ;add the byte into the
|
jz Success ;no-end of file reached-we're done
|
||||||
;checksum total
|
add si,[TempWord] ;add the byte into the
|
||||||
jmpChecksumLoop
|
;checksum total
|
||||||
ErrorEnd:
|
jmpChecksumLoop
|
||||||
sub ax,ax ;error
|
ErrorEnd:
|
||||||
jmp short Done
|
sub ax,ax ;error
|
||||||
Success:
|
jmp short Done
|
||||||
mov bx,[bp+Checksum] ;point to the checksum variable
|
Success:
|
||||||
mov [bx],si ;save the new checksum
|
mov bx,[bp+Checksum] ;point to the checksum variable
|
||||||
mov ax,1 ;success
|
mov [bx],si ;save the new checksum
|
||||||
;
|
mov ax,1 ;success
|
||||||
Done:
|
;
|
||||||
pop si ;restore C's register variable
|
Done:
|
||||||
pop bp
|
pop si ;restore C's register variable
|
||||||
ret
|
pop bp
|
||||||
_ChecksumFileendp
|
ret
|
||||||
end
|
_ChecksumFileendp
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
The lesson is clear: Optimization makes code faster, but without proper
|
The lesson is clear: Optimization makes code faster, but without proper
|
||||||
design, optimization just creates fast slow code.
|
design, optimization just creates fast slow code.
|
||||||
|
|
|
||||||
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**
|
**LISTING 1.4 L1-4.C**
|
||||||
|
|
||||||
/*
|
```c
|
||||||
* Program to calculate the 16-bit checksum of the stream of bytes
|
/*
|
||||||
* from the specified file. Obtains the bytes one at a time via
|
* Program to calculate the 16-bit checksum of the stream of bytes
|
||||||
* getc(), allowing C to perform data buffering.
|
* from the specified file. Obtains the bytes one at a time via
|
||||||
*/
|
* getc(), allowing C to perform data buffering.
|
||||||
#include <stdio.h>
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
main(int argc, char *argv[]) {
|
main(int argc, char *argv[]) {
|
||||||
FILE *CheckFile;
|
FILE *CheckFile;
|
||||||
int Byte;
|
int Byte;
|
||||||
unsigned int Checksum;
|
unsigned int Checksum;
|
||||||
|
|
||||||
if ( argc != 2 ) {
|
if ( argc != 2 ) {
|
||||||
printf("usage: checksum filename\n");
|
printf("usage: checksum filename\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if ( (CheckFile = fopen(argv[1], "rb")) == NULL ) {
|
if ( (CheckFile = fopen(argv[1], "rb")) == NULL ) {
|
||||||
printf("Can't open file: %s\n", argv[1]);
|
printf("Can't open file: %s\n", argv[1]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize the checksum accumulator */
|
/* Initialize the checksum accumulator */
|
||||||
Checksum = 0;
|
Checksum = 0;
|
||||||
|
|
||||||
/* Add each byte in turn into the checksum accumulator */
|
/* Add each byte in turn into the checksum accumulator */
|
||||||
while ( (Byte = getc(CheckFile)) != EOF ) {
|
while ( (Byte = getc(CheckFile)) != EOF ) {
|
||||||
Checksum += (unsigned int) Byte;
|
Checksum += (unsigned int) Byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Report the result */
|
/* Report the result */
|
||||||
printf("The checksum is: %u\n", Checksum);
|
printf("The checksum is: %u\n", Checksum);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Know When It Matters {#Heading10}
|
#### Know When It Matters {#Heading10}
|
||||||
|
|
||||||
|
|
|
||||||
194
01-05.md
194
01-05.md
|
|
@ -30,62 +30,64 @@ uses no assembly at all.
|
||||||
|
|
||||||
**LISTING 1.5 L1-5.C**
|
**LISTING 1.5 L1-5.C**
|
||||||
|
|
||||||
/*
|
```c
|
||||||
* Program to calculate the 16-bit checksum of the stream of bytes
|
/*
|
||||||
* from the specified file. Buffers the bytes internally, rather
|
* Program to calculate the 16-bit checksum of the stream of bytes
|
||||||
* than letting C or DOS do the work.
|
* from the specified file. Buffers the bytes internally, rather
|
||||||
*/
|
* than letting C or DOS do the work.
|
||||||
#include <stdio.h>
|
*/
|
||||||
#include <fcntl.h>
|
#include <stdio.h>
|
||||||
#include <alloc.h> /* alloc.h for Borland,
|
#include <fcntl.h>
|
||||||
malloc.h for Microsoft */
|
#include <alloc.h> /* alloc.h for Borland,
|
||||||
|
malloc.h for Microsoft */
|
||||||
|
|
||||||
#define BUFFER_SIZE 0x8000 /* 32Kb data buffer */
|
#define BUFFER_SIZE 0x8000 /* 32Kb data buffer */
|
||||||
|
|
||||||
main(int argc, char *argv[]) {
|
main(int argc, char *argv[]) {
|
||||||
int Handle;
|
int Handle;
|
||||||
unsigned int Checksum;
|
unsigned int Checksum;
|
||||||
unsigned char *WorkingBuffer, *WorkingPtr;
|
unsigned char *WorkingBuffer, *WorkingPtr;
|
||||||
int WorkingLength, LengthCount;
|
int WorkingLength, LengthCount;
|
||||||
|
|
||||||
if ( argc != 2 ) {
|
if ( argc != 2 ) {
|
||||||
printf("usage: checksum filename\n");
|
printf("usage: checksum filename\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||||
printf("Can't open file: %s\n", argv[1]);
|
printf("Can't open file: %s\n", argv[1]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get memory in which to buffer the data */
|
/* Get memory in which to buffer the data */
|
||||||
if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
|
if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
|
||||||
printf("Can't get enough memory\n");
|
printf("Can't get enough memory\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize the checksum accumulator */
|
/* Initialize the checksum accumulator */
|
||||||
Checksum = 0;
|
Checksum = 0;
|
||||||
|
|
||||||
/* Process the file in BUFFER_SIZE chunks */
|
/* Process the file in BUFFER_SIZE chunks */
|
||||||
do {
|
do {
|
||||||
if ( (WorkingLength = read(Handle, WorkingBuffer,
|
if ( (WorkingLength = read(Handle, WorkingBuffer,
|
||||||
BUFFER_SIZE)) == -1 ) {
|
BUFFER_SIZE)) == -1 ) {
|
||||||
printf("Error reading file %s\n", argv[1]);
|
printf("Error reading file %s\n", argv[1]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
/* Checksum this chunk */
|
/* Checksum this chunk */
|
||||||
WorkingPtr = WorkingBuffer;
|
WorkingPtr = WorkingBuffer;
|
||||||
LengthCount = WorkingLength;
|
LengthCount = WorkingLength;
|
||||||
while ( LengthCount-- ) {
|
while ( LengthCount-- ) {
|
||||||
/* Add each byte in turn into the checksum accumulator */
|
/* Add each byte in turn into the checksum accumulator */
|
||||||
Checksum += (unsigned int) *WorkingPtr++;
|
Checksum += (unsigned int) *WorkingPtr++;
|
||||||
}
|
}
|
||||||
} while ( WorkingLength );
|
} while ( WorkingLength );
|
||||||
|
|
||||||
/* Report the result */
|
/* Report the result */
|
||||||
printf("The checksum is: %u\n", Checksum);
|
printf("The checksum is: %u\n", Checksum);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
That brings us to the fourth reason: avoiding an internal-buffered
|
That brings us to the fourth reason: avoiding an internal-buffered
|
||||||
implementation like Listing 1.5 because of the difficulty of coding such
|
implementation like Listing 1.5 because of the difficulty of coding such
|
||||||
|
|
@ -122,56 +124,58 @@ the design has been maxed out.
|
||||||
|
|
||||||
**LISTING 1.6 L1-6.C**
|
**LISTING 1.6 L1-6.C**
|
||||||
|
|
||||||
/*
|
```c
|
||||||
* Program to calculate the 16-bit checksum of the stream of bytes
|
/*
|
||||||
* from the specified file. Buffers the bytes internally, rather
|
* Program to calculate the 16-bit checksum of the stream of bytes
|
||||||
* than letting C or DOS do the work, with the time-critical
|
* from the specified file. Buffers the bytes internally, rather
|
||||||
* portion of the code written in optimized assembler.
|
* than letting C or DOS do the work, with the time-critical
|
||||||
*/
|
* portion of the code written in optimized assembler.
|
||||||
#include <stdio.h>
|
*/
|
||||||
#include <fcntl.h>
|
#include <stdio.h>
|
||||||
#include <alloc.h> /* alloc.h for Borland,
|
#include <fcntl.h>
|
||||||
malloc.h for Microsoft */
|
#include <alloc.h> /* alloc.h for Borland,
|
||||||
|
malloc.h for Microsoft */
|
||||||
|
|
||||||
#define BUFFER_SIZE 0x8000 /* 32K data buffer */
|
#define BUFFER_SIZE 0x8000 /* 32K data buffer */
|
||||||
|
|
||||||
main(int argc, char *argv[]) {
|
main(int argc, char *argv[]) {
|
||||||
int Handle;
|
int Handle;
|
||||||
unsigned int Checksum;
|
unsigned int Checksum;
|
||||||
unsigned char *WorkingBuffer;
|
unsigned char *WorkingBuffer;
|
||||||
int WorkingLength;
|
int WorkingLength;
|
||||||
|
|
||||||
if ( argc != 2 ) {
|
if ( argc != 2 ) {
|
||||||
printf("usage: checksum filename\n");
|
printf("usage: checksum filename\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) {
|
||||||
printf("Can't open file: %s\n", argv[1]);
|
printf("Can't open file: %s\n", argv[1]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get memory in which to buffer the data */
|
/* Get memory in which to buffer the data */
|
||||||
if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
|
if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) {
|
||||||
printf("Can't get enough memory\n");
|
printf("Can't get enough memory\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize the checksum accumulator */
|
/* Initialize the checksum accumulator */
|
||||||
Checksum = 0;
|
Checksum = 0;
|
||||||
|
|
||||||
/* Process the file in 32K chunks */
|
/* Process the file in 32K chunks */
|
||||||
do {
|
do {
|
||||||
if ( (WorkingLength = read(Handle, WorkingBuffer,
|
if ( (WorkingLength = read(Handle, WorkingBuffer,
|
||||||
BUFFER_SIZE)) == -1 ) {
|
BUFFER_SIZE)) == -1 ) {
|
||||||
printf("Error reading file %s\n", argv[1]);
|
printf("Error reading file %s\n", argv[1]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
/* Checksum this chunk if there's anything in it */
|
/* Checksum this chunk if there's anything in it */
|
||||||
if ( WorkingLength )
|
if ( WorkingLength )
|
||||||
ChecksumChunk(WorkingBuffer, WorkingLength, &Checksum);
|
ChecksumChunk(WorkingBuffer, WorkingLength, &Checksum);
|
||||||
} while ( WorkingLength );
|
} while ( WorkingLength );
|
||||||
|
|
||||||
/* Report the result */
|
/* Report the result */
|
||||||
printf("The checksum is: %u\n", Checksum);
|
printf("The checksum is: %u\n", Checksum);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
|
||||||
100
01-06.md
100
01-06.md
|
|
@ -12,55 +12,57 @@ pages: 018-019
|
||||||
|
|
||||||
**LISTING 1.7 L1-7.ASM**
|
**LISTING 1.7 L1-7.ASM**
|
||||||
|
|
||||||
; Assembler subroutine to perform a 16-bit checksum on a block of
|
```nasm
|
||||||
; bytes 1 to 64K in size. Adds checksum for block into passed-in
|
; Assembler subroutine to perform a 16-bit checksum on a block of
|
||||||
; checksum.
|
; bytes 1 to 64K in size. Adds checksum for block into passed-in
|
||||||
;
|
; checksum.
|
||||||
; Call as:
|
;
|
||||||
; void ChecksumChunk(unsigned char *Buffer,
|
; Call as:
|
||||||
; unsigned int BufferLength, unsigned int *Checksum);
|
; void ChecksumChunk(unsigned char *Buffer,
|
||||||
;
|
; unsigned int BufferLength, unsigned int *Checksum);
|
||||||
; where:
|
;
|
||||||
; Buffer = pointer to start of block of bytes to checksum
|
; where:
|
||||||
; BufferLength = # of bytes to checksum (0 means 64K, not 0)
|
; Buffer = pointer to start of block of bytes to checksum
|
||||||
; Checksum = pointer to unsigned int variable checksum is
|
; BufferLength = # of bytes to checksum (0 means 64K, not 0)
|
||||||
;stored in
|
; Checksum = pointer to unsigned int variable checksum is
|
||||||
;
|
;stored in
|
||||||
; Parameter structure:
|
;
|
||||||
;
|
; Parameter structure:
|
||||||
Parms struc
|
;
|
||||||
dw ? ;pushed BP
|
Parms struc
|
||||||
dw ? ;return address
|
dw ? ;pushed BP
|
||||||
Buffer dw ?
|
dw ? ;return address
|
||||||
BufferLength dw ?
|
Buffer dw ?
|
||||||
Checksum dw ?
|
BufferLength dw ?
|
||||||
Parmsends
|
Checksum dw ?
|
||||||
;
|
Parmsends
|
||||||
.model small
|
;
|
||||||
.code
|
.model small
|
||||||
public _ChecksumChunk
|
.code
|
||||||
_ChecksumChunkprocnear
|
public _ChecksumChunk
|
||||||
push bp
|
_ChecksumChunkprocnear
|
||||||
mov bp,sp
|
push bp
|
||||||
push si ;save C's register variable
|
mov bp,sp
|
||||||
;
|
push si ;save C's register variable
|
||||||
cld ;make LODSB increment SI
|
;
|
||||||
mov si,[bp+Buffer] ;point to buffer
|
cld ;make LODSB increment SI
|
||||||
mov cx,[bp+BufferLength] ;get buffer length
|
mov si,[bp+Buffer] ;point to buffer
|
||||||
mov bx,[bp+Checksum] ;point to checksum variable
|
mov cx,[bp+BufferLength] ;get buffer length
|
||||||
mov dx,[bx] ;get the current checksum
|
mov bx,[bp+Checksum] ;point to checksum variable
|
||||||
sub ah,ah ;so AX will be a 16-bit value after LODSB
|
mov dx,[bx] ;get the current checksum
|
||||||
ChecksumLoop:
|
sub ah,ah ;so AX will be a 16-bit value after LODSB
|
||||||
lodsb ;get the next byte
|
ChecksumLoop:
|
||||||
add dx,ax ;add it into the checksum total
|
lodsb ;get the next byte
|
||||||
loop ChecksumLoop ;continue for all bytes in block
|
add dx,ax ;add it into the checksum total
|
||||||
mov [bx],dx ;save the new checksum
|
loop ChecksumLoop ;continue for all bytes in block
|
||||||
;
|
mov [bx],dx ;save the new checksum
|
||||||
pop si ;restore C's register variable
|
;
|
||||||
pop bp
|
pop si ;restore C's register variable
|
||||||
ret
|
pop bp
|
||||||
_ChecksumChunkendp
|
ret
|
||||||
end
|
_ChecksumChunkendp
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
Note that in Table 1.1, optimization makes little difference except in
|
Note that in Table 1.1, optimization makes little difference except in
|
||||||
the case of Listing 1.5, where the design has been refined considerably.
|
the case of Listing 1.5, where the design has been refined considerably.
|
||||||
|
|
|
||||||
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
|
a cycle there, until the code truly seemed to be optimized. When I was
|
||||||
done, the key part of the code looked something like this:
|
done, the key part of the code looked something like this:
|
||||||
|
|
||||||
LoopTop:
|
```nasm
|
||||||
lodsb ;get the next byte to extract a bit from
|
LoopTop:
|
||||||
and al,ah ;isolate the bit we want
|
lodsb ;get the next byte to extract a bit from
|
||||||
rol al,cl ;rotate the bit into the desired position
|
and al,ah ;isolate the bit we want
|
||||||
or bl,al ;insert the bit into the final nibble
|
rol al,cl ;rotate the bit into the desired position
|
||||||
dec cx ;the next bit goes 1 place to the right
|
or bl,al ;insert the bit into the final nibble
|
||||||
dec dx ;count down the number of bits
|
dec cx ;the next bit goes 1 place to the right
|
||||||
jnz LoopTop ;process the next bit, if any
|
dec dx ;count down the number of bits
|
||||||
|
jnz LoopTop ;process the next bit, if any
|
||||||
|
```
|
||||||
|
|
||||||
Now, it's hard to write code that's much faster than seven instructions,
|
Now, it's hard to write code that's much faster than seven instructions,
|
||||||
only one of which accesses memory, and most programmers would have
|
only one of which accesses memory, and most programmers would have
|
||||||
|
|
@ -68,15 +70,17 @@ total of four separate time-consuming multibit rotations!
|
||||||
|
|
||||||
I changed the code to the following:
|
I changed the code to the following:
|
||||||
|
|
||||||
LoopTop:
|
```nasm
|
||||||
lodsb ;get the next byte to extract a bit from
|
LoopTop:
|
||||||
and al,ah ;isolate the bit we want
|
lodsb ;get the next byte to extract a bit from
|
||||||
or bl,al ;insert the bit into the final nibble
|
and al,ah ;isolate the bit we want
|
||||||
rol bl,1 ;make room for the next bit
|
or bl,al ;insert the bit into the final nibble
|
||||||
dec dx ;count down the number of bits
|
rol bl,1 ;make room for the next bit
|
||||||
jnz LoopTop ;process the next bit, if any
|
dec dx ;count down the number of bits
|
||||||
rol bl,cl ;rotate all four bits into their final
|
jnz LoopTop ;process the next bit, if any
|
||||||
; positions at the same time
|
rol bl,cl ;rotate all four bits into their final
|
||||||
|
; positions at the same time
|
||||||
|
```
|
||||||
|
|
||||||
This moved the costly multibit rotation out of the loop so that it was
|
This moved the costly multibit rotation out of the loop so that it was
|
||||||
performed just once, rather than four times. While the code may not look
|
performed just once, rather than four times. While the code may not look
|
||||||
|
|
|
||||||
860
03-02.md
860
03-02.md
|
|
@ -12,442 +12,444 @@ pages: 035-042
|
||||||
|
|
||||||
**LISTING 3.1 PZTIMER.ASM**
|
**LISTING 3.1 PZTIMER.ASM**
|
||||||
|
|
||||||
; The precision Zen timer (PZTIMER.ASM)
|
```nasm
|
||||||
;
|
; The precision Zen timer (PZTIMER.ASM)
|
||||||
; Uses the 8253 timer to time the performance of code that takes
|
;
|
||||||
; less than about 54 milliseconds to execute, with a resolution
|
; Uses the 8253 timer to time the performance of code that takes
|
||||||
; of better than 10 microseconds.
|
; less than about 54 milliseconds to execute, with a resolution
|
||||||
;
|
; of better than 10 microseconds.
|
||||||
; By Michael Abrash
|
;
|
||||||
;
|
; By Michael Abrash
|
||||||
; Externally callable routines:
|
;
|
||||||
;
|
; Externally callable routines:
|
||||||
; ZTimerOn: Starts the Zen timer, with interrupts disabled.
|
;
|
||||||
;
|
; ZTimerOn: Starts the Zen timer, with interrupts disabled.
|
||||||
; ZTimerOff: Stops the Zen timer, saves the timer count,
|
;
|
||||||
; times the overhead code, and restores interrupts to the
|
; ZTimerOff: Stops the Zen timer, saves the timer count,
|
||||||
; state they were in when ZTimerOn was called.
|
; times the overhead code, and restores interrupts to the
|
||||||
;
|
; state they were in when ZTimerOn was called.
|
||||||
; ZTimerReport: Prints the net time that passed between starting
|
;
|
||||||
; and stopping the timer.
|
; ZTimerReport: Prints the net time that passed between starting
|
||||||
;
|
; and stopping the timer.
|
||||||
; Note: If longer than about 54 ms passes between ZTimerOn and
|
;
|
||||||
; ZTimerOff calls, the timer turns over and the count is
|
; Note: If longer than about 54 ms passes between ZTimerOn and
|
||||||
; inaccurate. When this happens, an error message is displayed
|
; ZTimerOff calls, the timer turns over and the count is
|
||||||
; instead of a count. The long-period Zen timer should be used
|
; inaccurate. When this happens, an error message is displayed
|
||||||
; in such cases.
|
; instead of a count. The long-period Zen timer should be used
|
||||||
;
|
; in such cases.
|
||||||
; Note: Interrupts *MUST* be left off between calls to ZTimerOn
|
;
|
||||||
; and ZTimerOff for accurate timing and for detection of
|
; Note: Interrupts *MUST* be left off between calls to ZTimerOn
|
||||||
; timer overflow.
|
; and ZTimerOff for accurate timing and for detection of
|
||||||
;
|
; timer overflow.
|
||||||
; Note: These routines can introduce slight inaccuracies into the
|
;
|
||||||
; system clock count for each code section timed even if
|
; Note: These routines can introduce slight inaccuracies into the
|
||||||
; timer 0 doesn't overflow. If timer 0 does overflow, the
|
; system clock count for each code section timed even if
|
||||||
; system clock can become slow by virtually any amount of
|
; timer 0 doesn't overflow. If timer 0 does overflow, the
|
||||||
; time, since the system clock can't advance while the
|
; system clock can become slow by virtually any amount of
|
||||||
; precison timer is timing. Consequently, it's a good idea
|
; time, since the system clock can't advance while the
|
||||||
; to reboot at the end of each timing session. (The
|
; precison timer is timing. Consequently, it's a good idea
|
||||||
; battery-backed clock, if any, is not affected by the Zen
|
; to reboot at the end of each timing session. (The
|
||||||
; timer.)
|
; battery-backed clock, if any, is not affected by the Zen
|
||||||
;
|
; timer.)
|
||||||
; All registers, and all flags except the interrupt flag, are
|
;
|
||||||
; preserved by all routines. Interrupts are enabled and then disabled
|
; All registers, and all flags except the interrupt flag, are
|
||||||
; by ZTimerOn, and are restored by ZTimerOff to the state they were
|
; preserved by all routines. Interrupts are enabled and then disabled
|
||||||
; in when ZTimerOn was called.
|
; by ZTimerOn, and are restored by ZTimerOff to the state they were
|
||||||
;
|
; in when ZTimerOn was called.
|
||||||
|
;
|
||||||
|
|
||||||
Code segment word public ‘CODE'
|
Code segment word public ‘CODE'
|
||||||
assumecs: Code, ds:nothing
|
assumecs: Code, ds:nothing
|
||||||
public ZTimerOn, ZTimerOff, ZTimerReport
|
public ZTimerOn, ZTimerOff, ZTimerReport
|
||||||
|
|
||||||
;
|
;
|
||||||
; Base address of the 8253 timer chip.
|
; Base address of the 8253 timer chip.
|
||||||
;
|
;
|
||||||
BASE_8253equ40h
|
BASE_8253equ40h
|
||||||
;
|
;
|
||||||
; The address of the timer 0 count registers in the 8253.
|
; The address of the timer 0 count registers in the 8253.
|
||||||
;
|
;
|
||||||
TIMER_0_8253 equBASE_8253 + 0
|
TIMER_0_8253 equBASE_8253 + 0
|
||||||
;
|
;
|
||||||
; The address of the mode register in the 8253.
|
; The address of the mode register in the 8253.
|
||||||
;
|
;
|
||||||
MODE_8253 equBASE_8253 + 3
|
MODE_8253 equBASE_8253 + 3
|
||||||
;
|
;
|
||||||
; The address of Operation Command Word 3 in the 8259 Programmable
|
; The address of Operation Command Word 3 in the 8259 Programmable
|
||||||
; Interrupt Controller (PIC) (write only, and writable only when
|
; Interrupt Controller (PIC) (write only, and writable only when
|
||||||
; bit 4 of the byte written to this address is 0 and bit 3 is 1).
|
; bit 4 of the byte written to this address is 0 and bit 3 is 1).
|
||||||
;
|
;
|
||||||
OCW3 equ20h
|
OCW3 equ20h
|
||||||
;
|
;
|
||||||
; The address of the Interrupt Request register in the 8259 PIC
|
; The address of the Interrupt Request register in the 8259 PIC
|
||||||
; (read only, and readable only when bit 1 of OCW3 = 1 and bit 0
|
; (read only, and readable only when bit 1 of OCW3 = 1 and bit 0
|
||||||
; of OCW3 = 0).
|
; of OCW3 = 0).
|
||||||
;
|
;
|
||||||
IRR equ20h
|
IRR equ20h
|
||||||
;
|
;
|
||||||
; Macro to emulate a POPF instruction in order to fix the bug in some
|
; Macro to emulate a POPF instruction in order to fix the bug in some
|
||||||
; 80286 chips which allows interrupts to occur during a POPF even when
|
; 80286 chips which allows interrupts to occur during a POPF even when
|
||||||
; interrupts remain disabled.
|
; interrupts remain disabled.
|
||||||
;
|
;
|
||||||
MPOPF macro
|
MPOPF macro
|
||||||
local p1, p2
|
local p1, p2
|
||||||
jmp short p2
|
jmp short p2
|
||||||
p1: iret ; jump to pushed address & pop flags
|
p1: iret ; jump to pushed address & pop flags
|
||||||
p2: push cs ; construct far return address to
|
p2: push cs ; construct far return address to
|
||||||
call p1 ; the next instruction
|
call p1 ; the next instruction
|
||||||
endm
|
endm
|
||||||
|
|
||||||
;
|
;
|
||||||
; Macro to delay briefly to ensure that enough time has elapsed
|
; Macro to delay briefly to ensure that enough time has elapsed
|
||||||
; between successive I/O accesses so that the device being accessed
|
; between successive I/O accesses so that the device being accessed
|
||||||
; can respond to both accesses even on a very fast PC.
|
; can respond to both accesses even on a very fast PC.
|
||||||
;
|
;
|
||||||
DELAY macro
|
DELAY macro
|
||||||
jmp $+2
|
jmp $+2
|
||||||
jmp $+2
|
jmp $+2
|
||||||
jmp $+2
|
jmp $+2
|
||||||
endm
|
endm
|
||||||
|
|
||||||
OriginalFlags db ? ; storage for upper byte of
|
OriginalFlags db ? ; storage for upper byte of
|
||||||
; FLAGS register when
|
; FLAGS register when
|
||||||
; ZTimerOn called
|
; ZTimerOn called
|
||||||
TimedCount dw ? ; timer 0 count when the timer
|
TimedCount dw ? ; timer 0 count when the timer
|
||||||
; is stopped
|
; is stopped
|
||||||
ReferenceCount dw ; number of counts required to
|
ReferenceCount dw ; number of counts required to
|
||||||
; execute timer overhead code
|
; execute timer overhead code
|
||||||
OverflowFlag db ? ; used to indicate whether the
|
OverflowFlag db ? ; used to indicate whether the
|
||||||
; timer overflowed during the
|
; timer overflowed during the
|
||||||
; timing interval
|
; timing interval
|
||||||
;
|
;
|
||||||
; String printed to report results.
|
; String printed to report results.
|
||||||
;
|
;
|
||||||
OutputStr label byte
|
OutputStr label byte
|
||||||
db 0dh, 0ah, ‘Timed count: ‘, 5 dup (?)
|
db 0dh, 0ah, ‘Timed count: ‘, 5 dup (?)
|
||||||
ASCIICountEnd labelbyte
|
ASCIICountEnd labelbyte
|
||||||
db ‘ microseconds', 0dh, 0ah
|
db ‘ microseconds', 0dh, 0ah
|
||||||
db ‘$'
|
db ‘$'
|
||||||
;
|
;
|
||||||
; String printed to report timer overflow.
|
; String printed to report timer overflow.
|
||||||
;
|
;
|
||||||
OverflowStr label byte
|
OverflowStr label byte
|
||||||
db 0dh, 0ah
|
db 0dh, 0ah
|
||||||
db ‘****************************************************'
|
db ‘****************************************************'
|
||||||
db 0dh, 0ah
|
db 0dh, 0ah
|
||||||
db ‘* The timer overflowed, so the interval timed was *'
|
db ‘* The timer overflowed, so the interval timed was *'
|
||||||
db 0dh, 0ah
|
db 0dh, 0ah
|
||||||
db ‘* too long for the precision timer to measure. *'
|
db ‘* too long for the precision timer to measure. *'
|
||||||
db 0dh, 0ah
|
db 0dh, 0ah
|
||||||
db ‘* Please perform the timing test again with the *'
|
db ‘* Please perform the timing test again with the *'
|
||||||
db0dh, 0ah
|
db0dh, 0ah
|
||||||
db ‘* long-period timer. *'
|
db ‘* long-period timer. *'
|
||||||
db 0dh, 0ah
|
db 0dh, 0ah
|
||||||
db ‘****************************************************'
|
db ‘****************************************************'
|
||||||
db 0dh, 0ah
|
db 0dh, 0ah
|
||||||
db ‘$'
|
db ‘$'
|
||||||
|
|
||||||
; ********************************************************************
|
; ********************************************************************
|
||||||
; * Routine called to start timing. *
|
; * Routine called to start timing. *
|
||||||
; ********************************************************************
|
; ********************************************************************
|
||||||
|
|
||||||
ZTimerOn proc near
|
ZTimerOn proc near
|
||||||
|
|
||||||
|
;
|
||||||
|
; Save the context of the program being timed.
|
||||||
|
;
|
||||||
|
push ax
|
||||||
|
pushf
|
||||||
|
pop ax ; get flags so we can keep
|
||||||
|
; interrupts off when leaving
|
||||||
|
; this routine
|
||||||
|
mov cs:[OriginalFlags],ah ; remember the state of the
|
||||||
|
; Interrupt flag
|
||||||
|
and ah,0fdh ; set pushed interrupt flag
|
||||||
|
; to 0
|
||||||
|
push ax
|
||||||
|
;
|
||||||
|
; Turn on interrupts, so the timer interrupt can occur if it's
|
||||||
|
; pending.
|
||||||
|
;
|
||||||
|
sti
|
||||||
|
;
|
||||||
|
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
|
||||||
|
; linear counting rather than count-by-two counting. Also
|
||||||
|
; leaves the 8253 waiting for the initial timer 0 count to
|
||||||
|
; be loaded.
|
||||||
|
;
|
||||||
|
mov al,00110100b ;mode 2
|
||||||
|
out MODE_8253,al
|
||||||
|
;
|
||||||
|
; Set the timer count to 0, so we know we won't get another
|
||||||
|
; timer interrupt right away.
|
||||||
|
; Note: this introduces an inaccuracy of up to 54 ms in the system
|
||||||
|
; clock count each time it is executed.
|
||||||
|
;
|
||||||
|
DELAY
|
||||||
|
sub al,al
|
||||||
|
out TIMER_0_8253,al ;lsb
|
||||||
|
DELAY
|
||||||
|
out TIMER_0_8253,al ;msb
|
||||||
|
;
|
||||||
|
; Wait before clearing interrupts to allow the interrupt generated
|
||||||
|
; when switching from mode 3 to mode 2 to be recognized. The delay
|
||||||
|
; must be at least 210 ns long to allow time for that interrupt to
|
||||||
|
; occur. Here, 10 jumps are used for the delay to ensure that the
|
||||||
|
; delay time will be more than long enough even on a very fast PC.
|
||||||
|
;
|
||||||
|
rept 10
|
||||||
|
jmp $+2
|
||||||
|
endm
|
||||||
|
;
|
||||||
|
; Disable interrupts to get an accurate count.
|
||||||
|
;
|
||||||
|
cli
|
||||||
|
;
|
||||||
|
; Set the timer count to 0 again to start the timing interval.
|
||||||
|
;
|
||||||
|
mov al,00110100b ; set up to load initial
|
||||||
|
out MODE_8253,al ; timer count
|
||||||
|
DELAY
|
||||||
|
sub al,al
|
||||||
|
out TIMER_0_8253,al ; load count lsb
|
||||||
|
DELAY
|
||||||
|
out TIMER_0_8253,al; load count msb
|
||||||
|
;
|
||||||
|
; Restore the context and return.
|
||||||
|
;
|
||||||
|
MPOPF ; keeps interrupts off
|
||||||
|
pop ax
|
||||||
|
ret
|
||||||
|
|
||||||
|
ZTimerOn endp
|
||||||
|
|
||||||
|
;********************************************************************
|
||||||
|
;* Routine called to stop timing and get count. *
|
||||||
|
;********************************************************************
|
||||||
|
|
||||||
|
ZTimerOff proc near
|
||||||
|
|
||||||
|
;
|
||||||
|
; Save the context of the program being timed.
|
||||||
|
;
|
||||||
|
push ax
|
||||||
|
push cx
|
||||||
|
pushf
|
||||||
|
;
|
||||||
|
; Latch the count.
|
||||||
|
;
|
||||||
|
mov al,00000000b ; latch timer 0
|
||||||
|
out MODE_8253,al
|
||||||
|
;
|
||||||
|
; See if the timer has overflowed by checking the 8259 for a pending
|
||||||
|
; timer interrupt.
|
||||||
|
;
|
||||||
|
mov al,00001010b ; OCW3, set up to read
|
||||||
|
out OCW3,al; Int errupt Request register
|
||||||
|
DELAY
|
||||||
|
ina l,IRR; read Interrupt Request
|
||||||
|
; register
|
||||||
|
and al,1 ; set AL to 1 if IRQ0 (the
|
||||||
|
; timer interrupt) is pending
|
||||||
|
mov cs:[OverflowFlag],al; store the timer overflow
|
||||||
|
; status
|
||||||
|
;
|
||||||
|
; Allow interrupts to happen again.
|
||||||
|
;
|
||||||
|
sti
|
||||||
|
;
|
||||||
|
; Read out the count we latched earlier.
|
||||||
|
;
|
||||||
|
in al,TIMER_0_8253 ; least significant byte
|
||||||
|
DELAY
|
||||||
|
mov ah,al
|
||||||
|
in al,TIMER_0_8253 ; most significant byte
|
||||||
|
xchg ah,al
|
||||||
|
neg ax ; convert from countdown
|
||||||
|
; remaining to elapsed
|
||||||
|
; count
|
||||||
|
mov cs:[TimedCount],ax
|
||||||
|
; Time a zero-length code fragment, to get a reference for how
|
||||||
|
; much overhead this routine has. Time it 16 times and average it,
|
||||||
|
; for accuracy, rounding the result.
|
||||||
|
;
|
||||||
|
mov cs:[ReferenceCount],0
|
||||||
|
mov cx,16
|
||||||
|
cli ; interrupts off to allow a
|
||||||
|
; precise reference count
|
||||||
|
RefLoop:
|
||||||
|
call ReferenceZTimerOn
|
||||||
|
call ReferenceZTimerOff
|
||||||
|
loop RefLoop
|
||||||
|
sti
|
||||||
|
add cs:[ReferenceCount],8; total + (0.5 * 16)
|
||||||
|
mov cl,4
|
||||||
|
shr cs:[ReferenceCount],cl; (total) / 16 + 0.5
|
||||||
|
;
|
||||||
|
; Restore original interrupt state.
|
||||||
|
;
|
||||||
|
pop ax ; retrieve flags when called
|
||||||
|
mov ch,cs:[OriginalFlags] ; get back the original upper
|
||||||
|
; byte of the FLAGS register
|
||||||
|
and ch,not 0fdh ; only care about original
|
||||||
|
; interrupt flag...
|
||||||
|
and ah,0fdh ; ...keep all other flags in
|
||||||
|
; their current condition
|
||||||
|
or ah,ch ; make flags word with original
|
||||||
|
; interrupt flag
|
||||||
|
push ax ; prepare flags to be popped
|
||||||
|
;
|
||||||
|
; Restore the context of the program being timed and return to it.
|
||||||
|
;
|
||||||
|
MPOPF ; restore the flags with the
|
||||||
|
; original interrupt state
|
||||||
|
pop cx
|
||||||
|
pop ax
|
||||||
|
ret
|
||||||
|
|
||||||
|
ZTimerOff endp
|
||||||
|
|
||||||
|
;
|
||||||
|
; Called by ZTimerOff to start timer for overhead measurements.
|
||||||
|
;
|
||||||
|
|
||||||
|
ReferenceZTimerOnproc near
|
||||||
|
;
|
||||||
|
; Save the context of the program being timed.
|
||||||
|
;
|
||||||
|
push ax
|
||||||
|
pushf ; interrupts are already off
|
||||||
|
;
|
||||||
|
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
|
||||||
|
; linear counting rather than count-by-two counting.
|
||||||
|
;
|
||||||
|
mov al,00110100b ; set up to load
|
||||||
|
out MODE_8253,al ; initial timer count
|
||||||
|
DELAY
|
||||||
|
;
|
||||||
|
; Set the timer count to 0.
|
||||||
|
;
|
||||||
|
sub al,al
|
||||||
|
out TIMER_0_8253,al; load count lsb
|
||||||
|
DELAY
|
||||||
|
out TIMER_0_8253,al; load count msb
|
||||||
|
;
|
||||||
|
; Restore the context of the program being timed and return to it.
|
||||||
|
;
|
||||||
|
MPOPF
|
||||||
|
pop ax
|
||||||
|
ret
|
||||||
|
|
||||||
|
ReferenceZTimerOnendp
|
||||||
|
|
||||||
|
;
|
||||||
|
; Called by ZTimerOff to stop timer and add result to ReferenceCount
|
||||||
|
; for overhead measurements.
|
||||||
|
;
|
||||||
|
|
||||||
|
ReferenceZTimerOff proc near
|
||||||
|
;
|
||||||
|
; Save the context of the program being timed.
|
||||||
|
;
|
||||||
|
push ax
|
||||||
|
push cx
|
||||||
|
pushf
|
||||||
|
;
|
||||||
|
; Latch the count and read it.
|
||||||
|
;
|
||||||
|
mov al,00000000b ; latch timer 0
|
||||||
|
out MODE_8253,al
|
||||||
|
DELAY
|
||||||
|
in al,TIMER_0_8253 ; lsb
|
||||||
|
DELAY
|
||||||
|
mov ah,al
|
||||||
|
in al,TIMER_0_8253 ; msb
|
||||||
|
xchg ah,al
|
||||||
|
neg ax ; convert from countdown
|
||||||
|
; remaining to amount
|
||||||
|
; counted down
|
||||||
|
add cs:[ReferenceCount],ax
|
||||||
|
;
|
||||||
|
; Restore the context of the program being timed and return to it.
|
||||||
|
;
|
||||||
|
MPOPF
|
||||||
|
pop cx
|
||||||
|
pop ax
|
||||||
|
ret
|
||||||
|
|
||||||
|
ReferenceZTimerOff endp
|
||||||
|
|
||||||
|
; ********************************************************************
|
||||||
|
; * Routine called to report timing results. *
|
||||||
|
; ********************************************************************
|
||||||
|
|
||||||
|
ZTimerReport procnear
|
||||||
|
|
||||||
;
|
|
||||||
; Save the context of the program being timed.
|
|
||||||
;
|
|
||||||
push ax
|
|
||||||
pushf
|
pushf
|
||||||
pop ax ; get flags so we can keep
|
push ax
|
||||||
; interrupts off when leaving
|
push bx
|
||||||
; this routine
|
push cx
|
||||||
mov cs:[OriginalFlags],ah ; remember the state of the
|
push dx
|
||||||
; Interrupt flag
|
push si
|
||||||
and ah,0fdh ; set pushed interrupt flag
|
push ds
|
||||||
; to 0
|
;
|
||||||
push ax
|
push cs ; DOS functions require that DS point
|
||||||
;
|
pop ds ; to text to be displayed on the screen
|
||||||
; Turn on interrupts, so the timer interrupt can occur if it's
|
assume ds :Code
|
||||||
; pending.
|
;
|
||||||
;
|
; Check for timer 0 overflow.
|
||||||
sti
|
;
|
||||||
;
|
cmp [OverflowFlag],0
|
||||||
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
|
jz PrintGoodCount
|
||||||
; linear counting rather than count-by-two counting. Also
|
mov dx,offset OverflowStr
|
||||||
; leaves the 8253 waiting for the initial timer 0 count to
|
mov ah,9
|
||||||
; be loaded.
|
int 21h
|
||||||
;
|
jmp short EndZTimerReport
|
||||||
mov al,00110100b ;mode 2
|
;
|
||||||
out MODE_8253,al
|
; Convert net count to decimal ASCII in microseconds.
|
||||||
;
|
;
|
||||||
; Set the timer count to 0, so we know we won't get another
|
PrintGoodCount:
|
||||||
; timer interrupt right away.
|
mov ax,[TimedCount]
|
||||||
; Note: this introduces an inaccuracy of up to 54 ms in the system
|
sub ax,[ReferenceCount]
|
||||||
; clock count each time it is executed.
|
mov si,offset ASCIICountEnd - 1
|
||||||
;
|
;
|
||||||
DELAY
|
; Convert count to microseconds by multiplying by .8381.
|
||||||
sub al,al
|
;
|
||||||
out TIMER_0_8253,al ;lsb
|
mov dx, 8381
|
||||||
DELAY
|
mul dx
|
||||||
out TIMER_0_8253,al ;msb
|
mov bx, 10000
|
||||||
;
|
div bx ;* .8381 = * 8381 / 10000
|
||||||
; Wait before clearing interrupts to allow the interrupt generated
|
;
|
||||||
; when switching from mode 3 to mode 2 to be recognized. The delay
|
; Convert time in microseconds to 5 decimal ASCII digits.
|
||||||
; must be at least 210 ns long to allow time for that interrupt to
|
;
|
||||||
; occur. Here, 10 jumps are used for the delay to ensure that the
|
mov bx, 10
|
||||||
; delay time will be more than long enough even on a very fast PC.
|
mov cx, 5
|
||||||
;
|
CTSLoop:
|
||||||
rept 10
|
sub dx, dx
|
||||||
jmp $+2
|
div bx
|
||||||
endm
|
add dl,'0'
|
||||||
;
|
mov [si],dl
|
||||||
; Disable interrupts to get an accurate count.
|
dec si
|
||||||
;
|
loop CTSLoop
|
||||||
cli
|
;
|
||||||
;
|
; Print the results.
|
||||||
; Set the timer count to 0 again to start the timing interval.
|
;
|
||||||
;
|
mov ah, 9
|
||||||
mov al,00110100b ; set up to load initial
|
mov dx, offset OutputStr
|
||||||
out MODE_8253,al ; timer count
|
int 21h
|
||||||
DELAY
|
;
|
||||||
sub al,al
|
EndZTimerReport:
|
||||||
out TIMER_0_8253,al ; load count lsb
|
pop ds
|
||||||
DELAY
|
pop si
|
||||||
out TIMER_0_8253,al; load count msb
|
pop dx
|
||||||
;
|
pop cx
|
||||||
; Restore the context and return.
|
pop bx
|
||||||
;
|
pop ax
|
||||||
MPOPF ; keeps interrupts off
|
MPOPF
|
||||||
pop ax
|
ret
|
||||||
ret
|
|
||||||
|
|
||||||
ZTimerOn endp
|
ZTimerReport endp
|
||||||
|
|
||||||
;********************************************************************
|
Code ends
|
||||||
;* Routine called to stop timing and get count. *
|
end
|
||||||
;********************************************************************
|
```
|
||||||
|
|
||||||
ZTimerOff proc near
|
|
||||||
|
|
||||||
;
|
|
||||||
; Save the context of the program being timed.
|
|
||||||
;
|
|
||||||
push ax
|
|
||||||
push cx
|
|
||||||
pushf
|
|
||||||
;
|
|
||||||
; Latch the count.
|
|
||||||
;
|
|
||||||
mov al,00000000b ; latch timer 0
|
|
||||||
out MODE_8253,al
|
|
||||||
;
|
|
||||||
; See if the timer has overflowed by checking the 8259 for a pending
|
|
||||||
; timer interrupt.
|
|
||||||
;
|
|
||||||
mov al,00001010b ; OCW3, set up to read
|
|
||||||
out OCW3,al; Int errupt Request register
|
|
||||||
DELAY
|
|
||||||
ina l,IRR; read Interrupt Request
|
|
||||||
; register
|
|
||||||
and al,1 ; set AL to 1 if IRQ0 (the
|
|
||||||
; timer interrupt) is pending
|
|
||||||
mov cs:[OverflowFlag],al; store the timer overflow
|
|
||||||
; status
|
|
||||||
;
|
|
||||||
; Allow interrupts to happen again.
|
|
||||||
;
|
|
||||||
sti
|
|
||||||
;
|
|
||||||
; Read out the count we latched earlier.
|
|
||||||
;
|
|
||||||
in al,TIMER_0_8253 ; least significant byte
|
|
||||||
DELAY
|
|
||||||
mov ah,al
|
|
||||||
in al,TIMER_0_8253 ; most significant byte
|
|
||||||
xchg ah,al
|
|
||||||
neg ax ; convert from countdown
|
|
||||||
; remaining to elapsed
|
|
||||||
; count
|
|
||||||
mov cs:[TimedCount],ax
|
|
||||||
; Time a zero-length code fragment, to get a reference for how
|
|
||||||
; much overhead this routine has. Time it 16 times and average it,
|
|
||||||
; for accuracy, rounding the result.
|
|
||||||
;
|
|
||||||
mov cs:[ReferenceCount],0
|
|
||||||
mov cx,16
|
|
||||||
cli ; interrupts off to allow a
|
|
||||||
; precise reference count
|
|
||||||
RefLoop:
|
|
||||||
call ReferenceZTimerOn
|
|
||||||
call ReferenceZTimerOff
|
|
||||||
loop RefLoop
|
|
||||||
sti
|
|
||||||
add cs:[ReferenceCount],8; total + (0.5 * 16)
|
|
||||||
mov cl,4
|
|
||||||
shr cs:[ReferenceCount],cl; (total) / 16 + 0.5
|
|
||||||
;
|
|
||||||
; Restore original interrupt state.
|
|
||||||
;
|
|
||||||
pop ax ; retrieve flags when called
|
|
||||||
mov ch,cs:[OriginalFlags] ; get back the original upper
|
|
||||||
; byte of the FLAGS register
|
|
||||||
and ch,not 0fdh ; only care about original
|
|
||||||
; interrupt flag...
|
|
||||||
and ah,0fdh ; ...keep all other flags in
|
|
||||||
; their current condition
|
|
||||||
or ah,ch ; make flags word with original
|
|
||||||
; interrupt flag
|
|
||||||
push ax ; prepare flags to be popped
|
|
||||||
;
|
|
||||||
; Restore the context of the program being timed and return to it.
|
|
||||||
;
|
|
||||||
MPOPF ; restore the flags with the
|
|
||||||
; original interrupt state
|
|
||||||
pop cx
|
|
||||||
pop ax
|
|
||||||
ret
|
|
||||||
|
|
||||||
ZTimerOff endp
|
|
||||||
|
|
||||||
;
|
|
||||||
; Called by ZTimerOff to start timer for overhead measurements.
|
|
||||||
;
|
|
||||||
|
|
||||||
ReferenceZTimerOnproc near
|
|
||||||
;
|
|
||||||
; Save the context of the program being timed.
|
|
||||||
;
|
|
||||||
push ax
|
|
||||||
pushf ; interrupts are already off
|
|
||||||
;
|
|
||||||
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
|
|
||||||
; linear counting rather than count-by-two counting.
|
|
||||||
;
|
|
||||||
mov al,00110100b ; set up to load
|
|
||||||
out MODE_8253,al ; initial timer count
|
|
||||||
DELAY
|
|
||||||
;
|
|
||||||
; Set the timer count to 0.
|
|
||||||
;
|
|
||||||
sub al,al
|
|
||||||
out TIMER_0_8253,al; load count lsb
|
|
||||||
DELAY
|
|
||||||
out TIMER_0_8253,al; load count msb
|
|
||||||
;
|
|
||||||
; Restore the context of the program being timed and return to it.
|
|
||||||
;
|
|
||||||
MPOPF
|
|
||||||
pop ax
|
|
||||||
ret
|
|
||||||
|
|
||||||
ReferenceZTimerOnendp
|
|
||||||
|
|
||||||
;
|
|
||||||
; Called by ZTimerOff to stop timer and add result to ReferenceCount
|
|
||||||
; for overhead measurements.
|
|
||||||
;
|
|
||||||
|
|
||||||
ReferenceZTimerOff proc near
|
|
||||||
;
|
|
||||||
; Save the context of the program being timed.
|
|
||||||
;
|
|
||||||
push ax
|
|
||||||
push cx
|
|
||||||
pushf
|
|
||||||
;
|
|
||||||
; Latch the count and read it.
|
|
||||||
;
|
|
||||||
mov al,00000000b ; latch timer 0
|
|
||||||
out MODE_8253,al
|
|
||||||
DELAY
|
|
||||||
in al,TIMER_0_8253 ; lsb
|
|
||||||
DELAY
|
|
||||||
mov ah,al
|
|
||||||
in al,TIMER_0_8253 ; msb
|
|
||||||
xchg ah,al
|
|
||||||
neg ax ; convert from countdown
|
|
||||||
; remaining to amount
|
|
||||||
; counted down
|
|
||||||
add cs:[ReferenceCount],ax
|
|
||||||
;
|
|
||||||
; Restore the context of the program being timed and return to it.
|
|
||||||
;
|
|
||||||
MPOPF
|
|
||||||
pop cx
|
|
||||||
pop ax
|
|
||||||
ret
|
|
||||||
|
|
||||||
ReferenceZTimerOff endp
|
|
||||||
|
|
||||||
; ********************************************************************
|
|
||||||
; * Routine called to report timing results. *
|
|
||||||
; ********************************************************************
|
|
||||||
|
|
||||||
ZTimerReport procnear
|
|
||||||
|
|
||||||
pushf
|
|
||||||
push ax
|
|
||||||
push bx
|
|
||||||
push cx
|
|
||||||
push dx
|
|
||||||
push si
|
|
||||||
push ds
|
|
||||||
;
|
|
||||||
push cs ; DOS functions require that DS point
|
|
||||||
pop ds ; to text to be displayed on the screen
|
|
||||||
assume ds :Code
|
|
||||||
;
|
|
||||||
; Check for timer 0 overflow.
|
|
||||||
;
|
|
||||||
cmp [OverflowFlag],0
|
|
||||||
jz PrintGoodCount
|
|
||||||
mov dx,offset OverflowStr
|
|
||||||
mov ah,9
|
|
||||||
int 21h
|
|
||||||
jmp short EndZTimerReport
|
|
||||||
;
|
|
||||||
; Convert net count to decimal ASCII in microseconds.
|
|
||||||
;
|
|
||||||
PrintGoodCount:
|
|
||||||
mov ax,[TimedCount]
|
|
||||||
sub ax,[ReferenceCount]
|
|
||||||
mov si,offset ASCIICountEnd - 1
|
|
||||||
;
|
|
||||||
; Convert count to microseconds by multiplying by .8381.
|
|
||||||
;
|
|
||||||
mov dx, 8381
|
|
||||||
mul dx
|
|
||||||
mov bx, 10000
|
|
||||||
div bx ;* .8381 = * 8381 / 10000
|
|
||||||
;
|
|
||||||
; Convert time in microseconds to 5 decimal ASCII digits.
|
|
||||||
;
|
|
||||||
mov bx, 10
|
|
||||||
mov cx, 5
|
|
||||||
CTSLoop:
|
|
||||||
sub dx, dx
|
|
||||||
div bx
|
|
||||||
add dl,'0'
|
|
||||||
mov [si],dl
|
|
||||||
dec si
|
|
||||||
loop CTSLoop
|
|
||||||
;
|
|
||||||
; Print the results.
|
|
||||||
;
|
|
||||||
mov ah, 9
|
|
||||||
mov dx, offset OutputStr
|
|
||||||
int 21h
|
|
||||||
;
|
|
||||||
EndZTimerReport:
|
|
||||||
pop ds
|
|
||||||
pop si
|
|
||||||
pop dx
|
|
||||||
pop cx
|
|
||||||
pop bx
|
|
||||||
pop ax
|
|
||||||
MPOPF
|
|
||||||
ret
|
|
||||||
|
|
||||||
ZTimerReport endp
|
|
||||||
|
|
||||||
Code ends
|
|
||||||
end
|
|
||||||
|
|
|
||||||
126
03-05.md
126
03-05.md
|
|
@ -82,43 +82,45 @@ and should contain calls to **ZTimerOn** and **ZTimerOff** .
|
||||||
|
|
||||||
**LISTING 3.2 PZTEST.ASM**
|
**LISTING 3.2 PZTEST.ASM**
|
||||||
|
|
||||||
; Program to measure performance of code that takes less than
|
```nasm
|
||||||
; 54 ms to execute. (PZTEST.ASM)
|
; Program to measure performance of code that takes less than
|
||||||
;
|
; 54 ms to execute. (PZTEST.ASM)
|
||||||
; Link with PZTIMER.ASM (Listing 3.1). PZTEST.BAT (Listing 3.4)
|
;
|
||||||
; can be used to assemble and link both files. Code to be
|
; Link with PZTIMER.ASM (Listing 3.1). PZTEST.BAT (Listing 3.4)
|
||||||
; measured must be in the file TESTCODE; Listing 3.3 shows
|
; can be used to assemble and link both files. Code to be
|
||||||
; a sample TESTCODE file.
|
; measured must be in the file TESTCODE; Listing 3.3 shows
|
||||||
;
|
; a sample TESTCODE file.
|
||||||
; By Michael Abrash
|
;
|
||||||
;
|
; By Michael Abrash
|
||||||
mystack segment para stack ‘STACK'
|
;
|
||||||
db 512 dup(?)
|
mystack segment para stack ‘STACK'
|
||||||
mystack ends
|
db 512 dup(?)
|
||||||
;
|
mystack ends
|
||||||
Code segment para public ‘CODE'
|
;
|
||||||
assume cs:Code, ds:Code
|
Code segment para public ‘CODE'
|
||||||
extrnZTimerOn:near, ZTimerOff:near, ZTimerReport:near
|
assume cs:Code, ds:Code
|
||||||
Start proc near
|
extrnZTimerOn:near, ZTimerOff:near, ZTimerReport:near
|
||||||
push cs
|
Start proc near
|
||||||
pop ds ; set DS to point to the code segment,
|
push cs
|
||||||
; so data as well as code can easily
|
pop ds ; set DS to point to the code segment,
|
||||||
; be included in TESTCODE
|
; so data as well as code can easily
|
||||||
;
|
; be included in TESTCODE
|
||||||
include TESTCODE ;code to be measured, including
|
;
|
||||||
; calls to ZTimerOn and ZTimerOff
|
include TESTCODE ;code to be measured, including
|
||||||
;
|
; calls to ZTimerOn and ZTimerOff
|
||||||
; Display the results.
|
;
|
||||||
;
|
; Display the results.
|
||||||
call ZTimerReport
|
;
|
||||||
;
|
call ZTimerReport
|
||||||
; Terminate the program.
|
;
|
||||||
;
|
; Terminate the program.
|
||||||
mov ah,4ch
|
;
|
||||||
int 21h
|
mov ah,4ch
|
||||||
Start endp
|
int 21h
|
||||||
Code ends
|
Start endp
|
||||||
end Start
|
Code ends
|
||||||
|
end Start
|
||||||
|
```
|
||||||
|
|
||||||
Listing 3.3 shows some sample code to be timed. This listing measures
|
Listing 3.3 shows some sample code to be timed. This listing measures
|
||||||
the time required to execute 1,000 loads of AL from the memory variable
|
the time required to execute 1,000 loads of AL from the memory variable
|
||||||
|
|
@ -130,30 +132,32 @@ after the code in Listing 3.3 has been run.
|
||||||
|
|
||||||
**LISTING 3.3 LST3-3.ASM**
|
**LISTING 3.3 LST3-3.ASM**
|
||||||
|
|
||||||
; Test file;
|
```nasm
|
||||||
; Measures the performance of 1,000 loads of AL from
|
; Test file;
|
||||||
; memory. (Use by renaming to TESTCODE, which is
|
; Measures the performance of 1,000 loads of AL from
|
||||||
; included by PZTEST.ASM (Listing 3.2). PZTIME.BAT
|
; memory. (Use by renaming to TESTCODE, which is
|
||||||
; (Listing 3.4) does this, along with all assembly
|
; included by PZTEST.ASM (Listing 3.2). PZTIME.BAT
|
||||||
; and linking.)
|
; (Listing 3.4) does this, along with all assembly
|
||||||
;
|
; and linking.)
|
||||||
jmp Skip ;jump around defined data
|
;
|
||||||
;
|
jmp Skip ;jump around defined data
|
||||||
MemVar db ?
|
;
|
||||||
;
|
MemVar db ?
|
||||||
Skip:
|
;
|
||||||
;
|
Skip:
|
||||||
; Start timing.
|
;
|
||||||
;
|
; Start timing.
|
||||||
call ZTimerOn
|
;
|
||||||
;
|
call ZTimerOn
|
||||||
rept 1000
|
;
|
||||||
mov al,[MemVar]
|
rept 1000
|
||||||
endm
|
mov al,[MemVar]
|
||||||
;
|
endm
|
||||||
; Stop timing.
|
;
|
||||||
;
|
; Stop timing.
|
||||||
call ZTimerOff
|
;
|
||||||
|
call ZTimerOff
|
||||||
|
```
|
||||||
|
|
||||||
It's worth noting that Listing 3.3 begins by jumping around the memory
|
It's worth noting that Listing 3.3 begins by jumping around the memory
|
||||||
variable **MemVar**. This approach lets us avoid reproducing Listing 3.2
|
variable **MemVar**. This approach lets us avoid reproducing Listing 3.2
|
||||||
|
|
|
||||||
132
03-06.md
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**
|
**LISTING 3.4 PZTIME.BAT**
|
||||||
|
|
||||||
echo off
|
```bat
|
||||||
rem
|
echo off
|
||||||
rem *** Listing 3.4 ***
|
rem
|
||||||
rem
|
rem *** Listing 3.4 ***
|
||||||
rem ***************************************************************
|
rem
|
||||||
rem * Batch file PZTIME.BAT, which builds and runs the precision *
|
rem ***************************************************************
|
||||||
rem * Zen timer program PZTEST.EXE to time the code named as the *
|
rem * Batch file PZTIME.BAT, which builds and runs the precision *
|
||||||
rem * command-line parameter. Listing 3.1 must be named *
|
rem * Zen timer program PZTEST.EXE to time the code named as the *
|
||||||
rem * PZTIMER.ASM, and Listing 3.2 must be named PZTEST.ASM. To *
|
rem * command-line parameter. Listing 3.1 must be named *
|
||||||
rem * time the code in LST3-3, you'd type the DOS command: *
|
rem * PZTIMER.ASM, and Listing 3.2 must be named PZTEST.ASM. To *
|
||||||
rem * *
|
rem * time the code in LST3-3, you'd type the DOS command: *
|
||||||
rem * pztime lst3-3 *
|
rem * *
|
||||||
rem * *
|
rem * pztime lst3-3 *
|
||||||
rem * Note that MASM and LINK must be in the current directory or *
|
rem * *
|
||||||
rem * on the current path in order for this batch file to work. *
|
rem * Note that MASM and LINK must be in the current directory or *
|
||||||
rem * *
|
rem * on the current path in order for this batch file to work. *
|
||||||
rem * This batch file can be speeded up by assembling PZTIMER.ASM *
|
rem * *
|
||||||
rem * once, then removing the lines: *
|
rem * This batch file can be speeded up by assembling PZTIMER.ASM *
|
||||||
rem * *
|
rem * once, then removing the lines: *
|
||||||
rem * masm pztimer; *
|
rem * *
|
||||||
rem * if errorlevel 1 goto errorend *
|
rem * masm pztimer; *
|
||||||
rem * *
|
rem * if errorlevel 1 goto errorend *
|
||||||
rem * from this file. *
|
rem * *
|
||||||
rem * *
|
rem * from this file. *
|
||||||
rem * By Michael Abrash *
|
rem * *
|
||||||
rem ***************************************************************
|
rem * By Michael Abrash *
|
||||||
rem
|
rem ***************************************************************
|
||||||
rem Make sure a file to test was specified.
|
rem
|
||||||
rem
|
rem Make sure a file to test was specified.
|
||||||
if not x%1==x goto ckexist
|
rem
|
||||||
echo ***************************************************************
|
if not x%1==x goto ckexist
|
||||||
echo * Please specify a file to test. *
|
echo ***************************************************************
|
||||||
echo ***************************************************************
|
echo * Please specify a file to test. *
|
||||||
goto end
|
echo ***************************************************************
|
||||||
rem
|
goto end
|
||||||
rem Make sure the file exists.
|
rem
|
||||||
rem
|
rem Make sure the file exists.
|
||||||
:ckexist
|
rem
|
||||||
if exist %1 goto docopy
|
:ckexist
|
||||||
echo ***************************************************************
|
if exist %1 goto docopy
|
||||||
echo * The specified file, "%1," doesn't exist. *
|
echo ***************************************************************
|
||||||
echo ***************************************************************
|
echo * The specified file, "%1," doesn't exist. *
|
||||||
goto end
|
echo ***************************************************************
|
||||||
rem
|
goto end
|
||||||
rem copy the file to measure to TESTCODE.
|
rem
|
||||||
rem
|
rem copy the file to measure to TESTCODE.
|
||||||
:docopy
|
rem
|
||||||
copy %1 testcode
|
:docopy
|
||||||
masm pztest;
|
copy %1 testcode
|
||||||
if errorlevel 1 goto errorend
|
masm pztest;
|
||||||
masm pztimer;
|
if errorlevel 1 goto errorend
|
||||||
if errorlevel 1 goto errorend
|
masm pztimer;
|
||||||
link pztest+pztimer;
|
if errorlevel 1 goto errorend
|
||||||
if errorlevel 1 goto errorend
|
link pztest+pztimer;
|
||||||
pztest
|
if errorlevel 1 goto errorend
|
||||||
goto end
|
pztest
|
||||||
:errorend
|
goto end
|
||||||
echo ***************************************************************
|
:errorend
|
||||||
echo * An error occurred while building the precision Zen timer. *
|
echo ***************************************************************
|
||||||
echo ***************************************************************
|
echo * An error occurred while building the precision Zen timer. *
|
||||||
:end
|
echo ***************************************************************
|
||||||
|
:end
|
||||||
|
```
|
||||||
|
|
||||||
Assuming that Listing 3.3 is named LST3-3.ASM and Listing 3.4 is named
|
Assuming that Listing 3.3 is named LST3-3.ASM and Listing 3.4 is named
|
||||||
PZTIME.BAT, the code in Listing 3.3 would be timed with the command:
|
PZTIME.BAT, the code in Listing 3.3 would be timed with the command:
|
||||||
|
|
||||||
pztime LST3-3.ASM
|
```sh
|
||||||
|
pztime LST3-3.ASM
|
||||||
|
```
|
||||||
|
|
||||||
which performs all assembly and linking, and reports the execution time
|
which performs all assembly and linking, and reports the execution time
|
||||||
of the code in Listing 3.3.
|
of the code in Listing 3.3.
|
||||||
|
|
@ -116,7 +120,9 @@ In order to perform any of the timing tests in this book, enter Listing
|
||||||
and enter Listing 3.4 and name it PZTIME.BAT. Then simply enter the
|
and enter Listing 3.4 and name it PZTIME.BAT. Then simply enter the
|
||||||
listing you wish to run into the file *filename* and enter the command:
|
listing you wish to run into the file *filename* and enter the command:
|
||||||
|
|
||||||
pztime <filename>
|
```sh
|
||||||
|
pztime <filename>
|
||||||
|
```
|
||||||
|
|
||||||
In fact, that's exactly how I timed each of the listings in this book.
|
In fact, that's exactly how I timed each of the listings in this book.
|
||||||
Code fragments you write yourself can be timed in just the same way. If
|
Code fragments you write yourself can be timed in just the same way. If
|
||||||
|
|
|
||||||
118
03-08.md
118
03-08.md
|
|
@ -70,64 +70,66 @@ timing.
|
||||||
|
|
||||||
**LISTING 3.6 LZTEST.ASM**
|
**LISTING 3.6 LZTEST.ASM**
|
||||||
|
|
||||||
; Program to measure performance of code that takes longer than
|
```nasm
|
||||||
; 54 ms to execute. (LZTEST.ASM)
|
; Program to measure performance of code that takes longer than
|
||||||
;
|
; 54 ms to execute. (LZTEST.ASM)
|
||||||
; Link with LZTIMER.ASM (Listing 3.5). LZTIME.BAT (Listing 3.7)
|
;
|
||||||
; can be used to assemble and link both files. Code to be
|
; Link with LZTIMER.ASM (Listing 3.5). LZTIME.BAT (Listing 3.7)
|
||||||
; measured must be in the file TESTCODE; Listing 3.8 shows
|
; can be used to assemble and link both files. Code to be
|
||||||
; a sample file (LST3-8.ASM) which should be named TESTCODE.
|
; measured must be in the file TESTCODE; Listing 3.8 shows
|
||||||
;
|
; a sample file (LST3-8.ASM) which should be named TESTCODE.
|
||||||
; By Michael Abrash
|
;
|
||||||
;
|
; By Michael Abrash
|
||||||
mystack segment para stack ‘STACK'
|
;
|
||||||
db 512 dup(?)
|
mystack segment para stack ‘STACK'
|
||||||
mystack ends
|
db 512 dup(?)
|
||||||
;
|
mystack ends
|
||||||
Code segment para public ‘CODE'
|
;
|
||||||
assume cs:Code, ds:Code
|
Code segment para public ‘CODE'
|
||||||
extrn ZTimerOn:near, ZTimerOff:near, ZTimerReport:near
|
assume cs:Code, ds:Code
|
||||||
Startproc near
|
extrn ZTimerOn:near, ZTimerOff:near, ZTimerReport:near
|
||||||
push cs
|
Startproc near
|
||||||
pop ds ;point DS to the code segment,
|
push cs
|
||||||
; so data as well as code can easily
|
pop ds ;point DS to the code segment,
|
||||||
; be included in TESTCODE
|
; so data as well as code can easily
|
||||||
;
|
; be included in TESTCODE
|
||||||
; Delay for 6-7 seconds, to let the Enter keystroke that started the
|
;
|
||||||
; program come back up.
|
; Delay for 6-7 seconds, to let the Enter keystroke that started the
|
||||||
;
|
; program come back up.
|
||||||
mov ah,2ch
|
;
|
||||||
int 21h ;get the current time
|
mov ah,2ch
|
||||||
mov bh,dh ;set the current time aside
|
int 21h ;get the current time
|
||||||
DelayLoop:
|
mov bh,dh ;set the current time aside
|
||||||
mov ah,2ch
|
DelayLoop:
|
||||||
push bx ;preserve start time
|
mov ah,2ch
|
||||||
int 21h ;get time
|
push bx ;preserve start time
|
||||||
pop bx ;retrieve start time
|
int 21h ;get time
|
||||||
cmp dh,bh ;is the new seconds count less than
|
pop bx ;retrieve start time
|
||||||
; the start seconds count?
|
cmp dh,bh ;is the new seconds count less than
|
||||||
jnb CheckDelayTime ;no
|
; the start seconds count?
|
||||||
add dh,60 ;yes, a minute must have turned over,
|
jnb CheckDelayTime ;no
|
||||||
; so add one minute
|
add dh,60 ;yes, a minute must have turned over,
|
||||||
CheckDelayTime:
|
; so add one minute
|
||||||
sub dh,bh ;get time that's passed
|
CheckDelayTime:
|
||||||
cmp dh,7 ;has it been more than 6 seconds yet?
|
sub dh,bh ;get time that's passed
|
||||||
jb DelayLoop ;not yet
|
cmp dh,7 ;has it been more than 6 seconds yet?
|
||||||
;
|
jb DelayLoop ;not yet
|
||||||
include TESTCODE ;code to be measured, including calls
|
;
|
||||||
; to ZTimerOn and ZTimerOff
|
include TESTCODE ;code to be measured, including calls
|
||||||
;
|
; to ZTimerOn and ZTimerOff
|
||||||
; Display the results.
|
;
|
||||||
;
|
; Display the results.
|
||||||
call ZTimerReport
|
;
|
||||||
;
|
call ZTimerReport
|
||||||
; Terminate the program.
|
;
|
||||||
;
|
; Terminate the program.
|
||||||
mov ah,4ch
|
;
|
||||||
int 21h
|
mov ah,4ch
|
||||||
Start endp
|
int 21h
|
||||||
Code ends
|
Start endp
|
||||||
end Start
|
Code ends
|
||||||
|
end Start
|
||||||
|
```
|
||||||
|
|
||||||
As with the precision Zen timer, the program in Listing 3.6 is used by
|
As with the precision Zen timer, the program in Listing 3.6 is used by
|
||||||
naming the file containing the code to be timed TESTCODE, then
|
naming the file containing the code to be timed TESTCODE, then
|
||||||
|
|
|
||||||
188
03-09.md
188
03-09.md
|
|
@ -12,67 +12,69 @@ pages: 067-070
|
||||||
|
|
||||||
**LISTING 3.7 LZTIME.BAT**
|
**LISTING 3.7 LZTIME.BAT**
|
||||||
|
|
||||||
echo off
|
```bat
|
||||||
rem
|
echo off
|
||||||
rem *** Listing 3.7 ***
|
rem
|
||||||
rem
|
rem *** Listing 3.7 ***
|
||||||
rem ***************************************************************
|
rem
|
||||||
rem * Batch file LZTIME.BAT, which builds and runs the *
|
rem ***************************************************************
|
||||||
rem * long-period Zen timer program LZTEST.EXE to time the code *
|
rem * Batch file LZTIME.BAT, which builds and runs the *
|
||||||
rem * named as the command-line parameter. Listing 3.5 must be *
|
rem * long-period Zen timer program LZTEST.EXE to time the code *
|
||||||
rem * named LZTIMER.ASM, and Listing 3.6 must be named *
|
rem * named as the command-line parameter. Listing 3.5 must be *
|
||||||
rem * LZTEST.ASM. To time the code in LST3-8, you'd type the *
|
rem * named LZTIMER.ASM, and Listing 3.6 must be named *
|
||||||
rem * DOS command: *
|
rem * LZTEST.ASM. To time the code in LST3-8, you'd type the *
|
||||||
rem * *
|
rem * DOS command: *
|
||||||
rem * lztime lst3-8 *
|
rem * *
|
||||||
rem * *
|
rem * lztime lst3-8 *
|
||||||
rem * Note that MASM and LINK must be in the current directory or *
|
rem * *
|
||||||
rem * on the current path in order for this batch file to work. *
|
rem * Note that MASM and LINK must be in the current directory or *
|
||||||
rem * *
|
rem * on the current path in order for this batch file to work. *
|
||||||
rem * This batch file can be speeded up by assembling LZTIMER.ASM *
|
rem * *
|
||||||
rem * once, then removing the lines: *
|
rem * This batch file can be speeded up by assembling LZTIMER.ASM *
|
||||||
rem * *
|
rem * once, then removing the lines: *
|
||||||
rem * masm lztimer; *
|
rem * *
|
||||||
rem * if errorlevel 1 goto errorend *
|
rem * masm lztimer; *
|
||||||
rem * *
|
rem * if errorlevel 1 goto errorend *
|
||||||
rem * from this file. *
|
rem * *
|
||||||
rem * *
|
rem * from this file. *
|
||||||
rem * By Michael Abrash *
|
rem * *
|
||||||
rem ***************************************************************
|
rem * By Michael Abrash *
|
||||||
rem
|
rem ***************************************************************
|
||||||
rem Make sure a file to test was specified.
|
rem
|
||||||
rem
|
rem Make sure a file to test was specified.
|
||||||
if not x%1==x goto ckexist
|
rem
|
||||||
echo ***************************************************************
|
if not x%1==x goto ckexist
|
||||||
echo * Please specify a file to test. *
|
echo ***************************************************************
|
||||||
echo ***************************************************************
|
echo * Please specify a file to test. *
|
||||||
goto end
|
echo ***************************************************************
|
||||||
rem
|
goto end
|
||||||
rem Make sure the file exists.
|
rem
|
||||||
rem
|
rem Make sure the file exists.
|
||||||
:ckexist
|
rem
|
||||||
if exist %1 goto docopy
|
:ckexist
|
||||||
echo ***************************************************************
|
if exist %1 goto docopy
|
||||||
echo * The specified file, "%1," doesn't exist. *
|
echo ***************************************************************
|
||||||
echo ***************************************************************
|
echo * The specified file, "%1," doesn't exist. *
|
||||||
goto end
|
echo ***************************************************************
|
||||||
rem
|
goto end
|
||||||
rem copy the file to measure to TESTCODE.
|
rem
|
||||||
:docopy
|
rem copy the file to measure to TESTCODE.
|
||||||
copy %1 testcode
|
:docopy
|
||||||
masm lztest;
|
copy %1 testcode
|
||||||
if errorlevel 1 goto errorend
|
masm lztest;
|
||||||
masm lztimer;
|
if errorlevel 1 goto errorend
|
||||||
if errorlevel 1 goto errorend
|
masm lztimer;
|
||||||
link lztest+lztimer;
|
if errorlevel 1 goto errorend
|
||||||
if errorlevel 1 goto errorend
|
link lztest+lztimer;
|
||||||
lztest
|
if errorlevel 1 goto errorend
|
||||||
goto end
|
lztest
|
||||||
:errorend
|
goto end
|
||||||
echo ***************************************************************
|
:errorend
|
||||||
echo * An error occurred while building the long-period Zen timer. *
|
echo ***************************************************************
|
||||||
echo ***************************************************************
|
echo * An error occurred while building the long-period Zen timer. *
|
||||||
:end
|
echo ***************************************************************
|
||||||
|
:end
|
||||||
|
```
|
||||||
|
|
||||||
Listing 3.8 shows sample code that can be timed with the test-bed
|
Listing 3.8 shows sample code that can be timed with the test-bed
|
||||||
program of Listing 3.6. Listing 3.8 measures the time required to
|
program of Listing 3.6. Listing 3.8 measures the time required to
|
||||||
|
|
@ -81,38 +83,42 @@ the precision Zen timer to handle on the 8088.
|
||||||
|
|
||||||
**LISTING 3.8 LST3-8.ASM**
|
**LISTING 3.8 LST3-8.ASM**
|
||||||
|
|
||||||
;
|
```nasm
|
||||||
; Measures the performance of 20,000 loads of AL from
|
;
|
||||||
; memory. (Use by renaming to TESTCODE, which is
|
; Measures the performance of 20,000 loads of AL from
|
||||||
; included by LZTEST.ASM (Listing 3.6). LZTIME.BAT
|
; memory. (Use by renaming to TESTCODE, which is
|
||||||
; (Listing 3.7) does this, along with all assembly
|
; included by LZTEST.ASM (Listing 3.6). LZTIME.BAT
|
||||||
; and linking.)
|
; (Listing 3.7) does this, along with all assembly
|
||||||
;
|
; and linking.)
|
||||||
; Note: takes about ten minutes to assemble on a slow PC if
|
;
|
||||||
;you are using MASM
|
; Note: takes about ten minutes to assemble on a slow PC if
|
||||||
;
|
;you are using MASM
|
||||||
jmpSkip;jump around defined data
|
;
|
||||||
;
|
jmpSkip;jump around defined data
|
||||||
MemVardb?
|
;
|
||||||
;
|
MemVardb?
|
||||||
Skip:
|
;
|
||||||
;
|
Skip:
|
||||||
; Start timing.
|
;
|
||||||
;
|
; Start timing.
|
||||||
callZTimerOn
|
;
|
||||||
;
|
callZTimerOn
|
||||||
rept20000
|
;
|
||||||
moval,[MemVar]
|
rept20000
|
||||||
endm
|
moval,[MemVar]
|
||||||
;
|
endm
|
||||||
; Stop timing.
|
;
|
||||||
;
|
; Stop timing.
|
||||||
callZTimerOff
|
;
|
||||||
|
callZTimerOff
|
||||||
|
```
|
||||||
|
|
||||||
When LZTIME.BAT is run on a PC with the following command line (assuming
|
When LZTIME.BAT is run on a PC with the following command line (assuming
|
||||||
the code in Listing 3.8 is the file LST3-8.ASM)
|
the code in Listing 3.8 is the file LST3-8.ASM)
|
||||||
|
|
||||||
lztime lst3-8.asm
|
```sh
|
||||||
|
lztime lst3-8.asm
|
||||||
|
```
|
||||||
|
|
||||||
the result is 72,544 µs, or about 3.63 µs per load of AL from memory.
|
the result is 72,544 µs, or about 3.63 µs per load of AL from memory.
|
||||||
This is just slightly longer than the time per load of AL measured by
|
This is just slightly longer than the time per load of AL measured by
|
||||||
|
|
@ -147,4 +153,6 @@ lines from Listing 3.1 that must be changed. These changes convert the
|
||||||
code to use C-style external label names and the small model C code
|
code to use C-style external label names and the small model C code
|
||||||
segment. (In C++, use the "C" specifier, as in
|
segment. (In C++, use the "C" specifier, as in
|
||||||
|
|
||||||
extern "C" ZTimerOn(void);
|
```c
|
||||||
|
extern "C" ZTimerOn(void);
|
||||||
|
```
|
||||||
|
|
|
||||||
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
|
That's all it takes; after doing this, you'll be able to use the Zen
|
||||||
timer from C, as, for example, in:
|
timer from C, as, for example, in:
|
||||||
|
|
||||||
ZTimerOn():
|
```c
|
||||||
for (i=0, x=0; i<100; i++)
|
ZTimerOn():
|
||||||
x += i;
|
for (i=0, x=0; i<100; i++)
|
||||||
ZTimerOff();
|
x += i;
|
||||||
ZTimerReport();
|
ZTimerOff();
|
||||||
|
ZTimerReport();
|
||||||
|
```
|
||||||
|
|
||||||
(I'm talking about the precision timer here. The long-period
|
(I'm talking about the precision timer here. The long-period
|
||||||
timer—Listing 3.5—requires the same modifications, but to different
|
timer—Listing 3.5—requires the same modifications, but to different
|
||||||
|
|
@ -46,12 +48,16 @@ One important safety tip when modifying the Zen timer for use with large
|
||||||
code model C code: Watch out for optimizing assemblers! TASM actually
|
code model C code: Watch out for optimizing assemblers! TASM actually
|
||||||
replaces
|
replaces
|
||||||
|
|
||||||
call far ptr ReferenceZTimerOn
|
```nasm
|
||||||
|
call far ptr ReferenceZTimerOn
|
||||||
|
```
|
||||||
|
|
||||||
with
|
with
|
||||||
|
|
||||||
push cs
|
```nasm
|
||||||
call near ptr ReferenceZTimerOn
|
push cs
|
||||||
|
call near ptr ReferenceZTimerOn
|
||||||
|
```
|
||||||
|
|
||||||
(and likewise for **ReferenceZTimerOff** ), which works because
|
(and likewise for **ReferenceZTimerOff** ), which works because
|
||||||
**ReferenceZTimerOn** is in the same segment as the calling code. This
|
**ReferenceZTimerOn** is in the same segment as the calling code. This
|
||||||
|
|
|
||||||
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
|
indicate that for code running on an 8088 an additional 4 cycles are
|
||||||
required for every word-sized access to a memory operand. For instance,
|
required for every word-sized access to a memory operand. For instance,
|
||||||
|
|
||||||
mov ax,word ptr [MemVar]
|
```nasm
|
||||||
|
mov ax,word ptr [MemVar]
|
||||||
|
```
|
||||||
|
|
||||||
takes 4 cycles longer to read the word at address **MemVar** than
|
takes 4 cycles longer to read the word at address **MemVar** than
|
||||||
|
|
||||||
mov al,byte ptr [MemVar]
|
```nasm
|
||||||
|
mov al,byte ptr [MemVar]
|
||||||
|
```
|
||||||
|
|
||||||
takes to read the byte at address **MemVar.** (Actually, the difference
|
takes to read the byte at address **MemVar.** (Actually, the difference
|
||||||
between the two isn't very likely to be exactly 4 cycles, for reasons
|
between the two isn't very likely to be exactly 4 cycles, for reasons
|
||||||
|
|
@ -72,11 +76,15 @@ prior to adding to it, and one to write the result of the addition back
|
||||||
to the destination operand—and thus incurs not one but two 4-cycle
|
to the destination operand—and thus incurs not one but two 4-cycle
|
||||||
penalties. As a result
|
penalties. As a result
|
||||||
|
|
||||||
add word ptr [MemVar],ax
|
```nasm
|
||||||
|
add word ptr [MemVar],ax
|
||||||
|
```
|
||||||
|
|
||||||
takes about 8 cycles longer to execute than:
|
takes about 8 cycles longer to execute than:
|
||||||
|
|
||||||
add byte ptr [MemVar],al
|
```nasm
|
||||||
|
add byte ptr [MemVar],al
|
||||||
|
```
|
||||||
|
|
||||||
String instructions can suffer from the 8-bit bus cycle-eater to a
|
String instructions can suffer from the 8-bit bus cycle-eater to a
|
||||||
greater extent than other instructions. Believe it or not, a single
|
greater extent than other instructions. Believe it or not, a single
|
||||||
|
|
@ -114,35 +122,39 @@ in all.
|
||||||
|
|
||||||
**LISTING 4.1 LST4-1.ASM**
|
**LISTING 4.1 LST4-1.ASM**
|
||||||
|
|
||||||
; Measures the performance of a loop which uses a
|
```nasm
|
||||||
; byte-sized memory variable as the loop counter.
|
; Measures the performance of a loop which uses a
|
||||||
;
|
; byte-sized memory variable as the loop counter.
|
||||||
jmp Skip
|
;
|
||||||
;
|
jmp Skip
|
||||||
Counter db 100
|
;
|
||||||
;
|
Counter db 100
|
||||||
Skip:
|
;
|
||||||
call ZTimerOn
|
Skip:
|
||||||
LoopTop:
|
call ZTimerOn
|
||||||
dec [Counter]
|
LoopTop:
|
||||||
jnz LoopTop
|
dec [Counter]
|
||||||
call ZTimerOff
|
jnz LoopTop
|
||||||
|
call ZTimerOff
|
||||||
|
```
|
||||||
|
|
||||||
**LISTING 4.2 LST4-2.ASM**
|
**LISTING 4.2 LST4-2.ASM**
|
||||||
|
|
||||||
; Measures the performance of a loop which uses a
|
```nasm
|
||||||
; word-sized memory variable as the loop counter.
|
; Measures the performance of a loop which uses a
|
||||||
;
|
; word-sized memory variable as the loop counter.
|
||||||
jmp Skip
|
;
|
||||||
;
|
jmp Skip
|
||||||
Counter dw 100
|
;
|
||||||
;
|
Counter dw 100
|
||||||
Skip:
|
;
|
||||||
call ZTimerOn
|
Skip:
|
||||||
LoopTop:
|
call ZTimerOn
|
||||||
dec [Counter]
|
LoopTop:
|
||||||
jnz LoopTop
|
dec [Counter]
|
||||||
call ZTimerOff
|
jnz LoopTop
|
||||||
|
call ZTimerOff
|
||||||
|
```
|
||||||
|
|
||||||
I'd like to make a brief aside concerning code optimization in the
|
I'd like to make a brief aside concerning code optimization in the
|
||||||
listings in this book. Throughout this book I've modeled the sample code
|
listings in this book. Throughout this book I've modeled the sample code
|
||||||
|
|
|
||||||
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
|
manipulate a word-sized memory variable in preference to 1 word-sized
|
||||||
memory access, as, for instance,
|
memory access, as, for instance,
|
||||||
|
|
||||||
mov dl,byte ptr [MemVar]
|
```nasm
|
||||||
mov dh,byte ptr [MemVar+1]
|
mov dl,byte ptr [MemVar]
|
||||||
|
mov dh,byte ptr [MemVar+1]
|
||||||
|
```
|
||||||
|
|
||||||
versus:
|
versus:
|
||||||
|
|
||||||
mov dx,word ptr [MemVar]
|
```nasm
|
||||||
|
mov dx,word ptr [MemVar]
|
||||||
|
```
|
||||||
|
|
||||||
Recall that every access to a memory byte takes at least 4 cycles; that
|
Recall that every access to a memory byte takes at least 4 cycles; that
|
||||||
limitation is built right into the 8088. The 8088 is also built so that
|
limitation is built right into the 8088. The 8088 is also built so that
|
||||||
|
|
@ -48,25 +52,29 @@ the second byte of each word.
|
||||||
|
|
||||||
**LISTING 4.3 LST4-3.ASM**
|
**LISTING 4.3 LST4-3.ASM**
|
||||||
|
|
||||||
; Measures the performance of reading 1,000 words
|
```nasm
|
||||||
; from memory with 1,000 word-sized accesses.
|
; Measures the performance of reading 1,000 words
|
||||||
;
|
; from memory with 1,000 word-sized accesses.
|
||||||
sub si,si
|
;
|
||||||
mov cx,1000
|
sub si,si
|
||||||
call ZTimerOn
|
mov cx,1000
|
||||||
rep lodsw
|
call ZTimerOn
|
||||||
call ZTimerOff
|
rep lodsw
|
||||||
|
call ZTimerOff
|
||||||
|
```
|
||||||
|
|
||||||
**LISTING 4.4 LST4-4.ASM**
|
**LISTING 4.4 LST4-4.ASM**
|
||||||
|
|
||||||
; Measures the performance of reading 1000 words
|
```nasm
|
||||||
; from memory with 2,000 byte-sized accesses.
|
; Measures the performance of reading 1000 words
|
||||||
;
|
; from memory with 2,000 byte-sized accesses.
|
||||||
sub si,si
|
;
|
||||||
mov cx,2000
|
sub si,si
|
||||||
call ZTimerOn
|
mov cx,2000
|
||||||
rep lodsb
|
call ZTimerOn
|
||||||
call ZTimerOff
|
rep lodsb
|
||||||
|
call ZTimerOff
|
||||||
|
```
|
||||||
|
|
||||||
In short, if you must perform a 16-bit memory access, let the 8088 break
|
In short, if you must perform a 16-bit memory access, let the 8088 break
|
||||||
the access into two byte-sized accesses for you. The 8088 is more
|
the access into two byte-sized accesses for you. The 8088 is more
|
||||||
|
|
@ -142,17 +150,21 @@ What makes the prefetch queue cycle-eater tricky is that it's
|
||||||
undocumented and unpredictable. That is, with a word-sized memory
|
undocumented and unpredictable. That is, with a word-sized memory
|
||||||
access, such as
|
access, such as
|
||||||
|
|
||||||
mov [bx],ax
|
```nasm
|
||||||
|
mov [bx],ax
|
||||||
|
```
|
||||||
|
|
||||||
it's well-documented that an extra 4 cycles will always be required to
|
it's well-documented that an extra 4 cycles will always be required to
|
||||||
write the upper byte of AX to memory. Not so with the prefetch queue
|
write the upper byte of AX to memory. Not so with the prefetch queue
|
||||||
cycle-eater lurking nearby. For instance, the instructions
|
cycle-eater lurking nearby. For instance, the instructions
|
||||||
|
|
||||||
shr ax,1
|
```nasm
|
||||||
shr ax,1
|
shr ax,1
|
||||||
shr ax,1
|
shr ax,1
|
||||||
shr ax,1
|
shr ax,1
|
||||||
shr ax,1
|
shr ax,1
|
||||||
|
shr ax,1
|
||||||
|
```
|
||||||
|
|
||||||
should execute in 10 cycles, since each **SHR** takes 2 cycles to
|
should execute in 10 cycles, since each **SHR** takes 2 cycles to
|
||||||
execute, according to Intel's specifications. Those specifications
|
execute, according to Intel's specifications. Those specifications
|
||||||
|
|
|
||||||
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**
|
**LISTING 4.5 LST4-5.ASM**
|
||||||
|
|
||||||
; Measures the performance of 1,000 SHR instructions
|
```nasm
|
||||||
; in a row. Since SHR executes in 2 cycles but is
|
; Measures the performance of 1,000 SHR instructions
|
||||||
; 2 bytes long, the prefetch queue is always empty,
|
; in a row. Since SHR executes in 2 cycles but is
|
||||||
; and prefetching time determines the overall
|
; 2 bytes long, the prefetch queue is always empty,
|
||||||
; performance of the code.
|
; and prefetching time determines the overall
|
||||||
;
|
; performance of the code.
|
||||||
call ZTimerOn
|
;
|
||||||
rept 1000
|
call ZTimerOn
|
||||||
shr ax,1
|
rept 1000
|
||||||
endm
|
shr ax,1
|
||||||
call ZTimerOff
|
endm
|
||||||
|
call ZTimerOff
|
||||||
|
```
|
||||||
|
|
||||||
**LISTING 4.6 LST4-6.ASM**
|
**LISTING 4.6 LST4-6.ASM**
|
||||||
|
|
||||||
; Measures the performance of 1,000 MUL/SHR instruction
|
```nasm
|
||||||
; pairs in a row. The lengthy execution time of MUL
|
; Measures the performance of 1,000 MUL/SHR instruction
|
||||||
; should keep the prefetch queue from ever emptying.
|
; pairs in a row. The lengthy execution time of MUL
|
||||||
;
|
; should keep the prefetch queue from ever emptying.
|
||||||
mov cx,1000
|
;
|
||||||
sub ax,ax
|
mov cx,1000
|
||||||
call ZTimerOn
|
sub ax,ax
|
||||||
rept 1000
|
call ZTimerOn
|
||||||
mul ax
|
rept 1000
|
||||||
shr ax,1
|
mul ax
|
||||||
endm
|
shr ax,1
|
||||||
call ZTimerOff
|
endm
|
||||||
|
call ZTimerOff
|
||||||
|
```
|
||||||
|
|
||||||

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

|

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

|

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

|

|
||||||
|
|
||||||
|
|
@ -68,26 +70,30 @@ The **POPF** workaround can best be implemented as a macro; we can also
|
||||||
emulate a far call by pushing CS and performing a near call, thereby
|
emulate a far call by pushing CS and performing a near call, thereby
|
||||||
shrinking the workaround code by 1 byte:
|
shrinking the workaround code by 1 byte:
|
||||||
|
|
||||||
EMULATE_POPF macro
|
```nasm
|
||||||
local popfskip, popfiret
|
EMULATE_POPF macro
|
||||||
jmp short popfskip
|
local popfskip, popfiret
|
||||||
popfiret:
|
jmp short popfskip
|
||||||
iret
|
popfiret:
|
||||||
popfskip:
|
iret
|
||||||
push cs
|
popfskip:
|
||||||
call popfiret
|
push cs
|
||||||
endm
|
call popfiret
|
||||||
|
endm
|
||||||
|
```
|
||||||
|
|
||||||
By the way, the flags can be popped much more quickly if you're willing
|
By the way, the flags can be popped much more quickly if you're willing
|
||||||
to alter a register in the process. For example, the following macro
|
to alter a register in the process. For example, the following macro
|
||||||
emulates **POPF** with just one branch, but wipes out AX:
|
emulates **POPF** with just one branch, but wipes out AX:
|
||||||
|
|
||||||
EMULATE_POPF_TRASH_AX macro
|
```nasm
|
||||||
push cs
|
EMULATE_POPF_TRASH_AX macro
|
||||||
mov ax,offset $+5
|
push cs
|
||||||
push ax
|
mov ax,offset $+5
|
||||||
iret
|
push ax
|
||||||
endm
|
iret
|
||||||
|
endm
|
||||||
|
```
|
||||||
|
|
||||||
It's not a perfect substitute for **POPF**, since **POPF** doesn't alter
|
It's not a perfect substitute for **POPF**, since **POPF** doesn't alter
|
||||||
any registers, but it's faster and shorter than **EMULATE\_POPF** when
|
any registers, but it's faster and shorter than **EMULATE\_POPF** when
|
||||||
|
|
@ -96,13 +102,15 @@ you can use which is shorter still, alters no registers, and branches
|
||||||
just once. (Of course, this version of **EMULATE\_POPF** won't work on
|
just once. (Of course, this version of **EMULATE\_POPF** won't work on
|
||||||
an 8088.)
|
an 8088.)
|
||||||
|
|
||||||
.286
|
```nasm
|
||||||
:
|
.286
|
||||||
EMULATE_POPFmacro
|
:
|
||||||
pushcs
|
EMULATE_POPFmacro
|
||||||
pushoffset $+4
|
pushcs
|
||||||
iret
|
pushoffset $+4
|
||||||
endm
|
iret
|
||||||
|
endm
|
||||||
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
|
||||||
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
|
As an example, you might adhere to this rule by replacing the code
|
||||||
|
|
||||||
LoopTop:
|
```nasm
|
||||||
add ax,[bx+si]
|
LoopTop:
|
||||||
add si,2
|
add ax,[bx+si]
|
||||||
dec cx
|
add si,2
|
||||||
jnz LoopTop
|
dec cx
|
||||||
|
jnz LoopTop
|
||||||
|
```
|
||||||
|
|
||||||
with this
|
with this
|
||||||
|
|
||||||
add si,bx
|
```nasm
|
||||||
LoopTop:
|
add si,bx
|
||||||
add ax,[si]
|
LoopTop:
|
||||||
add si,2
|
add ax,[si]
|
||||||
dec cx
|
add si,2
|
||||||
jnz LoopTop
|
dec cx
|
||||||
sub si,bx
|
jnz LoopTop
|
||||||
|
sub si,bx
|
||||||
|
```
|
||||||
|
|
|
||||||
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
|
when the preceding instruction modifies one of the target instruction's
|
||||||
addressing registers. For example, in the code
|
addressing registers. For example, in the code
|
||||||
|
|
||||||
MOV BX,OFFSET MemVar
|
```nasm
|
||||||
MOV AX,[BX]
|
MOV BX,OFFSET MemVar
|
||||||
|
MOV AX,[BX]
|
||||||
|
```
|
||||||
|
|
||||||
there's no way that the 486 can calculate the address referenced by
|
there's no way that the 486 can calculate the address referenced by
|
||||||
**MOV AX,[BX]** until **MOV BX,OFFSET MemVar** finishes, so pipelining
|
**MOV AX,[BX]** until **MOV BX,OFFSET MemVar** finishes, so pipelining
|
||||||
|
|
@ -68,19 +70,23 @@ rearranging your code so that at least one instruction lies between the
|
||||||
loading of the memory pointer and its use. For example,
|
loading of the memory pointer and its use. For example,
|
||||||
postdecrementing, as in the following
|
postdecrementing, as in the following
|
||||||
|
|
||||||
LoopTop:
|
```nasm
|
||||||
add ax,[si]
|
LoopTop:
|
||||||
add si,2
|
add ax,[si]
|
||||||
dec cx
|
add si,2
|
||||||
jnz LoopTop
|
dec cx
|
||||||
|
jnz LoopTop
|
||||||
|
```
|
||||||
|
|
||||||
is faster than preincrementing, as in:
|
is faster than preincrementing, as in:
|
||||||
|
|
||||||
LoopTop:
|
```nasm
|
||||||
add si,2
|
LoopTop:
|
||||||
add ax,[SI]
|
add si,2
|
||||||
dec cx
|
add ax,[SI]
|
||||||
jnz LoopTop
|
dec cx
|
||||||
|
jnz LoopTop
|
||||||
|
```
|
||||||
|
|
||||||
Now that we understand what Intel means by this rule, let me make a very
|
Now that we understand what Intel means by this rule, let me make a very
|
||||||
important comment: My observations indicate that for real-mode code, the
|
important comment: My observations indicate that for real-mode code, the
|
||||||
|
|
@ -108,28 +114,34 @@ instructions, but, because some 486 instructions take more than 1 cycle,
|
||||||
the 2 are not always equivalent) before it's used to point to memory, 1
|
the 2 are not always equivalent) before it's used to point to memory, 1
|
||||||
cycle is lost. Therefore, whereas this code
|
cycle is lost. Therefore, whereas this code
|
||||||
|
|
||||||
mov bx,offset MemVar
|
```nasm
|
||||||
mov ax,[bx]
|
mov bx,offset MemVar
|
||||||
inc dx
|
mov ax,[bx]
|
||||||
dec cx
|
inc dx
|
||||||
jnz LoopTop
|
dec cx
|
||||||
|
jnz LoopTop
|
||||||
|
```
|
||||||
|
|
||||||
loses two cycles from interrupting the address calculation pipeline,
|
loses two cycles from interrupting the address calculation pipeline,
|
||||||
this code
|
this code
|
||||||
|
|
||||||
mov bx,offset MemVar
|
```nasm
|
||||||
inc dx
|
mov bx,offset MemVar
|
||||||
mov ax,[bx]
|
inc dx
|
||||||
dec cx
|
mov ax,[bx]
|
||||||
jnz LoopTop
|
dec cx
|
||||||
|
jnz LoopTop
|
||||||
|
```
|
||||||
|
|
||||||
loses only one cycle, and this code
|
loses only one cycle, and this code
|
||||||
|
|
||||||
mov bx,offset MemVar
|
```nasm
|
||||||
inc dx
|
mov bx,offset MemVar
|
||||||
dec cx
|
inc dx
|
||||||
mov ax,[bx]
|
dec cx
|
||||||
jnz LoopTop
|
mov ax,[bx]
|
||||||
|
jnz LoopTop
|
||||||
|
```
|
||||||
|
|
||||||
loses no cycles at all. Apparently, the 486's addressing calculation
|
loses no cycles at all. Apparently, the 486's addressing calculation
|
||||||
pipeline actually starts 2 cycles ahead, as shown in Figure 12.2. (In
|
pipeline actually starts 2 cycles ahead, as shown in Figure 12.2. (In
|
||||||
|
|
|
||||||
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
|
For example, you'd certainly expect a sequence such as
|
||||||
|
|
||||||
:
|
```nasm
|
||||||
pop ax
|
:
|
||||||
ret
|
pop ax
|
||||||
pop ax
|
ret
|
||||||
et
|
pop ax
|
||||||
:
|
et
|
||||||
|
:
|
||||||
|
```
|
||||||
|
|
||||||
to exhibit the addressing pipeline interruption phenomenon (SP is both
|
to exhibit the addressing pipeline interruption phenomenon (SP is both
|
||||||
destination and addressing register for both instructions, according to
|
destination and addressing register for both instructions, according to
|
||||||
Intel), but this code runs in six cycles per **POP/RET** pair, matching
|
Intel), but this code runs in six cycles per **POP/RET** pair, matching
|
||||||
the official execution times exactly. Likewise, a sequence like
|
the official execution times exactly. Likewise, a sequence like
|
||||||
|
|
||||||
pop dx
|
```nasm
|
||||||
pop cx
|
pop dx
|
||||||
pop bx
|
pop cx
|
||||||
pop ax
|
pop bx
|
||||||
|
pop ax
|
||||||
|
```
|
||||||
|
|
||||||
runs in one cycle per instruction, just as it should.
|
runs in one cycle per instruction, just as it should.
|
||||||
|
|
||||||
|
|
@ -65,15 +69,19 @@ destination—for example, to deallocate local variables—and then using
|
||||||
**PUSH**, **POP**, or **RET**, definitely can interrupt the addressing
|
**PUSH**, **POP**, or **RET**, definitely can interrupt the addressing
|
||||||
pipeline. For example
|
pipeline. For example
|
||||||
|
|
||||||
add sp,10h
|
```nasm
|
||||||
ret
|
add sp,10h
|
||||||
|
ret
|
||||||
|
```
|
||||||
|
|
||||||
loses two cycles because SP is the explicit destination of one
|
loses two cycles because SP is the explicit destination of one
|
||||||
instruction and then the implied addressing register for the next, and
|
instruction and then the implied addressing register for the next, and
|
||||||
the sequence
|
the sequence
|
||||||
|
|
||||||
add sp,10h
|
```nasm
|
||||||
pop ax
|
add sp,10h
|
||||||
|
pop ax
|
||||||
|
```
|
||||||
|
|
||||||
loses two cycles for the same reason.
|
loses two cycles for the same reason.
|
||||||
|
|
||||||
|
|
@ -101,22 +109,26 @@ during the next instruction.
|
||||||
|
|
||||||
So, for example, it would be a bad idea to do this
|
So, for example, it would be a bad idea to do this
|
||||||
|
|
||||||
mov ah,o
|
```nasm
|
||||||
:
|
mov ah,o
|
||||||
mov cx,[MemVar1]
|
:
|
||||||
mov al,[MemVar2]
|
mov cx,[MemVar1]
|
||||||
add cx,ax
|
mov al,[MemVar2]
|
||||||
|
add cx,ax
|
||||||
|
```
|
||||||
|
|
||||||
because AL is loaded by one instruction, then AX is used as the source
|
because AL is loaded by one instruction, then AX is used as the source
|
||||||
register for the next instruction. A cycle can be saved simply by
|
register for the next instruction. A cycle can be saved simply by
|
||||||
rearranging the instructions so that the byte register load isn't
|
rearranging the instructions so that the byte register load isn't
|
||||||
immediately followed by the word register usage, like so:
|
immediately followed by the word register usage, like so:
|
||||||
|
|
||||||
mov ah,o
|
```nasm
|
||||||
:
|
mov ah,o
|
||||||
mov al,[MemVar2]
|
:
|
||||||
mov cx,[MemVar1]
|
mov al,[MemVar2]
|
||||||
add cx,ax
|
mov cx,[MemVar1]
|
||||||
|
add cx,ax
|
||||||
|
```
|
||||||
|
|
||||||
Strange as it may seem, this rule is neither arbitrary nor nonsensical.
|
Strange as it may seem, this rule is neither arbitrary nor nonsensical.
|
||||||
Basically, when a byte destination register is part of a word source
|
Basically, when a byte destination register is part of a word source
|
||||||
|
|
@ -135,10 +147,12 @@ works.
|
||||||
In case you're curious, there's no such penalty for the typical **XLAT**
|
In case you're curious, there's no such penalty for the typical **XLAT**
|
||||||
sequence like
|
sequence like
|
||||||
|
|
||||||
mov bx,offset MemTable
|
```nasm
|
||||||
:
|
mov bx,offset MemTable
|
||||||
mov al,[si]
|
:
|
||||||
xlat
|
mov al,[si]
|
||||||
|
xlat
|
||||||
|
```
|
||||||
|
|
||||||
even though AL must be converted to a word by **XLAT** before it can be
|
even though AL must be converted to a word by **XLAT** before it can be
|
||||||
added to BX and used to address memory. In fact, none of the penalties
|
added to BX and used to address memory. In fact, none of the penalties
|
||||||
|
|
|
||||||
105
12-04.md
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
|
any byte register is loaded, and then two cycles later any register is
|
||||||
used to point to memory, one cycle is lost. So, for example, this code
|
used to point to memory, one cycle is lost. So, for example, this code
|
||||||
|
|
||||||
mov al,bl
|
```nasm
|
||||||
mov cx,dx
|
mov al,bl
|
||||||
mov si,[di]
|
mov cx,dx
|
||||||
|
mov si,[di]
|
||||||
|
```
|
||||||
|
|
||||||
takes four rather than the expected three cycles to execute. Note that
|
takes four rather than the expected three cycles to execute. Note that
|
||||||
it is *not* required that the byte register be part of the register used
|
it is *not* required that the byte register be part of the register used
|
||||||
|
|
@ -35,17 +37,20 @@ to address memory; any byte register will do the trick.
|
||||||
Worse still, loading byte registers both one and two cycles before a
|
Worse still, loading byte registers both one and two cycles before a
|
||||||
register is used to address memory costs two cycles, as in
|
register is used to address memory costs two cycles, as in
|
||||||
|
|
||||||
mov bl,al
|
```nasm
|
||||||
mov cl,3
|
mov bl,al
|
||||||
mov bx,[si]
|
mov cl,3
|
||||||
|
mov bx,[si]
|
||||||
|
```
|
||||||
which takes five rather than three cycles to run. However, there is *no*
|
which takes five rather than three cycles to run. However, there is *no*
|
||||||
penalty if a byte register is loaded one cycle but not two cycles before
|
penalty if a byte register is loaded one cycle but not two cycles before
|
||||||
a register is used to address memory. Therefore,
|
a register is used to address memory. Therefore,
|
||||||
|
|
||||||
mov cx,3
|
```nasm
|
||||||
mov dl,al
|
mov cx,3
|
||||||
mov si,[bx]
|
mov dl,al
|
||||||
|
mov si,[bx]
|
||||||
|
```
|
||||||
|
|
||||||
runs in the expected three cycles.
|
runs in the expected three cycles.
|
||||||
|
|
||||||
|
|
@ -57,9 +62,11 @@ its interaction with the other rules—could lead to considerable
|
||||||
performance loss in seemingly air-tight code. For instance, a casual
|
performance loss in seemingly air-tight code. For instance, a casual
|
||||||
observer would expect the following code to run in 3 cycles:
|
observer would expect the following code to run in 3 cycles:
|
||||||
|
|
||||||
mov bx,offset MemVar
|
```nasm
|
||||||
mov cl,al
|
mov bx,offset MemVar
|
||||||
mov ax,[bx]
|
mov cl,al
|
||||||
|
mov ax,[bx]
|
||||||
|
```
|
||||||
|
|
||||||
A more sophisticated programmer would expect to lose one cycle, because
|
A more sophisticated programmer would expect to lose one cycle, because
|
||||||
BX is loaded two cycles before being used to address memory. In fact,
|
BX is loaded two cycles before being used to address memory. In fact,
|
||||||
|
|
@ -97,43 +104,47 @@ addressing memory.
|
||||||
|
|
||||||
**LISTING 12.1 LST12-1.ASM**
|
**LISTING 12.1 LST12-1.ASM**
|
||||||
|
|
||||||
; Measures the effect of loading a byte register 2 cycles before
|
```nasm
|
||||||
; using a register to address memory.
|
; Measures the effect of loading a byte register 2 cycles before
|
||||||
mov bp,2 ;run the test code twice to make sure
|
; using a register to address memory.
|
||||||
; it's cached
|
mov bp,2 ;run the test code twice to make sure
|
||||||
sub bx,bx
|
; it's cached
|
||||||
CacheFillLoop:
|
sub bx,bx
|
||||||
call ZTimerOn ;start timing
|
CacheFillLoop:
|
||||||
rept 1000
|
call ZTimerOn ;start timing
|
||||||
mov dl,cl
|
rept 1000
|
||||||
nop
|
mov dl,cl
|
||||||
mov ax,[bx]
|
nop
|
||||||
endm
|
mov ax,[bx]
|
||||||
call ZTimerOff ;stop timing
|
endm
|
||||||
dec bp
|
call ZTimerOff ;stop timing
|
||||||
jz Done
|
dec bp
|
||||||
jmp CacheFillLoop
|
jz Done
|
||||||
Done:
|
jmp CacheFillLoop
|
||||||
|
Done:
|
||||||
|
```
|
||||||
|
|
||||||
**LISTING 12.2 LST12-2.ASM**
|
**LISTING 12.2 LST12-2.ASM**
|
||||||
|
|
||||||
; Measures the effect of loading a byte register 1 cycle before
|
```nasm
|
||||||
; using a register to address memory.
|
; Measures the effect of loading a byte register 1 cycle before
|
||||||
mov bp,2 ;run the test code twice to make sure
|
; using a register to address memory.
|
||||||
; it's cached
|
mov bp,2 ;run the test code twice to make sure
|
||||||
sub bx,bx
|
; it's cached
|
||||||
CacheFillLoop:
|
sub bx,bx
|
||||||
call ZTimerOn ;start timing
|
CacheFillLoop:
|
||||||
rept 1000
|
call ZTimerOn ;start timing
|
||||||
nop
|
rept 1000
|
||||||
mov dl,cl
|
nop
|
||||||
mov ax,[bx]
|
mov dl,cl
|
||||||
endm
|
mov ax,[bx]
|
||||||
call ZTimerOff ;stop timing
|
endm
|
||||||
dec bp
|
call ZTimerOff ;stop timing
|
||||||
jz Done
|
dec bp
|
||||||
jmp CacheFillLoop
|
jz Done
|
||||||
Done:
|
jmp CacheFillLoop
|
||||||
|
Done:
|
||||||
|
```
|
||||||
|
|
||||||
Note that Listings 12.1 and 12.2 each repeat the timing of the code
|
Note that Listings 12.1 and 12.2 each repeat the timing of the code
|
||||||
under test a second time, to make sure that the instructions are in the
|
under test a second time, to make sure that the instructions are in the
|
||||||
|
|
|
||||||
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**
|
**LISTING 13.1 L13-1.ASM**
|
||||||
|
|
||||||
mov di,[bp+OFFS] ;get the next pair of characters
|
```nasm
|
||||||
mov bl,[di] ;get the state value for the pair
|
mov di,[bp+OFFS] ;get the next pair of characters
|
||||||
add dx,[bx+8000h] ;increment word and line count
|
mov bl,[di] ;get the state value for the pair
|
||||||
; appropriately for the pair
|
add dx,[bx+8000h] ;increment word and line count
|
||||||
|
; appropriately for the pair
|
||||||
|
```
|
||||||
|
|
||||||
Listing 13.1 looks as tight as it could be, with just two one-cycle
|
Listing 13.1 looks as tight as it could be, with just two one-cycle
|
||||||
instructions, one two-cycle instruction, and no branches. It *is* tight,
|
instructions, one two-cycle instruction, and no branches. It *is* tight,
|
||||||
|
|
@ -113,10 +115,12 @@ the intervening instruction takes two cycles, there's no penalty at all.
|
||||||
|
|
||||||
**LISTING 13.2 L13-2.ASM**
|
**LISTING 13.2 L13-2.ASM**
|
||||||
|
|
||||||
mov bl,[di] ;get the state value for the pair
|
```nasm
|
||||||
mov di,[bp+OFFS] ;get the next pair of characters
|
mov bl,[di] ;get the state value for the pair
|
||||||
add dx,[bx+8000h] ;increment word and line count
|
mov di,[bp+OFFS] ;get the next pair of characters
|
||||||
; appropriately for the pair
|
add dx,[bx+8000h] ;increment word and line count
|
||||||
|
; appropriately for the pair
|
||||||
|
```
|
||||||
|
|
||||||
At this point, Terje had nearly doubled the performance of this code
|
At this point, Terje had nearly doubled the performance of this code
|
||||||
simply by moving one instruction. (Note that swapping the instructions
|
simply by moving one instruction. (Note that swapping the instructions
|
||||||
|
|
|
||||||
54
13-02.md
54
13-02.md
|
|
@ -22,18 +22,22 @@ throughput of two cycles/char."
|
||||||
|
|
||||||
**LISTING 13.3 L13-3.ASM**
|
**LISTING 13.3 L13-3.ASM**
|
||||||
|
|
||||||
mov bl,[di] ;get the state value for the pair
|
```nasm
|
||||||
mov di,[bp+OFFS] ;get the next pair of characters
|
mov bl,[di] ;get the state value for the pair
|
||||||
mov ax,[bx+8000h] ;increment word and line count
|
mov di,[bp+OFFS] ;get the next pair of characters
|
||||||
add dx,ax ; appropriately for the pair
|
mov ax,[bx+8000h] ;increment word and line count
|
||||||
|
add dx,ax ; appropriately for the pair
|
||||||
|
```
|
||||||
|
|
||||||
**LISTING 13.4 L13-4.ASM**
|
**LISTING 13.4 L13-4.ASM**
|
||||||
|
|
||||||
mov bl,[di] ;get the state value for the pair
|
```nasm
|
||||||
mov di,[bp+OFFS] ;get the next pair of characters
|
mov bl,[di] ;get the state value for the pair
|
||||||
add dx,ax ;increment word and line count
|
mov di,[bp+OFFS] ;get the next pair of characters
|
||||||
; appropriately for the pair
|
add dx,ax ;increment word and line count
|
||||||
mov ax,[bx+8000h] ;get increments for next time
|
; appropriately for the pair
|
||||||
|
mov ax,[bx+8000h] ;get increments for next time
|
||||||
|
```
|
||||||
|
|
||||||
I'd like to point out two fairly remarkable things. First, the single
|
I'd like to point out two fairly remarkable things. First, the single
|
||||||
cycle that Terje saved in Listing 13.4 sped up his entire word-counting
|
cycle that Terje saved in Listing 13.4 sped up his entire word-counting
|
||||||
|
|
@ -57,9 +61,11 @@ and 16-bit registers are not valid operands.) The obvious use of
|
||||||
first in memory, also called *little endian*) to Motorola format (most
|
first in memory, also called *little endian*) to Motorola format (most
|
||||||
significant byte first in memory, or *big endian*), like so:
|
significant byte first in memory, or *big endian*), like so:
|
||||||
|
|
||||||
lodsd
|
```nasm
|
||||||
bswap
|
lodsd
|
||||||
stosd
|
bswap
|
||||||
|
stosd
|
||||||
|
```
|
||||||
|
|
||||||
**BSWAP** can also be useful for reversing the order of pixel bits from
|
**BSWAP** can also be useful for reversing the order of pixel bits from
|
||||||
a bitmap so that they can be rotated 32 bits at a time with an
|
a bitmap so that they can be rotated 32 bits at a time with an
|
||||||
|
|
@ -94,17 +100,19 @@ memory, isn't it?
|
||||||
|
|
||||||
**LISTING 13.5 L13-5.ASM**
|
**LISTING 13.5 L13-5.ASM**
|
||||||
|
|
||||||
mov cx,[initialskip]
|
```nasm
|
||||||
shl ecx,16 ;put skip value in upper half of ECX
|
mov cx,[initialskip]
|
||||||
mov cx,100 ;put loop count in CX
|
shl ecx,16 ;put skip value in upper half of ECX
|
||||||
looptop:
|
mov cx,100 ;put loop count in CX
|
||||||
:
|
looptop:
|
||||||
ror ecx,16 ;make skip value word accessible in CX
|
:
|
||||||
add bx,cx ;skip BX ahead
|
ror ecx,16 ;make skip value word accessible in CX
|
||||||
inc cx ;set next skip value
|
add bx,cx ;skip BX ahead
|
||||||
ror ecx,16 ;put loop count in CX
|
inc cx ;set next skip value
|
||||||
dec cx ;count down loop
|
ror ecx,16 ;put loop count in CX
|
||||||
jnz looptop
|
dec cx ;count down loop
|
||||||
|
jnz looptop
|
||||||
|
```
|
||||||
|
|
||||||
Not necessarily. Shifts and rotates are among the worst performing
|
Not necessarily. Shifts and rotates are among the worst performing
|
||||||
instructions of the 486, taking 2 to 3 cycles to execute. Thus, it takes
|
instructions of the 486, taking 2 to 3 cycles to execute. Thus, it takes
|
||||||
|
|
|
||||||
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**
|
**LISTING 13.6 L13-6.ASM**
|
||||||
|
|
||||||
mov cx,[initialskip]
|
```nasm
|
||||||
bswap ecx ;put skip value in upper half of ECX
|
mov cx,[initialskip]
|
||||||
mov cx,100 ;put loop count in CX
|
bswap ecx ;put skip value in upper half of ECX
|
||||||
looptop:
|
mov cx,100 ;put loop count in CX
|
||||||
:
|
looptop:
|
||||||
bswap ecx ;make skip value word accessible in CX
|
:
|
||||||
add bx,cx ;skip BX ahead
|
bswap ecx ;make skip value word accessible in CX
|
||||||
inc cx ;set next skip value
|
add bx,cx ;skip BX ahead
|
||||||
bswap ecx ;put loop count in CX
|
inc cx ;set next skip value
|
||||||
dec cx ;count down loop
|
bswap ecx ;put loop count in CX
|
||||||
jnz looptop
|
dec cx ;count down loop
|
||||||
|
jnz looptop
|
||||||
|
```
|
||||||
|
|
||||||
### Pushing and Popping Memory {#Heading5}
|
### Pushing and Popping Memory {#Heading5}
|
||||||
|
|
||||||
|
|
@ -44,12 +46,16 @@ contrast, loading a memory location into a register takes only one
|
||||||
cycle, and pushing a register takes just 1 more cycle, for a total of
|
cycle, and pushing a register takes just 1 more cycle, for a total of
|
||||||
two cycles. Therefore,
|
two cycles. Therefore,
|
||||||
|
|
||||||
mov ax,[bx]
|
```nasm
|
||||||
push ax
|
mov ax,[bx]
|
||||||
|
push ax
|
||||||
|
```
|
||||||
|
|
||||||
is twice as fast as
|
is twice as fast as
|
||||||
|
|
||||||
push word ptr [bx]
|
```nasm
|
||||||
|
push word ptr [bx]
|
||||||
|
```
|
||||||
|
|
||||||
and the only cost is that the previous contents of AX are destroyed.
|
and the only cost is that the previous contents of AX are destroyed.
|
||||||
|
|
||||||
|
|
@ -98,9 +104,11 @@ disassembly in a debugger or by having the assembler generate a listing
|
||||||
file. You could then insert the n-bit version of **SHL AX,1** in your
|
file. You could then insert the n-bit version of **SHL AX,1** in your
|
||||||
code as follows:
|
code as follows:
|
||||||
|
|
||||||
mov ax,1
|
```nasm
|
||||||
db 0c1h, 0e0h, 001h
|
mov ax,1
|
||||||
mov dx,ax
|
db 0c1h, 0e0h, 001h
|
||||||
|
mov dx,ax
|
||||||
|
```
|
||||||
|
|
||||||
At the end of this sequence, DX will contain 2, and the fast n-bit
|
At the end of this sequence, DX will contain 2, and the fast n-bit
|
||||||
version of **SHL AX,1** will have executed. If you use this approach,
|
version of **SHL AX,1** will have executed. If you use this approach,
|
||||||
|
|
|
||||||
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
|
any register may serve as the potentially scaled index register. For
|
||||||
example,
|
example,
|
||||||
|
|
||||||
mov al,BaseTable[ecx+edx*4]
|
```nasm
|
||||||
|
mov al,BaseTable[ecx+edx*4]
|
||||||
|
```
|
||||||
|
|
||||||
uses a perfectly valid 32-bit address, with the byte accessed being the
|
uses a perfectly valid 32-bit address, with the byte accessed being the
|
||||||
one at the offset in DS pointed to by the sum of EDX times 4 plus the
|
one at the offset in DS pointed to by the sum of EDX times 4 plus the
|
||||||
|
|
@ -82,21 +84,25 @@ registers when they're needed, but if you find yourself using them
|
||||||
inside key loops, you should see if it's possible to move the index
|
inside key loops, you should see if it's possible to move the index
|
||||||
calculation outside the loop as, for example, in a loop like this:
|
calculation outside the loop as, for example, in a loop like this:
|
||||||
|
|
||||||
LoopTop:
|
```nasm
|
||||||
add ax,DataTable[ebx*2]
|
LoopTop:
|
||||||
inc ebx
|
add ax,DataTable[ebx*2]
|
||||||
dec cx
|
inc ebx
|
||||||
jnz LoopTop
|
dec cx
|
||||||
|
jnz LoopTop
|
||||||
|
```
|
||||||
|
|
||||||
You could change this to the following for greater performance:
|
You could change this to the following for greater performance:
|
||||||
|
|
||||||
add ebx,ebx ;ebx*2
|
```nasm
|
||||||
LoopTop:
|
add ebx,ebx ;ebx*2
|
||||||
add ax,DataTable[ebx]
|
LoopTop:
|
||||||
add ebxX,2
|
add ax,DataTable[ebx]
|
||||||
dec cx
|
add ebxX,2
|
||||||
jnz LoopTop
|
dec cx
|
||||||
shr ebx,1 ;ebx*2/2
|
jnz LoopTop
|
||||||
|
shr ebx,1 ;ebx*2/2
|
||||||
|
```
|
||||||
|
|
||||||
I'll end this chapter with two more quirks of 32-bit addressing. First,
|
I'll end this chapter with two more quirks of 32-bit addressing. First,
|
||||||
as with 16-bit addressing, addressing that uses EBP as a base register
|
as with 16-bit addressing, addressing that uses EBP as a base register
|
||||||
|
|
|
||||||
256
14-04.md
256
14-04.md
|
|
@ -12,148 +12,152 @@ pages: 268-271
|
||||||
|
|
||||||
**LISTING 14.1 L14-1.C**
|
**LISTING 14.1 L14-1.C**
|
||||||
|
|
||||||
/* Searches a buffer for a specified pattern. In case of a mismatch,
|
```c
|
||||||
uses the value of the mismatched byte to skip across as many
|
/* Searches a buffer for a specified pattern. In case of a mismatch,
|
||||||
potential match locations as possible (partial Boyer-Moore).
|
uses the value of the mismatched byte to skip across as many
|
||||||
Returns start offset of first match searching forward, or NULL if
|
potential match locations as possible (partial Boyer-Moore).
|
||||||
no match is found.
|
Returns start offset of first match searching forward, or NULL if
|
||||||
Tested with Borland C++ in C mode and the small model. */
|
no match is found.
|
||||||
|
Tested with Borland C++ in C mode and the small model. */
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
unsigned char * FindString(unsigned char * BufferPtr,
|
unsigned char * FindString(unsigned char * BufferPtr,
|
||||||
unsigned int BufferLength, unsigned char * PatternPtr,
|
unsigned int BufferLength, unsigned char * PatternPtr,
|
||||||
unsigned int PatternLength)
|
unsigned int PatternLength)
|
||||||
{
|
{
|
||||||
unsigned char * WorkingPatternPtr, * WorkingBufferPtr;
|
unsigned char * WorkingPatternPtr, * WorkingBufferPtr;
|
||||||
unsigned int CompCount, SkipTable[256], Skip, DistanceMatched;
|
unsigned int CompCount, SkipTable[256], Skip, DistanceMatched;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* Reject if the buffer is too small */
|
/* Reject if the buffer is too small */
|
||||||
if (BufferLength < PatternLength) return(NULL);
|
if (BufferLength < PatternLength) return(NULL);
|
||||||
|
|
||||||
/* Return an instant match if the pattern is 0-length */
|
/* Return an instant match if the pattern is 0-length */
|
||||||
if (PatternLength == 0) return(BufferPtr);
|
if (PatternLength == 0) return(BufferPtr);
|
||||||
|
|
||||||
/* Create the table of distances by which to skip ahead on
|
/* Create the table of distances by which to skip ahead on
|
||||||
mismatches for every possible byte value */
|
mismatches for every possible byte value */
|
||||||
/* Initialize all skips to the pattern length; this is the skip
|
/* Initialize all skips to the pattern length; this is the skip
|
||||||
distance for bytes that don't appear in the pattern */
|
distance for bytes that don't appear in the pattern */
|
||||||
for (i = 0; i < 256; i++) SkipTable[i] = PatternLength;
|
for (i = 0; i < 256; i++) SkipTable[i] = PatternLength;
|
||||||
/*Set the skip values for the bytes that do appear in the pattern
|
/*Set the skip values for the bytes that do appear in the pattern
|
||||||
to the distance from the byte location to the end of the
|
to the distance from the byte location to the end of the
|
||||||
pattern. When there are multiple instances of the same byte,
|
pattern. When there are multiple instances of the same byte,
|
||||||
the rightmost instance's skip value is used. Note that the
|
the rightmost instance's skip value is used. Note that the
|
||||||
rightmost byte of the pattern isn't entered in the skip table;
|
rightmost byte of the pattern isn't entered in the skip table;
|
||||||
if we get that value for a mismatch, we know for sure that the
|
if we get that value for a mismatch, we know for sure that the
|
||||||
right end of the pattern has already passed the mismatch
|
right end of the pattern has already passed the mismatch
|
||||||
location, so this is not a relevant byte for skipping purposes */
|
location, so this is not a relevant byte for skipping purposes */
|
||||||
for (i = 0; i < (PatternLength - 1); i++)
|
for (i = 0; i < (PatternLength - 1); i++)
|
||||||
SkipTable[PatternPtr[i]] = PatternLength - i - 1;
|
SkipTable[PatternPtr[i]] = PatternLength - i - 1;
|
||||||
|
|
||||||
/* Point to rightmost byte of the pattern */
|
/* Point to rightmost byte of the pattern */
|
||||||
PatternPtr += PatternLength - 1;
|
PatternPtr += PatternLength - 1;
|
||||||
/* Point to last (rightmost) byte of the first potential pattern
|
/* Point to last (rightmost) byte of the first potential pattern
|
||||||
match location in the buffer */
|
match location in the buffer */
|
||||||
BufferPtr += PatternLength - 1;
|
BufferPtr += PatternLength - 1;
|
||||||
/* Count of number of potential pattern match locations in
|
/* Count of number of potential pattern match locations in
|
||||||
buffer */
|
buffer */
|
||||||
BufferLength -= PatternLength - 1;
|
BufferLength -= PatternLength - 1;
|
||||||
|
|
||||||
/* Search the buffer */
|
/* Search the buffer */
|
||||||
while (1) {
|
while (1) {
|
||||||
/* See if we have a match at this buffer location */
|
/* See if we have a match at this buffer location */
|
||||||
WorkingPatternPtr = PatternPtr;
|
WorkingPatternPtr = PatternPtr;
|
||||||
WorkingBufferPtr = BufferPtr;
|
WorkingBufferPtr = BufferPtr;
|
||||||
CompCount = PatternLength;
|
CompCount = PatternLength;
|
||||||
/* Compare the pattern and the buffer location, searching from
|
/* Compare the pattern and the buffer location, searching from
|
||||||
high memory toward low (right to left) */
|
high memory toward low (right to left) */
|
||||||
while (*WorkingPatternPtr— == *WorkingBufferPtr—) {
|
while (*WorkingPatternPtr— == *WorkingBufferPtr—) {
|
||||||
/* If we've matched the entire pattern, it's a match */
|
/* If we've matched the entire pattern, it's a match */
|
||||||
if (-CompCount == 0)
|
if (-CompCount == 0)
|
||||||
/* Return a pointer to the start of the match location */
|
/* Return a pointer to the start of the match location */
|
||||||
return(BufferPtr - PatternLength + 1);
|
return(BufferPtr - PatternLength + 1);
|
||||||
}
|
}
|
||||||
/* It's a mismatch; let's see what we can learn from it */
|
/* It's a mismatch; let's see what we can learn from it */
|
||||||
WorkingBufferPtr++; /* point back to the mismatch location */
|
WorkingBufferPtr++; /* point back to the mismatch location */
|
||||||
/* # of bytes that did match */
|
/* # of bytes that did match */
|
||||||
DistanceMatched = BufferPtr - WorkingBufferPtr;
|
DistanceMatched = BufferPtr - WorkingBufferPtr;
|
||||||
/*If, based on the mismatch character, we can't even skip ahead
|
/*If, based on the mismatch character, we can't even skip ahead
|
||||||
as far as where we started this particular comparison, then
|
as far as where we started this particular comparison, then
|
||||||
just advance by 1 to the next potential match; otherwise,
|
just advance by 1 to the next potential match; otherwise,
|
||||||
skip ahead from the mismatch location by the skip distance
|
skip ahead from the mismatch location by the skip distance
|
||||||
for the mismatch character */
|
for the mismatch character */
|
||||||
if (SkipTable[*WorkingBufferPtr] <= DistanceMatched)
|
if (SkipTable[*WorkingBufferPtr] <= DistanceMatched)
|
||||||
Skip = 1; /* skip doesn't do any good, advance by 1 */
|
Skip = 1; /* skip doesn't do any good, advance by 1 */
|
||||||
else
|
else
|
||||||
/* Use skip value, accounting for distance covered by the
|
/* Use skip value, accounting for distance covered by the
|
||||||
partial match */
|
partial match */
|
||||||
Skip = SkipTable[*WorkingBufferPtr] - DistanceMatched;
|
Skip = SkipTable[*WorkingBufferPtr] - DistanceMatched;
|
||||||
/* If skipping ahead would exhaust the buffer, we're done
|
/* If skipping ahead would exhaust the buffer, we're done
|
||||||
without a match */
|
without a match */
|
||||||
if (Skip >= BufferLength) return(NULL);
|
if (Skip >= BufferLength) return(NULL);
|
||||||
/* Skip ahead and perform the next comparison */
|
/* Skip ahead and perform the next comparison */
|
||||||
BufferLength -= Skip;
|
BufferLength -= Skip;
|
||||||
BufferPtr += Skip;
|
BufferPtr += Skip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
**LISTING 14.2 L14-2.C**
|
**LISTING 14.2 L14-2.C**
|
||||||
|
|
||||||
/* Program to exercise buffer-search routines in Listings 14.1 & 14.3.
|
```c
|
||||||
(Must be modified to put copy of pattern as sentinel at end of the
|
/* Program to exercise buffer-search routines in Listings 14.1 & 14.3.
|
||||||
search buffer in order to be used with Listing 14.4.) */
|
(Must be modified to put copy of pattern as sentinel at end of the
|
||||||
|
search buffer in order to be used with Listing 14.4.) */
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
#define DISPLAY_LENGTH 40
|
#define DISPLAY_LENGTH 40
|
||||||
#define BUFFER_SIZE 0x8000
|
#define BUFFER_SIZE 0x8000
|
||||||
|
|
||||||
extern unsigned char * FindString(unsigned char *, unsigned int,
|
extern unsigned char * FindString(unsigned char *, unsigned int,
|
||||||
unsigned char *, unsigned int);
|
unsigned char *, unsigned int);
|
||||||
void main(void);
|
void main(void);
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
unsigned char TempBuffer[DISPLAY_LENGTH+1];
|
unsigned char TempBuffer[DISPLAY_LENGTH+1];
|
||||||
unsigned char Filename[150], Pattern[150], *MatchPtr, *TestBuffer;
|
unsigned char Filename[150], Pattern[150], *MatchPtr, *TestBuffer;
|
||||||
int Handle;
|
int Handle;
|
||||||
unsigned int WorkingLength;
|
unsigned int WorkingLength;
|
||||||
|
|
||||||
printf("File to search:");
|
printf("File to search:");
|
||||||
gets(Filename);
|
gets(Filename);
|
||||||
printf("Pattern for which to search:");
|
printf("Pattern for which to search:");
|
||||||
gets(Pattern);
|
gets(Pattern);
|
||||||
|
|
||||||
if ( (Handle = open(Filename, O_RDONLY | O_BINARY)) == -1 ) {
|
if ( (Handle = open(Filename, O_RDONLY | O_BINARY)) == -1 ) {
|
||||||
printf("Can't open file: %s\n", Filename); exit(1);
|
printf("Can't open file: %s\n", Filename); exit(1);
|
||||||
}
|
}
|
||||||
/* Get memory in which to buffer the data */
|
/* Get memory in which to buffer the data */
|
||||||
if ( (TestBuffer=(unsigned char *)malloc(BUFFER_SIZE+1)) == NULL) {
|
if ( (TestBuffer=(unsigned char *)malloc(BUFFER_SIZE+1)) == NULL) {
|
||||||
printf("Can't get enough memory\n"); exit(1);
|
printf("Can't get enough memory\n"); exit(1);
|
||||||
}
|
}
|
||||||
/* Process a BUFFER_SIZE chunk */
|
/* Process a BUFFER_SIZE chunk */
|
||||||
if ( (int)(WorkingLength =
|
if ( (int)(WorkingLength =
|
||||||
read(Handle, TestBuffer, BUFFER_SIZE)) == -1 ) {
|
read(Handle, TestBuffer, BUFFER_SIZE)) == -1 ) {
|
||||||
printf("Error reading file %s\n", Filename); exit(1);
|
printf("Error reading file %s\n", Filename); exit(1);
|
||||||
}
|
}
|
||||||
TestBuffer[WorkingLength] = 0; /* 0-terminate buffer for printf */
|
TestBuffer[WorkingLength] = 0; /* 0-terminate buffer for printf */
|
||||||
/* Search for the pattern and report the results */
|
/* Search for the pattern and report the results */
|
||||||
if ((MatchPtr = FindString(TestBuffer, WorkingLength, Pattern,
|
if ((MatchPtr = FindString(TestBuffer, WorkingLength, Pattern,
|
||||||
(unsigned int) strlen(Pattern))) == NULL) {
|
(unsigned int) strlen(Pattern))) == NULL) {
|
||||||
/* Pattern wasn't found */
|
/* Pattern wasn't found */
|
||||||
printf("\"%s\" not found\n", Pattern);
|
printf("\"%s\" not found\n", Pattern);
|
||||||
} else {
|
} else {
|
||||||
/* Pattern was found. Zero-terminate TempBuffer; strncpy
|
/* Pattern was found. Zero-terminate TempBuffer; strncpy
|
||||||
won't do it if DISPLAY_LENGTH characters are copied */
|
won't do it if DISPLAY_LENGTH characters are copied */
|
||||||
TempBuffer[DISPLAY_LENGTH] = 0;
|
TempBuffer[DISPLAY_LENGTH] = 0;
|
||||||
printf("\"%s\" found. Next %d characters at match:\n\"%s\"\n",
|
printf("\"%s\" found. Next %d characters at match:\n\"%s\"\n",
|
||||||
Pattern, DISPLAY_LENGTH,
|
Pattern, DISPLAY_LENGTH,
|
||||||
strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH));
|
strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH));
|
||||||
}
|
}
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Well, architecture carries a lot of weight, but it sure as heck isn't
|
Well, architecture carries a lot of weight, but it sure as heck isn't
|
||||||
destiny. I had simply fallen into the trap of figuring that the
|
destiny. I had simply fallen into the trap of figuring that the
|
||||||
|
|
|
||||||
318
14-05.md
318
14-05.md
|
|
@ -12,163 +12,165 @@ pages: 271-274
|
||||||
|
|
||||||
**LISTING 14.3 L14-3.ASM**
|
**LISTING 14.3 L14-3.ASM**
|
||||||
|
|
||||||
; Searches a buffer for a specified pattern. In case of a mismatch,
|
```nasm
|
||||||
; uses the value of the mismatched byte to skip across as many
|
; Searches a buffer for a specified pattern. In case of a mismatch,
|
||||||
; potential match locations as possible (partial Boyer-Moore).
|
; uses the value of the mismatched byte to skip across as many
|
||||||
; Returns start offset of first match searching forward, or NULL if
|
; potential match locations as possible (partial Boyer-Moore).
|
||||||
; no match is found.
|
; Returns start offset of first match searching forward, or NULL if
|
||||||
; Tested with TASM.
|
; no match is found.
|
||||||
; C near-callable as:
|
; Tested with TASM.
|
||||||
; unsigned char * FindString(unsigned char * BufferPtr,
|
; C near-callable as:
|
||||||
; unsigned int BufferLength, unsigned char * PatternPtr,
|
; unsigned char * FindString(unsigned char * BufferPtr,
|
||||||
; unsigned int PatternLength);
|
; unsigned int BufferLength, unsigned char * PatternPtr,
|
||||||
|
; unsigned int PatternLength);
|
||||||
|
|
||||||
parms struc
|
parms struc
|
||||||
dw 2 dup(?) ;pushed BP & return address
|
dw 2 dup(?) ;pushed BP & return address
|
||||||
BufferPtr dw ? ;pointer to buffer to be searched
|
BufferPtr dw ? ;pointer to buffer to be searched
|
||||||
BufferLength dw ? ;# of bytes in buffer to be searched
|
BufferLength dw ? ;# of bytes in buffer to be searched
|
||||||
PatternPtr dw ? ;pointer to pattern for which to search
|
PatternPtr dw ? ;pointer to pattern for which to search
|
||||||
PatternLength dw ? ;length of pattern for which to search
|
PatternLength dw ? ;length of pattern for which to search
|
||||||
parms ends
|
parms ends
|
||||||
|
|
||||||
.model small
|
.model small
|
||||||
.code
|
.code
|
||||||
public _FindString
|
public _FindString
|
||||||
_FindString proc near
|
_FindString proc near
|
||||||
cld
|
cld
|
||||||
push bp ;preserve caller's stack frame
|
push bp ;preserve caller's stack frame
|
||||||
mov bp,sp ;point to our stack frame
|
mov bp,sp ;point to our stack frame
|
||||||
push si ;preserve caller's register variables
|
push si ;preserve caller's register variables
|
||||||
push di
|
push di
|
||||||
sub sp,256*2 ;allocate space for SkipTable
|
sub sp,256*2 ;allocate space for SkipTable
|
||||||
; Create the table of distances by which to skip ahead on mismatches
|
; Create the table of distances by which to skip ahead on mismatches
|
||||||
; for every possible byte value. First, initialize all skips to the
|
; for every possible byte value. First, initialize all skips to the
|
||||||
; pattern length; this is the skip distance for bytes that don't
|
; pattern length; this is the skip distance for bytes that don't
|
||||||
; appear in the pattern.
|
; appear in the pattern.
|
||||||
mov ax,[bp+PatternLength]
|
mov ax,[bp+PatternLength]
|
||||||
and ax,ax ;return an instant match if the pattern is
|
and ax,ax ;return an instant match if the pattern is
|
||||||
jz InstantMatch ;0-length
|
jz InstantMatch ;0-length
|
||||||
mov di,ds
|
mov di,ds
|
||||||
mov es,di ;ES=DS=SS
|
mov es,di ;ES=DS=SS
|
||||||
mov di,sp ;point to SkipBuffer
|
mov di,sp ;point to SkipBuffer
|
||||||
mov cx,256
|
mov cx,256
|
||||||
rep stosw
|
rep stosw
|
||||||
dec ax ;from now on, we only need
|
dec ax ;from now on, we only need
|
||||||
mov [bp+PatternLength],ax ; PatternLength - 1
|
mov [bp+PatternLength],ax ; PatternLength - 1
|
||||||
; Point to last (rightmost) byte of first potential pattern match
|
; Point to last (rightmost) byte of first potential pattern match
|
||||||
; location in buffer.
|
; location in buffer.
|
||||||
add [bp+BufferPtr],ax
|
add [bp+BufferPtr],ax
|
||||||
; Reject if buffer is too small, and set the count of the number of
|
; Reject if buffer is too small, and set the count of the number of
|
||||||
; potential pattern match locations in the buffer.
|
; potential pattern match locations in the buffer.
|
||||||
sub [bp+BufferLength],ax
|
sub [bp+BufferLength],ax
|
||||||
jbe NoMatch
|
jbe NoMatch
|
||||||
; Set the skip values for the bytes that do appear in the pattern to
|
; Set the skip values for the bytes that do appear in the pattern to
|
||||||
; the distance from the byte location to the end of the pattern.
|
; the distance from the byte location to the end of the pattern.
|
||||||
; When there are multiple instances of the same byte, the rightmost
|
; When there are multiple instances of the same byte, the rightmost
|
||||||
; instance's skip value is used. Note that the rightmost byte of the
|
; instance's skip value is used. Note that the rightmost byte of the
|
||||||
; pattern isn't entered in the skip table; if we get that value for
|
; pattern isn't entered in the skip table; if we get that value for
|
||||||
; a mismatch, we know for sure that the right end of the pattern has
|
; a mismatch, we know for sure that the right end of the pattern has
|
||||||
; already passed the mismatch location, so this is not a relevant byte
|
; already passed the mismatch location, so this is not a relevant byte
|
||||||
; for skipping purposes.
|
; for skipping purposes.
|
||||||
mov si,[bp+PatternPtr] ;point to start of pattern
|
mov si,[bp+PatternPtr] ;point to start of pattern
|
||||||
and ax,ax ;are there any skips to set?
|
and ax,ax ;are there any skips to set?
|
||||||
jz SetSkipDone ;no
|
jz SetSkipDone ;no
|
||||||
mov di,sp ;point to SkipBuffer
|
mov di,sp ;point to SkipBuffer
|
||||||
SetSkipLoop:
|
SetSkipLoop:
|
||||||
sub bx,bx ;prepare for word addressing off byte value
|
sub bx,bx ;prepare for word addressing off byte value
|
||||||
mov bl,[si] ;get the next pattern byte
|
mov bl,[si] ;get the next pattern byte
|
||||||
inc si ;advance the pattern pointer
|
inc si ;advance the pattern pointer
|
||||||
shl bx,1 ;prepare for word lookup
|
shl bx,1 ;prepare for word lookup
|
||||||
mov [di+bx],ax ;set the skip value when this byte value is
|
mov [di+bx],ax ;set the skip value when this byte value is
|
||||||
; the mismatch value in the buffer
|
; the mismatch value in the buffer
|
||||||
dec ax
|
dec ax
|
||||||
jnz SetSkipLoop
|
jnz SetSkipLoop
|
||||||
SetSkipDone:
|
SetSkipDone:
|
||||||
mov dl,[si] ;DL=rightmost pattern byte from now on
|
mov dl,[si] ;DL=rightmost pattern byte from now on
|
||||||
dec si ;point to next-to-rightmost byte of pattern
|
dec si ;point to next-to-rightmost byte of pattern
|
||||||
mov [bp+PatternPtr],si ; from now on
|
mov [bp+PatternPtr],si ; from now on
|
||||||
; Search the buffer.
|
; Search the buffer.
|
||||||
std ;for backward REPZ CMPSB
|
std ;for backward REPZ CMPSB
|
||||||
mov di,[bp+BufferPtr] ;point to first search location
|
mov di,[bp+BufferPtr] ;point to first search location
|
||||||
mov cx,[bp+BufferLength] ;# of match locations to check
|
mov cx,[bp+BufferLength] ;# of match locations to check
|
||||||
SearchLoop:
|
SearchLoop:
|
||||||
mov si,sp ;point SI to SkipTable
|
mov si,sp ;point SI to SkipTable
|
||||||
; Skip through until there's a match for the rightmost pattern byte.
|
; Skip through until there's a match for the rightmost pattern byte.
|
||||||
QuickSearchLoop:
|
QuickSearchLoop:
|
||||||
mov bl,[di] ;rightmost buffer byte at this location
|
mov bl,[di] ;rightmost buffer byte at this location
|
||||||
cmp dl,bl ;does it match the rightmost pattern byte?
|
cmp dl,bl ;does it match the rightmost pattern byte?
|
||||||
jz FullCompare ;yes, so keep going
|
jz FullCompare ;yes, so keep going
|
||||||
sub bh,bh ;convert to a word
|
sub bh,bh ;convert to a word
|
||||||
add bx,bx ;prepare for look-up in SkipTable
|
add bx,bx ;prepare for look-up in SkipTable
|
||||||
mov ax,[si+bx] ;get skip value from skip table for this
|
mov ax,[si+bx] ;get skip value from skip table for this
|
||||||
; mismatch value
|
; mismatch value
|
||||||
add di,ax ;BufferPtr += Skip;
|
add di,ax ;BufferPtr += Skip;
|
||||||
sub cx,ax ;BufferLength -= Skip;
|
sub cx,ax ;BufferLength -= Skip;
|
||||||
ja QuickSearchLoop ;continue if any buffer left
|
ja QuickSearchLoop ;continue if any buffer left
|
||||||
jmp short NoMatch
|
jmp short NoMatch
|
||||||
; Return a pointer to the start of the buffer (for 0-length pattern).
|
; Return a pointer to the start of the buffer (for 0-length pattern).
|
||||||
align 2
|
align 2
|
||||||
InstantMatch:
|
InstantMatch:
|
||||||
mov ax,[bp+BufferPtr]
|
mov ax,[bp+BufferPtr]
|
||||||
jmp short Done
|
jmp short Done
|
||||||
; Compare the pattern and the buffer location, searching from high
|
; Compare the pattern and the buffer location, searching from high
|
||||||
; memory toward low (right to left).
|
; memory toward low (right to left).
|
||||||
align 2
|
align 2
|
||||||
FullCompare:
|
FullCompare:
|
||||||
mov [bp+BufferPtr],di ;save the current state of
|
mov [bp+BufferPtr],di ;save the current state of
|
||||||
mov [bp+BufferLength],cx ; the search
|
mov [bp+BufferLength],cx ; the search
|
||||||
mov cx,[bp+PatternLength] ;# of bytes yet to compare
|
mov cx,[bp+PatternLength] ;# of bytes yet to compare
|
||||||
jcxz Match ;done if only one character
|
jcxz Match ;done if only one character
|
||||||
mov si,[bp+PatternPtr] ;point to next-to-rightmost bytes
|
mov si,[bp+PatternPtr] ;point to next-to-rightmost bytes
|
||||||
dec di ; of buffer location and pattern
|
dec di ; of buffer location and pattern
|
||||||
repz cmpsb ;compare the rest of the pattern
|
repz cmpsb ;compare the rest of the pattern
|
||||||
jz Match ;that's it; we've found a match
|
jz Match ;that's it; we've found a match
|
||||||
; It's a mismatch; let's see what we can learn from it.
|
; It's a mismatch; let's see what we can learn from it.
|
||||||
inc di ;compensate for 1-byte overrun of REPZ CMPSB;
|
inc di ;compensate for 1-byte overrun of REPZ CMPSB;
|
||||||
; point to mismatch location in buffer
|
; point to mismatch location in buffer
|
||||||
; # of bytes that did match.
|
; # of bytes that did match.
|
||||||
mov si,[bp+BufferPtr]
|
mov si,[bp+BufferPtr]
|
||||||
sub si,di
|
sub si,di
|
||||||
; If, based on the mismatch character, we can't even skip ahead as far
|
; If, based on the mismatch character, we can't even skip ahead as far
|
||||||
; as where we started this particular comparison, then just advance by
|
; as where we started this particular comparison, then just advance by
|
||||||
; 1 to the next potential match; otherwise, skip ahead from this
|
; 1 to the next potential match; otherwise, skip ahead from this
|
||||||
; comparison location by the skip distance for the mismatch character,
|
; comparison location by the skip distance for the mismatch character,
|
||||||
; less the distance covered by the partial match.
|
; less the distance covered by the partial match.
|
||||||
sub bx,bx ;prepare for word addressing off byte value
|
sub bx,bx ;prepare for word addressing off byte value
|
||||||
mov bl,[di] ;get the value of the mismatch byte in buffer
|
mov bl,[di] ;get the value of the mismatch byte in buffer
|
||||||
add bx,bx ;prepare for word look-up
|
add bx,bx ;prepare for word look-up
|
||||||
add bx,sp ;SP points to SkipTable
|
add bx,sp ;SP points to SkipTable
|
||||||
mov cx,[bx] ;get the skip value for this mismatch
|
mov cx,[bx] ;get the skip value for this mismatch
|
||||||
mov ax,1 ;assume we'll just advance to the next
|
mov ax,1 ;assume we'll just advance to the next
|
||||||
; potential match location
|
; potential match location
|
||||||
sub cx,si ;is the skip far enough to be worth taking?
|
sub cx,si ;is the skip far enough to be worth taking?
|
||||||
jna MoveAhead ;no, go with the default advance of 1
|
jna MoveAhead ;no, go with the default advance of 1
|
||||||
mov ax,cx ;yes; this is the distance to skip ahead from
|
mov ax,cx ;yes; this is the distance to skip ahead from
|
||||||
; the last potential match location checked
|
; the last potential match location checked
|
||||||
MoveAhead:
|
MoveAhead:
|
||||||
; Skip ahead and perform the next comparison, if there's any buffer
|
; Skip ahead and perform the next comparison, if there's any buffer
|
||||||
; left to check.
|
; left to check.
|
||||||
mov di,[bp+BufferPtr]
|
mov di,[bp+BufferPtr]
|
||||||
add di,ax ;BufferPtr += Skip;
|
add di,ax ;BufferPtr += Skip;
|
||||||
mov cx,[bp+BufferLength]
|
mov cx,[bp+BufferLength]
|
||||||
sub cx,ax ;BufferLength -= Skip;
|
sub cx,ax ;BufferLength -= Skip;
|
||||||
ja SearchLoop ;continue if any buffer left
|
ja SearchLoop ;continue if any buffer left
|
||||||
; Return a NULL pointer for no match.
|
; Return a NULL pointer for no match.
|
||||||
align 2
|
align 2
|
||||||
NoMatch:
|
NoMatch:
|
||||||
sub ax,ax
|
sub ax,ax
|
||||||
jmp short Done
|
jmp short Done
|
||||||
; Return start of match in buffer (BufferPtr - (PatternLength - 1)).
|
; Return start of match in buffer (BufferPtr - (PatternLength - 1)).
|
||||||
align 2
|
align 2
|
||||||
Match:
|
Match:
|
||||||
mov ax,[bp+BufferPtr]
|
mov ax,[bp+BufferPtr]
|
||||||
sub ax,[bp+PatternLength]
|
sub ax,[bp+PatternLength]
|
||||||
Done:
|
Done:
|
||||||
cld ;restore default direction flag
|
cld ;restore default direction flag
|
||||||
add sp,256*2 ;deallocate space for SkipTable
|
add sp,256*2 ;deallocate space for SkipTable
|
||||||
pop di ;restore caller's register variables
|
pop di ;restore caller's register variables
|
||||||
pop si
|
pop si
|
||||||
pop bp ;restore caller's stack frame
|
pop bp ;restore caller's stack frame
|
||||||
ret
|
ret
|
||||||
_FindString endp
|
_FindString endp
|
||||||
end
|
end
|
||||||
|
```
|
||||||
|
|
|
||||||
290
14-06.md
290
14-06.md
|
|
@ -35,152 +35,154 @@ about 60 percent faster than Listing 14.3.
|
||||||
|
|
||||||
**LISTING 14.4 L14-4.ASM**
|
**LISTING 14.4 L14-4.ASM**
|
||||||
|
|
||||||
; Searches a buffer for a specified pattern. In case of a mismatch,
|
```nasm
|
||||||
; uses the value of the mismatched byte to skip across as many
|
; Searches a buffer for a specified pattern. In case of a mismatch,
|
||||||
; potential match locations as possible (partial Boyer-Moore).
|
; uses the value of the mismatched byte to skip across as many
|
||||||
; Returns start offset of first match searching forward, or NULL if
|
; potential match locations as possible (partial Boyer-Moore).
|
||||||
; no match is found.
|
; Returns start offset of first match searching forward, or NULL if
|
||||||
; Requires that the pattern be no longer than 255 bytes, and that
|
; no match is found.
|
||||||
; there be a match for the pattern somewhere in the buffer (ie., a
|
; Requires that the pattern be no longer than 255 bytes, and that
|
||||||
; copy of the pattern should be placed as a sentinel at the end of
|
; there be a match for the pattern somewhere in the buffer (ie., a
|
||||||
; the buffer if the pattern isn't already known to be in the buffer).
|
; copy of the pattern should be placed as a sentinel at the end of
|
||||||
; Tested with TASM.
|
; the buffer if the pattern isn't already known to be in the buffer).
|
||||||
; C near-callable as:
|
; Tested with TASM.
|
||||||
; unsigned char * FindString(unsigned char * BufferPtr,
|
; C near-callable as:
|
||||||
; unsigned int BufferLength, unsigned char * PatternPtr,
|
; unsigned char * FindString(unsigned char * BufferPtr,
|
||||||
; unsigned int PatternLength);
|
; unsigned int BufferLength, unsigned char * PatternPtr,
|
||||||
|
; unsigned int PatternLength);
|
||||||
|
|
||||||
parms struc
|
parms struc
|
||||||
dw 2 dup(?) ;pushed BP & return address
|
dw 2 dup(?) ;pushed BP & return address
|
||||||
BufferPtr dw ? ;pointer to buffer to be searched
|
BufferPtr dw ? ;pointer to buffer to be searched
|
||||||
BufferLength dw ? ;# of bytes in buffer to be searched
|
BufferLength dw ? ;# of bytes in buffer to be searched
|
||||||
; (not used, actually)
|
; (not used, actually)
|
||||||
PatternPtr dw ? ;pointer to pattern for which to search
|
PatternPtr dw ? ;pointer to pattern for which to search
|
||||||
; (pattern *MUST* exist in the buffer)
|
; (pattern *MUST* exist in the buffer)
|
||||||
PatternLength dw ? ;length of pattern for which to search (must
|
PatternLength dw ? ;length of pattern for which to search (must
|
||||||
; be <= 255)
|
; be <= 255)
|
||||||
parms ends
|
parms ends
|
||||||
|
|
||||||
.model small
|
.model small
|
||||||
.code
|
.code
|
||||||
public _FindString
|
public _FindString
|
||||||
_FindString proc near
|
_FindString proc near
|
||||||
cld
|
cld
|
||||||
push bp ;preserve caller's stack frame
|
push bp ;preserve caller's stack frame
|
||||||
mov bp,sp ;point to our stack frame
|
mov bp,sp ;point to our stack frame
|
||||||
push si ;preserve caller's register variables
|
push si ;preserve caller's register variables
|
||||||
push di
|
push di
|
||||||
sub sp,256 ;allocate space for SkipTable
|
sub sp,256 ;allocate space for SkipTable
|
||||||
; Create the table of distances by which to skip ahead on mismatches
|
; Create the table of distances by which to skip ahead on mismatches
|
||||||
; for every possible byte value. First, initialize all skips to the
|
; for every possible byte value. First, initialize all skips to the
|
||||||
; pattern length; this is the skip distance for bytes that don't
|
; pattern length; this is the skip distance for bytes that don't
|
||||||
; appear in the pattern.
|
; appear in the pattern.
|
||||||
mov di,ds
|
mov di,ds
|
||||||
mov es,di ;ES=DS=SS
|
mov es,di ;ES=DS=SS
|
||||||
mov di,sp ;point to SkipBuffer
|
mov di,sp ;point to SkipBuffer
|
||||||
mov al,byte ptr [bp+PatternLength]
|
mov al,byte ptr [bp+PatternLength]
|
||||||
and al,al ;return an instant match if the pattern is
|
and al,al ;return an instant match if the pattern is
|
||||||
jz InstantMatch ; 0-length
|
jz InstantMatch ; 0-length
|
||||||
mov ah,al
|
mov ah,al
|
||||||
mov cx,256/2
|
mov cx,256/2
|
||||||
rep stosw
|
rep stosw
|
||||||
mov ax,[bp+PatternLength]
|
mov ax,[bp+PatternLength]
|
||||||
dec ax ;from now on, we only need
|
dec ax ;from now on, we only need
|
||||||
mov [bp+PatternLength],ax ; PatternLength - 1
|
mov [bp+PatternLength],ax ; PatternLength - 1
|
||||||
; Point to rightmost byte of first potential pattern match location
|
; Point to rightmost byte of first potential pattern match location
|
||||||
; in buffer.
|
; in buffer.
|
||||||
add [bp+BufferPtr],ax
|
add [bp+BufferPtr],ax
|
||||||
; Set the skip values for the bytes that do appear in the pattern to
|
; Set the skip values for the bytes that do appear in the pattern to
|
||||||
; the distance from the byte location to the end of the pattern.
|
; the distance from the byte location to the end of the pattern.
|
||||||
mov si,[bp+PatternPtr] ;point to start of pattern
|
mov si,[bp+PatternPtr] ;point to start of pattern
|
||||||
and ax,ax ;are there any skips to set?
|
and ax,ax ;are there any skips to set?
|
||||||
jz SetSkipDone ;no
|
jz SetSkipDone ;no
|
||||||
mov di,sp ;point to SkipBuffer
|
mov di,sp ;point to SkipBuffer
|
||||||
sub bx,bx ;prepare for word addressing off byte value
|
sub bx,bx ;prepare for word addressing off byte value
|
||||||
SetSkipLoop:
|
SetSkipLoop:
|
||||||
mov bl,[si] ;get the next pattern byte
|
mov bl,[si] ;get the next pattern byte
|
||||||
inc si ;advance the pattern pointer
|
inc si ;advance the pattern pointer
|
||||||
mov [di+bx],al ;set the skip value when this byte value is
|
mov [di+bx],al ;set the skip value when this byte value is
|
||||||
;the mismatch value in the buffer
|
;the mismatch value in the buffer
|
||||||
dec ax
|
dec ax
|
||||||
jnz SetSkipLoop
|
jnz SetSkipLoop
|
||||||
SetSkipDone:
|
SetSkipDone:
|
||||||
mov dl,[si] ;DL=rightmost pattern byte from now on
|
mov dl,[si] ;DL=rightmost pattern byte from now on
|
||||||
dec si ;point to next-to-rightmost byte of pattern
|
dec si ;point to next-to-rightmost byte of pattern
|
||||||
mov [bp+PatternPtr],si ; from now on
|
mov [bp+PatternPtr],si ; from now on
|
||||||
; Search the buffer.
|
; Search the buffer.
|
||||||
std ;for backward REPZ CMPSB
|
std ;for backward REPZ CMPSB
|
||||||
mov di,[bp+BufferPtr] ;point to the first search location
|
mov di,[bp+BufferPtr] ;point to the first search location
|
||||||
mov bx,sp ;point to SkipTable for XLAT
|
mov bx,sp ;point to SkipTable for XLAT
|
||||||
SearchLoop:
|
SearchLoop:
|
||||||
sub ah,ah ;used to convert AL to a word
|
sub ah,ah ;used to convert AL to a word
|
||||||
; Skip through until there's a match for the first pattern byte.
|
; Skip through until there's a match for the first pattern byte.
|
||||||
QuickSearchLoop:
|
QuickSearchLoop:
|
||||||
; See if we have a match at the first buffer location.
|
; See if we have a match at the first buffer location.
|
||||||
REPT 8 ;unroll loop 8 times to reduce branching
|
REPT 8 ;unroll loop 8 times to reduce branching
|
||||||
mov al,[di] ;next buffer byte
|
mov al,[di] ;next buffer byte
|
||||||
cmp dl,al ;does it match the pattern?
|
cmp dl,al ;does it match the pattern?
|
||||||
jz FullCompare ;yes, so keep going
|
jz FullCompare ;yes, so keep going
|
||||||
xlat ;no, look up the skip value for this mismatch
|
xlat ;no, look up the skip value for this mismatch
|
||||||
add di,ax ;BufferPtr += Skip;
|
add di,ax ;BufferPtr += Skip;
|
||||||
ENDM
|
ENDM
|
||||||
jmp QuickSearchLoop
|
jmp QuickSearchLoop
|
||||||
; Return a pointer to the start of the buffer (for 0-length pattern).
|
; Return a pointer to the start of the buffer (for 0-length pattern).
|
||||||
align 2
|
align 2
|
||||||
InstantMatch:
|
InstantMatch:
|
||||||
mov ax,[bp+BufferPtr]
|
mov ax,[bp+BufferPtr]
|
||||||
jmp short Done
|
jmp short Done
|
||||||
; Compare the pattern and the buffer location, searching from high
|
; Compare the pattern and the buffer location, searching from high
|
||||||
; memory toward low (right to left).
|
; memory toward low (right to left).
|
||||||
align 2
|
align 2
|
||||||
FullCompare:
|
FullCompare:
|
||||||
mov [bp+BufferPtr],di ;save the current buffer location
|
mov [bp+BufferPtr],di ;save the current buffer location
|
||||||
mov cx,[bp+PatternLength] ;# of bytes yet to compare
|
mov cx,[bp+PatternLength] ;# of bytes yet to compare
|
||||||
jcxz Match ;done if there was only one character
|
jcxz Match ;done if there was only one character
|
||||||
dec di ;point to next destination byte to compare (SI
|
dec di ;point to next destination byte to compare (SI
|
||||||
; points to next-to-rightmost source byte)
|
; points to next-to-rightmost source byte)
|
||||||
repz cmpsb ;compare the rest of the pattern
|
repz cmpsb ;compare the rest of the pattern
|
||||||
jz Match ;that's it; we've found a match
|
jz Match ;that's it; we've found a match
|
||||||
; It's a mismatch; let's see what we can learn from it.
|
; It's a mismatch; let's see what we can learn from it.
|
||||||
inc di ;compensate for 1-byte overrun of REPZ CMPSB;
|
inc di ;compensate for 1-byte overrun of REPZ CMPSB;
|
||||||
; point to mismatch location in buffer
|
; point to mismatch location in buffer
|
||||||
; # of bytes that did match.
|
; # of bytes that did match.
|
||||||
mov si,[bp+BufferPtr]
|
mov si,[bp+BufferPtr]
|
||||||
sub si,di
|
sub si,di
|
||||||
; If, based on the mismatch character, we can't even skip ahead as far
|
; If, based on the mismatch character, we can't even skip ahead as far
|
||||||
; as where we started this particular comparison, then just advance by
|
; as where we started this particular comparison, then just advance by
|
||||||
; 1 to the next potential match; otherwise, skip ahead from this
|
; 1 to the next potential match; otherwise, skip ahead from this
|
||||||
; comparison location by the skip distance for the mismatch character,
|
; comparison location by the skip distance for the mismatch character,
|
||||||
; less the distance covered by the partial match.
|
; less the distance covered by the partial match.
|
||||||
mov al,[di] ;get the value of the mismatch byte in buffer
|
mov al,[di] ;get the value of the mismatch byte in buffer
|
||||||
xlat ;get the skip value for this mismatch
|
xlat ;get the skip value for this mismatch
|
||||||
mov cx,1 ;assume we'll just advance to the next
|
mov cx,1 ;assume we'll just advance to the next
|
||||||
; potential match location
|
; potential match location
|
||||||
sub ax,si ;is the skip far enough to be worth taking?
|
sub ax,si ;is the skip far enough to be worth taking?
|
||||||
jna MoveAhead ;no, go with the default advance of 1
|
jna MoveAhead ;no, go with the default advance of 1
|
||||||
mov cx,ax ;yes, this is the distance to skip ahead from
|
mov cx,ax ;yes, this is the distance to skip ahead from
|
||||||
;the last potential match location checked
|
;the last potential match location checked
|
||||||
MoveAhead:
|
MoveAhead:
|
||||||
; Skip ahead and perform the next comparison.
|
; Skip ahead and perform the next comparison.
|
||||||
mov di,[bp+BufferPtr]
|
mov di,[bp+BufferPtr]
|
||||||
add di,cx ;BufferPtr += Skip;
|
add di,cx ;BufferPtr += Skip;
|
||||||
mov si,[bp+PatternPtr] ;point to the next-to-rightmost
|
mov si,[bp+PatternPtr] ;point to the next-to-rightmost
|
||||||
; pattern byte
|
; pattern byte
|
||||||
jmp SearchLoop
|
jmp SearchLoop
|
||||||
; Return start of match in buffer (BufferPtr - (PatternLength - 1)).
|
; Return start of match in buffer (BufferPtr - (PatternLength - 1)).
|
||||||
align 2
|
align 2
|
||||||
Match:
|
Match:
|
||||||
mov ax,[bp+BufferPtr]
|
mov ax,[bp+BufferPtr]
|
||||||
sub ax,[bp+PatternLength]
|
sub ax,[bp+PatternLength]
|
||||||
Done:
|
Done:
|
||||||
cld ;restore default direction flag
|
cld ;restore default direction flag
|
||||||
add sp,256 ;deallocate space for SkipTable
|
add sp,256 ;deallocate space for SkipTable
|
||||||
pop di ;restore caller's register variables
|
pop di ;restore caller's register variables
|
||||||
pop si
|
pop si
|
||||||
pop bp ;restore caller's stack frame
|
pop bp ;restore caller's stack frame
|
||||||
ret
|
ret
|
||||||
_FindString endp
|
_FindString endp
|
||||||
end
|
end
|
||||||
|
```
|
||||||
|
|
||||||
Note that Table 14.1 includes the time required to build the skip table
|
Note that Table 14.1 includes the time required to build the skip table
|
||||||
each time **FindString** is called. This time could be eliminated for
|
each time **FindString** is called. This time could be eliminated for
|
||||||
|
|
|
||||||
134
15-02.md
134
15-02.md
|
|
@ -12,57 +12,63 @@ pages: 284-287
|
||||||
|
|
||||||
**LISTING 15.1 L15-1.C**
|
**LISTING 15.1 L15-1.C**
|
||||||
|
|
||||||
/* Deletes the node in a linked list that follows the indicated node.
|
```c
|
||||||
Assumes list is headed by a dummy node, so no special testing for
|
/* Deletes the node in a linked list that follows the indicated node.
|
||||||
the head-of-list pointer is required. Returns the same pointer
|
Assumes list is headed by a dummy node, so no special testing for
|
||||||
that was passed in. */
|
the head-of-list pointer is required. Returns the same pointer
|
||||||
|
that was passed in. */
|
||||||
|
|
||||||
#include "llist.h"
|
#include "llist.h"
|
||||||
struct LinkNode *DeleteNodeAfter(struct LinkNode *NodeToDeleteAfter)
|
struct LinkNode *DeleteNodeAfter(struct LinkNode *NodeToDeleteAfter)
|
||||||
{
|
{
|
||||||
NodeToDeleteAfter->NextNode =
|
NodeToDeleteAfter->NextNode =
|
||||||
NodeToDeleteAfter->NextNode->NextNode;
|
NodeToDeleteAfter->NextNode->NextNode;
|
||||||
return(NodeToDeleteAfter);
|
return(NodeToDeleteAfter);
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
**LISTING 15.2 LLIST.H**
|
**LISTING 15.2 LLIST.H**
|
||||||
|
|
||||||
/* Linked list header file. */
|
```c
|
||||||
#define MAX_TEXT_LENGTH 100 /* longest allowed Text field */
|
/* Linked list header file. */
|
||||||
#define SENTINEL 32767 /* largest possible Value field */
|
#define MAX_TEXT_LENGTH 100 /* longest allowed Text field */
|
||||||
|
#define SENTINEL 32767 /* largest possible Value field */
|
||||||
|
|
||||||
struct LinkNode {
|
struct LinkNode {
|
||||||
struct LinkNode *NextNode;
|
struct LinkNode *NextNode;
|
||||||
int Value;
|
int Value;
|
||||||
char Text[MAX_TEXT_LENGTH+1];
|
char Text[MAX_TEXT_LENGTH+1];
|
||||||
/* Any number of additional data fields may by present */
|
/* Any number of additional data fields may by present */
|
||||||
};
|
};
|
||||||
struct LinkNode *DeleteNodeAfter(struct LinkNode *);
|
struct LinkNode *DeleteNodeAfter(struct LinkNode *);
|
||||||
struct LinkNode *FindNodeBeforeValue(struct LinkNode *, int);
|
struct LinkNode *FindNodeBeforeValue(struct LinkNode *, int);
|
||||||
struct LinkNode *InitLinkedList(void);
|
struct LinkNode *InitLinkedList(void);
|
||||||
struct LinkNode *InsertNodeSorted(struct LinkNode *,
|
struct LinkNode *InsertNodeSorted(struct LinkNode *,
|
||||||
struct LinkNode *);
|
struct LinkNode *);
|
||||||
|
```
|
||||||
|
|
||||||
**LISTING 15.3 L15-3.C**
|
**LISTING 15.3 L15-3.C**
|
||||||
|
|
||||||
/* Deletes the node in the specified linked list that follows the
|
```c
|
||||||
indicated node. List is headed by a head-of-list pointer; if the
|
/* Deletes the node in the specified linked list that follows the
|
||||||
pointer to the node to delete after points to the head-of-list
|
indicated node. List is headed by a head-of-list pointer; if the
|
||||||
pointer, special handling is performed. */
|
pointer to the node to delete after points to the head-of-list
|
||||||
#include "llist.h"
|
pointer, special handling is performed. */
|
||||||
struct LinkNode *DeleteNodeAfter(struct LinkNode **HeadOfListPtr,
|
#include "llist.h"
|
||||||
struct LinkNode *NodeToDeleteAfter)
|
struct LinkNode *DeleteNodeAfter(struct LinkNode **HeadOfListPtr,
|
||||||
{
|
struct LinkNode *NodeToDeleteAfter)
|
||||||
/* Handle specially if the node to delete after is actually the
|
{
|
||||||
head of the list (delete the first element in the list) */
|
/* Handle specially if the node to delete after is actually the
|
||||||
if (NodeToDeleteAfter == (struct LinkNode *)HeadOfListPtr) {
|
head of the list (delete the first element in the list) */
|
||||||
*HeadOfListPtr = (*HeadOfListPtr)->NextNode;
|
if (NodeToDeleteAfter == (struct LinkNode *)HeadOfListPtr) {
|
||||||
} else {
|
*HeadOfListPtr = (*HeadOfListPtr)->NextNode;
|
||||||
NodeToDeleteAfter->NextNode =
|
} else {
|
||||||
NodeToDeleteAfter->NextNode->NextNode;
|
NodeToDeleteAfter->NextNode =
|
||||||
}
|
NodeToDeleteAfter->NextNode->NextNode;
|
||||||
return(NodeToDeleteAfter);
|
}
|
||||||
}
|
return(NodeToDeleteAfter);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
However, it is true that if you're going to store a variety of types of
|
However, it is true that if you're going to store a variety of types of
|
||||||
structures in your linked lists, you should start each node with the
|
structures in your linked lists, you should start each node with the
|
||||||
|
|
@ -123,28 +129,30 @@ value has to perform two tests in the inner loop, as shown in Listing
|
||||||
|
|
||||||
**LISTING 15.4 L15-4.C**
|
**LISTING 15.4 L15-4.C**
|
||||||
|
|
||||||
/* Finds the first node in a linked list with a value field greater
|
```c
|
||||||
than or equal to a key value, and returns a pointer to the node
|
/* Finds the first node in a linked list with a value field greater
|
||||||
preceding that node (to facilitate insertion and deletion), or a
|
than or equal to a key value, and returns a pointer to the node
|
||||||
NULL pointer if no such value was found. Assumes the list is
|
preceding that node (to facilitate insertion and deletion), or a
|
||||||
terminated with a tail node pointing to itself as the next node. */
|
NULL pointer if no such value was found. Assumes the list is
|
||||||
#include <stdio.h>
|
terminated with a tail node pointing to itself as the next node. */
|
||||||
#include "llist.h"
|
#include <stdio.h>
|
||||||
struct LinkNode *FindNodeBeforeValueNotLess(
|
#include "llist.h"
|
||||||
struct LinkNode *HeadOfListNode, int SearchValue)
|
struct LinkNode *FindNodeBeforeValueNotLess(
|
||||||
{
|
struct LinkNode *HeadOfListNode, int SearchValue)
|
||||||
struct LinkNode *NodePtr = HeadOfListNode;
|
{
|
||||||
|
struct LinkNode *NodePtr = HeadOfListNode;
|
||||||
|
|
||||||
while ( (NodePtr->NextNode->NextNode != NodePtr->NextNode) &&
|
while ( (NodePtr->NextNode->NextNode != NodePtr->NextNode) &&
|
||||||
(NodePtr->NextNode->Value < SearchValue) )
|
(NodePtr->NextNode->Value < SearchValue) )
|
||||||
NodePtr = NodePtr->NextNode;
|
NodePtr = NodePtr->NextNode;
|
||||||
|
|
||||||
if (NodePtr->NextNode->NextNode == NodePtr->NextNode)
|
if (NodePtr->NextNode->NextNode == NodePtr->NextNode)
|
||||||
return(NULL); /* we found the sentinel; failed search */
|
return(NULL); /* we found the sentinel; failed search */
|
||||||
else
|
else
|
||||||
return(NodePtr); /* success; return pointer to node preceding
|
return(NodePtr); /* success; return pointer to node preceding
|
||||||
node that was >= */
|
node that was >= */
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Suppose, however, that we make the tail node a *sentinel* by giving it a
|
Suppose, however, that we make the tail node a *sentinel* by giving it a
|
||||||
value that is guaranteed to terminate the search, as shown in Figure
|
value that is guaranteed to terminate the search, as shown in Figure
|
||||||
|
|
|
||||||
166
15-03.md
166
15-03.md
|
|
@ -12,27 +12,29 @@ pages: 287-290
|
||||||
|
|
||||||
**LISTING 15.5 L15-5.C**
|
**LISTING 15.5 L15-5.C**
|
||||||
|
|
||||||
/* Finds the first node in a value-sorted linked list that
|
```c
|
||||||
has a Value field greater than or equal to a key value, and
|
/* Finds the first node in a value-sorted linked list that
|
||||||
returns a pointer to the node preceding that node (to facilitate
|
has a Value field greater than or equal to a key value, and
|
||||||
insertion and deletion), or a NULL pointer if no such value was
|
returns a pointer to the node preceding that node (to facilitate
|
||||||
found. Assumes the list is terminated with a sentinel tail node
|
insertion and deletion), or a NULL pointer if no such value was
|
||||||
containing the largest possible Value field setting and pointing
|
found. Assumes the list is terminated with a sentinel tail node
|
||||||
to itself as the next node. */
|
containing the largest possible Value field setting and pointing
|
||||||
#include <stdio.h>
|
to itself as the next node. */
|
||||||
#include "llist.h"
|
#include <stdio.h>
|
||||||
struct LinkNode *FindNodeBeforeValueNotLess(
|
#include "llist.h"
|
||||||
struct LinkNode *HeadOfListNode, int SearchValue)
|
struct LinkNode *FindNodeBeforeValueNotLess(
|
||||||
{
|
struct LinkNode *HeadOfListNode, int SearchValue)
|
||||||
struct LinkNode *NodePtr = HeadOfListNode;
|
{
|
||||||
while (NodePtr->NextNode->Value < SearchValue)
|
struct LinkNode *NodePtr = HeadOfListNode;
|
||||||
NodePtr = NodePtr->NextNode;
|
while (NodePtr->NextNode->Value < SearchValue)
|
||||||
if (NodePtr->NextNode->NextNode == NodePtr->NextNode)
|
NodePtr = NodePtr->NextNode;
|
||||||
return(NULL); /* we found the sentinel; failed search */
|
if (NodePtr->NextNode->NextNode == NodePtr->NextNode)
|
||||||
else
|
return(NULL); /* we found the sentinel; failed search */
|
||||||
return(NodePtr); /* success; return pointer to node preceding
|
else
|
||||||
node that was >= */
|
return(NodePtr); /* success; return pointer to node preceding
|
||||||
}
|
node that was >= */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||

|

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

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