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
53 lines
1.4 KiB
NASM
53 lines
1.4 KiB
NASM
; Tests a fault handler that steps over the byte it could not decode and carries on.
|
|
;
|
|
; The frame holds the address of the offending byte rather than the one after it, so a
|
|
; handler can see exactly what failed. The cost is that returning with a bare RETI meets
|
|
; the same byte again, which is why this handler moves the saved address on by one
|
|
; first. MVSD is what lets it reach the frame at all.
|
|
;
|
|
; 0xFE is not an instruction. Writing it as a literal is the only way past the
|
|
; assembler, which is what makes a program containing one buildable.
|
|
;
|
|
; Correct output is:
|
|
; O printed before the byte that fails
|
|
; K printed after the handler stepped over it
|
|
|
|
#Program
|
|
|
|
start:
|
|
INIA 0d79 ; 'O'
|
|
OUTA 0x00
|
|
INIA 0x0A
|
|
OUTA 0x00
|
|
|
|
0xFE ; Not an instruction. The handler steps over this.
|
|
|
|
INIA 0d75 ; 'K'. Reached only because the handler moved the address on.
|
|
OUTA 0x00
|
|
INIA 0x0A
|
|
OUTA 0x00
|
|
HALT
|
|
|
|
faultHandler:
|
|
; The frame sits above the Stack Pointer, which points at the next free slot. The
|
|
; address to resume at went on first, so it is furthest up: high byte 13 above the
|
|
; Stack Pointer, low byte 14 above.
|
|
MVSD.0
|
|
DPUP.0 0d14
|
|
LDA.0
|
|
INCA ; Step past the one byte that failed.
|
|
STA.0
|
|
BRC carried ; The low byte wrapped, so the high byte needs the carry.
|
|
RETI
|
|
|
|
carried:
|
|
DECD.0
|
|
LDA.0
|
|
INCA
|
|
STA.0
|
|
RETI
|
|
|
|
#Vectors
|
|
|
|
BadOpcode faultHandler
|