Use backtick code blocks for syntax highlighting

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

View file

@ -82,15 +82,16 @@ 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;
@ -121,7 +122,8 @@ It's *slow*.
/* 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

View file

@ -30,16 +30,17 @@ 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;
@ -61,74 +62,77 @@ disk caching turned off.
/* 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 ;
Parms struc
dw ? ;pushed BP dw ? ;pushed BP
dw ? ;return address dw ? ;return address
Handle dw ? Handle dw ?
Checksum dw ? Checksum dw ?
Parms ends Parms ends
; ;
.model small .model small
.data .data
TempWord label word TempWord label word
TempByte db ? ;each byte read by DOS will be stored here TempByte db ? ;each byte read by DOS will be stored here
db 0 ;high byte of TempWord is always 0 db 0 ;high byte of TempWord is always 0
;for 16-bit adds ;for 16-bit adds
; ;
.code .code
public _ChecksumFile public _ChecksumFile
_ChecksumFile proc near _ChecksumFile proc near
push bp push bp
mov bp,sp mov bp,sp
push si ;save C's register variable push si ;save C's register variable
; ;
mov bx,[bp+Handle] ;get file handle mov bx,[bp+Handle] ;get file handle
sub si,si ;zero the checksum ;accumulator sub si,si ;zero the checksum ;accumulator
mov cx,1 ;request one byte on each ;read mov cx,1 ;request one byte on each ;read
mov dx,offset TempByte ;point DX to the byte in mov dx,offset TempByte ;point DX to the byte in
;which DOS should store ;which DOS should store
;each byte read ;each byte read
ChecksumLoop: ChecksumLoop:
mov ah,3fh ;DOS read file function # mov ah,3fh ;DOS read file function #
int 21h ;read the byte int 21h ;read the byte
jcErrorEnd;an error occurred jcErrorEnd;an error occurred
and ax,ax ;any bytes read? and ax,ax ;any bytes read?
jz Success ;no-end of file reached-we're done jz Success ;no-end of file reached-we're done
add si,[TempWord] ;add the byte into the add si,[TempWord] ;add the byte into the
;checksum total ;checksum total
jmpChecksumLoop jmpChecksumLoop
ErrorEnd: ErrorEnd:
sub ax,ax ;error sub ax,ax ;error
jmp short Done jmp short Done
Success: Success:
mov bx,[bp+Checksum] ;point to the checksum variable mov bx,[bp+Checksum] ;point to the checksum variable
mov [bx],si ;save the new checksum mov [bx],si ;save the new checksum
mov ax,1 ;success mov ax,1 ;success
; ;
Done: Done:
pop si ;restore C's register variable pop si ;restore C's register variable
pop bp pop bp
ret ret
_ChecksumFileendp _ChecksumFileendp
end end
```
The lesson is clear: Optimization makes code faster, but without proper The lesson is clear: Optimization makes code faster, but without proper
design, optimization just creates fast slow code. design, optimization just creates fast slow code.

View file

@ -20,14 +20,15 @@ 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;
@ -52,7 +53,8 @@ libraries do their work. In other words, *know the territory*!
/* 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}

View file

@ -30,19 +30,20 @@ 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>
#include <alloc.h> /* alloc.h for Borland,
malloc.h for Microsoft */ 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;
@ -85,7 +86,8 @@ uses no assembly at all.
/* 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,20 +124,21 @@ 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>
#include <alloc.h> /* alloc.h for Borland,
malloc.h for Microsoft */ 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;
@ -174,4 +177,5 @@ the design has been maxed out.
/* Report the result */ /* Report the result */
printf("The checksum is: %u\n", Checksum); printf("The checksum is: %u\n", Checksum);
exit(0); exit(0);
} }
```

View file

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

View file

@ -45,7 +45,8 @@ 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
LoopTop:
lodsb ;get the next byte to extract a bit from lodsb ;get the next byte to extract a bit from
and al,ah ;isolate the bit we want and al,ah ;isolate the bit we want
rol al,cl ;rotate the bit into the desired position rol al,cl ;rotate the bit into the desired position
@ -53,6 +54,7 @@ done, the key part of the code looked something like this:
dec cx ;the next bit goes 1 place to the right dec cx ;the next bit goes 1 place to the right
dec dx ;count down the number of bits dec dx ;count down the number of bits
jnz LoopTop ;process the next bit, if any 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,7 +70,8 @@ total of four separate time-consuming multibit rotations!
I changed the code to the following: I changed the code to the following:
LoopTop: ```nasm
LoopTop:
lodsb ;get the next byte to extract a bit from lodsb ;get the next byte to extract a bit from
and al,ah ;isolate the bit we want and al,ah ;isolate the bit we want
or bl,al ;insert the bit into the final nibble or bl,al ;insert the bit into the final nibble
@ -77,6 +80,7 @@ I changed the code to the following:
jnz LoopTop ;process the next bit, if any jnz LoopTop ;process the next bit, if any
rol bl,cl ;rotate all four bits into their final rol bl,cl ;rotate all four bits into their final
; positions at the same time ; 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

456
03-02.md
View file

