labFind walked the index from the front, so every use of every label cost a scan of every label defined so far, each with a string compare. The cost grew with the program being built, which is what made it hurt: assembling CosmOS on the machine took 1,833,691,267 cycles against the assembler assembling itself at 57,257,133 - three times the source for thirty two times the time. Sorted and halved, the same build is 886,498,996. THE WALK WAS 52 PER CENT OF THE WHOLE ASSEMBLY, which settles a suspicion this project has carried unverified for weeks and puts a number on it. Eleven comparisons against two thousand entries where a walk averaged six hundred and seventy. The search hands back where a name WOULD go, which is what adding one needs and what a walk could never have offered, so labAdd gets its insertion point for nothing. sameText was already an ordering and did not have to change: Q is the difference at the first character that differed, and the Carry Flag from that same subtraction survives the return because nothing puts the Status register back. A name that runs out while the other carries on borrows against the other's character, which sorts the shorter first. Small programs pay about a tenth more - 57.3M to 63.5M for the assembler on itself - because adding a label now moves the tail of the index up and a short table was never expensive to walk. That is the right way round for a trade to fall. numHalve and numBack are new: a rotate right on a CIRCULAR sixteen bit register brings bit nought back in at the top, so halving means taking that bit off again, and the low half has to go down first because the mask wants B. WHICH END THE TABLE IS SORTED FROM DOES NOT MATTER. labAdd takes its insertion point from labFind, so the comparison that decides the order is the same one that searches it - turn it round and the table is built backwards and read backwards and no output changes. Tests/break.sh says so, correctly, by not noticing. Verified by CosmOS builds CosmOS and second generation staying byte identical. An indexing bug cannot hide behind a fixed point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
180 lines
3.9 KiB
NASM
180 lines
3.9 KiB
NASM
; The small things every other part of the assembler needs: sixteen bit arithmetic, for
|
|
; something that counts in addresses, and one string comparison.
|
|
;
|
|
; sbfs.asm has routines like these and the assembler cannot use them: it does not include
|
|
; the filesystem, because it reaches the disk through the system's services instead. That
|
|
; is the no-linker tax, paid in about a hundred and fifty bytes, and it is cheaper than the
|
|
; two and a half kilobytes including sbfs.asm would cost.
|
|
;
|
|
; Everything here works on numbers in memory rather than in registers, because a CALL puts
|
|
; A, B and Data Pointers 0 to 2 back as it found them. Only memory survives a return.
|
|
;
|
|
; Numbers are stored most significant byte first, the way every number on this machine is.
|
|
;
|
|
; Written by Anachronaut
|
|
|
|
#Program
|
|
|
|
; The two byte number at DP0 becomes the one at DP2. Destination first, so a call reads
|
|
; the way an assignment does.
|
|
numSet:
|
|
LDA.2
|
|
STA.0
|
|
INCD.2
|
|
INCD.0
|
|
LDA.2
|
|
STA.0
|
|
RET
|
|
|
|
; The two byte number at DP0 becomes itself plus the one at DP2.
|
|
numAdd:
|
|
INCD.0
|
|
INCD.2
|
|
LDA.0
|
|
LDB.2
|
|
CCF
|
|
ADD
|
|
MVQA
|
|
STA.0
|
|
DECD.0
|
|
DECD.2
|
|
LDA.0
|
|
LDB.2
|
|
ADD ; Carries in from the low half. Nothing between touches it.
|
|
MVQA
|
|
STA.0
|
|
RET
|
|
|
|
; The two byte number at DP0 becomes itself less the one at DP2.
|
|
numTake:
|
|
INCD.0
|
|
INCD.2
|
|
LDA.0
|
|
LDB.2
|
|
CCF
|
|
SUB
|
|
MVQA
|
|
STA.0
|
|
DECD.0
|
|
DECD.2
|
|
LDA.0
|
|
LDB.2
|
|
SUB ; Borrows in from the low half.
|
|
MVQA
|
|
STA.0
|
|
RET
|
|
|
|
; Adds the byte in A to the two byte number at DP0.
|
|
numAddByte:
|
|
INCD.0
|
|
LDB.0
|
|
CCF
|
|
ADD
|
|
MVQA
|
|
STA.0
|
|
BNC numAddByteDone
|
|
DECD.0
|
|
LDA.0
|
|
INCA
|
|
STA.0
|
|
numAddByteDone:
|
|
RET
|
|
|
|
; Adds one to the two byte number at DP0.
|
|
numStep:
|
|
INCD.0
|
|
LDA.0
|
|
INCA
|
|
STA.0
|
|
BNC numStepDone ; It did not wrap, so the high byte is untouched.
|
|
DECD.0
|
|
LDA.0
|
|
INCA
|
|
STA.0
|
|
numStepDone:
|
|
RET
|
|
|
|
; Compares the two byte number at DP0 with the one at DP2. Q is zero if they are equal,
|
|
; and the Carry Flag is set if the one at DP0 is the smaller. Both come back, because
|
|
; neither Q nor the Status register is put back by a return.
|
|
numCompare:
|
|
LDA.0
|
|
LDB.2
|
|
CCF
|
|
SUB ; The high bytes settle it unless they are the same.
|
|
BNQ numCompareDone
|
|
INCD.0
|
|
INCD.2
|
|
LDA.0
|
|
LDB.2
|
|
CCF
|
|
SUB
|
|
numCompareDone:
|
|
RET
|
|
|
|
; Takes one off the two byte number at DP0. The mirror of numStep, and wanted for the same
|
|
; reason: walking an index backwards is what moving a run of entries up needs.
|
|
numBack:
|
|
INCD.0
|
|
LDA.0
|
|
BNA numBackLow ; The low half has something to take, so the high half is safe.
|
|
DECD.0
|
|
LDA.0
|
|
DECA
|
|
STA.0
|
|
INCD.0
|
|
LDA.0
|
|
numBackLow:
|
|
DECA
|
|
STA.0
|
|
RET
|
|
|
|
; The two byte number at DP0 becomes half of itself.
|
|
;
|
|
; A and B are a CIRCULAR sixteen bit register, so a rotate right brings bit nought back in at
|
|
; the top rather than dropping it - which is a halving only once that bit is taken off again.
|
|
; The numbers this is asked about are index positions, well under thirty two thousand, so the
|
|
; top bit was nought before the rotate and clearing it afterwards loses nothing.
|
|
numHalve:
|
|
LDA.0
|
|
INCD.0
|
|
LDB.0
|
|
SHR
|
|
|
|
; The low half goes down FIRST, because taking the wrapped bit off the high half wants B
|
|
; for the mask and there is nowhere else to keep it.
|
|
STB.0
|
|
INIB 0x7F
|
|
AND
|
|
MVQA
|
|
DECD.0
|
|
STA.0
|
|
RET
|
|
|
|
; Q is zero if the strings at DP0 and DP1 are the same, both ending in a zero byte.
|
|
;
|
|
; Down here rather than with the label table, where it started, because four separate
|
|
; parts want it: labels, vector names, which file has already been included, and which
|
|
; directive a keyword is.
|
|
sameText:
|
|
LDA.0
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
BNQ sameTextDone
|
|
LDA.0
|
|
BRA sameTextDone ; They ended together, so they matched all the way.
|
|
INCD.0
|
|
INCD.1
|
|
BRI sameText
|
|
sameTextDone:
|
|
RET
|
|
|
|
; The two byte number at DP0 becomes zero.
|
|
numZero:
|
|
RSTA
|
|
STA.0
|
|
INCD.0
|
|
STA.0
|
|
RET
|