Somewhere to send the fault about there being nowhere to send it
Dispatching through a vector with nothing in it was the one fault this machine could not hand over, because the thing that would hand it over is the thing that has just found nothing to hand it to. It stopped the machine and no program could do anything about it - so calling a service the system does not implement was fatal, and that is an ordinary mistake to make. Two new fault vectors: 5 when a software vector was empty, 6 when a device interrupted and its hardware entry was. Separate, because they are separate mistakes with separate fixes - one is a program calling something that is not there, the other a program that asked to be interrupted and forgot the handler. WHICH ENTRY WAS EMPTY ARRIVES IN Q, and it is the only thing on this machine a handler is given in a register. Not a fault cause register by another route: the vector still says what happened and Q says which of the 256 entries it happened about, which is a parameter and not a cause. It costs no new state at all, because the frame already saved the Q the interrupted program had and RETI puts it back. The escalation happens once. If vector 5 or 6 is itself empty the machine stops the way it always did, having genuinely run out of places to go. swiFaultTest is what guards that, and it was written long before any of this: it installs nothing, so it must still get the old halt. Breaking the escalation fails the two new tests and not that one; making the escalation unbounded fails that one and not the two new ones. Each break fails exactly the half it belongs to. noDeviceTest is fed no input on purpose. The console raises its line once when input ENDS as well as when a byte arrives - which exists so a program driven by interrupts is told when nothing more is coming - so with no input at all, that end is what turns up. Groundwork for CosmOS's fault screen, which wanted to catch these two and could not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
4d976fc22a
commit
000a6d39cb
+30
-1
@@ -69,9 +69,31 @@ static uint16_t readVector(const uint8_t *programMemory, uint16_t base, uint8_t
|
||||
// Note that a zero entry means "no handler" to everything that dispatches, including the
|
||||
// two entries the CPU treats as start addresses when it reads them at reset. The
|
||||
// exemption belongs to that one read, not to the entries themselves.
|
||||
static uint8_t enterInterrupt(CPURegisters *cpu, uint16_t base, uint8_t index, uint16_t resumeAddress) {
|
||||
static uint8_t dispatch(CPURegisters *cpu, uint16_t base, uint8_t index,
|
||||
uint16_t resumeAddress, int mayEscalate) {
|
||||
uint16_t handler = readVector(cpu->Program, base, index);
|
||||
if (handler == 0x0000) {
|
||||
// ---- Nowhere to go is itself something to report ----
|
||||
//
|
||||
// This is the one fault the machine used to have no way of handing over, because
|
||||
// the thing that would hand it over is the thing that has just found nothing to
|
||||
// hand it to. So it goes to a vector of its own instead, with the number of the
|
||||
// empty entry in Q - and a missing software vector and a device nobody is
|
||||
// listening to are separate entries, because they are separate mistakes.
|
||||
//
|
||||
// NOT WHEN ALREADY ESCALATING. If the fault vector for this is itself empty then
|
||||
// the machine really has run out of places to go, and stopping is the only honest
|
||||
// answer left.
|
||||
if (mayEscalate) {
|
||||
const uint8_t escalation = (base == HARDWARE_VECTOR_BASE)
|
||||
? VECTOR_NO_DEVICE : VECTOR_NO_HANDLER;
|
||||
if (!dispatch(cpu, SOFTWARE_VECTOR_BASE, escalation, resumeAddress, 0)) {
|
||||
// After the frame, so the Q the interrupted program had is safely in it and
|
||||
// RETI will put it back. What the handler sees is which entry was empty.
|
||||
cpu->Q = index;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
cpu->Fault = FAULT_NO_HANDLER;
|
||||
cpu->FaultVector = index;
|
||||
cpu->Status |= STATUS_FAULT;
|
||||
@@ -109,6 +131,13 @@ static uint8_t enterInterrupt(CPURegisters *cpu, uint16_t base, uint8_t index, u
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Dispatching the ordinary way: through the vector asked for, and through the fault vector
|
||||
// for a missing one if that is what it turns out to be.
|
||||
static uint8_t enterInterrupt(CPURegisters *cpu, uint16_t base, uint8_t index,
|
||||
uint16_t resumeAddress) {
|
||||
return dispatch(cpu, base, index, resumeAddress, 1);
|
||||
}
|
||||
|
||||
// A device that refused what it was asked stops the machine where it stands, rather than
|
||||
// raising a line and letting execution carry on past the mistake. The frame carries the
|
||||
// address of the instruction that asked, so a handler can see which one it was, and so a
|
||||
|
||||
Reference in New Issue
Block a user