@ -12,125 +12,126 @@ 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
@ -139,22 +140,22 @@ pages: 035-042
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. ; Save the context of the program being timed.
; ;
push ax push ax
pushf pushf
pop ax ; get flags so we can keep pop ax ; get flags so we can keep
@ -165,47 +166,47 @@ pages: 035-042
and ah,0fdh ; set pushed interrupt flag and ah,0fdh ; set pushed interrupt flag
; to 0 ; to 0
push ax push ax
; ;
; Turn on interrupts, so the timer interrupt can occur if it's ; Turn on interrupts, so the timer interrupt can occur if it's
; pending. ; pending.
; ;
sti sti
; ;
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
; linear counting rather than count-by-two counting. Also ; linear counting rather than count-by-two counting. Also
; leaves the 8253 waiting for the initial timer 0 count to ; leaves the 8253 waiting for the initial timer 0 count to
; be loaded. ; be loaded.
; ;
mov al,00110100b ;mode 2 mov al,00110100b ;mode 2
out MODE_8253,al out MODE_8253,al
; ;
; Set the timer count to 0, so we know we won't get another ; Set the timer count to 0, so we know we won't get another
; timer interrupt right away. ; timer interrupt right away.
; Note: this introduces an inaccuracy of up to 54 ms in the system ; Note: this introduces an inaccuracy of up to 54 ms in the system
; clock count each time it is executed. ; clock count each time it is executed.
; ;
DELAY DELAY
sub al,al sub al,al
out TIMER_0_8253,al ;lsb out TIMER_0_8253,al ;lsb
DELAY DELAY
out TIMER_0_8253,al ;msb out TIMER_0_8253,al ;msb
; ;
; Wait before clearing interrupts to allow the interrupt generated ; Wait before clearing interrupts to allow the interrupt generated
; when switching from mode 3 to mode 2 to be recognized. The delay ; 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 ; 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 ; 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. ; delay time will be more than long enough even on a very fast PC.
; ;
rept 10 rept 10
jmp $+2 jmp $+2
endm endm
; ;
; Disable interrupts to get an accurate count. ; Disable interrupts to get an accurate count.
; ;
cli cli
; ;
; Set the timer count to 0 again to start the timing interval. ; Set the timer count to 0 again to start the timing interval.
; ;
mov al,00110100b ; set up to load initial mov al,00110100b ; set up to load initial
out MODE_8253,al ; timer count out MODE_8253,al ; timer count
DELAY DELAY
@ -213,36 +214,36 @@ pages: 035-042
out TIMER_0_8253,al ; load count lsb out TIMER_0_8253,al ; load count lsb
DELAY DELAY
out TIMER_0_8253,al; load count msb out TIMER_0_8253,al; load count msb
; ;
; Restore the context and return. ; Restore the context and return.
; ;
MPOPF ; keeps interrupts off MPOPF ; keeps interrupts off
pop ax pop ax
ret ret
ZTimerOn endp ZTimerOn endp
;******************************************************************** ;********************************************************************
;* Routine called to stop timing and get count. * ;* Routine called to stop timing and get count. *
;******************************************************************** ;********************************************************************
ZTimerOff proc near ZTimerOff proc near
; ;
; Save the context of the program being timed. ; Save the context of the program being timed.
; ;
push ax push ax
push cx push cx
pushf pushf
; ;
; Latch the count. ; Latch the count.
; ;
mov al,00000000b ; latch timer 0 mov al,00000000b ; latch timer 0
out MODE_8253,al out MODE_8253,al
; ;
; See if the timer has overflowed by checking the 8259 for a pending ; See if the timer has overflowed by checking the 8259 for a pending
; timer interrupt. ; timer interrupt.
; ;
mov al,00001010b ; OCW3, set up to read mov al,00001010b ; OCW3, set up to read
out OCW3,al; Int errupt Request register out OCW3,al; Int errupt Request register
DELAY DELAY
@ -252,13 +253,13 @@ pages: 035-042
; timer interrupt) is pending ; timer interrupt) is pending
mov cs:[OverflowFlag],al; store the timer overflow mov cs:[OverflowFlag],al; store the timer overflow
; status ; status
; ;
; Allow interrupts to happen again. ; Allow interrupts to happen again.
; ;
sti sti
; ;
; Read out the count we latched earlier. ; Read out the count we latched earlier.
; ;
in al,TIMER_0_8253 ; least significant byte in al,TIMER_0_8253 ; least significant byte
DELAY DELAY
mov ah,al mov ah,al
@ -268,10 +269,10 @@ pages: 035-042
; remaining to elapsed ; remaining to elapsed
; count ; count
mov cs:[TimedCount],ax mov cs:[TimedCount],ax
; Time a zero-length code fragment, to get a reference for how ; Time a zero-length code fragment, to get a reference for how
; much overhead this routine has. Time it 16 times and average it, ; much overhead this routine has. Time it 16 times and average it,
; for accuracy, rounding the result. ; for accuracy, rounding the result.
; ;
mov cs:[ReferenceCount],0 mov cs:[ReferenceCount],0
mov cx,16 mov cx,16
cli ; interrupts off to allow a cli ; interrupts off to allow a
@ -284,9 +285,9 @@ pages: 035-042
add cs:[ReferenceCount],8; total + (0.5 * 16) add cs:[ReferenceCount],8; total + (0.5 * 16)
mov cl,4 mov cl,4
shr cs:[ReferenceCount],cl; (total) / 16 + 0.5 shr cs:[ReferenceCount],cl; (total) / 16 + 0.5
; ;
; Restore original interrupt state. ; Restore original interrupt state.
; ;
pop ax ; retrieve flags when called pop ax ; retrieve flags when called
mov ch,cs:[OriginalFlags] ; get back the original upper mov ch,cs:[OriginalFlags] ; get back the original upper
; byte of the FLAGS register ; byte of the FLAGS register
@ -297,65 +298,65 @@ pages: 035-042
or ah,ch ; make flags word with original or ah,ch ; make flags word with original
; interrupt flag ; interrupt flag
push ax ; prepare flags to be popped push ax ; prepare flags to be popped
; ;
; Restore the context of the program being timed and return to it. ; Restore the context of the program being timed and return to it.
; ;
MPOPF ; restore the flags with the MPOPF ; restore the flags with the
; original interrupt state ; original interrupt state
pop cx pop cx
pop ax pop ax
ret ret
ZTimerOff endp ZTimerOff endp
; ;
; Called by ZTimerOff to start timer for overhead measurements. ; Called by ZTimerOff to start timer for overhead measurements.
; ;
ReferenceZTimerOnproc near ReferenceZTimerOnproc near
; ;
; Save the context of the program being timed. ; Save the context of the program being timed.
; ;
push ax push ax
pushf ; interrupts are already off pushf ; interrupts are already off
; ;
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
; linear counting rather than count-by-two counting. ; linear counting rather than count-by-two counting.
; ;
mov al,00110100b ; set up to load mov al,00110100b ; set up to load
out MODE_8253,al ; initial timer count out MODE_8253,al ; initial timer count
DELAY DELAY
; ;
; Set the timer count to 0. ; Set the timer count to 0.
; ;
sub al,al sub al,al
out TIMER_0_8253,al; load count lsb out TIMER_0_8253,al; load count lsb
DELAY DELAY
out TIMER_0_8253,al; load count msb out TIMER_0_8253,al; load count msb
; ;
; Restore the context of the program being timed and return to it. ; Restore the context of the program being timed and return to it.
; ;
MPOPF MPOPF
pop ax pop ax
ret ret
ReferenceZTimerOnendp ReferenceZTimerOnendp
; ;
; Called by ZTimerOff to stop timer and add result to ReferenceCount ; Called by ZTimerOff to stop timer and add result to ReferenceCount
; for overhead measurements. ; for overhead measurements.
; ;
ReferenceZTimerOff proc near ReferenceZTimerOff proc near
; ;
; Save the context of the program being timed. ; Save the context of the program being timed.
; ;
push ax push ax
push cx push cx
pushf pushf
; ;
; Latch the count and read it. ; Latch the count and read it.
; ;
mov al,00000000b ; latch timer 0 mov al,00000000b ; latch timer 0
out MODE_8253,al out MODE_8253,al
DELAY DELAY
@ -368,21 +369,21 @@ pages: 035-042
; remaining to amount ; remaining to amount
; counted down ; counted down
add cs:[ReferenceCount],ax add cs:[ReferenceCount],ax
; ;
; Restore the context of the program being timed and return to it. ; Restore the context of the program being timed and return to it.
; ;
MPOPF MPOPF
pop cx pop cx
pop ax pop ax
ret ret
ReferenceZTimerOff endp ReferenceZTimerOff endp
; ******************************************************************** ; ********************************************************************
; * Routine called to report timing results. * ; * Routine called to report timing results. *
; ******************************************************************** ; ********************************************************************
ZTimerReport procnear ZTimerReport procnear
pushf pushf
push ax push ax
@ -391,53 +392,53 @@ pages: 035-042
push dx push dx
push si push si
push ds push ds
; ;
push cs ; DOS functions require that DS point push cs ; DOS functions require that DS point
pop ds ; to text to be displayed on the screen pop ds ; to text to be displayed on the screen
assume ds :Code assume ds :Code
; ;
; Check for timer 0 overflow. ; Check for timer 0 overflow.
; ;
cmp [OverflowFlag],0 cmp [OverflowFlag],0
jz PrintGoodCount jz PrintGoodCount
mov dx,offset OverflowStr mov dx,offset OverflowStr
mov ah,9 mov ah,9
int 21h int 21h
jmp short EndZTimerReport jmp short EndZTimerReport
; ;
; Convert net count to decimal ASCII in microseconds. ; Convert net count to decimal ASCII in microseconds.
; ;
PrintGoodCount: PrintGoodCount:
mov ax,[TimedCount] mov ax,[TimedCount]
sub ax,[ReferenceCount] sub ax,[ReferenceCount]
mov si,offset ASCIICountEnd - 1 mov si,offset ASCIICountEnd - 1
; ;
; Convert count to microseconds by multiplying by .8381. ; Convert count to microseconds by multiplying by .8381.
; ;
mov dx, 8381 mov dx, 8381
mul dx mul dx
mov bx, 10000 mov bx, 10000
div bx ;* .8381 = * 8381 / 10000 div bx ;* .8381 = * 8381 / 10000
; ;
; Convert time in microseconds to 5 decimal ASCII digits. ; Convert time in microseconds to 5 decimal ASCII digits.
; ;
mov bx, 10 mov bx, 10
mov cx, 5 mov cx, 5
CTSLoop: CTSLoop:
sub dx, dx sub dx, dx
div bx div bx
add dl,'0' add dl,'0'
mov [si],dl mov [si],dl
dec si dec si
loop CTSLoop loop CTSLoop
; ;
; Print the results. ; Print the results.
; ;
mov ah, 9 mov ah, 9
mov dx, offset OutputStr mov dx, offset OutputStr
int 21h int 21h
; ;
EndZTimerReport: EndZTimerReport:
pop ds pop ds
pop si pop si
pop dx pop dx
@ -447,7 +448,8 @@ pages: 035-042
MPOPF MPOPF
ret ret
ZTimerReport endp ZTimerReport endp
Code ends Code ends
end end
```

View file

@ -82,43 +82,45 @@ and should contain calls to **ZTimerOn** and **ZTimerOff** .
**LISTING 3.2 PZTEST.ASM** **LISTING 3.2 PZTEST.ASM**
; Program to measure performance of code that takes less than ```nasm
; 54 ms to execute. (PZTEST.ASM) ; Program to measure performance of code that takes less than
; ; 54 ms to execute. (PZTEST.ASM)
; Link with PZTIMER.ASM (Listing 3.1). PZTEST.BAT (Listing 3.4) ;
; can be used to assemble and link both files. Code to be ; Link with PZTIMER.ASM (Listing 3.1). PZTEST.BAT (Listing 3.4)
; measured must be in the file TESTCODE; Listing 3.3 shows ; can be used to assemble and link both files. Code to be
; a sample TESTCODE file. ; measured must be in the file TESTCODE; Listing 3.3 shows
; ; a sample TESTCODE file.
; By Michael Abrash ;
; ; By Michael Abrash
mystack segment para stack ‘STACK' ;
mystack segment para stack ‘STACK'
db 512 dup(?) db 512 dup(?)
mystack ends mystack ends
; ;
Code segment para public ‘CODE' Code segment para public ‘CODE'
assume cs:Code, ds:Code assume cs:Code, ds:Code
extrnZTimerOn:near, ZTimerOff:near, ZTimerReport:near extrnZTimerOn:near, ZTimerOff:near, ZTimerReport:near
Start proc near Start proc near
push cs push cs
pop ds ; set DS to point to the code segment, pop ds ; set DS to point to the code segment,
; so data as well as code can easily ; so data as well as code can easily
; be included in TESTCODE ; be included in TESTCODE
; ;
include TESTCODE ;code to be measured, including include TESTCODE ;code to be measured, including
; calls to ZTimerOn and ZTimerOff ; calls to ZTimerOn and ZTimerOff
; ;
; Display the results. ; Display the results.
; ;
call ZTimerReport call ZTimerReport
; ;
; Terminate the program. ; Terminate the program.
; ;
mov ah,4ch mov ah,4ch
int 21h int 21h
Start endp Start endp
Code ends Code ends
end Start 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 rept 1000
mov al,[MemVar] mov al,[MemVar]
endm endm
; ;
; Stop timing. ; Stop timing.
; ;
call ZTimerOff call ZTimerOff
```
It's worth noting that Listing 3.3 begins by jumping around the memory It's worth noting that Listing 3.3 begins by jumping around the memory
variable **MemVar**. This approach lets us avoid reproducing Listing 3.2 variable **MemVar**. This approach lets us avoid reproducing Listing 3.2

132
03-06.md
View file

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

682
03-07.md
View file

@ -78,180 +78,181 @@ computers.
**LISTING 3.5 LZTIMER.ASM** **LISTING 3.5 LZTIMER.ASM**
; ```nasm
; The long-period Zen timer. (LZTIMER.ASM) ;
; Uses the 8253 timer and the BIOS time-of-day count to time the ; The long-period Zen timer. (LZTIMER.ASM)
; performance of code that takes less than an hour to execute. ; Uses the 8253 timer and the BIOS time-of-day count to time the
; Because interrupts are left on (in order to allow the timer ; performance of code that takes less than an hour to execute.
; interrupt to be recognized), this is less accurate than the ; Because interrupts are left on (in order to allow the timer
; precision Zen timer, so it is best used only to time code that takes ; interrupt to be recognized), this is less accurate than the
; more than about 54 milliseconds to execute (code that the precision ; precision Zen timer, so it is best used only to time code that takes
; Zen timer reports overflow on). Resolution is limited by the ; more than about 54 milliseconds to execute (code that the precision
; occurrence of timer interrupts. ; Zen timer reports overflow on). Resolution is limited by the
; ; occurrence of timer interrupts.
; By Michael Abrash ;
; ; By Michael Abrash
; Externally callable routines: ;
; ; Externally callable routines:
; ZTimerOn: Saves the BIOS time of day count and starts the ;
; long-period Zen timer. ; ZTimerOn: Saves the BIOS time of day count and starts the
; ; long-period Zen timer.
; ZTimerOff: Stops the long-period Zen timer and saves the timer ;
; count and the BIOS time-of-day count. ; ZTimerOff: Stops the long-period Zen timer and saves the timer
; ; count and the BIOS time-of-day count.
; ZTimerReport: Prints the time that passed between starting and ;
; stopping the timer. ; ZTimerReport: Prints the time that passed between starting and
; ; stopping the timer.
; Note: If either more than an hour passes or midnight falls between ;
; calls to ZTimerOn and ZTimerOff, an error is reported. For ; Note: If either more than an hour passes or midnight falls between
; timing code that takes more than a few minutes to execute, ; calls to ZTimerOn and ZTimerOff, an error is reported. For
; either the DOS TIME command in a batch file before and after ; timing code that takes more than a few minutes to execute,
; execution of the code to time or the use of the DOS ; either the DOS TIME command in a batch file before and after
; time-of-day function in place of the long-period Zen timer is ; execution of the code to time or the use of the DOS
; more than adequate. ; time-of-day function in place of the long-period Zen timer is
; ; more than adequate.
; Note: The PS/2 version is assembled by setting the symbol PS2 to 1. ;
; PS2 must be set to 1 on PS/2 computers because the PS/2's ; Note: The PS/2 version is assembled by setting the symbol PS2 to 1.
; timers are not compatible with an undocumented timer-stopping ; PS2 must be set to 1 on PS/2 computers because the PS/2's
; feature of the 8253; the alternative timing approach that ; timers are not compatible with an undocumented timer-stopping
; must be used on PS/2 computers leaves a short window ; feature of the 8253; the alternative timing approach that
; during which the timer 0 count and the BIOS timer count may ; must be used on PS/2 computers leaves a short window
; not be synchronized. You should also set the PS2 symbol to ; during which the timer 0 count and the BIOS timer count may
; 1 if you're getting erratic or obviously incorrect results. ; not be synchronized. You should also set the PS2 symbol to
; ; 1 if you're getting erratic or obviously incorrect results.
; Note: When PS2 is 0, the code relies on an undocumented 8253 ;
; feature to get more reliable readings. It is possible that ; Note: When PS2 is 0, the code relies on an undocumented 8253
; the 8253 (or whatever chip is emulating the 8253) may be put ; feature to get more reliable readings. It is possible that
; into an undefined or incorrect state when this feature is ; the 8253 (or whatever chip is emulating the 8253) may be put
; used. ; into an undefined or incorrect state when this feature is
; ; used.
; ****************************************************************** ;
; * If your computer displays any hint of erratic behavior * ; ******************************************************************
; * after the long-period Zen timer is used, such as the floppy* ; * If your computer displays any hint of erratic behavior *
; * drive failing to operate properly, reboot the system, set * ; * after the long-period Zen timer is used, such as the floppy*
; * PS2 to 1 and leave it that way! * ; * drive failing to operate properly, reboot the system, set *
; ****************************************************************** ; * PS2 to 1 and leave it that way! *
; ; ******************************************************************
; Note: Each block of code being timed should ideally be run several ;
; times, with at least two similar readings required to ; Note: Each block of code being timed should ideally be run several
; establish a true measurement, in order to eliminate any ; times, with at least two similar readings required to
; variability caused by interrupts. ; establish a true measurement, in order to eliminate any
; ; variability caused by interrupts.
; Note: Interrupts must not be disabled for more than 54 ms at a ;
; stretch during the timing interval. Because interrupts ; Note: Interrupts must not be disabled for more than 54 ms at a
; are enabled, keys, mice, and other devices that generate ; stretch during the timing interval. Because interrupts
; interrupts should not be used during the timing interval. ; are enabled, keys, mice, and other devices that generate
; ; interrupts should not be used during the timing interval.
; Note: Any extra code running off the timer interrupt (such as ;
; some memory-resident utilities) will increase the time ; Note: Any extra code running off the timer interrupt (such as
; measured by the Zen timer. ; some memory-resident utilities) will increase the time
; ; measured by the Zen timer.
; Note: These routines can introduce inaccuracies of up to a few ;
; tenths of a second into the system clock count for each ; Note: These routines can introduce inaccuracies of up to a few
; code section timed. Consequently, it's a good idea to ; tenths of a second into the system clock count for each
; reboot at the conclusion of timing sessions. (The ; code section timed. Consequently, it's a good idea to
; battery-backed clock, if any, is not affected by the Zen ; reboot at the conclusion of timing sessions. (The
; timer.) ; battery-backed clock, if any, is not affected by the Zen
; ; timer.)
; All registers and all flags are preserved by all routines. ;
; ; All registers and all flags are preserved by all routines.
;
Code segment word public ‘CODE' Code segment word public ‘CODE'
assume cs: Code, ds:nothing assume cs: Code, ds:nothing
public ZTimerOn, ZTimerOff, ZTimerReport public ZTimerOn, ZTimerOff, ZTimerReport
; ;
; Set PS2 to 0 to assemble for use on a fully 8253-compatible ; Set PS2 to 0 to assemble for use on a fully 8253-compatible
; system; when PS2 is 0, the readings are more reliable if the ; system; when PS2 is 0, the readings are more reliable if the
; computer supports the undocumented timer-stopping feature, ; computer supports the undocumented timer-stopping feature,
; but may be badly off if that feature is not supported. In ; but may be badly off if that feature is not supported. In
; fact, timer-stopping may interfere with your computer's ; fact, timer-stopping may interfere with your computer's
; overall operation by putting the 8253 into an undefined or ; overall operation by putting the 8253 into an undefined or
; incorrect state. Use with caution!!! ; incorrect state. Use with caution!!!
; ;
; Set PS2 to 1 to assemble for use on non-8253-compatible ; Set PS2 to 1 to assemble for use on non-8253-compatible
; systems, including PS/2 computers; when PS2 is 1, readings ; systems, including PS/2 computers; when PS2 is 1, readings
; may occasionally be off by 54 ms, but the code will work ; may occasionally be off by 54 ms, but the code will work
; properly on all systems. ; properly on all systems.
; ;
; A setting of 1 is safer and will work on more systems, ; A setting of 1 is safer and will work on more systems,
; while a setting of 0 produces more reliable results in systems ; while a setting of 0 produces more reliable results in systems
; which support the undocumented timer-stopping feature of the ; which support the undocumented timer-stopping feature of the
; 8253. The choice is yours. ; 8253. The choice is yours.
; ;
PS2 equ1 PS2 equ1
; ;
; Base address of the 8253 timer chip. ; Base address of the 8253 timer chip.
; ;
BASE_8253 equ40h BASE_8253 equ40h
; ;
; 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 the BIOS timer count variable in the BIOS ; The address of the BIOS timer count variable in the BIOS
; data segment. ; data segment.
; ;
TIMER_COUNT equ46ch TIMER_COUNT equ46ch
; ;
; 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: pushcs ;construct far return address to p2: pushcs ;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
StartBIOSCountLow dw ? ;BIOS count low word at the StartBIOSCountLow dw ? ;BIOS count low word at the
; start of the timing period ; start of the timing period
StartBIOSCountHigh dw ? ;BIOS count high word at the StartBIOSCountHigh dw ? ;BIOS count high word at the
; start of the timing period ; start of the timing period
EndBIOSCountLow dw ? ;BIOS count low word at the EndBIOSCountLow dw ? ;BIOS count low word at the
; end of the timing period ; end of the timing period
EndBIOSCountHigh dw ? ;BIOS count high word at the EndBIOSCountHigh dw ? ;BIOS count high word at the
; end of the timing period ; end of the timing period
EndTimedCount dw ? ;timer 0 count at the end of EndTimedCount dw ? ;timer 0 count at the end of
; the timing period ; the timing period
ReferenceCount dw ? ;number of counts required to ReferenceCount dw ? ;number of counts required to
; execute timer overhead code ; execute timer overhead code
; ;
; String printed to report results. ; String printed to report results.
; ;
OutputStr labelbyte OutputStr labelbyte
db 0dh, 0ah, ‘Timed count: ‘ db 0dh, 0ah, ‘Timed count: ‘
TimedCountStr db10 dup (?) TimedCountStr db10 dup (?)
db' microseconds', 0dh, 0ah db' microseconds', 0dh, 0ah
db ‘$' db ‘$'
; ;
; Temporary storage for timed count as it's divided down by powers ; Temporary storage for timed count as it's divided down by powers
; of ten when converting from doubleword binary to ASCII. ; of ten when converting from doubleword binary to ASCII.
; ;
CurrentCountLow dw ? CurrentCountLow dw ?
CurrentCountHigh dw ? CurrentCountHigh dw ?
; ;
; Powers of ten table used to perform division by 10 when doing ; Powers of ten table used to perform division by 10 when doing
; doubleword conversion from binary to ASCII. ; doubleword conversion from binary to ASCII.
; ;
PowersOfTenlabelword PowersOfTenlabelword
dd 1 dd 1
dd 10 dd 10
dd 100 dd 100
@ -262,13 +263,13 @@ computers.
dd 10000000 dd 10000000
dd 100000000 dd 100000000
dd 1000000000 dd 1000000000
PowersOfTenEnd labelword PowersOfTenEnd labelword
; ;
; String printed to report that the high word of the BIOS count ; String printed to report that the high word of the BIOS count
; changed while timing (an hour elapsed or midnight was crossed), ; changed while timing (an hour elapsed or midnight was crossed),
; and so the count is invalid and the test needs to be rerun. ; and so the count is invalid and the test needs to be rerun.
; ;
TurnOverStrlabelbyte TurnOverStrlabelbyte
db 0dh, 0ah db 0dh, 0ah
db ‘****************************************************' db ‘****************************************************'
db 0dh, 0ah db 0dh, 0ah
@ -290,56 +291,56 @@ computers.
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. ; Save the context of the program being timed.
; ;
push ax push ax
pus hf pus hf
; ;
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
; linear counting rather than count-by-two counting. Also stops ; linear counting rather than count-by-two counting. Also stops
; timer 0 until the timer count is loaded, except on PS/2 ; timer 0 until the timer count is loaded, except on PS/2
; computers. ; computers.
; ;
mov al,00110100b ;mode 2 mov al,00110100b ;mode 2
out MODE_8253,al out MODE_8253,al
; ;
; Set the timer count to 0, so we know we won't get another ; Set the timer count to 0, so we know we won't get another
; timer interrupt right away. ; timer interrupt right away.
; Note: this introduces an inaccuracy of up to 54 ms in the system ; Note: this introduces an inaccuracy of up to 54 ms in the system
; clock count each time it is executed. ; clock count each time it is executed.
; ;
DELAY DELAY
subal,al subal,al
outTIMER_0_8253,al ;lsb outTIMER_0_8253,al ;lsb
DELAY DELAY
outTIMER_0_8253,al ;msb outTIMER_0_8253,al ;msb
; ;
; In case interrupts are disabled, enable interrupts briefly to allow ; In case interrupts are disabled, enable interrupts briefly to allow
; the interrupt generated when switching from mode 3 to mode 2 to be ; the interrupt generated when switching from mode 3 to mode 2 to be
; recognized. Interrupts must be enabled for at least 210 ns to allow ; recognized. Interrupts must be enabled for at least 210 ns to allow
; time for that interrupt to occur. Here, 10 jumps are used for the ; 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 ; delay to ensure that the delay time will be more than long enough
; even on a very fast PC. ; even on a very fast PC.
; ;
pushf pushf
sti sti
rept 10 rept 10
jmp $+2 jmp $+2
endm endm
MPOPF MPOPF
; ;
; Store the timing start BIOS count. ; Store the timing start BIOS count.
; (Since the timer count was just set to 0, the BIOS count will ; (Since the timer count was just set to 0, the BIOS count will
; stay the same for the next 54 ms, so we don't need to disable ; stay the same for the next 54 ms, so we don't need to disable
; interrupts in order to avoid getting a half-changed count.) ; interrupts in order to avoid getting a half-changed count.)
; ;
push ds push ds
subax, ax subax, ax
movds, ax movds, ax
@ -348,9 +349,9 @@ computers.
movax, ds:[TIMER_COUNT] movax, ds:[TIMER_COUNT]
movcs: [StartBIOSCountLow],ax movcs: [StartBIOSCountLow],ax
pop ds pop ds
; ;
; Set the timer count to 0 again to start the timing interval. ; Set the timer count to 0 again to start the timing interval.
; ;
mov al,00110100b ;set up to load initial mov al,00110100b ;set up to load initial
out MODE_8253,al ; timer count out MODE_8253,al ; timer count
DELAY DELAY
@ -358,78 +359,78 @@ computers.
out TIMER_0_8253,al; load count lsb out TIMER_0_8253,al; load count lsb
DELAY DELAY
out TIMER_0_8253,al; load count msb out TIMER_0_8253,al; load count msb
; ;
; Restore the context of the program being timed and return to it. ; Restore the context of the program being timed and return to it.
; ;
MPOPF MPOPF
popax popax
ret ret
ZTimerOnendp ZTimerOnendp
;******************************************************************** ;********************************************************************
;* Routine called to stop timing and get count. * ;* Routine called to stop timing and get count. *
;******************************************************************** ;********************************************************************
ZTimerOff procnear ZTimerOff procnear
; ;
; Save the context of the program being timed. ; Save the context of the program being timed.
; ;
pushf pushf
pushax pushax
pushcx pushcx
; ;
; In case interrupts are disabled, enable interrupts briefly to allow ; In case interrupts are disabled, enable interrupts briefly to allow
; any pending timer interrupt to be handled. Interrupts must be ; any pending timer interrupt to be handled. Interrupts must be
; enabled for at least 210 ns to allow time for that interrupt to ; enabled for at least 210 ns to allow time for that interrupt to
; occur. Here, 10 jumps are used for the delay to ensure that the ; 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. ; delay time will be more than long enough even on a very fast PC.
; ;
sti sti
rept 10 rept 10
jmp $+2 jmp $+2
endm endm
; ;
; Latch the timer count. ; Latch the timer count.
; ;
if PS2 if PS2
mov al,00000000b mov al,00000000b
out MODE_8253,al ;latch timer 0 count out MODE_8253,al ;latch timer 0 count
; ;
; This is where a one-instruction-long window exists on the PS/2. ; This is where a one-instruction-long window exists on the PS/2.
; The timer count and the BIOS count can lose synchronization; ; The timer count and the BIOS count can lose synchronization;
; since the timer keeps counting after it's latched, it can turn ; since the timer keeps counting after it's latched, it can turn
; over right after it's latched and cause the BIOS count to turn ; over right after it's latched and cause the BIOS count to turn
; over before interrupts are disabled, leaving us with the timer ; over before interrupts are disabled, leaving us with the timer
; count from before the timer turned over coupled with the BIOS ; count from before the timer turned over coupled with the BIOS
; count from after the timer turned over. The result is a count ; count from after the timer turned over. The result is a count
; that's 54 ms too long. ; that's 54 ms too long.
; ;
else else
; ;
; Set timer 0 to mode 2 (divide-by-N), waiting for a 2-byte count ; Set timer 0 to mode 2 (divide-by-N), waiting for a 2-byte count
; load, which stops timer 0 until the count is loaded. (Only works ; load, which stops timer 0 until the count is loaded. (Only works
; on fully 8253-compatible chips.) ; on fully 8253-compatible chips.)
; ;
mov al,00110100b; mode 2 mov al,00110100b; mode 2
out MODE_8253,al out MODE_8253,al
DELAY DELAY
mov al,00000000b ;latch timer 0 count mov al,00000000b ;latch timer 0 count
out MODE_8253,al out MODE_8253,al
endif endif
cli ;stop the BIOS count cli ;stop the BIOS count
; ;
; Read the BIOS count. (Since interrupts are disabled, the BIOS ; Read the BIOS count. (Since interrupts are disabled, the BIOS
; count won't change.) ; count won't change.)
; ;
push ds push ds
sub ax,ax sub ax,ax
mov ds,ax mov ds,ax
@ -438,9 +439,9 @@ computers.
mov ax,ds:[TIMER_COUNT] mov ax,ds:[TIMER_COUNT]
mov cs:[EndBIOSCountLow],ax mov cs:[EndBIOSCountLow],ax
pop ds pop ds
; ;
; Read the timer count and save it. ; Read the timer count and save it.
; ;
in al,TIMER_0_8253 ;lsb in al,TIMER_0_8253 ;lsb
DELAY DELAY
mov ah,al mov ah,al
@ -450,12 +451,12 @@ computers.
; remaining to elapsed ; remaining to elapsed
; count ; count
mov cs:[EndTimedCount],ax mov cs:[EndTimedCount],ax
; ;
; Restart timer 0, which is still waiting for an initial count ; Restart timer 0, which is still waiting for an initial count
; to be loaded. ; to be loaded.
; ;
ife PS2 ife PS2
DELAY DELAY
mov al,00110100b ;mode 2, waiting to load a mov al,00110100b ;mode 2, waiting to load a
@ -469,19 +470,19 @@ computers.
out TIMER_0_8253,al ;msb out TIMER_0_8253,al ;msb
DELAY DELAY
endif endif
sti;let the BIOS count continue sti;let the BIOS count continue
; ;
; Time a zero-length code fragment, to get a reference for how ; Time a zero-length code fragment, to get a reference for how
; much overhead this routine has. Time it 16 times and average it, ; much overhead this routine has. Time it 16 times and average it,
; for accuracy, rounding the result. ; for accuracy, rounding the result.
; ;
mov cs:[ReferenceCount],0 mov cs:[ReferenceCount],0
mov cx,16 mov cx,16
cli ;interrupts off to allow a cli ;interrupts off to allow a
; precise reference count ; precise reference count
RefLoop: RefLoop:
call ReferenceZTimerOn call ReferenceZTimerOn
call ReferenceZTimerOff call ReferenceZTimerOff
loop RefLoop loop RefLoop
@ -489,67 +490,67 @@ computers.
add cs:[ReferenceCount],8; total + (0.5 * 16) add cs:[ReferenceCount],8; total + (0.5 * 16)
mov cl,4 mov cl,4
shr cs:[ReferenceCount],cl;(total) / 16 + 0.5 shr cs:[ReferenceCount],cl;(total) / 16 + 0.5
; ;
; Restore the context of the program being timed and return to it. ; Restore the context of the program being timed and return to it.
; ;
popcx popcx
popax popax
MPOPF MPOPF
ret ret
ZTimerOff endp ZTimerOff endp
; ;
; Called by ZTimerOff to start the timer for overhead measurements. ; Called by ZTimerOff to start the timer for overhead measurements.
; ;
ReferenceZTimerOnprocnear ReferenceZTimerOnprocnear
; ;
; Save the context of the program being timed. ; Save the context of the program being timed.
; ;
pushax pushax
pushf pushf
; ;
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
; linear counting rather than count-by-two counting. ; linear counting rather than count-by-two counting.
; ;
mov al,00110100b ;mode 2 mov al,00110100b ;mode 2
out MODE_8253,al out MODE_8253,al
; ;
; Set the timer count to 0. ; Set the timer count to 0.
; ;
DELAY DELAY
sub al,al sub al,al
out TIMER_0_8253,al ;lsb out TIMER_0_8253,al ;lsb
DELAY DELAY
out TIMER_0_8253,al ;msb out TIMER_0_8253,al ;msb
; ;
; Restore the context of the program being timed and return to it. ; Restore the context of the program being timed and return to it.
; ;
MPOPF MPOPF
popax popax
ret ret
ReferenceZTimerOnendp ReferenceZTimerOnendp
; ;
; Called by ZTimerOff to stop the timer and add the result to ; Called by ZTimerOff to stop the timer and add the result to
; ReferenceCount for overhead measurements. Doesn't need to look ; ReferenceCount for overhead measurements. Doesn't need to look
; at the BIOS count because timing a zero-length code fragment ; at the BIOS count because timing a zero-length code fragment
; isn't going to take anywhere near 54 ms. ; isn't going to take anywhere near 54 ms.
; ;
ReferenceZTimerOff procnear ReferenceZTimerOff procnear
; ;
; Save the context of the program being timed. ; Save the context of the program being timed.
; ;
pushf pushf
pushax pushax
pushcx pushcx
; ;
; Match the interrupt-window delay in ZTimerOff. ; Match the interrupt-window delay in ZTimerOff.
; ;
sti sti
rept10 rept10
jmp$+2 jmp$+2
@ -557,9 +558,9 @@ computers.
mov al,00000000b mov al,00000000b
out MODE_8253,al ;latch timer out MODE_8253,al ;latch timer
; ;
; Read the count and save it. ; Read the count and save it.
; ;
DELAY DELAY
in al,TIMER_0_8253 ;lsb in al,TIMER_0_8253 ;lsb
DELAY DELAY
@ -570,21 +571,21 @@ computers.
; remaining to elapsed ; remaining to elapsed
; count ; count
add cs:[ReferenceCount],ax add cs:[ReferenceCount],ax
; ;
; Restore the context and return. ; Restore the context and return.
; ;
popcx popcx
popax popax
MPOPF MPOPF
ret ret
ReferenceZTimerOff endp ReferenceZTimerOff endp
;******************************************************************** ;********************************************************************
;* Routine called to report timing results. * ;* Routine called to report timing results. *
;******************************************************************** ;********************************************************************
ZTimerReportprocnear ZTimerReportprocnear
pushf pushf
push ax push ax
@ -598,10 +599,10 @@ computers.
push cs ;DOS functions require that DS point push cs ;DOS functions require that DS point
pop ds ; to text to be displayed on the screen pop ds ; to text to be displayed on the screen
assume ds :Code assume ds :Code
; ;
; See if midnight or more than an hour passed during timing. If so, ; See if midnight or more than an hour passed during timing. If so,
; notify the user. ; notify the user.
; ;
mov ax,[StartBIOSCountHigh] mov ax,[StartBIOSCountHigh]
cmp ax,[EndBIOSCountHigh] cmp ax,[EndBIOSCountHigh]
jz CalcBIOSTime ;hour count didn't change, jz CalcBIOSTime ;hour count didn't change,
@ -618,21 +619,21 @@ computers.
; the total time wasn't more ; the total time wasn't more
; than an hour ; than an hour
; ;
; Over an hour elapsed or midnight passed during timing, which ; Over an hour elapsed or midnight passed during timing, which
; renders the results invalid. Notify the user. This misses the ; renders the results invalid. Notify the user. This misses the
; case where a multiple of 24 hours has passed, but we'll rely ; case where a multiple of 24 hours has passed, but we'll rely
; on the perspicacity of the user to detect that case. ; on the perspicacity of the user to detect that case.
; ;
TestTooLong: TestTooLong:
mov ah,9 mov ah,9
mov dx,offset TurnOverStr mov dx,offset TurnOverStr
int 21h int 21h
jmp short ZTimerReportDone jmp short ZTimerReportDone
; ;
; Convert the BIOS time to microseconds. ; Convert the BIOS time to microseconds.
; ;
CalcBIOSTime: CalcBIOSTime:
mov ax,[EndBIOSCountLow] mov ax,[EndBIOSCountLow]
sub ax,[StartBIOSCountLow] sub ax,[StartBIOSCountLow]
mov dx,54925 ;number of microseconds each mov dx,54925 ;number of microseconds each
@ -640,23 +641,23 @@ computers.
mul dx mul dx
mov bx,ax ;set aside BIOS count in mov bx,ax ;set aside BIOS count in
mov cx,dx ; microseconds mov cx,dx ; microseconds
; ;
; Convert timer count to microseconds. ; Convert timer count to microseconds.
; ;
mov ax,[EndTimedCount] mov ax,[EndTimedCount]
mov si,8381 mov si,8381
mul si mul si
mov si,10000 mov si,10000
div si ;* .8381 = * 8381 / 10000 div si ;* .8381 = * 8381 / 10000
; ;
; Add timer and BIOS counts together to get an overall time in ; Add timer and BIOS counts together to get an overall time in
; microseconds. ; microseconds.
; ;
add bx,ax add bx,ax
adc cx,0 adc cx,0
; ;
; Subtract the timer overhead and save the result. ; Subtract the timer overhead and save the result.
; ;
mov ax,[ReferenceCount] mov ax,[ReferenceCount]
mov si,8381 ;convert the reference count mov si,8381 ;convert the reference count
mul si ; to microseconds mul si ; to microseconds
@ -666,15 +667,15 @@ computers.
sbb cx,0 sbb cx,0
mov [CurrentCountLow],bx mov [CurrentCountLow],bx
mov [CurrentCountHigh],cx mov [CurrentCountHigh],cx
; ;
; Convert the result to an ASCII string by trial subtractions of ; Convert the result to an ASCII string by trial subtractions of
; powers of 10. ; powers of 10.
; ;
mov di,offset PowersOfTenEnd - offset PowersOfTen - 4 mov di,offset PowersOfTenEnd - offset PowersOfTen - 4
mov si,offset TimedCountStr mov si,offset TimedCountStr
CTSNextDigit: CTSNextDigit:
mov bl,'0' mov bl,'0'
CTSLoop: CTSLoop:
mov ax,[CurrentCountLow] mov ax,[CurrentCountLow]
mov dx,[CurrentCountHigh] mov dx,[CurrentCountHigh]
sub ax,PowersOfTen[di] sub ax,PowersOfTen[di]
@ -684,20 +685,20 @@ computers.
mov [CurrentCountLow],ax mov [CurrentCountLow],ax
mov [CurrentCountHigh],dx mov [CurrentCountHigh],dx
jmp CTSLoop jmp CTSLoop
CTSNextPowerDown: CTSNextPowerDown:
mov [si],bl mov [si],bl
inc si inc si
sub di,4 sub di,4
jns CTSNextDigit jns CTSNextDigit
; ;
; ;
; Print the results. ; Print the results.
; ;
mov ah,9 mov ah,9
mov dx,offset OutputStr mov dx,offset OutputStr
int 21h int 21h
; ;
ZTimerReportDone: ZTimerReportDone:
pop ds pop ds
pop di pop di
pop si pop si
@ -708,7 +709,8 @@ computers.
MPOPF MPOPF
ret ret
ZTimerReport endp ZTimerReport endp
Code ends Code ends
end end
```

View file

@ -70,36 +70,37 @@ 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' ;
mystack segment para stack ‘STACK'
db 512 dup(?) db 512 dup(?)
mystack ends mystack ends
; ;
Code segment para public ‘CODE' Code segment para public ‘CODE'
assume cs:Code, ds:Code assume cs:Code, ds:Code
extrn ZTimerOn:near, ZTimerOff:near, ZTimerReport:near extrn ZTimerOn:near, ZTimerOff:near, ZTimerReport:near
Startproc near Startproc near
push cs push cs
pop ds ;point DS to the code segment, pop ds ;point DS to the code segment,
; so data as well as code can easily ; so data as well as code can easily
; be included in TESTCODE ; be included in TESTCODE
; ;
; Delay for 6-7 seconds, to let the Enter keystroke that started the ; Delay for 6-7 seconds, to let the Enter keystroke that started the
; program come back up. ; program come back up.
; ;
mov ah,2ch mov ah,2ch
int 21h ;get the current time int 21h ;get the current time
mov bh,dh ;set the current time aside mov bh,dh ;set the current time aside
DelayLoop: DelayLoop:
mov ah,2ch mov ah,2ch
push bx ;preserve start time push bx ;preserve start time
int 21h ;get time int 21h ;get time
@ -109,25 +110,26 @@ timing.
jnb CheckDelayTime ;no jnb CheckDelayTime ;no
add dh,60 ;yes, a minute must have turned over, add dh,60 ;yes, a minute must have turned over,
; so add one minute ; so add one minute
CheckDelayTime: CheckDelayTime:
sub dh,bh ;get time that's passed sub dh,bh ;get time that's passed
cmp dh,7 ;has it been more than 6 seconds yet? cmp dh,7 ;has it been more than 6 seconds yet?
jb DelayLoop ;not yet jb DelayLoop ;not yet
; ;
include TESTCODE ;code to be measured, including calls include TESTCODE ;code to be measured, including calls
; to ZTimerOn and ZTimerOff ; to ZTimerOn and ZTimerOff
; ;
; Display the results. ; Display the results.
; ;
call ZTimerReport call ZTimerReport
; ;
; Terminate the program. ; Terminate the program.
; ;
mov ah,4ch mov ah,4ch
int 21h int 21h
Start endp Start endp
Code ends Code ends
end Start end Start
```
As with the precision Zen timer, the program in Listing 3.6 is used by As with the precision Zen timer, the program in Listing 3.6 is used by
naming the file containing the code to be timed TESTCODE, then naming the file containing the code to be timed TESTCODE, then

188
03-09.md
View file

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

View file

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

View file

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

View file

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

View file

@ -104,24 +104,27 @@ 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 call ZTimerOn
rept 1000 rept 1000
shr ax,1 shr ax,1
endm endm
call ZTimerOff 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 mov cx,1000
sub ax,ax sub ax,ax
call ZTimerOn call ZTimerOn
@ -130,6 +133,7 @@ that the "true" execution time of **SHR** is 8.64 cycles.
shr ax,1 shr ax,1
endm endm
call ZTimerOff call ZTimerOff
```
![**Figure 4.3**  *Execution and instruction prefetching sequence for ![**Figure 4.3**  *Execution and instruction prefetching sequence for
Listing 4.5.*](images/04-03.jpg) Listing 4.5.*](images/04-03.jpg)

View file

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

View file

@ -28,17 +28,19 @@ request a memory access from the Bus Interface Unit.)
**LISTING 4.9 LST4-9.ASM** **LISTING 4.9 LST4-9.ASM**
; Measures the performance of repeated MUL instructions, ```nasm
; which allow the prefetch queue to be full at all times, ; Measures the performance of repeated MUL instructions,
; to demonstrate a case in which DRAM refresh has no impact ; which allow the prefetch queue to be full at all times,
; on code performance. ; to demonstrate a case in which DRAM refresh has no impact
; ; on code performance.
;
sub ax,ax sub ax,ax
call ZTimerOn call ZTimerOn
rept 1000 rept 1000
mul ax mul ax
endm endm
call ZTimerOff 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 call ZTimerOn
rept 1000 rept 1000
shr ax,1 shr ax,1
endm endm
call ZTimerOff call ZTimerOff
```
Since 4 cycles are required to read each instruction byte, we'd expect Since 4 cycles are required to read each instruction byte, we'd expect
each **SHR** to execute in 8 cycles, or 1.676 µs, if there were no DRAM each **SHR** to execute in 8 cycles, or 1.676 µs, if there were no DRAM

View file

@ -86,15 +86,16 @@ 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 mov ax,0010h
int 10h; select hi-res EGA graphics int 10h; select hi-res EGA graphics
; mode 10 hex (AH=0 selects ; mode 10 hex (AH=0 selects
; BIOS set mode function, ; BIOS set mode function,
; with AL=mode to select) ; with AL=mode to select)
; ;
mov ax,0a000h mov ax,0a000h
mov ds,ax mov ds,ax
mov es,ax ;move to & from same segment mov es,ax ;move to & from same segment
@ -111,9 +112,10 @@ long as we had assumed, but a long time nonetheless.
; is just to measure memory access ; is just to measure memory access
; times ; times
call ZTimerOff call ZTimerOff
; ;
mov ax,0003h mov ax,0003h
int 10h ;return to text mode 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,9 +127,10 @@ 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 ax,ds
mov es,ax ;move to & from same segment mov es,ax ;move to & from same segment
sub si,si ;move to & from same offset sub si,si ;move to & from same offset
@ -143,6 +146,7 @@ cycle-eater can *more than double* the execution time of 8088 code!
; is just to measure memory access ; is just to measure memory access
; times ; times
call ZTimerOff 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

View file

@ -12,30 +12,31 @@ 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
/* Program to search the file specified by the first command-line
* argument for the string specified by the second command-line * argument for the string specified by the second command-line
* argument. Performs the search by reading and searching blocks * argument. Performs the search by reading and searching blocks
* of size BLOCK_SIZE. */ * 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
@ -66,9 +67,9 @@ pages: 118-121
} }
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 */
@ -176,4 +177,5 @@ pages: 118-121
} }
exit(Found); /* Return the found/not found status as the exit(Found); /* Return the found/not found status as the
DOS errorlevel */ DOS errorlevel */
} }
```

View file

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

View file

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

View file

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

View file

@ -12,34 +12,35 @@ 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
@ -60,31 +61,31 @@ pages: 141-143
;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
@ -92,16 +93,17 @@ pages: 141-143
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}

