Files
AnachronautandClaude Opus 5 4fd8bf7b3f Step a Data Pointer with INCD and DECD, not DPUP and DPDN by one
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
2026-08-21 18:16:27 -04:00

211 lines
4.5 KiB
NASM

; Blits and fills.
;
; A blit moves a run of bytes between any two banks, including from one place in a bank
; to another. A fill writes one byte across a run. Both are instantaneous: waiting
; belongs to a peripheral that has something to wait for, not to the moving of bytes.
;
; Everything a transfer will touch is checked before any of it moves, so a transfer that
; would run out of bank refuses outright rather than stopping halfway and leaving memory
; in a state nobody asked for. The last case here proves that: the blit is refused, and
; the bytes it would have written are still what they were.
;
; Correct output is:
; blitted copied from one place in Data Memory to another
; .... filled with a byte of our choosing
; DE AD blitted from Data Memory into Program Memory
; untouched a refused blit moved nothing at all
; ABABCDEFGH an overlapping blit slid the bytes rather than repeating one
#Include print.asm
#Program
start:
; ---- A blit inside Data Memory, which is what a memcpy is here. ----
SETD.0 Message
CALL aimSource
SETD.0 Landing
CALL aimDest
INIA 0d8
CALL setLength ; "blitted" and its terminator.
INIA 0x01
OUTA 0xE8 ; Blit.
SETD.0 Landing
CALL printString
CALL lineFeed
; ---- A fill, with a byte of our choosing rather than only zero. ----
SETD.0 Landing
CALL aimDest
INIA 0x2E ; '.', into SourceLow, which is where a fill takes its byte.
OUTA 0xE2
INIA 0d4
CALL setLength
INIA 0x02
OUTA 0xE8 ; Fill.
; Terminate it so printString knows where to stop.
SETD.0 Landing
DPUP.0 0d04
RSTA
STA.0
SETD.0 Landing
CALL printString
CALL lineFeed
; ---- Data Memory into Program Memory. This is what a loader does. ----
SETD.0 Code
CALL aimSource
SETD.0 Target
CALL aimDestProgram
INIA 0d2
CALL setLength
INIA 0x01
OUTA 0xE8 ; Blit.
; Read it back out of Program Memory to prove it landed.
SETD.0 Target
CALL aimSourceProgram
CALL readAndPrint
CALL readAndPrint
CALL lineFeed
; ---- A blit that would run past the end of bank 2. ----
; Bank 2 is 2048 bytes. Starting at 2040 and asking for 16 does not fit, so the whole
; thing is refused and Landing keeps what it already had.
SETD.0 Untouched
CALL aimSource
SETD.0 Landing
CALL aimDest
INIA 0d10
CALL setLength
INIA 0x01
OUTA 0xE8 ; This one works, so Landing says "untouched".
INIA 0d2
OUTA 0xE0 ; SourceBank = 2, the bank table.
INIA 0x07
OUTA 0xE1
INIA 0xF8
OUTA 0xE2 ; 0x07F8, eight bytes from the end.
SETD.0 Landing
CALL aimDest
INIA 0d16
CALL setLength
INIA 0x01
OUTA 0xE8 ; Refused: it would read past the end of bank 2.
SETD.0 Landing
CALL printString
CALL lineFeed
; ---- A blit whose source and destination overlap. ----
; Sliding a run of bytes along inside its own bank is an ordinary thing to want, so it
; works rather than quietly repeating the first byte the way a naive forward copy does.
SETD.0 Slide
CALL aimSource
SETD.0 Slide
DPUP.0 0d02
CALL aimDest
INIA 0d8
CALL setLength
INIA 0x01
OUTA 0xE8
SETD.0 Slide
CALL printString
CALL lineFeed
HALT
; ---- Helpers. DP0 holds the address to aim at. ----
aimSource:
INIA 0d1 ; Bank 1, Data Memory.
OUTA 0xE0
BRI aimSourceAddress
aimSourceProgram:
RSTA ; Bank 0, Program Memory.
OUTA 0xE0
aimSourceAddress:
PSHD.0
POPA
POPB
OUTB 0xE1
OUTA 0xE2
RET
aimDest:
INIA 0d1 ; Bank 1, Data Memory.
OUTA 0xE3
BRI aimDestAddress
aimDestProgram:
RSTA ; Bank 0, Program Memory.
OUTA 0xE3
aimDestAddress:
PSHD.0
POPA
POPB
OUTB 0xE4
OUTA 0xE5
RET
; A is the length, which is never more than a byte in this program.
setLength:
PSHA
RSTA
OUTA 0xE6
POPA
OUTA 0xE7
RET
readAndPrint:
INA 0xE9
CALL printByteHex
CALL blankSpace
RET
; The refused blit is caught so the program can carry on and show that nothing moved.
; The command write is two bytes, an opcode and a port, so stepping by two gets past it.
refusedHandler:
MVSD.0
DPUP.0 0d14
LDA.0
CCF
INIB 0d2
ADD
MVQA
STA.0
BRC refusedCarried
RETI
refusedCarried:
DECD.0
LDA.0
INCA
STA.0
RETI
; Never executed, only written over.
Target:
0x00 0x00
#Data
Message:
"blitted"
Untouched:
"untouched"
Code:
0xDE 0xAD
Landing:
#Reserve 0d16
Slide:
"ABCDEFGH"
#Reserve 0d4
#Vectors
BankFault refusedHandler