Twenty four more sites, and the interesting part is which ones were left alone. A rule emerged while reading them and it held all the way through: apply where the repetition is INSIDE one operation, skip where the author's own structure says it is a new thought, and never where two equal values mean different things. Taken: - Five registers reassigned to a value they already held, where both are the same quantity: two masks in one expression in Snake, two spaces printed by the monitor, both halves of block zero in waitTest, and a RSTA in Pour that the very next instruction overwrote. - Eighteen SETDs that reload a pointer inside one operation - a store back into the variable just read, or an INCD stepping to the second byte of a two byte value. Those read correctly without the reload. - sbfsNext, which branched to the label on the line below it. Left, with reasons that are the useful part of this: - Eight registers where the same number means two different things. CosmOS and the loader set A to 1 for a blit command and then to 1 again for a bank number; Asm compares a type against 3 and then a status against 3. Removing those couples one quantity to another that is equal by accident and would part company silently. - Ten RSTAs that open the RSTA/RSTB/CCF/ADD "return zero" block. The redundancy is what makes that idiom self contained; taking it out makes the return value depend on the line above. - Eleven SETDs that begin an arm of a comparison chain. Each arm loads, compares and branches, and they get reordered - the repetition is the reason a new arm can be dropped in anywhere. - Twenty five SETDs separated from their pointer by a blank line or a comment, which is the author saying a new thought starts here. - Two CCFs before arithmetic, which this codebase writes unconditionally. - Three redundant branches in test programs whose recorded output includes addresses, where three fewer bytes moves what the test demonstrates. Nine recorded outputs moved and every one is a size in a listing or, for Life, five more generations inside the same cycle budget. Behaviour is unchanged everywhere: cosmosSnake and cosmosEdit pass byte for byte while Snake loses eight bytes and Edit twelve. CosmOS is 10,902 bytes of program against 10,937, and the native assembler 12,173 against 12,183. The CosmOS README's size for Edit moved twice in one sitting, and this morning's check caught it both times - which it could not have done before that claim was reworded to name what it was about.
295 lines
6.0 KiB
NASM
295 lines
6.0 KiB
NASM
; The names in the Vector Segment, and what numbers they have.
|
|
;
|
|
; A vector name is not a label and the two are kept deliberately apart, so a program may
|
|
; call a routine `announce` and name a vector `announce` without either shadowing the
|
|
; other. They are looked up in different places because they mean different things: a label
|
|
; is an address and a vector is a number.
|
|
;
|
|
; FIXED FIELDS HERE, unlike the label table's arena. There are at most a couple of hundred
|
|
; of these against several hundred labels, and the names are short, so packing them would
|
|
; cost more code than it saved. Twenty eight bytes an entry:
|
|
;
|
|
; 0 23 the name, up to twenty two characters and a zero
|
|
; 23 1 the vector number
|
|
; 24 1 which table: 0 software, 1 hardware
|
|
; 25 2 the handler's address
|
|
; 27 1 whether a handler has been supplied
|
|
;
|
|
; A DECLARATION AND AN IMPLEMENTATION ARE THE SAME ENTRY. services.asm says a service is
|
|
; called osPrintString and has number 16; cosmos.asm says osPrintString is handled by
|
|
; handlePrintString. Both sides include the first file, so the name is met twice, and the
|
|
; second time fills in the handler rather than making a second entry. That is what lets one
|
|
; shared file serve a program that calls a service and the system that implements it.
|
|
;
|
|
; THE ORDER OF THIS TABLE IS THE ORDER OF THE OUTPUT FILE, and the order is declaration
|
|
; order rather than implementation order, because that is where the entry was made. It has
|
|
; to match what the other assembler does byte for byte.
|
|
;
|
|
; Numbers come from two places. A pinned one is written down in the source, and that is how
|
|
; anything two separately assembled programs must agree about is fixed - the system's
|
|
; services are all pinned. Everything else is numbered automatically from 64 up, out of a
|
|
; range nothing outside one program can name, so what number it gets cannot matter.
|
|
;
|
|
; Written by Anachronaut
|
|
|
|
#Program
|
|
|
|
vecReset:
|
|
SETD.0 VecCount
|
|
CALL numZero
|
|
INIA 0d64
|
|
SETD.0 VecNextAuto
|
|
STA.0
|
|
RET
|
|
|
|
; Declares the name at DP0 with the number in A, in the table VecPutBase names. Q is zero
|
|
; if it went in.
|
|
vecDeclare:
|
|
SETD.2 VecPutNumber
|
|
STA.2
|
|
SETD.2 VecSubject
|
|
STD.0.2
|
|
|
|
CALL vecFind
|
|
BNQ vecDeclareFresh
|
|
SETD.0 VecTwice
|
|
CALL clsComplain
|
|
BRI vecDeclareNo
|
|
|
|
vecDeclareFresh:
|
|
SETD.0 VecCount
|
|
SETD.2 VecLimit
|
|
CALL numCompare
|
|
BNC vecDeclareFull
|
|
|
|
SETD.0 VecWhich
|
|
SETD.2 VecCount
|
|
CALL numSet
|
|
CALL vecSlotAt
|
|
|
|
SETD.1 VecSubject
|
|
LDD.0.1
|
|
SETD.1 VecSlot
|
|
LDD.1.1
|
|
CALL srcKeepName
|
|
SETD.1 VecSlot
|
|
LDD.0.1
|
|
INIA 0d23
|
|
SETD.0 VecSlot
|
|
CALL numAddByte
|
|
SETD.1 VecSlot
|
|
LDD.0.1
|
|
SETD.2 VecPutNumber
|
|
LDA.2
|
|
STA.0
|
|
INCD.0
|
|
SETD.2 VecPutBase
|
|
LDA.2
|
|
STA.0 ; Which table it lives in.
|
|
INCD.0
|
|
RSTA
|
|
STA.0
|
|
INCD.0
|
|
STA.0
|
|
INCD.0
|
|
STA.0 ; No handler yet, and no address to go with one.
|
|
|
|
SETD.0 VecCount
|
|
CALL numStep
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
vecDeclareFull:
|
|
SETD.0 VecFull
|
|
CALL clsComplain
|
|
vecDeclareNo:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; The next number nothing has taken, into VecPutNumber. These start at 64, above everything
|
|
; that may be pinned, so a name a program made up for itself can never land on a system
|
|
; service.
|
|
;
|
|
; Into memory rather than into A, because a CALL puts A back as it found it.
|
|
vecTakeAuto:
|
|
SETD.0 VecNextAuto
|
|
LDA.0
|
|
SETD.0 VecPutNumber
|
|
STA.0
|
|
SETD.0 VecNextAuto
|
|
LDA.0
|
|
INCA
|
|
STA.0
|
|
RET
|
|
|
|
; Looks up the name at DP0. Q is zero if it is there, and then VecNumber is its number.
|
|
vecFind:
|
|
SETD.2 VecSought
|
|
STD.0.2
|
|
SETD.0 VecWhich
|
|
CALL numZero
|
|
|
|
vecFindLoop:
|
|
SETD.0 VecWhich
|
|
SETD.2 VecCount
|
|
CALL numCompare
|
|
BNC vecFindMissing
|
|
|
|
CALL vecSlotAt
|
|
SETD.1 VecSlot
|
|
LDD.0.1
|
|
SETD.1 VecSought
|
|
LDD.1.1
|
|
CALL sameText
|
|
BRQ vecFindGot
|
|
|
|
SETD.0 VecWhich
|
|
CALL numStep
|
|
BRI vecFindLoop
|
|
|
|
vecFindGot:
|
|
CALL vecReadFields
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
vecFindMissing:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; Everything the entry at VecSlot says, into VecNumber, VecBase, VecHandler and
|
|
; VecHasHandler. VecSlot is left pointing past the name, at the number.
|
|
vecReadFields:
|
|
INIA 0d23
|
|
SETD.0 VecSlot
|
|
CALL numAddByte
|
|
SETD.1 VecSlot
|
|
LDD.0.1
|
|
LDA.0
|
|
SETD.1 VecNumber
|
|
STA.1
|
|
INCD.0
|
|
LDA.0
|
|
SETD.1 VecBase
|
|
STA.1
|
|
INCD.0
|
|
LDA.0
|
|
SETD.1 VecHandler
|
|
STA.1
|
|
INCD.0
|
|
LDA.0
|
|
INCD.1
|
|
STA.1
|
|
INCD.0
|
|
LDA.0
|
|
SETD.1 VecHasHandler
|
|
STA.1
|
|
RET
|
|
|
|
; Gives the entry at VecSlot the handler in VecPutHandler. VecSlot must already have been
|
|
; walked past the name by vecReadFields, which is how it is always reached.
|
|
;
|
|
; A VARIABLE OF ITS OWN, not VecHandler, and that is not tidiness. Finding the entry to
|
|
; write to means calling vecFind, which reads the entry's fields out - including the
|
|
; handler it does not have yet. An address resolved into VecHandler before the find was
|
|
; overwritten with zero by the find itself, and the file came out with a vector pointing
|
|
; at address zero: a slot that looked installed and went nowhere.
|
|
vecWriteHandler:
|
|
SETD.1 VecSlot
|
|
LDD.0.1
|
|
INCD.0
|
|
INCD.0
|
|
SETD.2 VecPutHandler
|
|
LDA.2
|
|
STA.0
|
|
INCD.0
|
|
INCD.2
|
|
LDA.2
|
|
STA.0
|
|
INCD.0
|
|
INIA 0d1
|
|
STA.0
|
|
RET
|
|
|
|
; Where entry number VecWhich sits, into VecSlot. Twenty eight bytes an entry.
|
|
vecSlotAt:
|
|
SETD.0 VecSlot
|
|
SETD.2 ScratchVecNames
|
|
CALL numSet
|
|
SETD.0 VecSlotLeft
|
|
SETD.2 VecWhich
|
|
CALL numSet
|
|
vecSlotLoop:
|
|
SETD.0 VecSlotLeft
|
|
LDA.0
|
|
INCD.0
|
|
LDB.0
|
|
OR
|
|
BRQ vecSlotDone
|
|
INIA 0d28
|
|
SETD.0 VecSlot
|
|
CALL numAddByte
|
|
SETD.0 VecSlotLeft
|
|
SETD.2 VecOne
|
|
CALL numTake
|
|
BRI vecSlotLoop
|
|
vecSlotDone:
|
|
RET
|
|
|
|
#Data
|
|
|
|
VecCount:
|
|
0x00 0x00
|
|
VecWhich:
|
|
0x00 0x00
|
|
VecSlot:
|
|
0x00 0x00
|
|
VecSlotLeft:
|
|
0x00 0x00
|
|
VecSought:
|
|
0x00 0x00
|
|
VecSubject:
|
|
0x00 0x00
|
|
VecNumber:
|
|
0x00
|
|
VecPutNumber:
|
|
0x00
|
|
VecPutBase:
|
|
0x00
|
|
VecHandler:
|
|
0x00 0x00
|
|
VecPutHandler:
|
|
0x00 0x00
|
|
VecHasHandler:
|
|
0x00
|
|
VecBase:
|
|
0x00
|
|
VecNextAuto:
|
|
0x00
|
|
VecOne:
|
|
0x00 0x01
|
|
|
|
; Sixty four names, which is every number a program may name for itself.
|
|
VecLimit:
|
|
0x00 0x40
|
|
|
|
VecTwice:
|
|
"that vector name is declared twice"
|
|
VecFull:
|
|
"too many vector names"
|
|
|
|
VecName:
|
|
#Reserve 0d23
|
|
|