View file

@ -12,41 +12,42 @@ 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
@ -67,30 +68,30 @@ pages: 143-145
;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
@ -103,26 +104,26 @@ pages: 143-145
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
@ -130,16 +131,17 @@ pages: 143-145
jz ByteNotFound ;yes, we're done with failure jz ByteNotFound ;yes, we're done with failure
loop SearchMaxLengthLoop ;it's neither, so check the next loop SearchMaxLengthLoop ;it's neither, so check the next
; four bytes, if any ; four bytes, if any
ByteNotFound: ByteNotFound:
clc ;return "not found" status clc ;return "not found" status
ret ret
ByteFound: ByteFound:
dec si ;point back to the location at which dec si ;point back to the location at which
; we found the searched-for byte ; we found the searched-for byte
stc ;return "found" status stc ;return "found" status
ret ret
SearchMaxLengthendp SearchMaxLengthendp
end Start end Start
```
How much difference? Listing 7.2 runs in 121 µs—40 percent faster than How much difference? Listing 7.2 runs in 121 µs—40 percent faster than
Listing 7.1, even though Listing 7.2 still uses **LOOP** rather than Listing 7.1, even though Listing 7.2 still uses **LOOP** rather than

View file

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

View file

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

View file

@ -12,38 +12,39 @@ 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
/* Program to search an array spanning a linked list of variable-
sized blocks, for all entries with a specified ID number, sized blocks, for all entries with a specified ID number,
and return the average of the values of all such entries. Each of and return the average of the values of all such entries. Each of
the variable-sized blocks may contain any number of data entries, the variable-sized blocks may contain any number of data entries,
stored as an array of structures within the block. */ 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;
@ -85,17 +86,17 @@ pages: 156-160
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;
@ -130,7 +131,8 @@ pages: 156-160
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,10 +156,11 @@ 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;
;|*** WorkingBlockCount++, DataPointer++) {
mov WORD PTR [bp-6],0 ;WorkingBlockCount mov WORD PTR [bp-6],0 ;WorkingBlockCount
mov bx,WORD PTR [bp+6] ;BlockPointer mov bx,WORD PTR [bp+6] ;BlockPointer
cmp WORD PTR [bx+2],0 cmp WORD PTR [bx+2],0
@ -166,20 +169,21 @@ instruction can be used.
add WORD PTR [bp-6],cx ;WorkingBlockCount add WORD PTR [bp-6],cx ;WorkingBlockCount
mov di,WORD PTR [bp-2] ;IDMatchSum mov di,WORD PTR [bp-2] ;IDMatchSum
mov dx,WORD PTR [bp-4] ;IDMatchCount mov dx,WORD PTR [bp-4] ;IDMatchCount
$L20004: $L20004:
;|*** if (DataPointer->ID == SearchedForID) { ;|*** if (DataPointer->ID == SearchedForID) {
mov ax,WORD PTR [si] mov ax,WORD PTR [si]
cmp WORD PTR [bp+4],ax ;SearchedForID cmp WORD PTR [bp+4],ax ;SearchedForID
jne $I265 jne $I265
;|*** IDMatchCount++; ;|*** IDMatchCount++;
inc dx inc dx
;|*** IDMatchSum += DataPointer->Value; ;|*** IDMatchSum += DataPointer->Value;
add di,WORD PTR [si+2] add di,WORD PTR [si+2]
;|*** } ;|*** }
;|*** } ;|*** }
$I265: $I265:
add si,4 add si,4
loop $L20004 loop $L20004
mov WORD PTR [bp-2],di ;IDMatchSum mov WORD PTR [bp-2],di ;IDMatchSum
mov WORD PTR [bp-4],dx ;IDMatchCount mov WORD PTR [bp-4],dx ;IDMatchCount
$FB264: $FB264:
```

108
08-04.md
View file

@ -24,19 +24,21 @@ optimization, isn't it?
**LISTING 8.3 L8-3.ASM** **LISTING 8.3 L8-3.ASM**
; Typically optimized assembly language version of FindIDAverage. ```nasm
SearchedForID equ 4 ;Passed parameter offsets in the ; Typically optimized assembly language version of FindIDAverage.
BlockPointer equ 6 ; stack frame (skip over pushed BP SearchedForID equ 4 ;Passed parameter offsets in the
; and the return address) BlockPointer equ 6 ; stack frame (skip over pushed BP
NextBlock equ 0 ;Field offsets in struct BlockHeader ; and the return address)
BlockCount equ 2 NextBlock equ 0 ;Field offsets in struct BlockHeader
BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader BlockCount equ 2
ID equ 0 ;struct DataElement field offsets BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader
Value equ 2 ID equ 0 ;struct DataElement field offsets
DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement Value equ 2
DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement
.model small .model small
.code .code
public _FindIDAverage public _FindIDAverage
```
| | On 20 MHz 386 | On 10 MHz 286 | | | On 20 MHz 386 | On 10 MHz 286 |
|------------------------------------------------------------|------------------|------------------| |------------------------------------------------------------|------------------|------------------|
@ -47,7 +49,8 @@ optimization, isn't it?
Table: Table 8.1 Execution Times of FindIDAverage. Table: Table 8.1 Execution Times of FindIDAverage.
_FindIDAverage proc near ```nasm
_FindIDAverage 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
@ -56,40 +59,41 @@ Table: Table 8.1 Execution Times of FindIDAverage.
mov bx,dx ;IDMatchCount = 0 mov bx,dx ;IDMatchCount = 0
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
; 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:
; 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]
; 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 ;No data in this block jcxz DoNextBlock ;No data in this block
IntraBlockLoop: IntraBlockLoop:
cmp [di+ID],ax ;Do we have an ID match? cmp [di+ID],ax ;Do we have an ID match?
jnz NoMatch ;No match jnz NoMatch ;No match
inc bx ;We have a match; IDMatchCount++; inc bx ;We have a match; IDMatchCount++;
add dx,[di+Value] ;IDMatchSum += DataPointer->Value; add dx,[di+Value] ;IDMatchSum += DataPointer->Value;
NoMatch: NoMatch:
add di,DATA_ELEMENT_SIZE ;point to the next element add di,DATA_ELEMENT_SIZE ;point to the next element
loop IntraBlockLoop loop IntraBlockLoop
; 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 bx,bx and bx,bx
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 bx ;Return IDMatchSum / IDMatchCount div bx ;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
_FindIDAverage ENDP _FindIDAverage ENDP
end 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,21 +106,22 @@ 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
BlockPointer equ 6 ; stack frame (skip over pushed BP
; and the return address) ; and the return address)
NextBlock equ 0 ;Field offsets in struct BlockHeader NextBlock equ 0 ;Field offsets in struct BlockHeader
BlockCount equ 2 BlockCount equ 2
BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader BLOCK_HEADER_SIZE equ 4 ;Number of bytes in struct BlockHeader
ID equ 0 ;struct DataElement field offsets ID equ 0 ;struct DataElement field offsets
Value equ 2 Value equ 2
DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement DATA_ELEMENT_SIZE equ 4 ;Number of bytes in struct DataElement
.model small .model small
.code .code
public _FindIDAverage public _FindIDAverage
_FindIDAverage proc near _FindIDAverage 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
@ -128,13 +133,13 @@ but not a tremendous return for the optimization effort invested.
mov bx,dx ;IDMatchCount = 0 mov bx,dx ;IDMatchCount = 0
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
; Search through all of the linked blocks until the last block ; Search through all of 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:
; 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]
; 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] ;Number of elements in this block mov cx,[si+BlockCount] ;Number of elements in this block
jcxz DoNextBlock ;Skip this block if it's empty jcxz DoNextBlock ;Skip this block if it's empty
mov bp,cx ;***stack frame no longer available*** mov bp,cx ;***stack frame no longer available***
@ -147,23 +152,23 @@ but not a tremendous return for the optimization effort invested.
jmp cs:[LoopEntryTable+bp] ; the unrolled loop and jmp cs:[LoopEntryTable+bp] ; the unrolled loop and
; vector to that entry point ; vector to that entry point
align 2 align 2
LoopEntryTable label word LoopEntryTable label word
dw LoopEntry8,LoopEntry1,LoopEntry2,LoopEntry3 dw LoopEntry8,LoopEntry1,LoopEntry2,LoopEntry3
dw LoopEntry4,LoopEntry5,LoopEntry6,LoopEntry7 dw LoopEntry4,LoopEntry5,LoopEntry6,LoopEntry7
M_IBL macro P1 M_IBL macro P1
local NoMatch local NoMatch
LoopEntry&P1&: LoopEntry&P1&:
scasw ;Do we have an ID match? scasw ;Do we have an ID match?
jnz NoMatch ;No match jnz NoMatch ;No match
;We have a match ;We have a match
inc bx ;IDMatchCount++; inc bx ;IDMatchCount++;
add dx,[di] ;IDMatchSum += DataPointer->Value; add dx,[di] ;IDMatchSum += DataPointer->Value;
NoMatch: NoMatch:
add di,DATA_ELEMENT_SIZE-2 ;point to the next element add di,DATA_ELEMENT_SIZE-2 ;point to the next element
; (SCASW advanced 2 bytes already) ; (SCASW advanced 2 bytes already)
endm endm
align 2 align 2
IntraBlockLoop: IntraBlockLoop:
M_IBL 8 M_IBL 8
M_IBL 7 M_IBL 7
M_IBL 6 M_IBL 6
@ -173,20 +178,21 @@ but not a tremendous return for the optimization effort invested.
M_IBL 2 M_IBL 2
M_IBL 1 M_IBL 1
loop IntraBlockLoop loop IntraBlockLoop
; 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 bx,bx and bx,bx
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 bx ;Return IDMatchSum / IDMatchCount div bx ;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
_FindIDAverage ENDP _FindIDAverage ENDP
end end
```

View file

@ -20,37 +20,40 @@ 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
/* Program to search an array spanning a linked list of variable-
sized blocks, for all entries with a specified ID number, sized blocks, for all entries with a specified ID number,
and return the average of the values of all such entries. Each of and return the average of the values of all such entries. Each of
the variable-sized blocks may contain any number of data entries, the variable-sized blocks may contain any number of data entries,
stored in the form of two separate arrays, one for ID numbers and stored in the form of two separate arrays, one for ID numbers and
one for values. */ one for values. */
#include <stdio.h> #include <stdio.h>
#ifdef __TURBOC__ #ifdef __TURBOC__
#include <alloc.h> #include <alloc.h>
#else #else
#include <malloc.h> #include <malloc.h>
#endif #endif
void main(void); void main(void);
void exit(int); void exit(int);
extern unsigned int FindIDAverage2(unsigned int, extern unsigned int FindIDAverage2(unsigned int,
struct BlockHeader *); struct BlockHeader *);
```
![**Figure 8.3**  *Linked array storage format (version 2).*](images/08-03.jpg) ![**Figure 8.3**  *Linked array storage format (version 2).*](images/08-03.jpg)
/* Structure that starts each variable-sized block */ ```c
struct BlockHeader { /* Structure that starts each variable-sized block */
struct BlockHeader {
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 */
}; };
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;
@ -92,26 +95,28 @@ merely rearranged.
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
@ -124,45 +129,46 @@ merely rearranged.
sub dx,dx ;IDMatchSum = 0 sub dx,dx ;IDMatchSum = 0
mov bp,dx ;IDMatchCount = 0 mov bp,dx ;IDMatchCount = 0
;***stack frame no longer available*** ;***stack frame no longer available***
; Search through all the linked blocks until the last block ; Search through all the linked blocks until the last block
; (marked with a NULL pointer to the next block) has been searched. ; (marked with a NULL pointer to the next block) has been searched.
BlockLoop: BlockLoop:
; Search through all the DataElement entries within this block ; Search through all the DataElement entries within this block
; and accumulate data from all that match the desired ID. ; and accumulate data from all that match the desired ID.
mov cx,[si+BlockCount] mov cx,[si+BlockCount]
jcxz DoNextBlock;Skip this block if there's no data jcxz DoNextBlock;Skip this block if there's no data
; to search through ; to search through
mov bx,cx ;We'll use BX to point to the mov bx,cx ;We'll use BX to point to the
shl bx,1 ; corresponding value entry in the shl bx,1 ; corresponding value entry in the
; case of an ID match (BX is the ; case of an ID match (BX is the
; length in bytes of the ID array) ; length in bytes of the ID array)
; Point to the first DataElement entry within this block. ; Point to the first DataElement entry within this block.
lea di,[si+BLOCK_HEADER_SIZE] lea di,[si+BLOCK_HEADER_SIZE]
IntraBlockLoop: IntraBlockLoop:
repnz scasw ;Search for the ID repnz scasw ;Search for the ID
jnz DoNextBlock ;No match, the block is done jnz DoNextBlock ;No match, the block is done
inc bp ;We have a match; IDMatchCount++; inc bp ;We have a match; IDMatchCount++;
add dx,[di+bx-2];IDMatchSum += DataPointer->Value; add dx,[di+bx-2];IDMatchSum += DataPointer->Value;
; (SCASW has advanced DI 2 bytes) ; (SCASW has advanced DI 2 bytes)
and cx,cx ;Is there more data to search through? and cx,cx ;Is there more data to search through?
jnz IntraBlockLoop ;yes jnz IntraBlockLoop ;yes
; Point to the next block and continue if that pointer isn't NULL. ; Point to the next block and continue if that pointer isn't NULL.
DoNextBlock: DoNextBlock:
mov si,[si+NextBlock] ;Get pointer to the next block mov si,[si+NextBlock] ;Get pointer to the next block
and si,si ;Is it a NULL pointer? and si,si ;Is it a NULL pointer?
jnz BlockLoop ;No, continue jnz BlockLoop ;No, continue
; Calculate the average of all matches. ; Calculate the average of all matches.
sub ax,ax ;Assume we found no matches sub ax,ax ;Assume we found no matches
and bp,bp and bp,bp
jz Done ;We didn't find any matches, return 0 jz Done ;We didn't find any matches, return 0
xchg ax,dx ;Prepare for division xchg ax,dx ;Prepare for division
div bp ;Return IDMatchSum / IDMatchCount div bp ;Return IDMatchSum / IDMatchCount
Done: pop si ;Restore C register variables Done: pop si ;Restore C register variables
pop di pop di
pop bp ;Restore caller's stack frame pop bp ;Restore caller's stack frame
ret ret
_FindIDAverage2 ENDP _FindIDAverage2 ENDP
end end
```
The whole point of this rearrangement is to allow us to use **REP The whole point of this rearrangement is to allow us to use **REP
SCASW** to search through each block, and that's exactly what SCASW** to search through each block, and that's exactly what

View file

@ -88,13 +88,17 @@ machine word (32 bits in 386 protected mode, 16 bits otherwise), but it
renders **LEA** useless for multiword operations, which use the Carry renders **LEA** useless for multiword operations, which use the Carry
flag to tie together partial results. For example, these instructions flag to tie together partial results. For example, these instructions
ADD EAX,EBX ```nasm
ADC EDX,ECX ADD EAX,EBX
ADC EDX,ECX
```
could *not* be replaced could *not* be replaced
LEA EAX,[EAX+EBX] ```nasm
ADC EDX,ECX LEA EAX,[EAX+EBX]
ADC EDX,ECX
```
because **LEA** doesn't affect the Carry flag. because **LEA** doesn't affect the Carry flag.
@ -103,16 +107,18 @@ performing pointer arithmetic, however. For instance, the following code
uses **LEA** to advance the pointers while adding one 128-bit memory uses **LEA** to advance the pointers while adding one 128-bit memory
variable to another such variable: variable to another such variable:
```nasm
MOV ECX,4 ;# of 32-bit words to add MOV ECX,4 ;# of 32-bit words to add
CLC CLC
;no carry into the initial ADC ;no carry into the initial ADC
ADDLOOP: 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:
```nasm
AND AX,AX ;negative? AND AX,AX ;negative?
JNS IsPositive ;no JNS IsPositive ;no
NEG AX ;yes,negate it NEG AX ;yes,negate it
IsPositive: 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
ADC CX,CX ;CX=1 if copy length was odd,
;0 else ;0 else
REP MOVSB ;copy any odd byte 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:
```nasm
SHR CX,1 ;word count SHR CX,1 ;word count
REP MOVSW ;copy as many words as REP MOVSW ;copy as many words as
;possible ;possible
JNC CopyDone ;done if even copy length JNC CopyDone ;done if even copy length
MOVSB ;copy the odd byte MOVSB ;copy the odd byte
CopyDone: CopyDone:
```

View file

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

View file

@ -40,33 +40,34 @@ 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.
Parmsstruc
dw 2 dup(?) ;pushed BP/return address dw 2 dup(?) ;pushed BP/return address
Buffer dw ? ;pointer to buffer to search Buffer dw ? ;pointer to buffer to search
BufferLength dw ? ;length of buffer to search BufferLength dw ? ;length of buffer to search
SearchString dw ? ;pointer to string for which to search SearchString dw ? ;pointer to string for which to search
SearchStringLength dw ? ;length of string for which to search SearchStringLength dw ? ;length of string for which to search
Parmsends Parmsends
.model small .model small
.code .code
public _FindString public _FindString
_FindStringprocnear _FindStringprocnear
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
@ -90,7 +91,7 @@ using **REPZ CMPS** to check scanning matches.
mov bp,si ;set aside pointer to the second search byte mov bp,si ;set aside pointer to the second search byte
dec bx ;don't need to compare the first byte of the dec bx ;don't need to compare the first byte of the
; string with CMPS; we'll do it with SCAS ; string with CMPS; we'll do it with SCAS
FindStringLoop: FindStringLoop:
mov cx,dx ;put remaining buffer search length in CX mov cx,dx ;put remaining buffer search length in CX
repnz scasb ;scan for the first byte of the string repnz scasb ;scan for the first byte of the string
jnz FindStringNotFound ;not found, so there's no match jnz FindStringNotFound ;not found, so there's no match
@ -106,28 +107,29 @@ using **REPZ CMPS** to check scanning matches.
cmpsb ;compare the odd byte cmpsb ;compare the odd byte
jnz FindStringNoMatch ;odd byte doesn't match, so we jnz FindStringNoMatch ;odd byte doesn't match, so we
; haven't found the search string here ; haven't found the search string here
FindStringWord: FindStringWord:
jcxz FindStringFound ;test whether we've already checked jcxz FindStringFound ;test whether we've already checked
; the whole string; if so, this is a match ; the whole string; if so, this is a match
; bytes long; if so, we've found a match ; bytes long; if so, we've found a match
repz cmpsw ;check the rest of the string a word at a time repz cmpsw ;check the rest of the string a word at a time
jz FindStringFound ;it's a match jz FindStringFound ;it's a match
FindStringNoMatch: FindStringNoMatch:
pop di ;get back pointer to the next byte to scan pop di ;get back pointer to the next byte to scan
and dx,dx ;is there anything left to check? and dx,dx ;is there anything left to check?
jnz FindStringLoop ;yes-check next byte jnz FindStringLoop ;yes-check next byte
FindStringNotFound: FindStringNotFound:
sub ax,ax ;return a NULL pointer indicating that the sub ax,ax ;return a NULL pointer indicating that the
jmp FindStringDone ; string was not found jmp FindStringDone ; string was not found
FindStringFound: FindStringFound:
pop ax ;point to the buffer location at which the pop ax ;point to the buffer location at which the
dec ax ; string was found (earlier we pushed the dec ax ; string was found (earlier we pushed the
; address of the byte after the start of the ; address of the byte after the start of the
; potential match) ; potential match)
FindStringDone: FindStringDone:
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
_FindStringendp _FindStringendp
end end
```

View file

@ -12,34 +12,35 @@ 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.
Parms struc
dw 2 dup(?) ;pushed BP/return address dw 2 dup(?) ;pushed BP/return address
Buffer dw ? ;pointer to buffer to search Buffer dw ? ;pointer to buffer to search
BufferLength dw ? ;length of buffer to search BufferLength dw ? ;length of buffer to search
SearchString dw ? ;pointer to string for which to search SearchString dw ? ;pointer to string for which to search
SearchStringLength dw ? ;length of string for which to search SearchStringLength dw ? ;length of string for which to search
ScanCharOffset dw ? ;offset in string of character for ScanCharOffset dw ? ;offset in string of character for
; which to scan ; which to scan
Parmsends Parmsends
.model small .model small
.code .code
public _FindString public _FindString
_FindStringprocnear _FindStringprocnear
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
@ -67,7 +68,7 @@ pages: 178-180
; potential full match after a scan match, ; potential full match after a scan match,
; accounting for the 1-byte overrun of ; accounting for the 1-byte overrun of
; REPNZ SCASB ; REPNZ SCASB
FindStringLoop: FindStringLoop:
mov cx,dx ;put remaining buffer search length in CX mov cx,dx ;put remaining buffer search length in CX
repnz scasb ;scan for the scan byte repnz scasb ;scan for the scan byte
jnz FindStringNotFound ;not found, so there's no match jnz FindStringNotFound ;not found, so there's no match
@ -85,46 +86,48 @@ pages: 178-180
cmpsb ;compare the odd byte cmpsb ;compare the odd byte
jnz FindStringNoMatch ;odd byte doesn't match, so we jnz FindStringNoMatch ;odd byte doesn't match, so we
; haven't found the search string here ; haven't found the search string here
FindStringWord: FindStringWord:
jcxz FindStringFound ;if the string is only 1 byte long, jcxz FindStringFound ;if the string is only 1 byte long,
; we've found a match ; we've found a match
repz cmpsw ;check the rest of the string a word at a time repz cmpsw ;check the rest of the string a word at a time
jz FindStringFound ;it's a match jz FindStringFound ;it's a match
FindStringNoMatch: FindStringNoMatch:
pop di ;get back pointer to the next byte to scan pop di ;get back pointer to the next byte to scan
and dx,dx ;is there anything left to check? and dx,dx ;is there anything left to check?
jnz FindStringLoop ;yes-check next byte jnz FindStringLoop ;yes-check next byte
FindStringNotFound: FindStringNotFound:
sub ax,ax ;return a NULL pointer indicating that the sub ax,ax ;return a NULL pointer indicating that the
jmp FindStringDone ; string was not found jmp FindStringDone ; string was not found
FindStringFound: FindStringFound:
pop ax ;point to the buffer location at which the pop ax ;point to the buffer location at which the
sub ax,bx ; string was found (earlier we pushed the sub ax,bx ; string was found (earlier we pushed the
; address of the byte after the scan match) ; address of the byte after the scan match)
FindStringDone: FindStringDone:
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
_FindStringendp _FindStringendp
end 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;
@ -142,4 +145,5 @@ pages: 178-180
TestString, DISPLAY_LENGTH, TestString, DISPLAY_LENGTH,
strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH)); strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH));
} }
} }
```

View file

@ -43,19 +43,19 @@ 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?
@ -63,7 +63,7 @@ one.
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
@ -75,6 +75,7 @@ one.
ret ret
end end
```
#### Full 32-Bit Division {#Heading8} #### Full 32-Bit Division {#Heading8}

View file

@ -12,36 +12,37 @@ 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
@ -63,7 +64,7 @@ pages: 182-185
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
@ -76,27 +77,30 @@ pages: 182-185
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
/* Sample use of Div function to perform division when the result
doesn't fit in 16 bits */ doesn't fit in 16 bits */
#include <stdio.h> #include <stdio.h>
extern unsigned int Div(unsigned int * Dividend, extern unsigned int Div(unsigned int * Dividend,
int DividendLength, unsigned int Divisor, int DividendLength, unsigned int Divisor,
unsigned int * Quotient); unsigned int * Quotient);
main() { main() {
unsigned long m, i = 0x20000001; unsigned long m, i = 0x20000001;
unsigned int k, j = 0x10; unsigned int k, j = 0x10;
k = Div((unsigned int *)&i, sizeof(i), j, (unsigned int *)&m); k = Div((unsigned int *)&i, sizeof(i), j, (unsigned int *)&m);
printf("%lu / %u = %lu r %u\n", i, j, m, k); printf("%lu / %u = %lu r %u\n", i, j, m, k);
} }
```
#### Sweet Spot Revisited {#Heading9} #### Sweet Spot Revisited {#Heading9}

View file

@ -52,11 +52,13 @@ PTR 1000:5**, but you'd be wrong. That won't even assemble. You might
then think to construct in memory a far pointer containing 1000:5, as in then think to construct in memory a far pointer containing 1000:5, as in
the following: the following:
Ptr dd ? ```nasm
Ptr dd ?
: :
mov word ptr [Ptr],5 mov word ptr [Ptr],5
mov word ptr [Ptr+2],1000h mov word ptr [Ptr+2],1000h
jmp [Ptr] 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

View file

