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

75 lines
1.7 KiB
NASM

; Tests a device refusing what it was asked to do.
;
; Interrupting is a device asking for attention later. Refusing is a device saying no to
; the instruction happening now, so it has to stop the machine where it stands instead of
; letting execution carry on past the mistake.
;
; The device on port 0x11 refuses everything, in both directions, so that this path can
; be exercised without the memory controller. A refusal names a software vector, so the
; handler knows what happened from the entry it arrived through.
;
; The handler steps the saved address past the instruction that was refused, the same way
; the fault handler steps past a byte that would not decode. Without that a bare RETI
; would meet the same refused instruction again forever.
;
; Correct output is:
; caught the handler caught a refused write
; caught and a refused read
; done
#Include print.asm
#Program
start:
INIA 0d1
OUTA 0x11 ; Refused. The handler runs and steps us past this.
INA 0x11 ; Refused as well, and reads are caught the same way.
SETD.0 Done
CALL printString
CALL lineFeed
HALT
; A refusal arrives with a full frame, so the handler may use whatever it likes.
refusalHandler:
CALL reportRefusal
; Step the saved address past the instruction that was refused. Both OUT and IN are
; two bytes, an opcode and a port, so two is what it takes.
MVSD.0
DPUP.0 0d14
LDA.0
CCF
INIB 0d2
ADD
MVQA
STA.0
BRC carried
RETI
carried:
DECD.0
LDA.0
INCA
STA.0
RETI
reportRefusal:
SETD.0 Caught
CALL printString
CALL lineFeed
RET
#Data
Caught:
"caught"
Done:
"done"
#Vectors
GuardViolation refusalHandler