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
173 lines
3.9 KiB
NASM
173 lines
3.9 KiB
NASM
; The fence.
|
|
;
|
|
; A guarded range is a fence rather than a wall. Any program may raise one and any
|
|
; program may lower it, so nobody is ever told no. What it stops is walking into
|
|
; something by accident, which is the failure that costs an afternoon: without it, code
|
|
; that gets walked over is found much later, when the wreckage is finally executed and
|
|
; the evidence is long gone.
|
|
;
|
|
; The handler reads the controller's destination registers to say where the refused write
|
|
; was aimed. That is the whole point of catching it at the instruction that did it: the
|
|
; address is still sitting there to be read.
|
|
;
|
|
; The addresses here are absolute rather than labels, so that this says the same thing no
|
|
; matter what else ends up in the Data Segment. Data Memory is 64K and this program uses
|
|
; a hundred bytes of it, so 0x0200 is empty ground.
|
|
;
|
|
; Correct output is:
|
|
; outside ok a write just below the fence went through
|
|
; caught 0200 a write inside it was refused, and the handler says where
|
|
; caught 01FC a blit that merely clipped it was refused whole
|
|
; read ok reading inside the fence is allowed: it guards writing
|
|
; lowered ok after GuardOff the same write goes through
|
|
; fenced 05 the bank table shows bank 1 present and fenced
|
|
|
|
#Include print.asm
|
|
|
|
#Program
|
|
|
|
start:
|
|
; Fence off 0x0200 to 0x020F in Data Memory.
|
|
INIA 0d1
|
|
OUTA 0xEB ; GuardBank = 1
|
|
INIA 0x02
|
|
OUTA 0xEC
|
|
RSTA
|
|
OUTA 0xED ; GuardStart = 0x0200
|
|
INIA 0x02
|
|
OUTA 0xEE
|
|
INIA 0x0F
|
|
OUTA 0xEF ; GuardEnd = 0x020F
|
|
INIA 0x10
|
|
OUTA 0xE8 ; GuardOn
|
|
|
|
; Just below the fence is ordinary ground.
|
|
INIA 0x01
|
|
OUTA 0xE4
|
|
INIA 0xFF
|
|
OUTA 0xE5 ; Dest = 0x01FF
|
|
INIA 0d1
|
|
OUTA 0xE3 ; DestBank = 1
|
|
INIA 0x41
|
|
OUTA 0xE9
|
|
SETD.0 OutsideOk
|
|
CALL say
|
|
|
|
; Inside it is not.
|
|
INIA 0x02
|
|
OUTA 0xE4
|
|
RSTA
|
|
OUTA 0xE5 ; Dest = 0x0200
|
|
INIA 0x42
|
|
OUTA 0xE9 ; Refused.
|
|
|
|
; A blit from 0x01FC for eight bytes runs to 0x0203, so it clips the fence. It is
|
|
; refused whole rather than doing the four bytes that would have fitted.
|
|
INIA 0x01
|
|
OUTA 0xE4
|
|
INIA 0xFC
|
|
OUTA 0xE5 ; Dest = 0x01FC
|
|
INIA 0d1
|
|
OUTA 0xE0
|
|
RSTA
|
|
OUTA 0xE1
|
|
OUTA 0xE2 ; Source = bank 1, 0x0000
|
|
OUTA 0xE6
|
|
INIA 0d8
|
|
OUTA 0xE7 ; Length = 8
|
|
INIA 0x01
|
|
OUTA 0xE8 ; Refused.
|
|
|
|
; Reading inside a fence is allowed. A debugger has to be able to see what is being
|
|
; protected.
|
|
INIA 0d1
|
|
OUTA 0xE0
|
|
INIA 0x02
|
|
OUTA 0xE1
|
|
RSTA
|
|
OUTA 0xE2 ; Source = 0x0200
|
|
INA 0xE9
|
|
SETD.0 ReadOk
|
|
CALL say
|
|
|
|
; Lower it, and the write that was refused goes through.
|
|
INIA 0x11
|
|
OUTA 0xE8 ; GuardOff
|
|
INIA 0x02
|
|
OUTA 0xE4
|
|
RSTA
|
|
OUTA 0xE5
|
|
INIA 0x43
|
|
OUTA 0xE9
|
|
SETD.0 LoweredOk
|
|
CALL say
|
|
|
|
; Raise it again, and read the flags back out of the bank table, where a fence is
|
|
; published along with everything else about a bank.
|
|
INIA 0x10
|
|
OUTA 0xE8
|
|
INIA 0d2
|
|
OUTA 0xE0
|
|
RSTA
|
|
OUTA 0xE1
|
|
INIA 0d8
|
|
OUTA 0xE2 ; Bank 1's record begins at 8.
|
|
SETD.0 Fenced
|
|
CALL printString
|
|
CALL blankSpace
|
|
INA 0xE9
|
|
CALL printByteHex
|
|
CALL lineFeed
|
|
HALT
|
|
|
|
; Says where the refused write was aimed, by reading the controller's own registers.
|
|
guardHandler:
|
|
SETD.0 Caught
|
|
CALL printString
|
|
CALL blankSpace
|
|
INA 0xE4 ; DestHigh
|
|
CALL printByteHex
|
|
INA 0xE5 ; DestLow
|
|
CALL printByteHex
|
|
CALL lineFeed
|
|
|
|
; Step past the refused instruction. Every write here is an opcode and a port.
|
|
MVSD.0
|
|
DPUP.0 0d14
|
|
LDA.0
|
|
CCF
|
|
INIB 0d2
|
|
ADD
|
|
MVQA
|
|
STA.0
|
|
BRC stepCarried
|
|
RETI
|
|
stepCarried:
|
|
DECD.0
|
|
LDA.0
|
|
INCA
|
|
STA.0
|
|
RETI
|
|
|
|
say:
|
|
CALL printString
|
|
CALL lineFeed
|
|
RET
|
|
|
|
#Data
|
|
|
|
OutsideOk:
|
|
"outside ok"
|
|
Caught:
|
|
"caught"
|
|
ReadOk:
|
|
"read ok"
|
|
LoweredOk:
|
|
"lowered ok"
|
|
Fenced:
|
|
"fenced"
|
|
|
|
#Vectors
|
|
|
|
GuardViolation guardHandler
|