@ -64,13 +64,14 @@ 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
/* Finds and returns the greatest common divisor of two positive
integers. Works by trying every integral divisor between the integers. Works by trying every integral divisor between the
smaller of the two integers and 1, until a divisor that divides smaller of the two integers and 1, until a divisor that divides
both integers evenly is found. All C code tested with Microsoft both integers evenly is found. All C code tested with Microsoft
and Borland compilers.*/ 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) {
@ -85,7 +86,8 @@ default optimization was used. All times measured with the Zen timer
((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,14 +101,15 @@ 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
/* Finds and returns the greatest common divisor of two positive
integers. Works by subtracting the smaller integer from the integers. Works by subtracting the smaller integer from the
larger integer until either the values match (in which case larger integer until either the values match (in which case
that's the gcd), or the larger integer becomes the smaller of that's the gcd), or the larger integer becomes the smaller of
the two, in which case the two integers swap roles and the the two, in which case the two integers swap roles and the
subtraction process continues. */ 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 */
@ -127,4 +130,5 @@ in Listing 10.2.
} 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));
} }
```

View file

@ -45,16 +45,17 @@ 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
/* Finds and returns the greatest common divisor of two integers.
Uses Euclid's algorithm: divides the larger integer by the Uses Euclid's algorithm: divides the larger integer by the
smaller; if the remainder is 0, the smaller integer is the GCD, smaller; if the remainder is 0, the smaller integer is the GCD,
otherwise the smaller integer becomes the larger integer, the otherwise the smaller integer becomes the larger integer, the
remainder becomes the smaller integer, and the process is remainder becomes the smaller integer, and the process is
repeated. */ 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 */
@ -71,11 +72,11 @@ Figure 10.3. Listing 10.3 is an implementation of Euclid's algorithm.
/* 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,
@ -87,7 +88,8 @@ Figure 10.3. Listing 10.3 is an implementation of Euclid's algorithm.
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,14 +131,15 @@ 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
/* Finds and returns the greatest common divisor of two integers.
Uses Euclid's algorithm: divides the larger integer by the Uses Euclid's algorithm: divides the larger integer by the
smaller; if the remainder is 0, the smaller integer is the GCD, smaller; if the remainder is 0, the smaller integer is the GCD,
otherwise the smaller integer becomes the larger integer, the otherwise the smaller integer becomes the larger integer, the
remainder becomes the smaller integer, and the process is remainder becomes the smaller integer, and the process is
repeated. Avoids code recursion. */ 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 */
@ -160,7 +163,8 @@ recursive operations that Listing 10.3 does.
int1 = int2; int1 = int2;
int2 = temp; int2 = temp;
} }
} }
```
#### Patient Optimization {#Heading7} #### Patient Optimization {#Heading7}

View file

@ -12,48 +12,49 @@ 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
@ -66,39 +67,40 @@ pages: 200-203
mov ax,bx ;int1 = int2; mov ax,bx ;int1 = int2;
mov bx,dx ;int2 = remainder from DIV mov bx,dx ;int2 = remainder from DIV
;—start of loop unrolling; the above is repeated three times— ;—start of loop unrolling; the above is repeated three times—
sub dx,dx ;prepare int1 in DX:AX for division sub dx,dx ;prepare int1 in DX:AX for division
div bx ;int1/int2; remainder is in DX div bx ;int1/int2; remainder is in DX
and dx,dx ;is the remainder zero? and dx,dx ;is the remainder zero?
jz Done ;yes, so int2 (BX) is the gcd jz Done ;yes, so int2 (BX) is the gcd
mov ax,bx ;int1 = int2; mov ax,bx ;int1 = int2;
mov bx,dx ;int2 = remainder from DIV mov bx,dx ;int2 = remainder from DIV
;— ;—
sub dx,dx ;prepare int1 in DX:AX for division sub dx,dx ;prepare int1 in DX:AX for division
div bx ;int1/int2; remainder is in DX div bx ;int1/int2; remainder is in DX
and dx,dx ;is the remainder zero? and dx,dx ;is the remainder zero?
jz Done ;yes, so int2 (BX) is the gcd jz Done ;yes, so int2 (BX) is the gcd
mov ax,bx ;int1 = int2; mov ax,bx ;int1 = int2;
mov bx,dx ;int2 = remainder from DIV mov bx,dx ;int2 = remainder from DIV
;— ;—
sub dx,dx ;prepare int1 in DX:AX for division sub dx,dx ;prepare int1 in DX:AX for division
div bx ;int1/int2; remainder is in DX div bx ;int1/int2; remainder is in DX
and dx,dx ;is the remainder zero? and dx,dx ;is the remainder zero?
jz Done ;yes, so int2 (BX) is the gcd jz Done ;yes, so int2 (BX) is the gcd
mov ax,bx ;int1 = int2; mov ax,bx ;int1 = int2;
mov bx,dx ;int2 = remainder from DIV mov bx,dx ;int2 = remainder from DIV
;—end of loop unrolling— ;—end of loop unrolling—
jmp GCDLoop jmp GCDLoop
align2 align2
Done: Done:
mov ax,bx ;return the GCD mov ax,bx ;return the GCD
pop di ;restore caller's register variables pop di ;restore caller's register variables
pop si pop si
pop bp ;restore caller's stack frame pop bp ;restore caller's stack frame
ret ret
_gcd endp _gcd endp
end end
```
Assembly language optimization is pattern matching on a local scale. Assembly language optimization is pattern matching on a local scale.
Frankly, it's also the sort of boring, brute-force work that people are Frankly, it's also the sort of boring, brute-force work that people are

View file

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

View file

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

View file

@ -101,14 +101,15 @@ 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: ;
Skip:
push ds push ds
pop es pop es
mov si,1 ;source and destination are the same mov si,1 ;source and destination are the same
@ -118,6 +119,7 @@ refresh.
call ZTimerOn call ZTimerOn
rep movsw rep movsw
call ZTimerOff 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,13 +130,14 @@ 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: ;
Skip:
push ds push ds
pop es pop es
sub si,si ;source and destination are the same sub si,si ;source and destination are the same
@ -144,6 +147,7 @@ we predicted.
call ZTimerOn call ZTimerOn
rep movsw rep movsw
call ZTimerOff call ZTimerOff
```
The data alignment cycle-eater has intriguing implications for speeding The data alignment cycle-eater has intriguing implications for speeding
up 286/386 code. The expenditure of a little care and a few bytes to up 286/386 code. The expenditure of a little care and a few bytes to

View file

@ -15,12 +15,13 @@ AT clone to verify the basic functionality of the timer by measuring the
performance of simple instruction sequences. I was cruising along with performance of simple instruction sequences. I was cruising along with
no problems until I timed the following code: no problems until I timed the following code:
```nasm
mov cx,1000 mov cx,1000
call ZTimerOn call ZTimerOn
LoopTop: LoopTop:
loop LoopTop loop LoopTop
call ZTimerOff call ZTimerOff
```
![**Figure 11.2**  *Word-aligned prefetching on the 286.*](images/11-02.jpg) ![**Figure 11.2**  *Word-aligned prefetching on the 286.*](images/11-02.jpg)
@ -39,12 +40,14 @@ resided at the start of the next word-aligned word.
One simple change brought the execution time down to a reasonable 12.5 One simple change brought the execution time down to a reasonable 12.5
cycles per loop: cycles per loop:
```nasm
mov cx,1000 mov cx,1000
call ZTimerOn call ZTimerOn
even even
LoopTop: LoopTop:
loop LoopTop loop LoopTop
call ZTimerOff 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:
```nasm
even even
FindChar proc near FindChar proc near
: :
```
In my experience, this simple practice is the one form of code alignment In my experience, this simple practice is the one form of code alignment
that consistently provides a reasonable return for bytes and effort that consistently provides a reasonable return for bytes and effort

View file

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

View file

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

View file

@ -43,22 +43,24 @@ a segment and an offset. We'll also branch backward so that the address
pushed on the stack will point to the instruction we want to continue pushed on the stack will point to the instruction we want to continue
with. The code works out like this: with. The code works out like this:
```nasm
jmpshort popfskip jmpshort popfskip
popfiret: popfiret:
iret; branches to the instruction after the iret; branches to the instruction after the
; call, popping the word below the address ; call, popping the word below the address
; pushed by CALL into the FLAGS register ; pushed by CALL into the FLAGS register
popfskip: popfskip:
call far ptr popfiret call far ptr popfiret
;pushes the segment:offset of the next ;pushes the segment:offset of the next
; instruction on the stack just above ; instruction on the stack just above
; the flags word, setting things up so ; the flags word, setting things up so
; that IRET will branch to the next ; that IRET will branch to the next
; instruction and pop the flags ; instruction and pop the flags
; When execution reaches the instruction following this comment, ; When execution reaches the instruction following this comment,
; the word that was on top of the stack when JMP SHORT POPFSKIP ; the word that was on top of the stack when JMP SHORT POPFSKIP
; was reached has been popped into the FLAGS register, just as ; was reached has been popped into the FLAGS register, just as
; if a POPF instruction had been executed. ; if a POPF instruction had been executed.
```
![**Figure 11.5**  *The operation of IRET.*](images/11-05.jpg) ![**Figure 11.5**  *The operation of IRET.*](images/11-05.jpg)
@ -68,26 +70,30 @@ The **POPF** workaround can best be implemented as a macro; we can also
emulate a far call by pushing CS and performing a near call, thereby emulate a far call by pushing CS and performing a near call, thereby
shrinking the workaround code by 1 byte: shrinking the workaround code by 1 byte:
EMULATE_POPF macro ```nasm
EMULATE_POPF macro
local popfskip, popfiret local popfskip, popfiret
jmp short popfskip jmp short popfskip
popfiret: popfiret:
iret iret
popfskip: popfskip:
push cs push cs
call popfiret call popfiret
endm 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
EMULATE_POPF_TRASH_AX macro
push cs push cs
mov ax,offset $+5 mov ax,offset $+5
push ax push ax
iret iret
endm 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.)
```nasm
.286 .286
: :
EMULATE_POPFmacro EMULATE_POPFmacro
pushcs pushcs
pushoffset $+4 pushoffset $+4
iret iret
endm endm
```
![**Figure 11.6**  *Workaround code for the POPF bug.*](images/11-06.jpg) ![**Figure 11.6**  *Workaround code for the POPF bug.*](images/11-06.jpg)

View file

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

View file

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

View file

@ -41,22 +41,26 @@ pops up only spottily when the stack pointer is involved.
For example, you'd certainly expect a sequence such as For example, you'd certainly expect a sequence such as
: ```nasm
pop ax :
ret pop ax
pop ax ret
et pop ax
: et
:
```
to exhibit the addressing pipeline interruption phenomenon (SP is both to exhibit the addressing pipeline interruption phenomenon (SP is both
destination and addressing register for both instructions, according to destination and addressing register for both instructions, according to
Intel), but this code runs in six cycles per **POP/RET** pair, matching Intel), but this code runs in six cycles per **POP/RET** pair, matching
the official execution times exactly. Likewise, a sequence like the official execution times exactly. Likewise, a sequence like
pop dx ```nasm
pop cx pop dx
pop bx pop cx
pop ax pop bx
pop ax
```
runs in one cycle per instruction, just as it should. runs in one cycle per instruction, just as it should.
@ -65,15 +69,19 @@ destination—for example, to deallocate local variables—and then using
**PUSH**, **POP**, or **RET**, definitely can interrupt the addressing **PUSH**, **POP**, or **RET**, definitely can interrupt the addressing
pipeline. For example pipeline. For example
add sp,10h ```nasm
ret add sp,10h
ret
```
loses two cycles because SP is the explicit destination of one loses two cycles because SP is the explicit destination of one
instruction and then the implied addressing register for the next, and instruction and then the implied addressing register for the next, and
the sequence the sequence
add sp,10h ```nasm
pop ax add sp,10h
pop ax
```
loses two cycles for the same reason. loses two cycles for the same reason.
@ -101,22 +109,26 @@ during the next instruction.
So, for example, it would be a bad idea to do this So, for example, it would be a bad idea to do this
mov ah,o ```nasm
mov ah,o
: :
mov cx,[MemVar1] mov cx,[MemVar1]
mov al,[MemVar2] mov al,[MemVar2]
add cx,ax 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 al,[MemVar2]
mov cx,[MemVar1] mov cx,[MemVar1]
add cx,ax 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] mov al,[si]
xlat 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

View file

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

View file

@ -78,10 +78,12 @@ also lines, at a rate of three instructions for every two characters!
**LISTING 13.1 L13-1.ASM** **LISTING 13.1 L13-1.ASM**
mov di,[bp+OFFS] ;get the next pair of characters ```nasm
mov bl,[di] ;get the state value for the pair mov di,[bp+OFFS] ;get the next pair of characters
add dx,[bx+8000h] ;increment word and line count mov bl,[di] ;get the state value for the pair
add dx,[bx+8000h] ;increment word and line count
; appropriately for the pair ; 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
add dx,[bx+8000h] ;increment word and line count
; appropriately for the pair ; appropriately for the pair
```
At this point, Terje had nearly doubled the performance of this code At this point, Terje had nearly doubled the performance of this code
simply by moving one instruction. (Note that swapping the instructions simply by moving one instruction. (Note that swapping the instructions

View file

@ -22,18 +22,22 @@ throughput of two cycles/char."
**LISTING 13.3 L13-3.ASM** **LISTING 13.3 L13-3.ASM**
mov bl,[di] ;get the state value for the pair ```nasm
mov di,[bp+OFFS] ;get the next pair of characters mov bl,[di] ;get the state value for the pair
mov ax,[bx+8000h] ;increment word and line count mov di,[bp+OFFS] ;get the next pair of characters
add dx,ax ; appropriately for the pair mov ax,[bx+8000h] ;increment word and line count
add dx,ax ; appropriately for the pair
```
**LISTING 13.4 L13-4.ASM** **LISTING 13.4 L13-4.ASM**
mov bl,[di] ;get the state value for the pair ```nasm
mov di,[bp+OFFS] ;get the next pair of characters mov bl,[di] ;get the state value for the pair
add dx,ax ;increment word and line count mov di,[bp+OFFS] ;get the next pair of characters
add dx,ax ;increment word and line count
; appropriately for the pair ; appropriately for the pair
mov ax,[bx+8000h] ;get increments for next time 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,10 +100,11 @@ 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 ror ecx,16 ;make skip value word accessible in CX
add bx,cx ;skip BX ahead add bx,cx ;skip BX ahead
@ -105,6 +112,7 @@ memory, isn't it?
ror ecx,16 ;put loop count in CX ror ecx,16 ;put loop count in CX
dec cx ;count down loop dec cx ;count down loop
jnz looptop jnz looptop
```
Not necessarily. Shifts and rotates are among the worst performing Not necessarily. Shifts and rotates are among the worst performing
instructions of the 486, taking 2 to 3 cycles to execute. Thus, it takes instructions of the 486, taking 2 to 3 cycles to execute. Thus, it takes

View file

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

View file

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

View file

@ -12,19 +12,20 @@ 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
/* Searches a buffer for a specified pattern. In case of a mismatch,
uses the value of the mismatched byte to skip across as many uses the value of the mismatched byte to skip across as many
potential match locations as possible (partial Boyer-Moore). potential match locations as possible (partial Boyer-Moore).
Returns start offset of first match searching forward, or NULL if Returns start offset of first match searching forward, or NULL if
no match is found. no match is found.
Tested with Borland C++ in C mode and the small model. */ 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;
@ -96,26 +97,28 @@ pages: 268-271
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
/* Program to exercise buffer-search routines in Listings 14.1 & 14.3.
(Must be modified to put copy of pattern as sentinel at end of the (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.) */ 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;
@ -153,7 +156,8 @@ pages: 268-271
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

122
14-05.md
View file

@ -12,39 +12,40 @@ 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
@ -55,26 +56,26 @@ pages: 271-274
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
@ -83,18 +84,18 @@ pages: 271-274
; 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
@ -106,15 +107,15 @@ pages: 271-274
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
@ -123,17 +124,17 @@ pages: 271-274
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
@ -145,30 +146,31 @@ pages: 271-274
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
```

110
14-06.md
View file

@ -35,46 +35,47 @@ 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
@ -87,36 +88,36 @@ about 60 percent faster than Listing 14.3.
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?
@ -125,15 +126,15 @@ about 60 percent faster than Listing 14.3.
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
@ -141,17 +142,17 @@ about 60 percent faster than Listing 14.3.
; 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
@ -160,27 +161,28 @@ about 60 percent faster than Listing 14.3.
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

View file

@ -12,47 +12,52 @@ 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
/* Deletes the node in a linked list that follows the indicated node.
Assumes list is headed by a dummy node, so no special testing for Assumes list is headed by a dummy node, so no special testing for
the head-of-list pointer is required. Returns the same pointer the head-of-list pointer is required. Returns the same pointer
that was passed in. */ 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
/* Deletes the node in the specified linked list that follows the
indicated node. List is headed by a head-of-list pointer; if the indicated node. List is headed by a head-of-list pointer; if the
pointer to the node to delete after points to the head-of-list pointer to the node to delete after points to the head-of-list
pointer, special handling is performed. */ pointer, special handling is performed. */
#include "llist.h" #include "llist.h"
struct LinkNode *DeleteNodeAfter(struct LinkNode **HeadOfListPtr, struct LinkNode *DeleteNodeAfter(struct LinkNode **HeadOfListPtr,
struct LinkNode *NodeToDeleteAfter) struct LinkNode *NodeToDeleteAfter)
{ {
/* Handle specially if the node to delete after is actually the /* Handle specially if the node to delete after is actually the
head of the list (delete the first element in the list) */ head of the list (delete the first element in the list) */
if (NodeToDeleteAfter == (struct LinkNode *)HeadOfListPtr) { if (NodeToDeleteAfter == (struct LinkNode *)HeadOfListPtr) {
@ -62,7 +67,8 @@ pages: 284-287
NodeToDeleteAfter->NextNode->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,16 +129,17 @@ 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
/* Finds the first node in a linked list with a value field greater
than or equal to a key value, and returns a pointer to the node than or 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 such value was found. Assumes the list is NULL pointer if no such value was found. Assumes the list is
terminated with a tail node pointing to itself as the next node. */ terminated with a tail node pointing to itself as the next node. */
#include <stdio.h> #include <stdio.h>
#include "llist.h" #include "llist.h"
struct LinkNode *FindNodeBeforeValueNotLess( struct LinkNode *FindNodeBeforeValueNotLess(
struct LinkNode *HeadOfListNode, int SearchValue) 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) &&
@ -144,7 +151,8 @@ value has to perform two tests in the inner loop, as shown in Listing
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

View file

@ -12,18 +12,19 @@ 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
/* Finds the first node in a value-sorted linked list that
has a Value field greater than or equal to a key value, and has a Value field greater than or equal to a key value, and
returns a pointer to the node preceding that node (to facilitate returns a pointer to the node preceding that node (to facilitate
insertion and deletion), or a NULL pointer if no such value was insertion and deletion), or a NULL pointer if no such value was
found. Assumes the list is terminated with a sentinel tail node found. Assumes the list is terminated with a sentinel tail node
containing the largest possible Value field setting and pointing containing the largest possible Value field setting and pointing
to itself as the next node. */ to itself as the next node. */
#include <stdio.h> #include <stdio.h>
#include "llist.h" #include "llist.h"
struct LinkNode *FindNodeBeforeValueNotLess( struct LinkNode *FindNodeBeforeValueNotLess(
struct LinkNode *HeadOfListNode, int SearchValue) struct LinkNode *HeadOfListNode, 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;
@ -32,7 +33,8 @@ pages: 287-290
else else
return(NodePtr); /* success; return pointer to node preceding return(NodePtr); /* success; return pointer to node preceding
node that was >= */ node that was >= */
} }
```
![**Figure 15.4**  *List terminated by a sentinel.*](images/15-04.jpg) ![**Figure 15.4**  *List terminated by a sentinel.*](images/15-04.jpg)
@ -80,20 +82,21 @@ 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
/* Suite of functions for maintaining a linked list sorted by
ascending order of the Value field. The list is circular; that ascending order of the Value field. The list is circular; that
is,it has a dummy node as both the head and the tail of the list. is,it has a dummy node as both the head and the tail of the list.
The dummy node is a sentinel, containing the largest possible The dummy node is a sentinel, containing the largest possible
Value field setting. Tested with Borland C++ in C mode. */ Value field setting. Tested with Borland C++ in C mode. */
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "llist.h" #include "llist.h"
/* Initializes an empty linked list of LinkNode structures, /* Initializes an empty linked list of LinkNode structures,
consisting of a single head/tail/sentinel node, and returns a consisting of a single head/tail/sentinel node, and returns a
pointer to the list. Returns NULL for failure. */ pointer to the list. Returns NULL for failure. */
struct LinkNode *InitLinkedList() struct LinkNode *InitLinkedList()
{ {
struct LinkNode *Sentinel; struct LinkNode *Sentinel;
if ((Sentinel = malloc(sizeof(struct LinkNode))) == NULL) if ((Sentinel = malloc(sizeof(struct LinkNode))) == NULL)
@ -102,17 +105,17 @@ before you write a single line of code.
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)
@ -129,14 +132,14 @@ before you write a single line of code.
} 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)
@ -144,4 +147,5 @@ before you write a single line of code.
NodeToInsert->NextNode = NodePtr->NextNode; NodeToInsert->NextNode = NodePtr->NextNode;
NodePtr->NextNode = NodeToInsert; NodePtr->NextNode = NodeToInsert;
return(NodePtr); return(NodePtr);
} }
```

View file

@ -12,37 +12,38 @@ 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
@ -51,7 +52,7 @@ pages: 290-293
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
@ -69,21 +70,23 @@ pages: 290-293
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];
@ -154,7 +157,8 @@ pages: 290-293
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

View file

@ -98,6 +98,7 @@ timed from a RAM disk on a 20 MHz 386.
**LISTING 16.1 L16-1.C** **LISTING 16.1 L16-1.C**
```c
/* Word-counting program. Tested with Borland C++ in C /* Word-counting program. Tested with Borland C++ in C
compilation mode and the small model. */ compilation mode and the small model. */
@ -170,3 +171,4 @@ timed from a RAM disk on a 20 MHz 386.
printf("\nTotal words in file: %lu\n", WordCount); printf("\nTotal words in file: %lu\n", WordCount);
return(0); return(0);
} }
```

View file

@ -22,21 +22,22 @@ generates.
**LISTING 16.2 L16-2.C** **LISTING 16.2 L16-2.C**
/* Word-counting program incorporating assembly language. Tested ```c
/* Word-counting program incorporating assembly language. Tested
with Borland C++ in C compilation mode & the small model. */ 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;
@ -79,33 +80,35 @@ generates.
} }
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
@ -118,7 +121,7 @@ generates.
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
@ -128,17 +131,17 @@ generates.
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;
@ -146,8 +149,8 @@ generates.
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
@ -161,8 +164,9 @@ generates.
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}

View file

@ -12,32 +12,33 @@ 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
@ -52,7 +53,7 @@ pages: 303-305
.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
@ -66,7 +67,7 @@ pages: 303-305
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
@ -78,10 +79,10 @@ pages: 303-305
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]
@ -94,15 +95,15 @@ pages: 303-305
ret ret
align 2 align 2
CountWord: CountWord:
add di,1 ;increment the word count add di,1 ;increment the word count
adc dx,0 adc dx,0
dec cx ;count down buffer length dec cx ;count down buffer length
jnz ScanLoop jnz ScanLoop
jmp Done jmp Done
_ScanBuffer endp _ScanBuffer endp
end end
```
Listing 16.4 features several interesting tricks. First, it uses Listing 16.4 features several interesting tricks. First, it uses
**LODSB** and **XLAT** in succession, a very neat way to get a **LODSB** and **XLAT** in succession, a very neat way to get a

View file

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

View file

@ -36,40 +36,41 @@ Table: Table 16.2 The top four word-counting entries.
**LISTING 16.5 QSCAN3.ASM** **LISTING 16.5 QSCAN3.ASM**
```nasm
; QSCAN3.ASM ; QSCAN3.ASM
; David Stafford ; 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
@ -214,3 +215,4 @@ Table: Table 16.2 The top four word-counting entries.
.fardata WordTable .fardata WordTable
include qscan3.inc ;built by MAKETAB include qscan3.inc ;built by MAKETAB
end end
```

View file

@ -12,26 +12,27 @@ 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 ;
parms struc
dw 2 dup(?) dw 2 dup(?)
buffer dw ? buffer dw ?
bufferlength dw ? bufferlength dw ?
charflag dw ? charflag dw ?
wordcount dw ? wordcount dw ?
parms ends parms ends
.model small .model small
.data .data
charstatustable label byte charstatustable label byte
rept 2 rept 2
db 39 dup(0) db 39 dup(0)
db 1 db 1
@ -45,7 +46,7 @@ pages: 313-316
endm endm
.code .code
public _ScanBuffer public _ScanBuffer
_ScanBuffer proc near _ScanBuffer proc near
push bp push bp
mov bp,sp mov bp,sp
push si push si
@ -61,18 +62,18 @@ pages: 313-316
cmp al,01h ; check if last one is char cmp al,01h ; check if last one is char
jne scanloop4 ; if not so, search for char jne scanloop4 ; if not so, search for char
jmp scanloop1 ; if so, search for zero jmp scanloop1 ; if so, search for zero
oddentry: xchg al,ah ; last one in ah oddentry: xchg al,ah ; last one in ah
lodsb ; get first byte lodsb ; get first byte
inc cx inc cx
cmp ah,01h ; check if last one was char cmp ah,01h ; check if last one was char
jne scanloop5 ; if not so, search for char jne scanloop5 ; if not so, search for char
jmp scanloop2 ; if so, search for zero jmp scanloop2 ; if so, search for zero
; ;
; locate the end of a word ; locate the end of a word
scanloop1: lodsw ; get two chars scanloop1: lodsw ; get two chars
xlat ; translate first xlat ; translate first
xchg al,ah ; first in ah xchg al,ah ; first in ah
scanloop2: xlat ; translate second scanloop2: xlat ; translate second
dec cx ; count down dec cx ; count down
jz done1 ; no more bytes left jz done1 ; no more bytes left
cmp ax,0101h ; check if two chars cmp ax,0101h ; check if two chars
@ -80,12 +81,12 @@ pages: 313-316
inc di ; increase wordcount inc di ; increase wordcount
cmp al,01h ; check if new word started cmp al,01h ; check if new word started
je scanloop1 ; locate end of word je scanloop1 ; locate end of word
; ;
; locate the begin of a word ; locate the begin of a word
scanloop4: lodsw ; get two chars scanloop4: lodsw ; get two chars
xlat ; translate first xlat ; translate first
xchg al,ah ; first in ah xchg al,ah ; first in ah
scanloop5: xlat ; translate second scanloop5: xlat ; translate second
dec cx ; count down dec cx ; count down
jz done2 ; no more bytes left jz done2 ; no more bytes left
cmp ax,0 ; check if word started cmp ax,0 ; check if word started
@ -94,14 +95,14 @@ pages: 313-316
je scanloop1 ; if not, locate end of word je scanloop1 ; if not, locate end of word
inc di ; increase wordcount inc di ; increase wordcount
jmp scanloop4 ; locate begin of next word jmp scanloop4 ; locate begin of next word
done1: cmp ax,0101h ; check if end-of-word done1: cmp ax,0101h ; check if end-of-word
je done ; if not, we have finished je done ; if not, we have finished
inc di ; increase wordcount inc di ; increase wordcount
jmp done jmp done
done2: cmp ax,0100h ; check for one-letter word done2: cmp ax,0100h ; check for one-letter word
jne done ; if not, we have finished jne done ; if not, we have finished
inc di ; increase wordcount inc di ; increase wordcount
done: mov si,[bp+charflag] done: mov si,[bp+charflag]
mov [si],al mov [si],al
mov bx,[bp+wordcount] mov bx,[bp+wordcount]
mov ax,[bx] mov ax,[bx]
@ -114,8 +115,9 @@ pages: 313-316
pop si pop si
pop bp pop bp
ret ret
_ScanBuffer endp _ScanBuffer endp
end end
```
### Level 2: A New Perspective {#Heading11} ### Level 2: A New Perspective {#Heading11}

