SRET: a handler answers the way a subroutine does

CALL saves A, B and Data Pointers 0 to 2 and nothing else, which is exactly
why Q and DP3 are how a subroutine hands something back. An interrupt saves
all of it, so a service with an answer had to reach into its own frame and
un-save two fields by hand:

  MVSD.2
  DPUP.2 0d02           ; the saved Q, by an offset it had to know
  STA.2
  RETI

Thirty places in CosmOS did that. Every one knew the frame's layout by
heart, and all thirty would have gone quietly wrong the day the frame
gained a field - the same duplicated fact this project keeps being bitten
by, except duplicated into thirty places AND into the CPU.

SRET is 0x76, in the seat the block split left for it. It is RETI's frame
with RET's rule applied: A, B and DP0 to DP2 come back, the saved Q and DP3
are dropped, and the Interrupt Flag is restored from the frame - only that
bit, so carry survives a service the way it survives a call, and there is
one rule rather than two. RETI stays exactly as it was: a hardware handler
has nothing to say and must leave no trace.

CosmOS is 10,969 bytes against 11,122, and no handler knows a frame offset.

TWO MISTAKES WORTH RECORDING, both mine, both caught by tests.

The first conversion matched STA.2 with a regular expression that did not
allow a trailing comment, so it ran past the end of one handler and into
the next. The second understood the pattern and still got it wrong: the old
frame write carried the answer from A into the saved Q slot, so simply
deleting the write left Q holding whatever it happened to hold. Services
that answer by calling something were fine - Q already had it - and
services that set A directly silently reported success for every failure.
cosmosCwd is what noticed, by saying "cannot go there" about a directory
that was there. Sixteen handlers move the answer into Q now.

