DPUP takes an immediate, so an offset of one is legal and does exactly the right thing. It is also three bytes where INCD is two, and reads as "offset the pointer up by one" where INCD reads as "step the pointer". 56 of them across 15 files: the system, the assembler, the editor, and eight test programs. CosmOS is 9,564 bytes to 9,537, the native assembler 11,648 to 11,635, and every program in the repository together 49 bytes lighter. The worst offender was numbers.asm, written this week, where every sixteen bit helper reaches the low byte and comes back the long way round. It is the file every other part of the assembler includes, so it is the first thing anybody reads when they go looking - and it was teaching them the long way. Pattern matched off sbfs.asm rather than off the instruction table I had just embedded in two programs. THIS IS NOT TWO WAYS TO DO ONE THING. DPUP takes an arbitrary number, so one is inevitably among them; INCD earns its place by making the common case a byte cheaper. The overlap is structural and the choice is a usage question, which is a linter's job rather than an ISA's - "DPUP.n 0d01: INCD.n does this in a byte less" is a mechanical rule with no judgement in it. Nothing needed re-recording, which was not a foregone conclusion: cosmosBreak prints the system addresses the registers happened to hold, and they did not move. Both assemblers still produce identical bytes and CosmOS still builds itself to a fixed point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
141 lines
2.8 KiB
NASM
141 lines
2.8 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
|
|
|
|
; 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
|