View file

@ -12,7 +12,8 @@ pages: 316-319
**Listing 16.7 L16-7.ASM** **Listing 16.7 L16-7.ASM**
ScanLoop: ```nasm
ScanLoop:
lodsw ;get the next 2 bytes (AL = first, AH = 2nd) lodsw ;get the next 2 bytes (AL = first, AH = 2nd)
xlat ;look up first's char/not status xlat ;look up first's char/not status
xor dl,al ;see if there's a new char/not status xor dl,al ;see if there's a new char/not status
@ -25,7 +26,7 @@ pages: 316-319
mov dl,al mov dl,al
dec dx dec dx
jnz ScanLoop 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,17 +67,18 @@ 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" );
@ -98,7 +100,7 @@ report a "location counter overflow" warning; ignore it.)
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.

182
17-02.md
View file

@ -12,33 +12,34 @@ 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
/* C++ Game of Life implementation for any mode for which mode set
and draw pixel functions can be provided. and draw pixel functions can be provided.
Tested with Borland C++ in the small model. */ Tested with Borland C++ in the small model. */
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <iostream.h> #include <iostream.h>
#include <conio.h> #include <conio.h>
#include <time.h> #include <time.h>
#include <dos.h> #include <dos.h>
#include <bios.h> #include <bios.h>
#include <mem.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);
@ -46,24 +47,24 @@ pages: 325-329
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];
@ -102,85 +103,85 @@ pages: 325-329
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++) {
@ -205,27 +206,29 @@ pages: 325-329
} }
} }
} }
} }
```
**LISTING 17.2 L17-2.CPP** **LISTING 17.2 L17-2.CPP**
/* VGA mode 13h functions for Game of Life. ```cpp
/* VGA mode 13h functions for Game of Life.
Tested with Borland C++. */ Tested with Borland C++. */
#include <stdio.h> #include <stdio.h>
#include <conio.h> #include <conio.h>
#include <dos.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;
@ -238,30 +241,31 @@ pages: 325-329
} }
screen_ptr += SCREEN_WIDTH_IN_BYTES; screen_ptr += SCREEN_WIDTH_IN_BYTES;
} }
} }
/* Mode 13h mode-set function. */ /* Mode 13h mode-set function. */
void enter_display_mode() void enter_display_mode()
{ {
union REGS regset; union REGS regset;
regset.x.ax = 0x0013; regset.x.ax = 0x0013;
int86(0x10, &regset, &regset); int86(0x10, &regset, &regset);
} }
/* Text mode mode-set function. */ /* Text mode mode-set function. */
void exit_display_mode() void exit_display_mode()
{ {
union REGS regset; union REGS regset;
regset.x.ax = 0x0003; regset.x.ax = 0x0003;
int86(0x10, &regset, &regset); int86(0x10, &regset, &regset);
} }
/* Text display function. Offsets text to non-graphics area of /* Text display function. Offsets text to non-graphics area of
screen. */ screen. */
void show_text(int x, int y, char *text) void show_text(int x, int y, char *text)
{ {
gotoxy(TEXT_X_OFFSET + x, y); gotoxy(TEXT_X_OFFSET + x, y);
puts(text); puts(text);
} }
```

View file

@ -48,19 +48,20 @@ improvement.
**LISTING 17.3 L17-3.CPP** **LISTING 17.3 L17-3.CPP**
/* cellmap class definition, constructor, copy_cells(), set_cell(), ```cpp
/* cellmap class definition, constructor, copy_cells(), set_cell(),
clear_cell(), cell_state(), count_neighbors(), and clear_cell(), cell_state(), count_neighbors(), and
next_generation() for fast, hard-wired neighbor count approach. next_generation() for fast, hard-wired neighbor count approach.
Otherwise, the same as Listing 17.1 */ 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);
@ -69,12 +70,12 @@ improvement.
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
@ -83,71 +84,71 @@ improvement.
// 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;
@ -184,12 +185,12 @@ improvement.
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++) {
@ -208,4 +209,5 @@ improvement.
} }
} }
} }
} }
```

View file

@ -51,14 +51,15 @@ 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
/* next_generation(), implemented using fast, all-in-one hard-wired
neighbor count/update/draw function. Otherwise, the same as neighbor count/update/draw function. Otherwise, the same as
Listing 17.3. */ 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;
@ -83,7 +84,7 @@ Listing 17.3.
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;
@ -130,7 +131,8 @@ Listing 17.3.
} }
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

128
17-07.md
View file

@ -12,36 +12,37 @@ 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
/* C++ Game of Life implementation for any mode for which mode set
and draw pixel functions can be provided. The cellmap stores the and draw pixel functions can be provided. The cellmap stores the
neighbor count for each cell as well as the state of each cell; neighbor count for each cell as well as the state of each cell;
this allows very fast next-state determination. Edges always wrap this allows very fast next-state determination. Edges always wrap
in this implementation. in this implementation.
Tested with Borland C++. To run, link with Listing 17.2 Tested with Borland C++. To run, link with Listing 17.2
in the large model. */ in the large model. */
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <iostream.h> #include <iostream.h>
#include <conio.h> #include <conio.h>
#include <time.h> #include <time.h>
#include <dos.h> #include <dos.h>
#include <bios.h> #include <bios.h>
#include <mem.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);
@ -50,28 +51,28 @@ pages: 340-345
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;
@ -92,24 +93,24 @@ pages: 340-345
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;
@ -120,19 +121,19 @@ pages: 340-345
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;
@ -165,12 +166,12 @@ pages: 340-345
*(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;
@ -203,20 +204,20 @@ pages: 340-345
*(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;
@ -257,13 +258,13 @@ pages: 340-345
// 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
@ -284,4 +285,5 @@ pages: 340-345
set_cell(x, y); set_cell(x, y);
} }
} while (—init_length); } while (—init_length);
} }
```

View file

@ -12,30 +12,33 @@ 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 = ""; char *Seg = "";
@ -48,7 +51,7 @@ pages: 352-361
printf( "jmp dx\n" ); printf( "jmp dx\n" );
} }
void Next2( void ) void Next2( void )
{ {
printf( "mov bp,es:[si]\n" ); printf( "mov bp,es:[si]\n" );
printf( "add si,2\n" ); printf( "add si,2\n" );
@ -57,7 +60,7 @@ pages: 352-361
printf( "jmp dx\n" ); printf( "jmp dx\n" );
} }
void BuildMaps( void ) void BuildMaps( void )
{ {
unsigned short i, j, Size, x = 0, y, N1, N2, N3, C1, C2, C3; unsigned short i, j, Size, x = 0, y, N1, N2, N3, C1, C2, C3;
@ -165,7 +168,7 @@ pages: 352-361
printf( "ends\n\n" ); printf( "ends\n\n" );
} }
void GetUpAndDown( void ) void GetUpAndDown( void )
{ {
printf( "mov ax,[bp+_RowColMap-_CellMap]\n" ); printf( "mov ax,[bp+_RowColMap-_CellMap]\n" );
printf( "or ah,ah\n" ); printf( "or ah,ah\n" );
@ -179,7 +182,7 @@ pages: 352-361
printf( "D%d:\n", Label ); printf( "D%d:\n", Label );
} }
void FirstPass( void ) void FirstPass( void )
{ {
char *Op; char *Op;
unsigned short UpDown = 0; unsigned short UpDown = 0;
@ -334,7 +337,7 @@ pages: 352-361
Next1(); Next1();
} }
void Test( char *Offset, char *Str ) void Test( char *Offset, char *Str )
{ {
printf( "mov bx,[bp+%s]\n", Offset ); printf( "mov bx,[bp+%s]\n", Offset );
printf( "cmp bh,[bx]\n" ); printf( "cmp bh,[bx]\n" );
@ -342,7 +345,7 @@ pages: 352-361
printf( "%s%d:\n", Str, Label ); printf( "%s%d:\n", Str, Label );
} }
void Fix( char *Offset, char *Str, int JumpBack ) void Fix( char *Offset, char *Str, int JumpBack )
{ {
printf( "FIX_%s%d:\n", Str, Label ); printf( "FIX_%s%d:\n", Str, Label );
printf( "mov bh,[bx]\n" ); printf( "mov bh,[bx]\n" );
@ -356,7 +359,7 @@ pages: 352-361
if( JumpBack ) printf( "jmp short %s%d\n", Str, Label ); if( JumpBack ) printf( "jmp short %s%d\n", Str, Label );
} }
void SecondPass( void ) void SecondPass( void )
{ {
printf( "org 0%02x00h\n", printf( "org 0%02x00h\n",
(Edge << 7) + (New << 4) + (Old << 1) + 1 ); (Edge << 7) + (New << 4) + (Old << 1) + 1 );
@ -459,7 +462,7 @@ pages: 352-361
Next2(); Next2();
} }
void main( void ) void main( void )
{ {
char *Seg = "ds"; char *Seg = "ds";
@ -512,3 +515,4 @@ pages: 352-361
printf( "LIFE ends\nend\n" ); printf( "LIFE ends\nend\n" );
} }
```

106
18-04.md
View file

@ -12,24 +12,25 @@ 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; unsigned int i, j, t, x, y, init;
@ -52,7 +53,7 @@ pages: 361-365
NextGen(); // Set cell states, prime the pump. NextGen(); // Set cell states, prime the pump.
} }
void main( void ) void main( void )
{ {
unsigned long generation = 0; unsigned long generation = 0;
char gen_text[ 80 ]; char gen_text[ 80 ];
@ -102,65 +103,70 @@ pages: 361-365
printf( "Time: %f generations/second\n", printf( "Time: %f generations/second\n",
(double)generation / (double)end_time * 18.2 ); (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
/* VGA mode 13h functions for Game of Life.
Tested with Borland C++. */ Tested with Borland C++. */
#include <stdio.h> #include <stdio.h>
#include <conio.h> #include <conio.h>
#include <dos.h> #include <dos.h>
#define TEXT_X_OFFSET 28 #define TEXT_X_OFFSET 28
#define SCREEN_WIDTH_IN_BYTES 320 #define SCREEN_WIDTH_IN_BYTES 320
#define SCREEN_SEGMENT 0xA000 #define SCREEN_SEGMENT 0xA000
/* Mode 13h mode-set function. */ /* Mode 13h mode-set function. */
void enter_display_mode() void enter_display_mode()
{ {
union REGS regset; union REGS regset;
regset.x.ax = 0x0013; regset.x.ax = 0x0013;
int86(0x10, &regset, &regset); int86(0x10, &regset, &regset);
} }
/* Text mode mode-set function. */ /* Text mode mode-set function. */
void exit_display_mode() void exit_display_mode()
{ {
union REGS regset; union REGS regset;
regset.x.ax = 0x0003; regset.x.ax = 0x0003;
int86(0x10, &regset, &regset); int86(0x10, &regset, &regset);
} }
/* Text display function. Offsets text to non-graphics area of /* Text display function. Offsets text to non-graphics area of
screen. */ screen. */
void show_text(int x, int y, char *text) void show_text(int x, int y, char *text)
{ {
gotoxy(TEXT_X_OFFSET + x, y); gotoxy(TEXT_X_OFFSET + x, y);
puts(text); puts(text);
} }
```
**LISTING 18.5 LIFE.H** **LISTING 18.5 LIFE.H**
void far NextGen( void ); ```c
void far NextGen( void );
extern unsigned short CellMap[]; extern unsigned short CellMap[];
extern unsigned short far ChangeList1[]; extern unsigned short far ChangeList1[];
#define LEFT (-2) #define LEFT (-2)
#define RIGHT (+2) #define RIGHT (+2)
#define UP (WIDTH * LEFT) #define UP (WIDTH * LEFT)
#define DOWN (WIDTH * RIGHT) #define DOWN (WIDTH * RIGHT)
#define UPPERLEFT (UP + LEFT) #define UPPERLEFT (UP + LEFT)
#define UPPERRIGHT (UP + RIGHT) #define UPPERRIGHT (UP + RIGHT)
#define LOWERLEFT (DOWN + LEFT) #define LOWERLEFT (DOWN + LEFT)
#define LOWERRIGHT (DOWN + RIGHT) #define LOWERRIGHT (DOWN + RIGHT)
#define WRAPLEFT (RIGHT * (WIDTH - 1)) #define WRAPLEFT (RIGHT * (WIDTH - 1))
#define WRAPRIGHT (LEFT * (WIDTH - 1)) #define WRAPRIGHT (LEFT * (WIDTH - 1))
#define WRAPUP (DOWN * (HEIGHT - 1)) #define WRAPUP (DOWN * (HEIGHT - 1))
#define WRAPDOWN (UP * (HEIGHT - 1)) #define WRAPDOWN (UP * (HEIGHT - 1))
```
### Keeping Track of Change with a Change List {#Heading5} ### Keeping Track of Change with a Change List {#Heading5}

View file

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

View file

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

View file

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

View file

@ -40,43 +40,45 @@ waits until the first instruction is done, then executes in the U-pipe,
possibly pairing with the next instruction in line if all pairing possibly pairing with the next instruction in line if all pairing
conditions are met. conditions are met.
MOV reg,reg (1 cycle) ```nasm
MOV reg,reg (1 cycle)
mem,reg (1 cycle) mem,reg (1 cycle)
reg,mem (1 cycle) reg,mem (1 cycle)
reg,immediate (1 cycle) reg,immediate (1 cycle)
mem,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,40 +102,42 @@ 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
MOV reg,reg (1 cycle)
mem,reg (1 cycle) mem,reg (1 cycle)
reg,mem (1 cycle) reg,mem (1 cycle)
reg,immediate (1 cycle) reg,immediate (1 cycle)
mem,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
@ -155,25 +159,33 @@ One downside of this "RISCification" (turning complex instructions into
simple, RISC-like ones) of Pentium-optimized code is that it makes for simple, RISC-like ones) of Pentium-optimized code is that it makes for
substantially larger code. For example, substantially larger code. For example,
push dword ptr [esi] ```nasm
push dword ptr [esi]
```
is one byte smaller than this sequence: is one byte smaller than this sequence:
mov eax,[esi] ```nasm
push eax mov eax,[esi]
push eax
```
![**Figure 20.3**  *Pushing a value from memory effectively in one ![**Figure 20.3**  *Pushing a value from memory effectively in one
cycle.*](images/20-03.jpg) cycle.*](images/20-03.jpg)
A more telling example is the following A more telling example is the following
add [MemVar],eax ```nasm
add [MemVar],eax
```
versus the equivalent: versus the equivalent:
mov edx,[MemVar] ```nasm
add edx,eax mov edx,[MemVar]
mov [MemVar],edx add edx,eax
mov [MemVar],edx
```
The single complex instruction takes 3 cycles and is 6 bytes long; with The single complex instruction takes 3 cycles and is 6 bytes long; with
proper sequencing, interleaving the simple instructions with other proper sequencing, interleaving the simple instructions with other

View file

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

View file

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

View file

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

View file

@ -15,16 +15,22 @@ stack pointer. Implicit modifiers of ESP, such as **PUSH** and **POP**,
are special-cased so you don't have to worry about AGIs. However, if you are special-cased so you don't have to worry about AGIs. However, if you
explicitly modify ESP with this instruction explicitly modify ESP with this instruction
sub esp,100h ```nasm
sub esp,100h
```
for example, or with the popular for example, or with the popular
mov esp,ebp ```nasm
mov esp,ebp
```
you can then get AGIs if you attempt to use ESP to address memory, you can then get AGIs if you attempt to use ESP to address memory,
either explicitly with instructions like this one either explicitly with instructions like this one
moveax,[esp+20h] ```nasm
moveax,[esp+20h]
```
or via **PUSH**, **POP**, or other instructions that implicitly use ESP or via **PUSH**, **POP**, or other instructions that implicitly use ESP
as an addressing register. as an addressing register.
@ -32,25 +38,31 @@ as an addressing register.
On the 486, any instruction that had both a constant value and an On the 486, any instruction that had both a constant value and an
addressing displacement, such as addressing displacement, such as
mov dword ptr [ebp+16],1 ```nasm
mov dword ptr [ebp+16],1
```
suffered a 1-cycle penalty, taking a total of 2 cycles. Such suffered a 1-cycle penalty, taking a total of 2 cycles. Such
instructions take only one cycle on the Pentium, but they cannot pair, instructions take only one cycle on the Pentium, but they cannot pair,
so they're still the most expensive sort of **MOV**. Knowing this can so they're still the most expensive sort of **MOV**. Knowing this can
speed up something as simple as zeroing two memory variables, as in speed up something as simple as zeroing two memory variables, as in
sub eax,eax ;U-pipe 1 ```nasm
sub eax,eax ;U-pipe 1
;any V-pipe pairable ;any V-pipe pairable
; instruction can go here, ; instruction can go here,
; or SUB could be in V-pipe ; or SUB could be in V-pipe
mov [MemVar1],eax ;U-pipe 2 mov [MemVar1],eax ;U-pipe 2
mov [MemVar2],eax ;V-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
inc eax ;U-pipe cycle 1
;V-pipe idle cycle 1 ;V-pipe idle cycle 1
; due to dependency ; due to dependency
and ebx,eax ;U-pipe cycle 2 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
sub eax,eax ;U-pipe cycle 1
;V-pipe idle cycle 1 ;V-pipe idle cycle 1
; due to register contention ; due to register contention
mov al,[Var] ;U-pipe cycle 2 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
LoopTop:
mov [esi],eax ;U-pipe cycle 1 mov [esi],eax ;U-pipe cycle 1
add esi,4 ;V-pipe cycle 1 add esi,4 ;V-pipe cycle 1
dec ecx ;U-pipe cycle 2 dec ecx ;U-pipe cycle 2
jnz LoopTop ;V-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,7 +181,8 @@ 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
LoopTop:
inc edx ;U-pipe cycle 1 inc edx ;U-pipe cycle 1
mov [esi],eax ;V-pipe cycle 1 mov [esi],eax ;V-pipe cycle 1
add esi,4 ;U-pipe cycle 2 add esi,4 ;U-pipe cycle 2
@ -168,3 +191,4 @@ even though we only added 25 percent more cycles:
;V-pipe idle cycle 3 ;V-pipe idle cycle 3
; because JNZ can't ; because JNZ can't
; pair in the U-pipe ; pair in the U-pipe
```

View file

@ -40,22 +40,23 @@ 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
@ -68,6 +69,7 @@ right?
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

View file

@ -27,12 +27,13 @@ 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
@ -40,7 +41,7 @@ same cache data bank, as discussed in the last chapter).
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
@ -50,9 +51,10 @@ same cache data bank, as discussed in the last chapter).
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,12 +70,13 @@ 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
@ -82,13 +85,13 @@ placement of **ADD ESI,4** to avoid an AGI.
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
@ -103,7 +106,7 @@ placement of **ADD ESI,4** to avoid an AGI.
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
@ -113,7 +116,8 @@ placement of **ADD ESI,4** to avoid an AGI.
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

View file

@ -12,13 +12,14 @@ 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
@ -27,7 +28,7 @@ pages: 409-411
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
@ -35,13 +36,14 @@ pages: 409-411
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,13 +55,14 @@ 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
@ -67,14 +70,14 @@ more registers.
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
@ -84,15 +87,16 @@ more registers.
dec ecx ;cycle 4 U-pipe dec ecx ;cycle 4 U-pipe
jnz ckloop ;cycle 4 V-pipe jnz ckloop ;cycle 4 V-pipe
ckloopend: ckloopend:
add eax,edx ;checksum the last two dwords add eax,edx ;checksum the last two dwords
adc eax,ebx adc eax,ebx
adc eax,0 adc eax,0
ckloopdone: ckloopdone:
mov edx,eax ;compress the 32-bit checksum mov edx,eax ;compress the 32-bit checksum
shr edx,16 ; into a 16-bit checksum shr edx,16 ; into a 16-bit checksum
add ax,dx add ax,dx
adc eax,0 adc eax,0
```
Listing 21.5 is undeniably intricate code, and not the sort of thing one Listing 21.5 is undeniably intricate code, and not the sort of thing one
would choose to write as a matter of course. On the other hand, it's would choose to write as a matter of course. On the other hand, it's

View file

@ -55,25 +55,26 @@ 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 ;
ClearS proc near
push bp ;save caller's BP push bp ;save caller's BP
mov bp,sp ;point to stack frame mov bp,sp ;point to stack frame
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
jne Start ; pointer is passed jne Start ; pointer is passed
cmp word ptr [bp].BufOfs,0 cmp word ptr [bp].BufOfs,0
je Bye je Bye
Start: cld ;make STOSW count up Start: cld ;make STOSW count up
mov ax,[bp].Attrib ;load AX with attribute parameter mov ax,[bp].Attrib ;load AX with attribute parameter
and ax,0ff00h ;prepare for merging with fill char and ax,0ff00h ;prepare for merging with fill char
mov bx,[bp].Filler ;load BX with fill char mov bx,[bp].Filler ;load BX with fill char
@ -85,10 +86,11 @@ are mine.
mov es,bx mov es,bx
mov cx,[bp].BufSize ;load CX with buffer size mov cx,[bp].BufSize ;load CX with buffer size
rep stosw ;fill the buffer rep stosw ;fill the buffer
Bye:mov sp,bp ;restore original stack pointer Bye:mov sp,bp ;restore original stack pointer
pop bp ; and caller's BP pop bp ; and caller's BP
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp ClearS endp
```
The first thing you'll notice about Listing 22.1 is that **ClearS** uses The first thing you'll notice about Listing 22.1 is that **ClearS** uses
a **REP STOSW** instruction. That means that we're not going to improve a **REP STOSW** instruction. That means that we're not going to improve

View file

@ -12,14 +12,15 @@ pages: 417-418
**LISTING 22.2 L22-2.ASM** **LISTING 22.2 L22-2.ASM**
ClearS proc near ```nasm
ClearS proc near
push bp ;save caller's BP push bp ;save caller's BP
mov bp,sp ;point to stack frame mov bp,sp ;point to stack frame
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
jne Start ; pointer is passed jne Start ; pointer is passed
cmp word ptr [bp].BufOfs,0 cmp word ptr [bp].BufOfs,0
je Bye je Bye
Start: cld ;make STOSW count up Start: cld ;make STOSW count up
mov ax,[bp].Attrib ;load AX with attribute parameter mov ax,[bp].Attrib ;load AX with attribute parameter
and ax,0ff00h ;prepare for merging with fill char and ax,0ff00h ;prepare for merging with fill char
mov bx,[bp].Filler ;load BX with fill char mov bx,[bp].Filler ;load BX with fill char
@ -29,10 +30,11 @@ pages: 417-418
mov es,[bp].BufSeg ;load ES with target buffer segment mov es,[bp].BufSeg ;load ES with target buffer segment
mov cx,[bp].BufSize ;load CX with buffer size mov cx,[bp].BufSize ;load CX with buffer size
rep stosw ;fill the buffer rep stosw ;fill the buffer
Bye: Bye:
pop bp ;restore caller's BP pop bp ;restore caller's BP
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp 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,14 +49,15 @@ 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
ClearS proc near
push bp ;save caller's BP push bp ;save caller's BP
mov bp,sp ;point to stack frame mov bp,sp ;point to stack frame
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
jne Start ; pointer is passed jne Start ; pointer is passed
cmp word ptr [bp].BufOfs,0 cmp word ptr [bp].BufOfs,0
je Bye je Bye
Start: cld ;make STOSW count up Start: cld ;make STOSW count up
mov ax,[bp].Attrib ;load AX with attribute parameter mov ax,[bp].Attrib ;load AX with attribute parameter
and ax,0ff00h ;prepare for merging with fill char and ax,0ff00h ;prepare for merging with fill char
mov bx,[bp].Filler ;load BX with fill char mov bx,[bp].Filler ;load BX with fill char
@ -64,10 +67,11 @@ loading ES and DI as shown in Listing 22.3.
;segment:offset ;segment:offset
mov cx,[bp].BufSize ;load CX with buffer size mov cx,[bp].BufSize ;load CX with buffer size
rep stosw ;fill the buffer rep stosw ;fill the buffer
Bye: Bye:
pop bp ;restore caller's BP pop bp ;restore caller's BP
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp 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.

View file