Seven MVQA went with it. They copied Q into A so the frame write could
carry it; SRET puts A back, so they moved a value nobody would ever read.
This commit is contained in:
Anachronaut
2026-08-27 18:18:36 -04:00
parent cd5f548736
commit c8c9f0b363
9 changed files with 275 additions and 133 deletions
+2 -1
View File
@@ -37,7 +37,7 @@ AsmShapeSelectors:
0d0 0d0 0d0 0d1 0d1 0d1 0d2 0d0 0d0 0d0 0d1 0d1 0d1 0d2
AsmInstructionCount: AsmInstructionCount:
0d71 0d72
AsmInstructions: AsmInstructions:
0x10 0d0 "ADD " 0x10 0d0 "ADD "
@@ -65,6 +65,7 @@ AsmInstructions:
0x73 0d0 "RETI" 0x73 0d0 "RETI"
0x74 0d0 "RRET" 0x74 0d0 "RRET"
0x75 0d0 "RET " 0x75 0d0 "RET "
0x76 0d0 "SRET"
0x20 0d0 "RSTA" 0x20 0d0 "RSTA"
0x21 0d0 "RSTB" 0x21 0d0 "RSTB"
0x22 0d0 "INCA" 0x22 0d0 "INCA"
+28
View File
@@ -858,6 +858,34 @@ A file's length is its block count times 256 plus its tail, which is the same as
The other implementation of this format is SplitDisk, on the host. Nothing is shared between the two but the specification, so a change to either has to be a change to both. The other implementation of this format is SplitDisk, on the host. Nothing is shared between the two but the specification, so a change to either has to be a change to both.
### How A Service Answers:
A handler arrives with the caller's registers pushed rather than cleared, and **`RETI`
restores every one of them** - which is what makes an interrupt safe to arrive at an
arbitrary moment, since the interrupted code cannot tell it happened. A service is not
arbitrary. It was asked for, and it has something to say.
It says it with `SRET`, which is `RET` adapted to an interrupt frame: **A, B and Data
Pointers 0 through 2 come back, the saved Q and Data Pointer 3 are dropped, and the
Interrupt Flag is put back from the frame.** So a service answers in exactly the registers
a subroutine answers in, and there is one rule on this machine rather than two.
Before it existed, a handler with an answer wrote into its own frame:
```asm
MVSD.2
DPUP.2 0d02 ; the saved Q, by an offset it had to know
STA.2
RETI
```
Thirty places did that, each knowing the frame's layout by heart, and all thirty would have
gone quietly wrong the day the frame gained a field. None of them knows it now.
`RETI` is still right for a **hardware** handler, which has nothing to say and must leave
no trace. The two returns are not a choice of style: one says *I was never here* and the
other says *here is your answer*.
### What A Subroutine Can And Cannot Hand Back: ### What A Subroutine Can And Cannot Hand Back:
This is the thing that catches people, including whoever wrote the last three pieces of system code, so it is worth stating once and plainly. This is the thing that catches people, including whoever wrote the last three pieces of system code, so it is worth stating once and plainly.
+106 -130
View File
@@ -1011,19 +1011,15 @@ handleFileStart:
; What a name means on the disk is about to change, so the remembered file goes. ; What a name means on the disk is about to change, so the remembered file goes.
CALL fileForget CALL fileForget
CALL sbfsStreamStart CALL sbfsStreamStart
MVQA SRET
MVSD.2
DPUP.2 0d02
STA.2
RETI
fileStartNoDisk: fileStartNoDisk:
POPA POPA
MVSD.2
DPUP.2 0d02
INIA 0d1 INIA 0d1
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
; DP1 is where the block comes from, and A and B together say which block of the file it ; DP1 is where the block comes from, and A and B together say which block of the file it
; is, counting from zero - the same way osFileBlock is told which one to fetch. ; is, counting from zero - the same way osFileBlock is told which one to fetch.
@@ -1033,11 +1029,7 @@ handleFileWrite:
INCD.2 INCD.2
STB.2 STB.2
CALL sbfsStreamWrite CALL sbfsStreamWrite
MVQA SRET
MVSD.2
DPUP.2 0d02
STA.2
RETI
; DP1 is where the block goes, and A and B together say which one, the same way writing is ; DP1 is where the block goes, and A and B together say which one, the same way writing is
; told. Reads back a block of the file being written. ; told. Reads back a block of the file being written.
@@ -1047,11 +1039,7 @@ handleFileFetch:
INCD.2 INCD.2
STB.2 STB.2
CALL sbfsStreamFetch CALL sbfsStreamFetch
MVQA SRET
MVSD.2
DPUP.2 0d02
STA.2
RETI
; DP3 is how many whole blocks it came to and A is what is left over, told the same way ; DP3 is how many whole blocks it came to and A is what is left over, told the same way
; osFileStart is told. It need not be what was asked for: a writer that cannot know its ; osFileStart is told. It need not be what was asked for: a writer that cannot know its
@@ -1071,11 +1059,7 @@ handleFileDone:
STB.2 STB.2
CALL fileForget CALL fileForget
CALL sbfsStreamDone CALL sbfsStreamDone
MVQA SRET
MVSD.2
DPUP.2 0d02
STA.2
RETI
; ---- osChangeDir ---- ; ---- osChangeDir ----
; ;
@@ -1115,18 +1099,18 @@ changeDirTake:
SETD.1 SbfsCwd SETD.1 SbfsCwd
CALL sbfsCopyWord CALL sbfsCopyWord
MVSD.2
DPUP.2 0d02
RSTA RSTA
STA.2 ; Q is zero: the machine is there now. RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
changeDirNo: changeDirNo:
MVSD.2
DPUP.2 0d02
INIA 0d1 INIA 0d1
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
; ---- cd ---- ; ---- cd ----
; ;
@@ -1776,24 +1760,25 @@ handleFileRead:
SETD.2 SbfsFileTail SETD.2 SbfsFileTail
LDB.2 LDB.2
MVSD.2 ; A two byte answer, and DP3 is where a routine hands one back. Built with the
DPUP.2 0d05 ; The saved DP3, high byte first. ; Stack rather than written into the frame: SRET leaves DP3 alone, so there is
STA.2 ; nothing to reach into. PSHA then PSHB puts the high byte above the low one,
INCD.2 ; which is the order POPD reads them in.
STB.2 PSHA
PSHB
MVSD.2 POPD.3
DPUP.2 0d02 ; And the saved Q.
RSTA RSTA
STA.2 RSTB
RETI CCF
ADD ; Q is zero: it is there.
SRET
fileReadNo: fileReadNo:
MVSD.2
DPUP.2 0d02
INIA 0d1 INIA 0d1
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
; DP0 names the file, DP1 is the bytes, and A and B together are how many. Q is zero if it ; DP0 names the file, DP1 is the bytes, and A and B together are how many. Q is zero if it
; saved. Whether it was there before makes no difference, which is what saving means. ; saved. Whether it was there before makes no difference, which is what saving means.
@@ -1818,19 +1803,15 @@ handleFileSave:
; What a name means on the disk is about to change, so the remembered file goes. ; What a name means on the disk is about to change, so the remembered file goes.
CALL fileForget CALL fileForget
CALL sbfsSaveFile CALL sbfsSaveFile
MVQA SRET
MVSD.2
DPUP.2 0d02
STA.2
RETI
fileSaveNoDisk: fileSaveNoDisk:
POPA POPA
MVSD.2
DPUP.2 0d02
INIA 0d1 INIA 0d1
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
; DP0 names it. Q is zero if it went. ; DP0 names it. Q is zero if it went.
handleFileDelete: handleFileDelete:
@@ -1840,11 +1821,7 @@ handleFileDelete:
; What a name means on the disk is about to change, so the remembered file goes. ; What a name means on the disk is about to change, so the remembered file goes.
CALL fileForget CALL fileForget
CALL sbfsDelete CALL sbfsDelete
MVQA SRET
MVSD.2
DPUP.2 0d02
STA.2
RETI
; DP0 is the name it has, DP1 the name it should have. Q is zero if it moved. ; DP0 is the name it has, DP1 the name it should have. Q is zero if it moved.
handleFileRename: handleFileRename:
@@ -1854,18 +1831,14 @@ handleFileRename:
; What a name means on the disk is about to change, so the remembered file goes. ; What a name means on the disk is about to change, so the remembered file goes.
CALL fileForget CALL fileForget
CALL sbfsRename CALL sbfsRename
MVQA SRET
MVSD.2
DPUP.2 0d02
STA.2
RETI
serviceNoDisk: serviceNoDisk:
MVSD.2
DPUP.2 0d02
INIA 0d1 INIA 0d1
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
; ---- Reading a file that will not fit ---- ; ---- Reading a file that will not fit ----
; ;
@@ -1978,31 +1951,32 @@ handleFileInfo:
INCD.2 INCD.2
LDB.2 LDB.2
MVSD.2 ; A two byte answer, and DP3 is where a routine hands one back. Built with the
DPUP.2 0d05 ; The saved DP3, high byte first. ; Stack rather than written into the frame: SRET leaves DP3 alone, so there is
STA.2 ; nothing to reach into. PSHA then PSHB puts the high byte above the low one,
INCD.2 ; which is the order POPD reads them in.
STB.2 PSHA
PSHB
MVSD.2 POPD.3
DPUP.2 0d02
RSTA RSTA
STA.2 ; And the saved Q: it is there. RSTB
RETI CCF
ADD ; Q is zero: it is there.
SRET
fileInfoNoDisk: fileInfoNoDisk:
MVSD.2
DPUP.2 0d02
INIA 0d1 INIA 0d1
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
fileInfoMissing: fileInfoMissing:
MVSD.2
DPUP.2 0d02
INIA 0d2 INIA 0d2
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
; DP0 names it, DP1 says where to put it, and A and B together are which block, counting ; DP0 names it, DP1 says where to put it, and A and B together are which block, counting
; from zero. Q is zero if it read, and DP3 comes back holding how many of the block's bytes ; from zero. Q is zero if it read, and DP3 comes back holding how many of the block's bytes
@@ -2053,45 +2027,46 @@ fileBlockWhole:
RSTB RSTB
fileBlockAnswer: fileBlockAnswer:
MVSD.2 ; A two byte answer, and DP3 is where a routine hands one back. Built with the
DPUP.2 0d05 ; Stack rather than written into the frame: SRET leaves DP3 alone, so there is
STA.2 ; nothing to reach into. PSHA then PSHB puts the high byte above the low one,
INCD.2 ; which is the order POPD reads them in.
STB.2 PSHA
PSHB
MVSD.2 POPD.3
DPUP.2 0d02
RSTA RSTA
STA.2 RSTB
RETI CCF
ADD ; Q is zero: it is there.
SRET
fileBlockNoDisk: fileBlockNoDisk:
MVSD.2
DPUP.2 0d02
INIA 0d1 INIA 0d1
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
fileBlockMissing: fileBlockMissing:
MVSD.2
DPUP.2 0d02
INIA 0d2 INIA 0d2
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
fileBlockPastEnd: fileBlockPastEnd:
MVSD.2
DPUP.2 0d02
INIA 0d3 INIA 0d3
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
fileBlockFailed: fileBlockFailed:
MVSD.2
DPUP.2 0d02
INIA 0d4 INIA 0d4
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
; A breakpoint. Shows every register as the interrupted program had them, waits for a key, ; A breakpoint. Shows every register as the interrupted program had them, waits for a key,
; and returns as though nothing happened. ; and returns as though nothing happened.
@@ -2291,18 +2266,18 @@ handleBootState:
BNQ bootStateNone BNQ bootStateNone
SETD.2 SbfsStateWas SETD.2 SbfsStateWas
LDA.2 LDA.2
MVSD.2 RSTB
DPUP.2 0d02 CCF
STA.2 ADD ; A is the answer, so Q becomes it.
RETI SRET
bootStateNone: bootStateNone:
; No disk, or one that would not answer. Nothing there to be unsettled about. ; No disk, or one that would not answer. Nothing there to be unsettled about.
MVSD.2
DPUP.2 0d02
RSTA RSTA
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
handleBootSettle: handleBootSettle:
SETD.2 DiskReady SETD.2 DiskReady
@@ -2312,18 +2287,18 @@ handleBootSettle:
RSTA RSTA
CALL sbfsSetBootState CALL sbfsSetBootState
BNQ bootSettleNo BNQ bootSettleNo
MVSD.2
DPUP.2 0d02
RSTA RSTA
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
bootSettleNo: bootSettleNo:
MVSD.2
DPUP.2 0d02
INIA 0d1 INIA 0d1
STA.2 RSTB
RETI CCF
ADD ; A is the answer, so Q becomes it.
SRET
handleExit: handleExit:
SETD.1 SystemStack SETD.1 SystemStack
@@ -3829,7 +3804,7 @@ ShapeLength:
0d1 0d3 0d2 0d2 0d3 0d4 0d3 0d1 0d3 0d2 0d2 0d3 0d4 0d3
InstructionCount: InstructionCount:
0d71 0d72
; ---- The instruction table ---- ; ---- The instruction table ----
; ;
@@ -3862,6 +3837,7 @@ Instructions:
0x73 0d0 "RETI" 0x73 0d0 "RETI"
0x74 0d0 "RRET" 0x74 0d0 "RRET"
0x75 0d0 "RET " 0x75 0d0 "RET "
0x76 0d0 "SRET"
0x20 0d0 "RSTA" 0x20 0d0 "RSTA"
0x21 0d0 "RSTB" 0x21 0d0 "RSTB"
0x22 0d0 "INCA" 0x22 0d0 "INCA"
@@ -26,6 +26,21 @@
; quiet: 7 a service that says nothing leaves Q as it found it ; quiet: 7 a service that says nothing leaves Q as it found it
; answer: 42 one that does, does not ; answer: 42 one that does, does not
; pointer: ABC and DP3 comes back the same way ; pointer: ABC and DP3 comes back the same way
; sret: 42 and SRET says the same thing without touching the frame
; sretptr: ABC for a pointer too
; sretkept: 7 while everything a RET would restore still comes back
;
; ---- The frame editing above is what SRET exists to replace ----
;
; Everything between MVSD and RETI in "answer" and "pointer" is a routine reaching into its
; own frame to un-save two fields, using offsets it has to know by heart. Thirty places in
; CosmOS did that, and all thirty would have gone quietly wrong the day the frame gained a
; field. SRET is the same instruction sequence as RETI with the saved Q and DP3 stepped
; over instead of restored, so a handler answers the way a subroutine does and nothing
; below has to know what a frame looks like.
;
; Both are kept here on purpose. RETI is still how a HARDWARE handler says it was never
; there, and a service that has nothing to say should still use it.
#Include console.asm #Include console.asm
@@ -59,6 +74,39 @@ start:
POPD.0 POPD.0
CALL printString CALL printString
CALL newLine CALL newLine
; ---- And the same three answers, given with SRET instead ----
SETD.0 SretText
CALL printString
SWI sretAnswer
MVQA
CALL printByteDecimal
CALL newLine
SETD.0 SretPtrText
CALL printString
SWI sretPointer
PSHD.3
POPD.0
CALL printString
CALL newLine
; What SRET must still put back. A handler that trampled A, B and DP0 to DP2 would break
; every caller, which is exactly why RET restores them - so SRET does too, and the number
; printed here is the caller's own, loaded before the service was asked for anything.
INIA 0d7
RSTB
CCF
ADD ; Q is the caller's seven,
SETD.0 SretKeptText ; and DP0 names the label, BEFORE the service is asked.
SWI sretTrample
; Printed after the call rather than before it, which is the whole of the check: the
; handler pointed DP0 somewhere else, so this says "sretkept" only if SRET brought the
; caller's own pointer back. Printing first would have tested nothing at all.
CALL printString
MVQA
CALL printByteDecimal
CALL newLine
HALT HALT
; Says nothing, so whatever the caller had in Q is still there afterwards. ; Says nothing, so whatever the caller had in Q is still there afterwards.
@@ -88,6 +136,31 @@ pointer:
STA.1 STA.1
RETI RETI
; ---- The same answers, without the frame ----
;
; Q and DP3 are set the way any subroutine sets them, and SRET leaves them alone. There is
; no MVSD, no offset, and nothing here that would need revisiting if the frame changed.
sretAnswer:
INIA 0d42
RSTB
CCF
ADD
SRET
sretPointer:
SETD.3 Letters
SRET
; Sets everything a RET would restore to something wrong, to show that SRET restores it.
; If any of these came back, the caller's 7 would not.
sretTrample:
INIA 0d99
INIB 0d98
SETD.0 Letters
SETD.1 Letters
SETD.2 Letters
SRET
#Data #Data
QuietText: QuietText:
@@ -96,6 +169,12 @@ AnswerText:
"answer: " "answer: "
PointerText: PointerText:
"pointer: " "pointer: "
SretText:
"sret: "
SretPtrText:
"sretptr: "
SretKeptText:
"sretkept: "
Letters: Letters:
"ABC" "ABC"
@@ -105,3 +184,6 @@ Letters:
quiet quiet quiet quiet
answer answer answer answer
pointer pointer pointer pointer
sretAnswer sretAnswer
sretPointer sretPointer
sretTrample sretTrample
+1 -1
View File
@@ -34,7 +34,7 @@ wrote Asm.sbx: program 7533, data 4099, labels 555
## The Machine: ## The Machine:
- **Harvard architecture.** Two 64K memories, one for instructions and one for data. An instruction can only read the second, which is why strings live there and why the memory controller exists. - **Harvard architecture.** Two 64K memories, one for instructions and one for data. An instruction can only read the second, which is why strings live there and why the memory controller exists.
- **Its own instruction set**, 71 instructions, four Data Pointers, and a Q register that holds what the ALU last worked out. Small enough that the table describing it fits in the machine's own memory, which is what lets it disassemble and assemble for itself. - **Its own instruction set**, 72 instructions, four Data Pointers, and a Q register that holds what the ALU last worked out. Small enough that the table describing it fits in the machine's own memory, which is what lets it disassemble and assemble for itself.
- **Interrupts.** Software traps, hardware lines from devices, and faults, all arriving through one vector table with a full context save. - **Interrupts.** Software traps, hardware lines from devices, and faults, all arriving through one vector table with a full context save.
- **A bus programs can enumerate**, so a program can ask what a machine is made of rather than being told. - **A bus programs can enumerate**, so a program can ask what a machine is made of rather than being told.
- **A memory controller** that reads and writes Program Memory, moves blocks between banks, reaches memory that devices bring with them, and guards a range against being written by accident. It is how a SplitBit machine loads a program. - **A memory controller** that reads and writes Program Memory, moves blocks between banks, reaches memory that devices bring with them, and guards a range against being written by accident. It is how a SplitBit machine loads a program.
+4
View File
@@ -56,6 +56,10 @@ Instruction instruction_set[] = {
{0x73, "RETI"}, {0x73, "RETI"},
{0x74, "RRET"}, {0x74, "RRET"},
{0x75, "RET"}, {0x75, "RET"},
// A handler with an answer. RETI restores everything and is how a hardware handler
// says it was never here; this restores what a RET restores, and is how a service
// says it has replied. See the note in cpu.c.
{0x76, "SRET"},
// Register Operations: // Register Operations:
{0x20, "RSTA"}, {0x20, "RSTA"},
{0x21, "RSTB"}, {0x21, "RSTB"},
+46
View File
@@ -405,6 +405,52 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
// same job with its +2, for the same reason. // same job with its +2, for the same reason.
cpu->ProgramCounter = resumeAddress - 1; cpu->ProgramCounter = resumeAddress - 1;
} break; } break;
case 0x76: {
// SRET - Return from a handler that has an answer.
//
// THE SAME FRAME AS RETI, WITH THE CALL CONVENTION'S RULE APPLIED TO IT. CALL
// saves A, B and Data Pointers 0 to 2 and nothing else, which is exactly why
// Q and DP3 are how a subroutine hands something back. An interrupt saves all
// of it, so a handler with an answer had to reach into its own frame and
// un-save two fields by hand - thirty places in CosmOS did that, each of them
// knowing the frame's layout by an offset, and all thirty would have gone
// quietly wrong the day the frame gained a field.
//
// So: RETI restores everything and is how a hardware handler says it was never
// here. SRET restores what a RET restores and is how a service says it has
// replied. The saved Q and DP3 are stepped over and dropped.
cpu->StackPointer++;
uint8_t savedStatus = readData(cpu, cpu->StackPointer);
// The Interrupt Flag, and only that. Entering a handler clears it and the
// frame is what puts it back, so dropping the whole byte would leave a service
// silently turning interrupts off. Everything else in Status - the carry
// above all - is left as the handler leaves it, because that is what a RET
// does and the point of this instruction is that there is one rule.
cpu->Status = (uint8_t)((cpu->Status & ~STATUS_INTERRUPT)
| (savedStatus & STATUS_INTERRUPT));
cpu->StackPointer++; // The saved Q, dropped: the handler's answer stands.
cpu->StackPointer++;
cpu->A = readData(cpu, cpu->StackPointer);
cpu->StackPointer++;
cpu->B = readData(cpu, cpu->StackPointer);
for (int i = DATA_POINTERS - 1; i >= 0; i--) {
cpu->StackPointer++;
uint16_t high = (uint16_t)readData(cpu, cpu->StackPointer) << 8;
cpu->StackPointer++;
uint16_t low = (uint16_t)readData(cpu, cpu->StackPointer);
// Data Pointer 3 is stepped over for the same reason as Q. The other three
// come back, exactly as a RET brings them back.
if (i != DATA_POINTERS - 1) {
cpu->DataPointer[i] = high | low;
}
}
uint16_t resumeAddress;
cpu->StackPointer++;
resumeAddress = (uint16_t)readData(cpu, cpu->StackPointer) << 8;
cpu->StackPointer++;
resumeAddress = resumeAddress | (uint16_t)readData(cpu, cpu->StackPointer);
cpu->ProgramCounter = resumeAddress - 1;
} break;
case 0x74: case 0x74:
// RRET - Return from an RCAL, taking back nothing but the return address. // RRET - Return from an RCAL, taking back nothing but the return address.
cpu->StackPointer++; cpu->StackPointer++;
+3 -1
View File
@@ -45,6 +45,7 @@ It has ten registers:
- Bit 0 is the Carry/Borrow Flag. Any arithmetic operation either sets or clears it depending on whether or not the result causes Q to overflow/underflow. It is a 1 if a carry/underflow occurred, and a 0 otherwise. If A or B overflows or underflows from the use of an increment or decrement instruction, this flag will also be set. Non-overflowing increments or decrements will also reset it. - Bit 0 is the Carry/Borrow Flag. Any arithmetic operation either sets or clears it depending on whether or not the result causes Q to overflow/underflow. It is a 1 if a carry/underflow occurred, and a 0 otherwise. If A or B overflows or underflows from the use of an increment or decrement instruction, this flag will also be set. Non-overflowing increments or decrements will also reset it.
- Bit 1 is the Fault Flag. It is set when the CPU cannot get past something and no handler was installed to deal with it: a byte that is not an instruction, or a dispatch through an empty vector. See Faults. - Bit 1 is the Fault Flag. It is set when the CPU cannot get past something and no handler was installed to deal with it: a byte that is not an instruction, or a dispatch through an empty vector. See Faults.
- Bit 2 is the Interrupt Flag. It is set by SIF and cleared by CIF. While it is set the CPU answers devices asking for attention; while it is clear they wait. Arriving at a handler clears it, and RETI restores it along with the rest of the Status register. See Hardware Interrupts. - Bit 2 is the Interrupt Flag. It is set by SIF and cleared by CIF. While it is set the CPU answers devices asking for attention; while it is clear they wait. Arriving at a handler clears it, and RETI restores it along with the rest of the Status register. See Hardware Interrupts.
- RETI restores this register whole; SRET restores only the Interrupt Flag from it and leaves the rest as the handler left it, so a service can answer in the Carry Flag the same way a subroutine can.
- Bit 7 is the Halt Flag. It is set by the HALT instruction, and by a fault. - Bit 7 is the Halt Flag. It is set by the HALT instruction, and by a fault.
- There is no Wait Flag. The CPU stopped in a WAIT is stopped in a way no program can see, precisely because this register is saved and restored across an interrupt and a wait must not be. See WAIT under Special Operations. - There is no Wait Flag. The CPU stopped in a WAIT is stopped in a way no program can see, precisely because this register is saved and restored across an interrupt and a wait must not be. See WAIT under Special Operations.
@@ -91,7 +92,7 @@ The Bytes column is the total length of the instruction, counting its opcode, an
| 68 | BNB | 3 | Branch if B is not zero. | | 68 | BNB | 3 | Branch if B is not zero. |
| 69 | BNC | 3 | Branch if the Carry Flag is clear. | | 69 | BNC | 3 | Branch if the Carry Flag is clear. |
### Subroutine Operations: 6 Instructions ### Subroutine Operations: 7 Instructions
A block of their own, because the branches and these outgrew one nibble between them. Each A block of their own, because the branches and these outgrew one nibble between them. Each
raw form sits immediately below the ordinary one it cannot be mixed with - RCAL under CALL, raw form sits immediately below the ordinary one it cannot be mixed with - RCAL under CALL,
@@ -105,6 +106,7 @@ machine somewhere nobody named.
| 72 | SWI | 2 | Software Interrupt. The next byte names a software vector. Pushes an interrupt frame and dispatches through it. Never masked. | | 72 | SWI | 2 | Software Interrupt. The next byte names a software vector. Pushes an interrupt frame and dispatches through it. Never masked. |
| 73 | RETI | 1 | Return from an interrupt. Restores everything the frame holds and carries on from where the interrupt arrived. | | 73 | RETI | 1 | Return from an interrupt. Restores everything the frame holds and carries on from where the interrupt arrived. |
| 74 | RRET | 1 | Return from a raw call. Takes back the Program Counter and nothing else. | | 74 | RRET | 1 | Return from a raw call. Takes back the Program Counter and nothing else. |
| 76 | SRET | 1 | Return from a handler that has an answer. Restores what RET restores - A, B and Data Pointers 0 through 2 - and puts the Interrupt Flag back from the frame. The saved Q and Data Pointer 3 are dropped, so what the handler left in them is what the caller receives. |
| 75 | RET | 1 | Return from subroutine. Restores A, B, and Data Pointers 0 through 2 from the Stack, then sets the Program Counter to the instruction after the CALL. Data Pointer 3 and Q are left as the subroutine leaves them. | | 75 | RET | 1 | Return from subroutine. Restores A, B, and Data Pointers 0 through 2 from the Stack, then sets the Program Counter to the instruction after the CALL. Data Pointer 3 and Q are left as the subroutine leaves them. |
### Register Operations: 13 Instructions ### Register Operations: 13 Instructions
+3
View File
@@ -1,5 +1,8 @@
quiet: 7 quiet: 7
answer: 42 answer: 42
pointer: ABC pointer: ABC
sret: 42
sretptr: ABC
sretkept: 7
Execution halted. Execution halted.
[exit 0] [exit 0]