@ -12,23 +12,25 @@ pages: 419-420
**LISTING 22.5 L22-5.ASM** **LISTING 22.5 L22-5.ASM**
ClearS proc near ```nasm
ClearS proc near
push bp ;save caller's BP push bp ;save caller's BP
mov bp,sp ;point to stack frame mov bp,sp ;point to stack frame
cmp word ptr [bp].BufSeg,0 ;skip the fill if a null cmp word ptr [bp].BufSeg,0 ;skip the fill if a null
jne Start ; pointer is passed jne Start ; pointer is passed
cmp word ptr [bp].BufOfs,0 cmp word ptr [bp].BufOfs,0
je Bye je Bye
Start: cld ;make STOSW count up Start: cld ;make STOSW count up
mov ah,byte ptr [bp].Attrib[1];load AH with attribute mov ah,byte ptr [bp].Attrib[1];load AH with attribute
mov al,byte ptr [bp].Filler ;load AL with fill char mov al,byte ptr [bp].Filler ;load AL with fill char
les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer segment:offset les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer segment:offset
mov cx,[bp].BufSize ;load CX with buffer size mov cx,[bp].BufSize ;load CX with buffer size
rep stosw ;fill the buffer rep stosw ;fill the buffer
Bye: Bye:
pop bp ;restore caller's BP pop bp ;restore caller's BP
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp 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
ClearS proc near
push bp ;save caller's BP push bp ;save caller's BP
mov bp,sp ;point to stack frame mov bp,sp ;point to stack frame
les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer;segment:offset les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer;segment:offset
mov ax,es ;put segment where we can test it mov ax,es ;put segment where we can test it
or ax,di ;is it a null pointer? or ax,di ;is it a null pointer?
je Bye ;yes, so we're done je Bye ;yes, so we're done
Start: cld ;make STOSW count up Start: cld ;make STOSW count up
mov ah,byte ptr [bp].Attrib[1];load AH with attribute mov ah,byte ptr [bp].Attrib[1];load AH with attribute
mov al,byte ptr [bp].Filler ;load AL with fill char mov al,byte ptr [bp].Filler ;load AL with fill char
mov cx,[bp].BufSize ;load CX with buffer size mov cx,[bp].BufSize ;load CX with buffer size
rep stosw ;fill the buffer rep stosw ;fill the buffer
Bye: Bye:
pop bp ;restore caller's BP pop bp ;restore caller's BP
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp 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,7 +107,8 @@ 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
ClearS procnear
pop dx ;get the return address pop dx ;get the return address
pop ax ;put fill char into AL pop ax ;put fill char into AL
pop bx ;get the attribute pop bx ;get the attribute
@ -116,9 +121,10 @@ With that problem dealt with, Listing 22.7 shows the Zenned version of
je Bye ;yes, so we're done je Bye ;yes, so we're done
cld ;make STOSW count up cld ;make STOSW count up
rep stosw ;do the string store rep stosw ;do the string store
Bye: Bye:
jmp dx ;return to the calling code jmp dx ;return to the calling code
ClearS endp 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

474
23-03.md
View file

@ -72,61 +72,62 @@ seen the features in action.
**LISTING 23.1 L23-1.ASM** **LISTING 23.1 L23-1.ASM**
; Sample VGA program. ```nasm
; Animates four balls bouncing around a playfield by using ; Sample VGA program.
; page flipping. Playfield is panned smoothly both horizontally ; Animates four balls bouncing around a playfield by using
; and vertically. ; page flipping. Playfield is panned smoothly both horizontally
; By Michael Abrash. ; and vertically.
; ; By Michael Abrash.
stack segment para stack 'STACK' ;
stack segment para stack 'STACK'
db 512 dup(?) db 512 dup(?)
stack ends stack ends
; ;
MEDRES_VIDEO_MODE equ 0 ;define for 640x350 video mode MEDRES_VIDEO_MODE equ 0 ;define for 640x350 video mode
; comment out for 640x200 mode ; comment out for 640x200 mode
VIDEO_SEGMENT equ 0a000h ;display memory segment for VIDEO_SEGMENT equ 0a000h ;display memory segment for
; true VGA graphics modes ; true VGA graphics modes
LOGICAL_SCREEN_WIDTH equ 672/8 ;width in bytes and height in scan LOGICAL_SCREEN_WIDTH equ 672/8 ;width in bytes and height in scan
LOGICAL_SCREEN_HEIGHT equ 384 ; lines of the virtual screen LOGICAL_SCREEN_HEIGHT equ 384 ; lines of the virtual screen
; we'll work with ; we'll work with
PAGE0 equ 0 ;flag for page 0 when page flipping PAGE0 equ 0 ;flag for page 0 when page flipping
PAGE1 equ 1 ;flag for page 1 when page flipping PAGE1 equ 1 ;flag for page 1 when page flipping
PAGE0_OFFSET equ 0 ;start offset of page 0 in VGA memory PAGE0_OFFSET equ 0 ;start offset of page 0 in VGA memory
PAGE1_OFFSET equ LOGICAL_SCREEN_WIDTH * LOGICAL_SCREEN_HEIGHT PAGE1_OFFSET equ LOGICAL_SCREEN_WIDTH * LOGICAL_SCREEN_HEIGHT
;start offset of page 1 (both pages ;start offset of page 1 (both pages
; are 672x384 virtual screens) ; are 672x384 virtual screens)
BALL_WIDTH equ 24/8 ;width of ball in display memory bytes BALL_WIDTH equ 24/8 ;width of ball in display memory bytes
BALL_HEIGHT equ 24 ;height of ball in scan lines BALL_HEIGHT equ 24 ;height of ball in scan lines
BLANK_OFFSET equ PAGE1_OFFSET * 2 ;start of blank image BLANK_OFFSET equ PAGE1_OFFSET * 2 ;start of blank image
; in VGA memory ; in VGA memory
BALL_OFFSET equ BLANK_OFFSET + (BALL_WIDTH * BALL_HEIGHT) BALL_OFFSET equ BLANK_OFFSET + (BALL_WIDTH * BALL_HEIGHT)
;start offset of ball image in VGA memory ;start offset of ball image in VGA memory
NUM_BALLS equ 4 ;number of balls to animate NUM_BALLS equ 4 ;number of balls to animate
; ;
; VGA register equates. ; VGA register equates.
; ;
SC_INDEX equ 3c4h ;SC index register SC_INDEX equ 3c4h ;SC index register
MAP_MASK equ 2 ;SC map mask register MAP_MASK equ 2 ;SC map mask register
GC_INDEX equ 3ceh ;GC index register GC_INDEX equ 3ceh ;GC index register
GC_MODE equ 5 ;GC mode register GC_MODE equ 5 ;GC mode register
CRTC_INDEX equ 03d4h ;CRTC index register CRTC_INDEX equ 03d4h ;CRTC index register
START_ADDRESS_HIGH equ 0ch ;CRTC start address high byte START_ADDRESS_HIGH equ 0ch ;CRTC start address high byte
START_ADDRESS_LOW equ 0dh ;CRTC start address low byte START_ADDRESS_LOW equ 0dh ;CRTC start address low byte
CRTC_OFFSET equ 13h ;CRTC offset register CRTC_OFFSET equ 13h ;CRTC offset register
INPUT_STATUS_1 equ 03dah ;VGA status register INPUT_STATUS_1 equ 03dah ;VGA status register
VSYNC_MASK equ 08h ;vertical sync bit in status register 1 VSYNC_MASK equ 08h ;vertical sync bit in status register 1
DE_MASK equ 01h ;display enable bit in status register 1 DE_MASK equ 01h ;display enable bit in status register 1
AC_INDEX equ 03c0h ;AC index register AC_INDEX equ 03c0h ;AC index register
HPELPAN equ 20h OR 13h ;AC horizontal pel panning register HPELPAN equ 20h OR 13h ;AC horizontal pel panning register
; (bit 7 is high to keep palette RAM ; (bit 7 is high to keep palette RAM
; addressing on) ; addressing on)
dseg segment para common 'DATA' dseg segment para common 'DATA'
CurrentPage db PAGE1 ;page to draw to CurrentPage db PAGE1 ;page to draw to
CurrentPageOffset dw PAGE1_OFFSET CurrentPageOffset dw PAGE1_OFFSET
; ;
; Four plane's worth of multicolored ball image. ; Four plane's worth of multicolored ball image.
; ;
BallPlane0Image label byte ;blue plane image BallPlane0Image label byte ;blue plane image
db 000h, 03ch, 000h, 001h, 0ffh, 080h db 000h, 03ch, 000h, 001h, 0ffh, 080h
db 007h, 0ffh, 0e0h, 00fh, 0ffh, 0f0h db 007h, 0ffh, 0e0h, 00fh, 0ffh, 0f0h
db 4 * 3 dup(000h) db 4 * 3 dup(000h)
@ -136,7 +137,7 @@ seen the features in action.
db 07fh, 0ffh, 0feh, 03fh, 0ffh, 0fch db 07fh, 0ffh, 0feh, 03fh, 0ffh, 0fch
db 03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h db 03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h
db 4 * 3 dup(000h) db 4 * 3 dup(000h)
BallPlane1Image label byte ;green plane image BallPlane1Image label byte ;green plane image
db 4 * 3 dup(000h) db 4 * 3 dup(000h)
db 01fh, 0ffh, 0f8h, 03fh, 0ffh, 0fch db 01fh, 0ffh, 0f8h, 03fh, 0ffh, 0fch
db 03fh, 0ffh, 0fch, 07fh, 0ffh, 0feh db 03fh, 0ffh, 0fch, 07fh, 0ffh, 0feh
@ -145,7 +146,7 @@ seen the features in action.
db 8 * 3 dup(000h) db 8 * 3 dup(000h)
db 00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h db 00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h
db 001h, 0ffh, 080h, 000h, 03ch, 000h db 001h, 0ffh, 080h, 000h, 03ch, 000h
BallPlane2Image label byte ;red plane image BallPlane2Image label byte ;red plane image
db 12 * 3 dup(000h) db 12 * 3 dup(000h)
db 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh db 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
db 0ffh, 0ffh, 0ffh, 07fh, 0ffh, 0feh db 0ffh, 0ffh, 0ffh, 07fh, 0ffh, 0feh
@ -153,7 +154,7 @@ seen the features in action.
db 03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h db 03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h
db 00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h db 00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h
db 001h, 0ffh, 080h, 000h, 03ch, 000h db 001h, 0ffh, 080h, 000h, 03ch, 000h
BallPlane3Image label byte ;intensity on for all planes, BallPlane3Image label byte ;intensity on for all planes,
; to produce high-intensity colors ; to produce high-intensity colors
db 000h, 03ch, 000h, 001h, 0ffh, 080h db 000h, 03ch, 000h, 001h, 0ffh, 080h
db 007h, 0ffh, 0e0h, 00fh, 0ffh, 0f0h db 007h, 0ffh, 0e0h, 00fh, 0ffh, 0f0h
@ -167,91 +168,91 @@ seen the features in action.
db 03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h db 03fh, 0ffh, 0fch, 01fh, 0ffh, 0f8h
db 00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h db 00fh, 0ffh, 0f0h, 007h, 0ffh, 0e0h
db 001h, 0ffh, 080h, 000h, 03ch, 000h db 001h, 0ffh, 080h, 000h, 03ch, 000h
; ;
BallX dw 15, 50, 40, 70 ;array of ball x coords BallX dw 15, 50, 40, 70 ;array of ball x coords
BallY dw 40, 200, 110, 300 ;array of ball y coords BallY dw 40, 200, 110, 300 ;array of ball y coords
LastBallX dw 15, 50, 40, 70 ;previous ball x coords LastBallX dw 15, 50, 40, 70 ;previous ball x coords
LastBallY dw 40, 100, 160, 30 ;previous ball y coords LastBallY dw 40, 100, 160, 30 ;previous ball y coords
BallXInc dw 1, 1, 1, 1 ;x move factors for ball BallXInc dw 1, 1, 1, 1 ;x move factors for ball
BallYInc dw 8, 8, 8, 8 ;y move factors for ball BallYInc dw 8, 8, 8, 8 ;y move factors for ball
BallRep dw 1, 1, 1, 1 ;# times to keep moving BallRep dw 1, 1, 1, 1 ;# times to keep moving
; ball according to current ; ball according to current
; increments ; increments
BallControl dw Ball0Control, Ball1Control ;pointers to current BallControl dw Ball0Control, Ball1Control ;pointers to current
dw Ball2Control, Ball3Control ; locations in ball dw Ball2Control, Ball3Control ; locations in ball
; control strings ; control strings
BallControlString dw Ball0Control, Ball1Control ;pointers to BallControlString dw Ball0Control, Ball1Control ;pointers to
dw Ball2Control, Ball3Control ; start of ball dw Ball2Control, Ball3Control ; start of ball
; control strings ; control strings
; ;
; Ball control strings. ; Ball control strings.
; ;
Ball0Control label word Ball0Control label word
dw 10, 1, 4, 10, -1, 4, 10, -1, -4, 10, 1, -4, 0 dw 10, 1, 4, 10, -1, 4, 10, -1, -4, 10, 1, -4, 0
Ball1Control label word Ball1Control label word
dw 12, -1, 1, 28, -1, -1, 12, 1, -1, 28, 1, 1, 0 dw 12, -1, 1, 28, -1, -1, 12, 1, -1, 28, 1, 1, 0
Ball2Control label word Ball2Control label word
dw 20, 0, -1, 40, 0, 1, 20, 0, -1, 0 dw 20, 0, -1, 40, 0, 1, 20, 0, -1, 0
Ball3Control label word Ball3Control label word
dw 8, 1, 0, 52, -1, 0, 44, 1, 0, 0 dw 8, 1, 0, 52, -1, 0, 44, 1, 0, 0
; ;
; Panning control string. ; Panning control string.
; ;
ifdef MEDRES_VIDEO_MODE ifdef MEDRES_VIDEO_MODE
PanningControlString dw 32, 1, 0, 34, 0, 1, 32, -1, 0, 34, 0, -1, 0 PanningControlString dw 32, 1, 0, 34, 0, 1, 32, -1, 0, 34, 0, -1, 0
else else
PanningControlString dw 32, 1, 0, 184, 0, 1, 32, -1, 0, 184, 0, -1, 0 PanningControlString dw 32, 1, 0, 184, 0, 1, 32, -1, 0, 184, 0, -1, 0
endif endif
PanningControl dw PanningControlString ;pointer to current location PanningControl dw PanningControlString ;pointer to current location
; in panning control string ; in panning control string
PanningRep dw 1 ;# times to pan according to current PanningRep dw 1 ;# times to pan according to current
; panning increments ; panning increments
PanningXInc dw 1 ;x panning factor PanningXInc dw 1 ;x panning factor
PanningYInc dw 0 ;y panning factor PanningYInc dw 0 ;y panning factor
HPan db 0 ;horizontal pel panning setting HPan db 0 ;horizontal pel panning setting
PanningStartOffset dw 0 ;start offset adjustment to produce vertical PanningStartOffset dw 0 ;start offset adjustment to produce vertical
; panning & coarse horizontal panning ; panning & coarse horizontal panning
dseg ends dseg ends
; ;
; Macro to set indexed register P2 of chip with index register ; Macro to set indexed register P2 of chip with index register
; at P1 to AL. ; at P1 to AL.
; ;
SETREG macro P1, P2 SETREG macro P1, P2
mov dx,P1 mov dx,P1
mov ah,al mov ah,al
mov al,P2 mov al,P2
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 graphics mode. ; Select graphics mode.
; ;
ifdef MEDRES_VIDEO_MODE ifdef MEDRES_VIDEO_MODE
mov ax,010h mov ax,010h
else else
mov ax,0eh mov ax,0eh
endif endif
int 10h int 10h
; ;
; ES always points to VGA memory. ; ES always points to VGA memory.
; ;
mov ax,VIDEO_SEGMENT mov ax,VIDEO_SEGMENT
mov es,ax mov es,ax
; ;
; Draw border around playfield in both pages. ; Draw border around playfield in both pages.
; ;
mov di,PAGE0_OFFSET mov di,PAGE0_OFFSET
call DrawBorder ;page 0 border call DrawBorder ;page 0 border
mov di,PAGE1_OFFSET mov di,PAGE1_OFFSET
call DrawBorder ;page 1 border call DrawBorder ;page 1 border
; ;
; Draw all four plane's worth of the ball to undisplayed VGA memory. ; Draw all four plane's worth of the ball to undisplayed VGA memory.
; ;
mov al,01h ;enable plane 0 mov al,01h ;enable plane 0
SETREG SC_INDEX, MAP_MASK SETREG SC_INDEX, MAP_MASK
mov si,offset BallPlane0Image mov si,offset BallPlane0Image
@ -276,18 +277,18 @@ seen the features in action.
mov di,BALL_OFFSET mov di,BALL_OFFSET
mov cx,BALL_WIDTH * BALL_HEIGHT mov cx,BALL_WIDTH * BALL_HEIGHT
rep movsb rep movsb
; ;
; Draw a blank image the size of the ball to undisplayed VGA memory. ; Draw a blank image the size of the ball to undisplayed VGA memory.
; ;
mov al,0fh ;enable all memory planes, since the mov al,0fh ;enable all memory planes, since the
SETREG SC_INDEX, MAP_MASK ; blank has to erase all planes SETREG SC_INDEX, MAP_MASK ; blank has to erase all planes
mov di,BLANK_OFFSET mov di,BLANK_OFFSET
mov cx,BALL_WIDTH * BALL_HEIGHT mov cx,BALL_WIDTH * BALL_HEIGHT
sub al,al sub al,al
rep stosb rep stosb
; ;
; Set VGA to write mode 1, for block copying ball and blank images. ; Set VGA to write mode 1, for block copying ball and blank images.
; ;
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,GC_MODE mov al,GC_MODE
out dx,al ;point GC Index to GC Mode register out dx,al ;point GC Index to GC Mode register
@ -298,35 +299,35 @@ seen the features in action.
or al,1 ;set the write mode field to 1 or al,1 ;set the write mode field to 1
jmp $+2 ;delay to let bus settle jmp $+2 ;delay to let bus settle
out dx,al out dx,al
; ;
; Set VGA offset register in words to define logical screen width. ; Set VGA offset register in words to define logical screen width.
; ;
mov al,LOGICAL_SCREEN_WIDTH / 2 mov al,LOGICAL_SCREEN_WIDTH / 2
SETREG CRTC_INDEX, CRTC_OFFSET SETREG CRTC_INDEX, CRTC_OFFSET
; ;
; Move the balls by erasing each ball, moving it, and ; Move the balls by erasing each ball, moving it, and
; redrawing it, then switching pages when they're all moved. ; redrawing it, then switching pages when they're all moved.
; ;
BallAnimationLoop: BallAnimationLoop:
mov bx,( NUM_BALLS * 2 ) - 2 mov bx,( NUM_BALLS * 2 ) - 2
EachBallLoop: EachBallLoop:
; ;
; Erase old image of ball in this page (at location from one more earlier). ; Erase old image of ball in this page (at location from one more earlier).
; ;
mov si,BLANK_OFFSET ;point to blank image mov si,BLANK_OFFSET ;point to blank image
mov cx,[LastBallX+bx] mov cx,[LastBallX+bx]
mov dx,[LastBallY+bx] mov dx,[LastBallY+bx]
call DrawBall call DrawBall
; ;
; Set new last ball location. ; Set new last ball location.
; ;
mov ax,[BallX+bx] mov ax,[BallX+bx]
mov [LastballX+bx],ax mov [LastballX+bx],ax
mov ax,[BallY+bx] mov ax,[BallY+bx]
mov [LastballY+bx],ax mov [LastballY+bx],ax
; ;
; Change the ball movement values if it's time to do so. ; Change the ball movement values if it's time to do so.
; ;
dec [BallRep+bx] ;has current repeat factor run out? dec [BallRep+bx] ;has current repeat factor run out?
jnz MoveBall jnz MoveBall
mov si,[BallControl+bx] ;it's time to change movement values mov si,[BallControl+bx] ;it's time to change movement values
@ -336,48 +337,48 @@ seen the features in action.
jnz SetNewMove jnz SetNewMove
mov si,[BallControlString+bx] ;reset control string mov si,[BallControlString+bx] ;reset control string
lodsw ;get new repeat factor lodsw ;get new repeat factor
SetNewMove: SetNewMove:
mov [BallRep+bx],ax ;set new movement repeat factor mov [BallRep+bx],ax ;set new movement repeat factor
lodsw ;set new x movement increment lodsw ;set new x movement increment
mov [BallXInc+bx],ax mov [BallXInc+bx],ax
lodsw ;set new y movement increment lodsw ;set new y movement increment
mov [BallYInc+bx],ax mov [BallYInc+bx],ax
mov [BallControl+bx],si ;save new control string pointer mov [BallControl+bx],si ;save new control string pointer
; ;
; Move the ball. ; Move the ball.
; ;
MoveBall: MoveBall:
mov ax,[BallXInc+bx] mov ax,[BallXInc+bx]
add [BallX+bx],ax ;move in x direction add [BallX+bx],ax ;move in x direction
mov ax,[BallYInc+bx] mov ax,[BallYInc+bx]
add [BallY+bx],ax ;move in y direction add [BallY+bx],ax ;move in y direction
; ;
; Draw ball at new location. ; Draw ball at new location.
; ;
mov si,BALL_OFFSET ;point to ball's image mov si,BALL_OFFSET ;point to ball's image
mov cx,[BallX+bx] mov cx,[BallX+bx]
mov dx,[BallY+bx] mov dx,[BallY+bx]
call DrawBall call DrawBall
; ;
dec bx dec bx
dec bx dec bx
jns EachBallLoop jns EachBallLoop
; ;
; Set up the next panning state (but don't program it into the ; Set up the next panning state (but don't program it into the
; VGA yet). ; VGA yet).
; ;
call AdjustPanning call AdjustPanning
; ;
; Wait for display enable (pixel data being displayed) so we know ; Wait for display enable (pixel data being displayed) so we know
; we're nowhere near vertical sync, where the start address gets ; we're nowhere near vertical sync, where the start address gets
; latched and used. ; latched and used.
; ;
call WaitDisplayEnable call WaitDisplayEnable
; ;
; Flip to the new page by changing the start address. ; Flip to the new page by changing the start address.
; ;
mov ax,[CurrentPageOffset] mov ax,[CurrentPageOffset]
add ax,[PanningStartOffset] add ax,[PanningStartOffset]
push ax push ax
@ -386,14 +387,14 @@ seen the features in action.
pop ax pop ax
mov al,ah mov al,ah
SETREG CRTC_INDEX, START_ADDRESS_HIGH SETREG CRTC_INDEX, START_ADDRESS_HIGH
; ;
; Wait for vertical sync so the new start address has a chance ; Wait for vertical sync so the new start address has a chance
; to take effect. ; to take effect.
; ;
call WaitVSync call WaitVSync
; ;
; Set horizontal panning now, just as new start address takes effect. ; Set horizontal panning now, just as new start address takes effect.
; ;
mov al,[HPan] mov al,[HPan]
mov dx,INPUT_STATUS_1 mov dx,INPUT_STATUS_1
in al,dx ;reset AC addressing to index reg in al,dx ;reset AC addressing to index reg
@ -402,43 +403,43 @@ seen the features in action.
out dx,al ;set AC index to pel pan reg out dx,al ;set AC index to pel pan reg
mov al,[HPan] mov al,[HPan]
out dx,al ;set new pel panning out dx,al ;set new pel panning
; ;
; Flip the page to draw to to the undisplayed page. ; Flip the page to draw to to the undisplayed page.
; ;
xor [CurrentPage],1 xor [CurrentPage],1
jnz IsPage1 jnz IsPage1
mov [CurrentPageOffset],PAGE0_OFFSET mov [CurrentPageOffset],PAGE0_OFFSET
jmp short EndFlipPage jmp short EndFlipPage
IsPage1: IsPage1:
mov [CurrentPageOffset],PAGE1_OFFSET mov [CurrentPageOffset],PAGE1_OFFSET
EndFlipPage: EndFlipPage:
; ;
; Exit if a key's been hit. ; Exit if a key's been hit.
; ;
mov ah,1 mov ah,1
int 16h int 16h
jnz Done jnz Done
jmp BallAnimationLoop jmp BallAnimationLoop
; ;
; Finished, clear key, reset screen mode and exit. ; Finished, clear key, reset screen mode and exit.
; ;
Done: Done:
mov ah,0 ;clear key mov ah,0 ;clear key
int 16h int 16h
; ;
mov ax,3 ;reset to text mode mov ax,3 ;reset to text mode
int 10h int 10h
; ;
mov ah,4ch ;exit to DOS mov ah,4ch ;exit to DOS
int 21h int 21h
; ;
start endp start endp
; ;
; Routine to draw a ball-sized image to all planes, copying from ; Routine to draw a ball-sized image to all planes, copying from
; offset SI in VGA memory to offset CX,DX (x,y) in VGA memory in ; offset SI in VGA memory to offset CX,DX (x,y) in VGA memory in
; the current page. ; the current page.
; ;
DrawBall proc near DrawBall proc near
mov ax,LOGICAL_SCREEN_WIDTH mov ax,LOGICAL_SCREEN_WIDTH
mul dx ;offset of start of top image scan line mul dx ;offset of start of top image scan line
add ax,cx ;offset of upper left of image add ax,cx ;offset of upper left of image
@ -448,7 +449,7 @@ seen the features in action.
push ds push ds
push es push es
pop ds ;move from VGA memory to VGA memory pop ds ;move from VGA memory to VGA memory
DrawBallLoop: DrawBallLoop:
push di push di
mov cx,BALL_WIDTH mov cx,BALL_WIDTH
rep movsb ;draw a scan line of image rep movsb ;draw a scan line of image
@ -458,40 +459,40 @@ seen the features in action.
jnz DrawBallLoop jnz DrawBallLoop
pop ds pop ds
ret ret
DrawBall endp DrawBall endp
; ;
; Wait for the leading edge of vertical sync pulse. ; Wait for the leading edge of vertical sync pulse.
; ;
WaitVSync proc near WaitVSync proc near
mov dx,INPUT_STATUS_1 mov dx,INPUT_STATUS_1
WaitNotVSyncLoop: WaitNotVSyncLoop:
in al,dx in al,dx
and al,VSYNC_MASK and al,VSYNC_MASK
jnz WaitNotVSyncLoop jnz WaitNotVSyncLoop
WaitVSyncLoop: WaitVSyncLoop:
in al,dx in al,dx
and al,VSYNC_MASK and al,VSYNC_MASK
jz WaitVSyncLoop jz WaitVSyncLoop
ret ret
WaitVSync endp WaitVSync endp
; ;
; Wait for display enable to happen (pixels to be scanned to ; Wait for display enable to happen (pixels to be scanned to
; the screen, indicating we're in the middle of displaying a frame). ; the screen, indicating we're in the middle of displaying a frame).
; ;
WaitDisplayEnable proc near WaitDisplayEnable proc near
mov dx,INPUT_STATUS_1 mov dx,INPUT_STATUS_1
WaitDELoop: WaitDELoop:
in al,dx in al,dx
and al,DE_MASK and al,DE_MASK
jnz WaitDELoop jnz WaitDELoop
ret ret
WaitDisplayEnable endp WaitDisplayEnable endp
; ;
; Perform horizontal/vertical panning. ; Perform horizontal/vertical panning.
; ;
AdjustPanning proc near AdjustPanning proc near
dec [PanningRep] ;time to get new panning values? dec [PanningRep] ;time to get new panning values?
jnz DoPan jnz DoPan
mov si,[PanningControl] ;point to current location in mov si,[PanningControl] ;point to current location in
@ -501,7 +502,7 @@ seen the features in action.
jnz SetnewPanValues jnz SetnewPanValues
mov si,offset PanningControlString ;reset to start of string mov si,offset PanningControlString ;reset to start of string
lodsw ;get panning repeat factor lodsw ;get panning repeat factor
SetNewPanValues: SetNewPanValues:
mov [PanningRep],ax ;set new panning repeat value mov [PanningRep],ax ;set new panning repeat value
lodsw lodsw
mov [PanningXInc],ax ;horizontal panning value mov [PanningXInc],ax ;horizontal panning value
@ -509,10 +510,10 @@ seen the features in action.
mov [PanningYInc],ax ;vertical panning value mov [PanningYInc],ax ;vertical panning value
mov [PanningControl],si ;save current location in panning mov [PanningControl],si ;save current location in panning
; control string ; control string
; ;
; Pan according to panning values. ; Pan according to panning values.
; ;
DoPan: DoPan:
mov ax,[PanningXInc] ;horizontal panning mov ax,[PanningXInc] ;horizontal panning
and ax,ax and ax,ax
js PanLeft ;negative means pan left js PanLeft ;negative means pan left
@ -524,15 +525,15 @@ seen the features in action.
sub al,al ; and a start offset that's one sub al,al ; and a start offset that's one
inc [PanningStartOffset] ; higher inc [PanningStartOffset] ; higher
jmp short SetHPan jmp short SetHPan
PanLeft: PanLeft:
mov al,[HPan] mov al,[HPan]
dec al ;pan left; if pel pan reaches -1, dec al ;pan left; if pel pan reaches -1,
jns SetHPan ; it's time to move to the next jns SetHPan ; it's time to move to the next
mov al,7 ; byte with a pel pan of 7 and a mov al,7 ; byte with a pel pan of 7 and a
dec [PanningStartOffset] ; start offset that's one lower dec [PanningStartOffset] ; start offset that's one lower
SetHPan: SetHPan:
mov [HPan],al ;save new pel pan value mov [HPan],al ;save new pel pan value
CheckVerticalPan: CheckVerticalPan:
mov ax,[PanningYInc] ;vertical panning mov ax,[PanningYInc] ;vertical panning
and ax,ax and ax,ax
js PanUp ;negative means pan up js PanUp ;negative means pan up
@ -541,22 +542,22 @@ seen the features in action.
;pan down by advancing the start ;pan down by advancing the start
; address by a scan line ; address by a scan line
jmp short EndPan jmp short EndPan
PanUp: PanUp:
sub [PanningStartOffset],LOGICAL_SCREEN_WIDTH sub [PanningStartOffset],LOGICAL_SCREEN_WIDTH
;pan up by retarding the start ;pan up by retarding the start
; address by a scan line ; address by a scan line
EndPan: EndPan:
ret ret
; ;
; Draw textured border around playfield that starts at DI. ; Draw textured border around playfield that starts at DI.
; ;
DrawBorder proc near DrawBorder proc near
; ;
; Draw the left border. ; Draw the left border.
; ;
push di push di
mov cx,LOGICAL_SCREEN_HEIGHT / 16 mov cx,LOGICAL_SCREEN_HEIGHT / 16
DrawLeftBorderLoop: DrawLeftBorderLoop:
mov al,0ch ;select red color for block mov al,0ch ;select red color for block
call DrawBorderBlock call DrawBorderBlock
add di,LOGICAL_SCREEN_WIDTH * 8 add di,LOGICAL_SCREEN_WIDTH * 8
@ -565,13 +566,13 @@ seen the features in action.
add di,LOGICAL_SCREEN_WIDTH * 8 add di,LOGICAL_SCREEN_WIDTH * 8
loop DrawLeftBorderLoop loop DrawLeftBorderLoop
pop di pop di
; ;
; Draw the right border. ; Draw the right border.
; ;
push di push di
add di,LOGICAL_SCREEN_WIDTH - 1 add di,LOGICAL_SCREEN_WIDTH - 1
mov cx,LOGICAL_SCREEN_HEIGHT / 16 mov cx,LOGICAL_SCREEN_HEIGHT / 16
DrawRightBorderLoop: DrawRightBorderLoop:
mov al,0eh ;select yellow color for block mov al,0eh ;select yellow color for block
call DrawBorderBlock call DrawBorderBlock
add di,LOGICAL_SCREEN_WIDTH * 8 add di,LOGICAL_SCREEN_WIDTH * 8
@ -580,12 +581,12 @@ seen the features in action.
add di,LOGICAL_SCREEN_WIDTH * 8 add di,LOGICAL_SCREEN_WIDTH * 8
loop DrawRightBorderLoop loop DrawRightBorderLoop
pop di pop di
; ;
; Draw the top border. ; Draw the top border.
; ;
push di push di
mov cx,(LOGICAL_SCREEN_WIDTH - 2) / 2 mov cx,(LOGICAL_SCREEN_WIDTH - 2) / 2
DrawTopBorderLoop: DrawTopBorderLoop:
inc di inc di
mov al,0eh ;select yellow color for block mov al,0eh ;select yellow color for block
call DrawBorderBlock call DrawBorderBlock
@ -594,12 +595,12 @@ seen the features in action.
call DrawBorderBlock call DrawBorderBlock
loop DrawTopBorderLoop loop DrawTopBorderLoop
pop di pop di
; ;
; Draw the bottom border. ; Draw the bottom border.
; ;
add di,(LOGICAL_SCREEN_HEIGHT - 8) * LOGICAL_SCREEN_WIDTH add di,(LOGICAL_SCREEN_HEIGHT - 8) * LOGICAL_SCREEN_WIDTH
mov cx,(LOGICAL_SCREEN_WIDTH - 2) / 2 mov cx,(LOGICAL_SCREEN_WIDTH - 2) / 2
DrawBottomBorderLoop: DrawBottomBorderLoop:
inc di inc di
mov al,0ch ;select red color for block mov al,0ch ;select red color for block
call DrawBorderBlock call DrawBorderBlock
@ -608,12 +609,12 @@ seen the features in action.
call DrawBorderBlock call DrawBorderBlock
loop DrawBottomBorderLoop loop DrawBottomBorderLoop
ret ret
DrawBorder endp DrawBorder endp
; ;
; Draws an 8x8 border block in color in AL at location DI. ; Draws an 8x8 border block in color in AL at location DI.
; DI preserved. ; DI preserved.
; ;
DrawBorderBlock proc near DrawBorderBlock proc near
push di push di
SETREG SC_INDEX, MAP_MASK SETREG SC_INDEX, MAP_MASK
mov al,0ffh mov al,0ffh
@ -623,7 +624,8 @@ seen the features in action.
endm endm
pop di pop di
ret ret
DrawBorderBlock endp DrawBorderBlock endp
AdjustPanning endp AdjustPanning endp
cseg ends cseg ends
end start end start
```

248
24-02.md
View file

@ -12,70 +12,71 @@ 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' ;
stack segment para stack ‘STACK'
db 512 dup(?) db 512 dup(?)
stack ends stack ends
; ;
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
SCREEN_HEIGHT equ 350 SCREEN_HEIGHT equ 350
SCREEN_WIDTH_IN_BYTES equ 80 SCREEN_WIDTH_IN_BYTES equ 80
DEMO_AREA_HEIGHT equ 336 ;# of scan lines in area DEMO_AREA_HEIGHT equ 336 ;# of scan lines in area
; logical function operation ; logical function operation
; is demonstrated in ; is demonstrated in
DEMO_AREA_WIDTH_IN_BYTES equ 40 ;width in bytes of area DEMO_AREA_WIDTH_IN_BYTES equ 40 ;width in bytes of area
; logical function operation ; logical function operation
; is demonstrated in ; is demonstrated in
VERTICAL_BOX_WIDTH_IN_BYTES equ 10 ;width in bytes of the box used to VERTICAL_BOX_WIDTH_IN_BYTES equ 10 ;width in bytes of the box used to
; demonstrate each logical function ; demonstrate each logical function
; ;
; VGA register equates. ; VGA register equates.
; ;
GC_INDEX equ 3ceh ;GC index register GC_INDEX equ 3ceh ;GC index register
GC_ROTATE equ 3 ;GC data rotate/logical function GC_ROTATE equ 3 ;GC data rotate/logical function
; register index ; register index
GC_MODE equ 5 ;GC mode 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 LabelString label byte
db ‘UNMODIFIED AND OR XOR ' db ‘UNMODIFIED AND OR XOR '
LABEL_STRING_LENGTH equ $-LabelString LABEL_STRING_LENGTH equ $-LabelString
; ;
; Strings used to label fill patterns. ; Strings used to label fill patterns.
; ;
FillPatternFF db ‘Fill Pattern: 0FFh' FillPatternFF db ‘Fill Pattern: 0FFh'
FILL_PATTERN_FF_LENGTH equ $ - FillPatternFF FILL_PATTERN_FF_LENGTH equ $ - FillPatternFF
FillPattern00 db ‘Fill Pattern: 000h' FillPattern00 db ‘Fill Pattern: 000h'
FILL_PATTERN_00_LENGTH equ $ - FillPattern00 FILL_PATTERN_00_LENGTH equ $ - FillPattern00
FillPatternVert db ‘Fill Pattern: Vertical Bar' FillPatternVert db ‘Fill Pattern: Vertical Bar'
FILL_PATTERN_VERT_LENGTH equ $ - FillPatternVert FILL_PATTERN_VERT_LENGTH equ $ - FillPatternVert
FillPatternHorz db ‘Fill Pattern: Horizontal Bar' FillPatternHorz db ‘Fill Pattern: Horizontal Bar'
FILL_PATTERN_HORZ_LENGTH equ $ - FillPatternHorz 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 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
; ;
; ;
; Macro to call BIOS write string function to display text string ; Macro to call BIOS write string function to display text string
; TEXT_STRING, of length TEXT_LENGTH, at location ROW,COLUMN. ; TEXT_STRING, of length TEXT_LENGTH, at location ROW,COLUMN.
; ;
TEXT_UP macro TEXT_STRING, TEXT_LENGTH, ROW, COLUMN TEXT_UP macro TEXT_STRING, TEXT_LENGTH, ROW, COLUMN
mov ah,13h ;BIOS write string function mov ah,13h ;BIOS write string function
mov bp,offset TEXT_STRING ;ES:BP points to string mov bp,offset TEXT_STRING ;ES:BP points to string
mov cx,TEXT_LENGTH mov cx,TEXT_LENGTH
@ -84,25 +85,25 @@ pages: 453-458
mov bl,7 ;text attribute is white (light gray) mov bl,7 ;text attribute is white (light gray)
int 10h int 10h
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 640x350 graphics mode. ; Select 640x350 graphics mode.
; ;
mov ax,010h mov ax,010h
int 10h int 10h
; ;
; ES points to VGA memory. ; ES points to VGA memory.
; ;
mov ax,VGA_VIDEO_SEGMENT mov ax,VGA_VIDEO_SEGMENT
mov es,ax mov es,ax
; ;
; Draw background of horizontal bars. ; Draw background of horizontal bars.
; ;
mov dx,SCREEN_HEIGHT/4 mov dx,SCREEN_HEIGHT/4
;# of bars to draw (each 4 pixels high) ;# of bars to draw (each 4 pixels high)
sub di,di ;start at offset 0 in display memory sub di,di ;start at offset 0 in display memory
@ -110,7 +111,7 @@ pages: 453-458
mov bx,DEMO_AREA_WIDTH_IN_BYTES / 2 ;length of each bar mov bx,DEMO_AREA_WIDTH_IN_BYTES / 2 ;length of each bar
mov si,SCREEN_WIDTH_IN_BYTES - DEMO_AREA_WIDTH_IN_BYTES mov si,SCREEN_WIDTH_IN_BYTES - DEMO_AREA_WIDTH_IN_BYTES
mov bp,(SCREEN_WIDTH_IN_BYTES * 3) - DEMO_AREA_WIDTH_IN_BYTES mov bp,(SCREEN_WIDTH_IN_BYTES * 3) - DEMO_AREA_WIDTH_IN_BYTES
BackgroundLoop: BackgroundLoop:
mov cx,bx ;length of bar mov cx,bx ;length of bar
rep stosw ;draw top half of bar rep stosw ;draw top half of bar
add di,si ;point to start of bottom half of bar add di,si ;point to start of bottom half of bar
@ -119,89 +120,89 @@ pages: 453-458
add di,bp ;point to start of top of next bar add di,bp ;point to start of top of next bar
dec dx dec dx
jnz BackgroundLoop jnz BackgroundLoop
; ;
; Draw vertical boxes filled with a variety of fill patterns ; Draw vertical boxes filled with a variety of fill patterns
; using each of the 4 logical functions in turn. ; using each of the 4 logical functions in turn.
; ;
SETGC GC_ROTATE, 0 ;select data unmodified SETGC GC_ROTATE, 0 ;select data unmodified
; logical function... ; logical function...
mov di,0 mov di,0
call DrawVerticalBox ;...and draw box call DrawVerticalBox ;...and draw box
; ;
SETGC GC_ROTATE, 08h ;select AND logical function... SETGC GC_ROTATE, 08h ;select AND logical function...
mov di,10 mov di,10
call DrawVerticalBox ;...and draw box call DrawVerticalBox ;...and draw box
; ;
SETGC GC_ROTATE, 10h ;select OR logical function... SETGC GC_ROTATE, 10h ;select OR logical function...
mov di,20 mov di,20
call DrawVerticalBox ;...and draw box call DrawVerticalBox ;...and draw box
; ;
SETGC GC_ROTATE, 18h ;select XOR logical function... SETGC GC_ROTATE, 18h ;select XOR logical function...
mov di,30 mov di,30
call DrawVerticalBox ;...and draw box 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 push ds
pop es ;strings we'll display are passed to BIOS pop es ;strings we'll display are passed to BIOS
; by pointing ES:BP to them ; by pointing ES:BP to them
; ;
; Label the logical functions, using the VGA BIOS's ; Label the logical functions, using the VGA BIOS's
; write string function. ; 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 ; Label the fill patterns, using the VGA BIOS's
; write string function. ; write string function.
; ;
TEXT_UP FillPatternFF, FILL_PATTERN_FF_LENGTH, 3, 42 TEXT_UP FillPatternFF, FILL_PATTERN_FF_LENGTH, 3, 42
TEXT_UP FillPattern00, FILL_PATTERN_00_LENGTH, 9, 42 TEXT_UP FillPattern00, FILL_PATTERN_00_LENGTH, 9, 42
TEXT_UP FillPatternVert, FILL_PATTERN_VERT_LENGTH, 15, 42 TEXT_UP FillPatternVert, FILL_PATTERN_VERT_LENGTH, 15, 42
TEXT_UP FillPatternHorz, FILL_PATTERN_HORZ_LENGTH, 21, 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: WaitForKey:
mov ah,1 mov ah,1
int 16h int 16h
jz WaitForKey jz WaitForKey
; ;
; Finished. Clear key, reset screen mode and exit. ; Finished. Clear key, reset screen mode and exit.
; ;
Done: Done:
mov ah,0 ;clear key that we just detected mov ah,0 ;clear key that we just detected
int 16h int 16h
; ;
mov ax,3 ;reset to text mode mov ax,3 ;reset to text mode
int 10h int 10h
; ;
mov ah,4ch ;exit to DOS mov ah,4ch ;exit to DOS
int 21h int 21h
; ;
start endp start endp
; ;
; Subroutine to draw a box 80x336 in size, using currently selected ; Subroutine to draw a box 80x336 in size, using currently selected
; logical function, with upper left corner at the display memory offset ; logical function, with upper left corner at the display memory offset
; in DI. Box is filled with four patterns. Top quarter of area is ; in DI. Box is filled with four patterns. Top quarter of area is
; filled with 0FFh (solid) pattern, next quarter is filled with 00h ; filled with 0FFh (solid) pattern, next quarter is filled with 00h
; (empty) pattern, next quarter is filled with 33h (double pixel wide ; (empty) pattern, next quarter is filled with 33h (double pixel wide
; vertical bar) pattern, and bottom quarter is filled with double pixel ; vertical bar) pattern, and bottom quarter is filled with double pixel
; high horizontal bar pattern. ; high horizontal bar pattern.
; ;
; Macro to draw a column of the specified width in bytes, one-quarter ; Macro to draw a column of the specified width in bytes, one-quarter
; of the height of the box, with the specified fill pattern. ; of the height of the box, with the specified fill pattern.
; ;
DRAW_BOX_QUARTER macro FILL, WIDTH DRAW_BOX_QUARTER macro FILL, WIDTH
local RowLoop, ColumnLoop local RowLoop, ColumnLoop
mov al,FILL ;fill pattern mov al,FILL ;fill pattern
mov dx,DEMO_AREA_HEIGHT / 4 ;1/4 of the full box height mov dx,DEMO_AREA_HEIGHT / 4 ;1/4 of the full box height
RowLoop: RowLoop:
mov cx,WIDTH mov cx,WIDTH
ColumnLoop: ColumnLoop:
mov ah,es:[di] ;load display memory contents into mov ah,es:[di] ;load display memory contents into
; GC latches (we don't actually care ; GC latches (we don't actually care
; about value read into AH) ; about value read into AH)
@ -215,8 +216,8 @@ pages: 453-458
dec dx dec dx
jnz RowLoop jnz RowLoop
endm endm
; ;
DrawVerticalBox proc near DrawVerticalBox proc near
DRAW_BOX_QUARTER 0ffh, VERTICAL_BOX_WIDTH_IN_BYTES DRAW_BOX_QUARTER 0ffh, VERTICAL_BOX_WIDTH_IN_BYTES
;first fill pattern: solid fill ;first fill pattern: solid fill
DRAW_BOX_QUARTER 0, VERTICAL_BOX_WIDTH_IN_BYTES DRAW_BOX_QUARTER 0, VERTICAL_BOX_WIDTH_IN_BYTES
@ -229,37 +230,38 @@ pages: 453-458
; sets of 4 scan lines ; sets of 4 scan lines
sub ax,ax sub ax,ax
mov si,VERTICAL_BOX_WIDTH_IN_BYTES ;width of fill area mov si,VERTICAL_BOX_WIDTH_IN_BYTES ;width of fill area
HorzBarLoop: HorzBarLoop:
dec ax ;0ffh fill (smaller to do word than byte DEC) dec ax ;0ffh fill (smaller to do word than byte DEC)
mov cx,si ;width to fill mov cx,si ;width to fill
HBLoop1: HBLoop1:
mov bl,es:[di] ;load latches (don't care about value) mov bl,es:[di] ;load latches (don't care about value)
stosb ;write solid pattern, through ALUs stosb ;write solid pattern, through ALUs
loop HBLoop1 loop HBLoop1
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
mov cx,si ;width to fill mov cx,si ;width to fill
HBLoop2: HBLoop2:
mov bl,es:[di] ;load latches mov bl,es:[di] ;load latches
stosb ;write solid pattern, through ALUs stosb ;write solid pattern, through ALUs
loop HBLoop2 loop HBLoop2
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
inc ax ;0 fill (smaller to do word than byte DEC) inc ax ;0 fill (smaller to do word than byte DEC)
mov cx,si ;width to fill mov cx,si ;width to fill
HBLoop3: HBLoop3:
mov bl,es:[di] ;load latches mov bl,es:[di] ;load latches
stosb ;write empty pattern, through ALUs stosb ;write empty pattern, through ALUs
loop HBLoop3 loop HBLoop3
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
mov cx,si ;width to fill mov cx,si ;width to fill
HBLoop4: HBLoop4:
mov bl,es:[di] ;load latches mov bl,es:[di] ;load latches
stosb ;write empty pattern, through ALUs stosb ;write empty pattern, through ALUs
loop HBLoop4 loop HBLoop4
add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES
dec dx dec dx
jnz HorzBarLoop jnz HorzBarLoop
; ;
ret ret
DrawVerticalBox endp DrawVerticalBox endp
cseg ends cseg ends
end start end start
```

View file

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

246
25-02.md
View file

@ -12,111 +12,112 @@ 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' ;
stack segment para stack ‘STACK'
db 512 dup(?) db 512 dup(?)
stack ends stack ends
; ;
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable
FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char
; ;
; VGA register equates. ; VGA register equates.
; ;
GC_INDEX equ 3ceh ;GC index register GC_INDEX equ 3ceh ;GC index register
GC_ROTATE equ 3 ;GC data rotate/logical function GC_ROTATE equ 3 ;GC data rotate/logical function
; register index ; register index
GC_BIT_MASK equ 8 ;GC bit mask register index GC_BIT_MASK equ 8 ;GC bit mask register index
; ;
dseg segment para common ‘DATA' dseg segment para common ‘DATA'
TEST_TEXT_ROW equ 69 ;row to display test text at TEST_TEXT_ROW equ 69 ;row to display test text at
TEST_TEXT_COL equ 17 ;column to display test text at TEST_TEXT_COL equ 17 ;column to display test text at
TEST_TEXT_WIDTH equ 8 ;width of a character in pixels 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
@ -125,15 +126,15 @@ pages: 466-470
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
@ -149,78 +150,78 @@ pages: 466-470
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
@ -230,18 +231,19 @@ pages: 466-470
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

View file

@ -12,24 +12,25 @@ 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' ;
stack segment para stack ‘STACK'
db 512 dup(?) db 512 dup(?)
stack ends 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_INDEX equ 3c4h ;SC index register
SC_MAP_MASK equ 2 ;SC map mask 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 SETSC macro INDEX, SETTING
mov dx,SC_INDEX mov dx,SC_INDEX
mov al,INDEX mov al,INDEX
out dx,al out dx,al
@ -38,36 +39,36 @@ pages: 472-474
out dx,al out dx,al
dec dx dec dx
endm endm
; ;
cseg segment para public ‘CODE#146; cseg segment para public ‘CODE#146;
assume cs:cseg assume cs:cseg
start proc near start proc near
; ;
; Select 640x480 graphics mode. ; Select 640x480 graphics mode.
; ;
mov ax,012h mov ax,012h
int 10h int 10h
; ;
mov ax,EGA_VIDEO_SEGMENT mov ax,EGA_VIDEO_SEGMENT
mov es,ax ;point to video memory 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 SETSC SC_MAP_MASK,02h ;map mask setting enables only
; plane 1, the green plane ; plane 1, the green plane
sub di,di ;start at beginning of video memory sub di,di ;start at beginning of video memory
mov al,0ffh mov al,0ffh
mov bp,24 ;# bars to draw mov bp,24 ;# bars to draw
HorzBarLoop: HorzBarLoop:
mov cx,80*10 ;# bytes per horizontal bar mov cx,80*10 ;# bytes per horizontal bar
rep stosb ;draw bar rep stosb ;draw bar
add di,80*10 ;point to start of next bar add di,80*10 ;point to start of next bar
dec bp dec bp
jnz HorzBarLoop jnz HorzBarLoop
; ;
; Fill screen with blue, using Map Mask register to enable writes ; Fill screen with blue, using Map Mask register to enable writes
; to blue plane only. ; to blue plane only.
; ;
SETSC SC_MAP_MASK,01h ;map mask setting enables only SETSC SC_MAP_MASK,01h ;map mask setting enables only
; plane 0, the blue plane ; plane 0, the blue plane
sub di,di sub di,di
@ -75,24 +76,25 @@ pages: 472-474
mov al,0ffh mov al,0ffh
rep stosb ;perform fill (affects only rep stosb ;perform fill (affects only
; plane 0, the blue plane) ; plane 0, the blue plane)
; ;
; Wait for a keystroke. ; Wait for a keystroke.
; ;
mov ah,1 mov ah,1
int 21h int 21h
; ;
; Restore text mode. ; Restore 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
cseg ends cseg ends
end start end start
```
#### Setting All Planes to a Single Color {#Heading6} #### Setting All Planes to a Single Color {#Heading6}

218
25-05.md
View file

@ -12,27 +12,28 @@ 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; ;
stack segment para stack ‘STACK#146;
db 512 dup(?) db 512 dup(?)
stack ends 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_INDEX equ 3c4h ;SC index register
SC_MAP_MASK equ 2 ;SC map mask register SC_MAP_MASK equ 2 ;SC map mask register
GC_INDEX equ 3ceh ;GC index register GC_INDEX equ 3ceh ;GC index register
GC_SET_RESET equ 0 ;GC set/reset register GC_SET_RESET equ 0 ;GC set/reset register
GC_ENABLE_SET_RESET equ 1 ;GC enable 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 SETSC macro INDEX, SETTING
mov dx,SC_INDEX mov dx,SC_INDEX
mov al,INDEX mov al,INDEX
out dx,al out dx,al
@ -41,10 +42,10 @@ pages: 474-478
out dx,al out dx,al
dec dx dec dx
endm 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 SETGC macro INDEX, SETTING
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,INDEX mov al,INDEX
out dx,al out dx,al
@ -53,36 +54,36 @@ pages: 474-478
out dx,al out dx,al
dec dx dec dx
endm endm
; ;
cseg segment para public ‘CODE#146; cseg segment para public ‘CODE#146;
assume cs:cseg assume cs:cseg
start proc near start proc near
; ;
; Select 640x480 graphics mode. ; Select 640x480 graphics mode.
; ;
mov ax,012h mov ax,012h
int 10h int 10h
; ;
mov ax,EGA_VIDEO_SEGMENT mov ax,EGA_VIDEO_SEGMENT
mov es,ax ;point to video memory 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 SETSC SC_MAP_MASK,02h ;map mask setting enables only
; plane 1, the green plane ; plane 1, the green plane
sub di,di ;start at beginning of video memory sub di,di ;start at beginning of video memory
mov al,0ffh mov al,0ffh
mov bp,24 ;# bars to draw mov bp,24 ;# bars to draw
HorzBarLoop: HorzBarLoop:
mov cx,80*10 ;# bytes per horizontal bar mov cx,80*10 ;# bytes per horizontal bar
rep stosb ;draw bar rep stosb ;draw bar
add di,80*10 ;point to start of next bar add di,80*10 ;point to start of next bar
dec bp dec bp
jnz HorzBarLoop jnz HorzBarLoop
; ;
; Fill screen with blue, using set/reset to force plane 0 to 1#146;s and all ; Fill screen with blue, using set/reset to force plane 0 to 1#146;s and all
; other plane to 0#146;s. ; other plane to 0#146;s.
; ;
SETSC SC_MAP_MASK,0fh ;must set map mask to enable all SETSC SC_MAP_MASK,0fh ;must set map mask to enable all
; planes, so set/reset values can ; planes, so set/reset values can
; be written to memory ; be written to memory
@ -98,28 +99,29 @@ pages: 474-478
; only the act of writing is ; only the act of writing is
; important ; important
rep stosb ;perform fill (affects all planes) 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 mov ah,1
int 21h int 21h
; ;
; Restore text mode. ; Restore 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
cseg ends cseg ends
end start end start
```
#### Manipulating Planes Individually {#Heading7} #### Manipulating Planes Individually {#Heading7}
@ -142,27 +144,28 @@ 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; ;
stack segment para stack ‘STACK#146;
db 512 dup(?) db 512 dup(?)
stack ends 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_INDEX equ 3c4h ;SC index register
SC_MAP_MASK equ 2 ;SC map mask register SC_MAP_MASK equ 2 ;SC map mask register
GC_INDEX equ 3ceh ;GC index register GC_INDEX equ 3ceh ;GC index register
GC_SET_RESET equ 0 ;GC set/reset register GC_SET_RESET equ 0 ;GC set/reset register
GC_ENABLE_SET_RESET equ 1 ;GC enable 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 SETSC macro INDEX, SETTING
mov dx,SC_INDEX mov dx,SC_INDEX
mov al,INDEX mov al,INDEX
out dx,al out dx,al
@ -171,10 +174,10 @@ be used to control individual pixels.
out dx,al out dx,al
dec dx dec dx
endm 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 SETGC macro INDEX, SETTING
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,INDEX mov al,INDEX
out dx,al out dx,al
@ -183,36 +186,36 @@ be used to control individual pixels.
out dx,al out dx,al
dec dx dec dx
endm endm
; ;
cseg segment para public ‘CODE#146; cseg segment para public ‘CODE#146;
assume cs:cseg assume cs:cseg
start proc near start proc near
; ;
; Select 640x350 graphics mode. ; Select 640x350 graphics mode.
; ;
mov ax,010h mov ax,010h
int 10h int 10h
; ;
mov ax,EGA_VIDEO_SEGMENT mov ax,EGA_VIDEO_SEGMENT
mov es,ax ;point to video memory 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 SETSC SC_MAP_MASK,02h;map mask setting enables only
; plane 1, the green plane ; plane 1, the green plane
sub di,di;start at beginning of video memory sub di,di;start at beginning of video memory
mov al,0ffh mov al,0ffh
mov bp,18;# bars to draw mov bp,18;# bars to draw
HorzBarLoop: HorzBarLoop:
mov cx,80*10;# bytes per horizontal bar mov cx,80*10;# bytes per horizontal bar
rep stosb;draw bar rep stosb;draw bar
add di,80*10;point to start of next bar add di,80*10;point to start of next bar
dec bp dec bp
jnz HorzBarLoop jnz HorzBarLoop
; ;
; Fill screen with alternating bars of red and brown, using CPU data ; 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. ; 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 SETSC SC_MAP_MASK,0fh ;must set map mask to enable all
; planes, so set/reset values can ; planes, so set/reset values can
; be written to planes 0, 2 & 3 ; be written to planes 0, 2 & 3
@ -228,25 +231,26 @@ be used to control individual pixels.
mov ax,07e0h ;CPU data controls only plane 1; mov ax,07e0h ;CPU data controls only plane 1;
; set/reset controls other planes ; set/reset controls other planes
rep stosw ;perform fill (affects all 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 mov ah,1
int 21h int 21h
; ;
; Restore text mode. ; Restore 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
cseg ends cseg ends
end start end start
```

284
26-02.md
View file

@ -12,58 +12,59 @@ 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' ;
stack segment para stack ‘STACK'
db 512 dup(?) db 512 dup(?)
stack ends stack ends
; ;
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable
FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char FONT_CHARACTER_SIZE equ 8 ;# bytes in each font char
; ;
; VGA register equates. ; VGA register equates.
; ;
SC_INDEX equ 3c4h ;SC index register SC_INDEX equ 3c4h ;SC index register
SC_MAP_MASK equ 2 ;SC map mask register index SC_MAP_MASK equ 2 ;SC map mask register index
GC_INDEX equ 3ceh ;GC index register GC_INDEX equ 3ceh ;GC index register
GC_SET_RESET equ 0 ;GC set/reset register index GC_SET_RESET equ 0 ;GC set/reset register index
GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index
GC_ROTATE equ 3 ;GC data rotate/logical function GC_ROTATE equ 3 ;GC data rotate/logical function
; register index ; register index
GC_MODE equ 5 ;GC Mode register GC_MODE equ 5 ;GC Mode register
GC_BIT_MASK equ 8 ;GC bit mask register index GC_BIT_MASK equ 8 ;GC bit mask register index
; ;
dseg segment para common ‘DATA' dseg segment para common ‘DATA'
TEST_TEXT_ROW equ 69 ;row to display test text at TEST_TEXT_ROW equ 69 ;row to display test text at
TEST_TEXT_COL equ 17 ;column to display test text at TEST_TEXT_COL equ 17 ;column to display test text at
TEST_TEXT_WIDTH equ 8 ;width of a character in pixels 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
; ;
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 the screen to all blue, using the readability of VGA registers ; Set the screen to all blue, using the readability of VGA registers
; to preserve reserved bits. ; to preserve reserved bits.
; ;
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,GC_SET_RESET mov al,GC_SET_RESET
out dx,al out dx,al
@ -87,22 +88,22 @@ pages: 484-489
mov ax,0ffffh ;because of set/reset, the value mov ax,0ffffh ;because of set/reset, the value
; written actually doesn't matter ; written actually doesn't matter
rep stosw ;fill with blue 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 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, cycling through colors. ; Print the test string, cycling through colors.
; ;
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
mov ah,0 ;start with color 0 mov ah,0 ;start with color 0
StringOutLoop: StringOutLoop:
lodsb lodsb
and al,al and al,al
jz StringOutDone jz StringOutDone
@ -113,36 +114,36 @@ pages: 484-489
and ah,0fh ;colors range from 0 to 15 and ah,0fh ;colors range from 0 to 15
add cx,TEST_TEXT_WIDTH add cx,TEST_TEXT_WIDTH
jmp StringOutLoop jmp StringOutLoop
StringOutDone: StringOutDone:
; ;
; Wait for a key, then set to text mode & end. ; Wait for a key, then set to text mode & end.
; ;
mov ah,1 mov ah,1
int 21h ;wait for a key int 21h ;wait for a key
mov ax,3 mov ax,3
int 10h ;restore text mode int 10h ;restore text mode
; ;
; 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). Background around the pixels that ; (0Dh, 0Eh, 0Fh, 010h, 012h). Background around the pixels that
; make up the character is preserved. ; make up the character is preserved.
; 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
; AH = color to draw character in (0-15) ; AH = color to draw character in (0-15)
; 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".
; Forces write mode 3. ; Forces write mode 3.
; ;
DrawChar proc near DrawChar proc near
push ax push ax
push bx push bx
push cx push cx
@ -152,10 +153,10 @@ pages: 484-489
push bp push bp
push ds push ds
push ax ;preserve character to draw in AL push ax ;preserve character to draw in AL
; ;
; Set up set/reset to produce character color, using the readability ; Set up set/reset to produce character color, using the readability
; of VGA register to preserve the setting of reserved bits 7-4. ; of VGA register to preserve the setting of reserved bits 7-4.
; ;
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,GC_SET_RESET mov al,GC_SET_RESET
out dx,al out dx,al
@ -165,10 +166,10 @@ pages: 484-489
and ah,0fh and ah,0fh
or al,ah or al,ah
out dx,al out dx,al
; ;
; Select write mode 3, using the readability of VGA registers ; Select write mode 3, using the readability of VGA registers
; to leave bits other than the write mode bits unchanged. ; to leave bits other than the write mode bits unchanged.
; ;
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,GC_MODE mov al,GC_MODE
out dx,al out dx,al
@ -176,15 +177,15 @@ pages: 484-489
in al,dx in al,dx
or al,3 or al,3
out dx,al 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 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.
; ;
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
@ -202,20 +203,20 @@ pages: 484-489
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
@ -224,66 +225,66 @@ pages: 484-489
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
@ -293,15 +294,16 @@ pages: 484-489
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
```

308
26-03.md
View file

@ -80,59 +80,60 @@ 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' ;
stack segment para stack ‘STACK'
db 512 dup(?) db 512 dup(?)
stack ends stack ends
; ;
VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment VGA_VIDEO_SEGMENT equ 0a000h ;VGA display memory segment
SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable SCREEN_WIDTH_IN_BYTES equ 044ah ;offset of BIOS variable
FONT_CHARACTER_SIZE equ 14 ;# bytes in each font char FONT_CHARACTER_SIZE equ 14 ;# bytes in each font char
; ;
; VGA register equates. ; VGA register equates.
; ;
SC_INDEX equ 3c4h ;SC index register SC_INDEX equ 3c4h ;SC index register
SC_MAP_MASK equ 2 ;SC map mask register index SC_MAP_MASK equ 2 ;SC map mask register index
GC_INDEX equ 3ceh ;GC index register GC_INDEX equ 3ceh ;GC index register
GC_SET_RESET equ 0 ;GC set/reset register index GC_SET_RESET equ 0 ;GC set/reset register index
GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index GC_ENABLE_SET_RESET equ 1 ;GC enable set/reset register index
GC_ROTATE equ 3 ;GC data rotate/logical function GC_ROTATE equ 3 ;GC data rotate/logical function
; register index ; register index
GC_MODE equ 5 ;GC Mode register GC_MODE equ 5 ;GC Mode register
GC_BIT_MASK equ 8 ;GC bit mask register index GC_BIT_MASK equ 8 ;GC bit mask register index
; ;
dseg segment para common ‘DATA' dseg segment para common ‘DATA'
TEST_TEXT_ROW equ 69 ;row to display test text at TEST_TEXT_ROW equ 69 ;row to display test text at
TEST_TEXT_COL equ 17 ;column to display test text at TEST_TEXT_COL equ 17 ;column to display test text at
TEST_TEXT_COLOR equ 0fh ;high intensity white TEST_TEXT_COLOR equ 0fh ;high intensity white
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
; ;
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 the screen to all blue, using the readability of VGA registers ; Set the screen to all blue, using the readability of VGA registers
; to preserve reserved bits. ; to preserve reserved bits.
; ;
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,GC_SET_RESET mov al,GC_SET_RESET
out dx,al out dx,al
@ -156,52 +157,52 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into
mov ax,0ffffh ;because of set/reset, the value mov ax,0ffffh ;because of set/reset, the value
; written actually doesn't matter ; written actually doesn't matter
rep stosw ;fill with blue 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 ah,11h ;VGA BIOS character generator function,
mov al,30h ; return info subfunction mov al,30h ; return info subfunction
mov bh,2 ;get 8x14 font pointer mov bh,2 ;get 8x14 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
mov ah,TEST_TEXT_COLOR mov ah,TEST_TEXT_COLOR
call DrawString 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 mov ah,1
int 21h ;wait for a key int 21h ;wait for a key
mov ax,3 mov ax,3
int 10h ;restore text mode int 10h ;restore text mode
; ;
; 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 string left-to-right in a linear ; Subroutine to draw a text string left-to-right in a linear
; graphics mode (0Dh, 0Eh, 0Fh, 010h, 012h) with 8-dot-wide ; graphics mode (0Dh, 0Eh, 0Fh, 010h, 012h) with 8-dot-wide
; characters. Background around the pixels that make up the ; characters. Background around the pixels that make up the
; characters is preserved. ; characters is preserved.
; Font used should be pointed to by FontPointer. ; Font used should be pointed to by FontPointer.
; ;
; Input: ; Input:
; AH = color to draw string in ; AH = color to draw string in
; BX = row to draw string on ; BX = row to draw string on
; CX = column to start string at ; CX = column to start string at
; DS:SI = string to draw ; DS:SI = string to draw
; ;
; Forces ALU function to "move". ; Forces ALU function to "move".
; Forces write mode 3. ; Forces write mode 3.
; ;
DrawString proc near DrawString proc near
push ax push ax
push bx push bx
push cx push cx
@ -210,10 +211,10 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into
push di push di
push bp push bp
push ds push ds
; ;
; Set up set/reset to produce character color, using the readability ; Set up set/reset to produce character color, using the readability
; of VGA register to preserve the setting of reserved bits 7-4. ; of VGA register to preserve the setting of reserved bits 7-4.
; ;
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,GC_SET_RESET mov al,GC_SET_RESET
out dx,al out dx,al
@ -223,10 +224,10 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into
and ah,0fh and ah,0fh
or al,ah or al,ah
out dx,al out dx,al
; ;
; Select write mode 3, using the readability of VGA registers ; Select write mode 3, using the readability of VGA registers
; to leave bits other than the write mode bits unchanged. ; to leave bits other than the write mode bits unchanged.
; ;
mov dx,GC_INDEX mov dx,GC_INDEX
mov al,GC_MODE mov al,GC_MODE
out dx,al out dx,al
@ -236,9 +237,9 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into
out dx,al out dx,al
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
@ -254,12 +255,12 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into
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
; ;
; 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
@ -268,71 +269,71 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into
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 all characters, left portion first, then right portion in the ; Draw all characters, left portion first, then right portion 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.
; ;
; 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 pop cx ;get back screen width
push si push si
push di push di
push bx 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 dx,GC_INDEX
mov al,GC_BIT_MASK mov al,GC_BIT_MASK
mov ah,bh mov ah,bh
out dx,ax out dx,ax
LeftHalfLoop: LeftHalfLoop:
lodsb lodsb
and al,al and al,al
jz LeftHalfLoopDone jz LeftHalfLoopDone
call CharacterUp call CharacterUp
inc di ;point to next character location inc di ;point to next character location
jmp LeftHalfLoop jmp LeftHalfLoop
LeftHalfLoopDone: LeftHalfLoopDone:
pop bx pop bx
pop di pop di
pop si 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 inc di ;right portion of each character is across
; byte boundary ; 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 dx,GC_INDEX
mov al,GC_BIT_MASK mov al,GC_BIT_MASK
mov ah,bl mov ah,bl
out dx,ax out dx,ax
RightHalfLoop: RightHalfLoop:
lodsb lodsb
and al,al and al,al
jz RightHalfLoopDone jz RightHalfLoopDone
call CharacterUp call CharacterUp
inc di ;point to next character location inc di ;point to next character location
jmp RightHalfLoop jmp RightHalfLoop
RightHalfLoopDone: RightHalfLoopDone:
; ;
pop ds pop ds
pop bp pop bp
pop di pop di
@ -342,62 +343,63 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into
pop bx pop bx
pop ax pop ax
ret ret
DrawString endp DrawString endp
; ;
; Draw a character. ; Draw a character.
; ;
; Input: ; Input:
; AL = character ; AL = character
; CX = screen width ; CX = screen width
; ES:DI = address to draw character at ; ES:DI = address to draw character at
; ;
CharacterUp proc near CharacterUp proc near
push cx push cx
push si push si
push di push di
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
; ;
; Calculate font address of character. ; Calculate font address of character.
; ;
mov bl,14 ;14 bytes per character mov bl,14 ;14 bytes per character
mul bl mul bl
add si,ax ;offset in font segment of character 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,

178
27-02.md
View file

@ -52,37 +52,38 @@ 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' ;
Stack segment para stack ‘STACK'
db 512 dup(0) db 512 dup(0)
Stack ends 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
@ -97,27 +98,27 @@ image.
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
@ -125,67 +126,67 @@ 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
@ -200,9 +201,9 @@ 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
@ -213,13 +214,13 @@ image.
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
@ -233,7 +234,7 @@ image.
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
@ -247,16 +248,16 @@ image.
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
@ -266,6 +267,7 @@ 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
```

322
27-03.md
View file

@ -53,95 +53,96 @@ 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' ;
Stack segment para stack ‘STACK'
db 512 dup(0) db 512 dup(0)
Stack ends 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
@ -169,74 +170,74 @@ the CPU byte in write mode 2 to select the color in which to draw.
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
@ -251,73 +252,73 @@ the CPU byte in write mode 2 to select the color in which to draw.
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
@ -326,29 +327,29 @@ the CPU byte in write mode 2 to select the color in which to draw.
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
@ -375,7 +376,8 @@ the CPU byte in write mode 2 to select the color in which to draw.
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
```

138
27-05.md
View file

@ -53,53 +53,54 @@ 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' ;
Stack segment para stack ‘STACK'
db 512 dup(0) db 512 dup(0)
Stack ends 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
@ -110,7 +111,7 @@ rewarding!) VGA to cover.
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
@ -123,22 +124,22 @@ rewarding!) VGA to cover.
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
@ -153,14 +154,14 @@ rewarding!) VGA to cover.
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
@ -169,10 +170,10 @@ rewarding!) VGA to cover.
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
@ -185,15 +186,15 @@ rewarding!) VGA to cover.
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
@ -202,14 +203,14 @@ rewarding!) VGA to cover.
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
@ -224,23 +225,24 @@ rewarding!) VGA to cover.
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
```

226
28-02.md
View file

@ -12,138 +12,139 @@ 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 PatternPlane0 label byte
db 32 dup (0ffh,0ffh,0,0) db 32 dup (0ffh,0ffh,0,0)
PatternPlane1 labelbyte PatternPlane1 labelbyte
db 32 dup (0ffh,0,0ffh,0) db 32 dup (0ffh,0,0ffh,0)
PatternPlane2 labelbyte PatternPlane2 labelbyte
db 32 dup (0f0h,0f0h,0f0h,0f0h) db 32 dup (0f0h,0f0h,0f0h,0f0h)
PatternPlane3 labelbyte PatternPlane3 labelbyte
db 32 dup (0cch,0cch,0cch,0cch) 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 (?) ImagePlane0 db 32*4 dup (?)
ImagePlane1 db 32*4 dup (?) ImagePlane1 db 32*4 dup (?)
ImagePlane2 db 32*4 dup (?) ImagePlane2 db 32*4 dup (?)
ImagePlane3 db 32*4 dup (?) ImagePlane3 db 32*4 dup (?)
; ;
; Current image location & direction. ; Current image location & direction.
; ;
ImageX dw 40 ;in bytes ImageX dw 40 ;in bytes
ImageY dw 100 ;in pixels ImageY dw 100 ;in pixels
ImageXDirection dw 1 ;in bytes ImageXDirection dw 1 ;in bytes
dataends dataends
; ;
code segment word 'CODE' code segment word 'CODE'
assume cs:code,ds:data assume cs:code,ds:data
Start proc near Start proc near
cld cld
mov ax,data mov ax,data
mov ds,ax mov ds,ax
; ;
; Select graphics mode 10h. ; Select graphics mode 10h.
; ;
mov ax,10h mov ax,10h
int 10h int 10h
; ;
; Draw the initial image. ; Draw the initial image.
; ;
mov si,offset PatternPlane0 mov si,offset PatternPlane0
call DrawImage call DrawImage
; ;
; Loop to animate by copying the image from VGA memory to system memory, ; 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 ; erasing the image, and copying the image from system memory to a new
; location in VGA memory. Ends when a key is hit. ; 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 mov di,offset ImagePlane0
call GetImage 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 ; Advance the image X coordinate, reversing direction if either edge
; of the screen has been reached. ; of the screen has been reached.
; ;
mov ax,[ImageX] mov ax,[ImageX]
cmp ax,LEFT_BOUND cmp ax,LEFT_BOUND
jz ReverseDirection jz ReverseDirection
cmp ax,RIGHT_BOUND cmp ax,RIGHT_BOUND
jnz SetNewX jnz SetNewX
ReverseDirection: ReverseDirection:
neg [ImageXDirection] neg [ImageXDirection]
SetNewX: SetNewX:
add ax,[ImageXDirection] add ax,[ImageXDirection]
mov [ImageX],ax 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 mov si,offset ImagePlane0
call DrawImage 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 mov cx,0
DelayLoop: DelayLoop:
loop 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 mov ah,1
int 16h int 16h
jz AnimateLoop 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 sub ah,ah
int 16h int 16h
mov ax,3 mov ax,3
int 10h int 10h
mov ah,4ch mov ah,4ch
int 21h int 21h
Startendp Startendp
; ;
; Draws the image at offset DS:SI to the current image location in ; Draws the image at offset DS:SI to the current image location in
; VGA memory. ; VGA memory.
; ;
DrawImageprocnear DrawImageprocnear
mov ax,VGA_SEGMENT mov ax,VGA_SEGMENT
mov es,ax mov es,ax
call GetImageOffset ;ES:DI is the destination address for the call GetImageOffset ;ES:DI is the destination address for the
; image in VGA memory ; image in VGA memory
mov dx,SC_INDEX mov dx,SC_INDEX
mov al,1 ;do plane 0 first mov al,1 ;do plane 0 first
DrawImagePlaneLoop: DrawImagePlaneLoop:
push di ;image is drawn at the same offset in push di ;image is drawn at the same offset in
; each plane ; each plane
push ax ;preserve plane select push ax ;preserve plane select
@ -155,7 +156,7 @@ pages: 526-530
; the plane of interest ; the plane of interest
dec dx ;point back to SC Data register dec dx ;point back to SC Data register
mov bx,IMAGE_HEIGHT ;# of scan lines in image mov bx,IMAGE_HEIGHT ;# of scan lines in image
DrawImageLoop: DrawImageLoop:
mov cx,IMAGE_WIDTH ;# of bytes across image mov cx,IMAGE_WIDTH ;# of bytes across image
rep movsb rep movsb
add di,SCREEN_WIDTH-IMAGE_WIDTH add di,SCREEN_WIDTH-IMAGE_WIDTH
@ -167,12 +168,12 @@ pages: 526-530
cmp al,10h ;have we done all four planes? cmp al,10h ;have we done all four planes?
jnz DrawImagePlaneLoop jnz DrawImagePlaneLoop
ret ret
DrawImageendp DrawImageendp
; ;
; Copies the image from its current location in VGA memory into the ; Copies the image from its current location in VGA memory into the
; buffer at DS:DI. ; buffer at DS:DI.
; ;
GetImage proc near GetImage proc near
mov si,di ;move destination offset into SI mov si,di ;move destination offset into SI
call GetImageOffset ;DI is offset of image in VGA memory call GetImageOffset ;DI is offset of image in VGA memory
xchg si,di ;SI is offset of image, DI is destination offset xchg si,di ;SI is offset of image, DI is destination offset
@ -180,10 +181,10 @@ pages: 526-530
pop es ;ES:DI is destination pop es ;ES:DI is destination
mov ax,VGA_SEGMENT mov ax,VGA_SEGMENT
mov ds,ax ;DS:SI is source mov ds,ax ;DS:SI is source
; ;
mov dx,GC_INDEX mov dx,GC_INDEX
sub al,al;do plane 0 first sub al,al;do plane 0 first
GetImagePlaneLoop: GetImagePlaneLoop:
push si ;image comes from same offset in each plane push si ;image comes from same offset in each plane
push ax ;preserve plane select push ax ;preserve plane select
mov al,READ_MAP ;Read Map index mov al,READ_MAP ;Read Map index
@ -194,7 +195,7 @@ pages: 526-530
; the plane of interest ; the plane of interest
dec dx ;point back to GC data register dec dx ;point back to GC data register
mov bx,IMAGE_HEIGHT ;# of scan lines in image mov bx,IMAGE_HEIGHT ;# of scan lines in image
GetImageLoop: GetImageLoop:
mov cx,IMAGE_WIDTH ;# of bytes across image mov cx,IMAGE_WIDTH ;# of bytes across image
rep movsb rep movsb
add si,SCREEN_WIDTH-IMAGE_WIDTH add si,SCREEN_WIDTH-IMAGE_WIDTH
@ -208,11 +209,11 @@ pages: 526-530
push es push es
pop ds ;restore original DS pop ds ;restore original DS
ret ret
GetImageendp GetImageendp
; ;
; Erases the image at its current location. ; Erases the image at its current location.
; ;
EraseImage proc near EraseImage proc near
mov dx,SC_INDEX mov dx,SC_INDEX
mov al,MAP_MASK mov al,MAP_MASK
out dx,al ;point SC Index to the Map Mask register out dx,al ;point SC Index to the Map Mask register
@ -226,7 +227,7 @@ pages: 526-530
; of the image ; of the image
sub al,al ;erase with zeros sub al,al ;erase with zeros
mov bx,IMAGE_HEIGHT ;# of scan lines in image mov bx,IMAGE_HEIGHT ;# of scan lines in image
EraseImageLoop: EraseImageLoop:
mov cx,IMAGE_WIDTH ;# of bytes across image mov cx,IMAGE_WIDTH ;# of bytes across image
rep stosb rep stosb
add di,SCREEN_WIDTH-IMAGE_WIDTH add di,SCREEN_WIDTH-IMAGE_WIDTH
@ -234,16 +235,17 @@ pages: 526-530
dec bx ;any more scan lines? dec bx ;any more scan lines?
jnz EraseImageLoop jnz EraseImageLoop
ret ret
EraseImage endp 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 GetImageOffset proc near
mov ax,SCREEN_WIDTH mov ax,SCREEN_WIDTH
mul [ImageY] mul [ImageY]
add ax,[ImageX] add ax,[ImageX]
mov di,ax mov di,ax
ret ret
GetImageOffset endp GetImageOffset endp
code ends code ends
end Start end Start
```

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