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:
@@ -37,7 +37,7 @@ AsmShapeSelectors:
|
||||
0d0 0d0 0d0 0d1 0d1 0d1 0d2
|
||||
|
||||
AsmInstructionCount:
|
||||
0d71
|
||||
0d72
|
||||
|
||||
AsmInstructions:
|
||||
0x10 0d0 "ADD "
|
||||
@@ -65,6 +65,7 @@ AsmInstructions:
|
||||
0x73 0d0 "RETI"
|
||||
0x74 0d0 "RRET"
|
||||
0x75 0d0 "RET "
|
||||
0x76 0d0 "SRET"
|
||||
0x20 0d0 "RSTA"
|
||||
0x21 0d0 "RSTB"
|
||||
0x22 0d0 "INCA"
|
||||
|
||||
@@ -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.
|
||||
|
||||
### 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:
|
||||
|
||||
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
@@ -1011,19 +1011,15 @@ handleFileStart:
|
||||
; What a name means on the disk is about to change, so the remembered file goes.
|
||||
CALL fileForget
|
||||
CALL sbfsStreamStart
|
||||
MVQA
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
STA.2
|
||||
RETI
|
||||
SRET
|
||||
|
||||
fileStartNoDisk:
|
||||
POPA
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
INIA 0d1
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
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
|
||||
; is, counting from zero - the same way osFileBlock is told which one to fetch.
|
||||
@@ -1033,11 +1029,7 @@ handleFileWrite:
|
||||
INCD.2
|
||||
STB.2
|
||||
CALL sbfsStreamWrite
|
||||
MVQA
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
STA.2
|
||||
RETI
|
||||
SRET
|
||||
|
||||
; 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.
|
||||
@@ -1047,11 +1039,7 @@ handleFileFetch:
|
||||
INCD.2
|
||||
STB.2
|
||||
CALL sbfsStreamFetch
|
||||
MVQA
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
STA.2
|
||||
RETI
|
||||
SRET
|
||||
|
||||
; 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
|
||||
@@ -1071,11 +1059,7 @@ handleFileDone:
|
||||
STB.2
|
||||
CALL fileForget
|
||||
CALL sbfsStreamDone
|
||||
MVQA
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
STA.2
|
||||
RETI
|
||||
SRET
|
||||
|
||||
; ---- osChangeDir ----
|
||||
;
|
||||
@@ -1115,18 +1099,18 @@ changeDirTake:
|
||||
SETD.1 SbfsCwd
|
||||
CALL sbfsCopyWord
|
||||
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
RSTA
|
||||
STA.2 ; Q is zero: the machine is there now.
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; A is the answer, so Q becomes it.
|
||||
SRET
|
||||
|
||||
changeDirNo:
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
INIA 0d1
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; A is the answer, so Q becomes it.
|
||||
SRET
|
||||
|
||||
; ---- cd ----
|
||||
;
|
||||
@@ -1776,24 +1760,25 @@ handleFileRead:
|
||||
SETD.2 SbfsFileTail
|
||||
LDB.2
|
||||
|
||||
MVSD.2
|
||||
DPUP.2 0d05 ; The saved DP3, high byte first.
|
||||
STA.2
|
||||
INCD.2
|
||||
STB.2
|
||||
|
||||
MVSD.2
|
||||
DPUP.2 0d02 ; And the saved Q.
|
||||
; A two byte answer, and DP3 is where a routine hands one back. Built with the
|
||||
; Stack rather than written into the frame: SRET leaves DP3 alone, so there is
|
||||
; nothing to reach into. PSHA then PSHB puts the high byte above the low one,
|
||||
; which is the order POPD reads them in.
|
||||
PSHA
|
||||
PSHB
|
||||
POPD.3
|
||||
RSTA
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Q is zero: it is there.
|
||||
SRET
|
||||
|
||||
fileReadNo:
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
INIA 0d1
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
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
|
||||
; 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.
|
||||
CALL fileForget
|
||||
CALL sbfsSaveFile
|
||||
MVQA
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
STA.2
|
||||
RETI
|
||||
SRET
|
||||
|
||||
fileSaveNoDisk:
|
||||
POPA
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
INIA 0d1
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; A is the answer, so Q becomes it.
|
||||
SRET
|
||||
|
||||
; DP0 names it. Q is zero if it went.
|
||||
handleFileDelete:
|
||||
@@ -1840,11 +1821,7 @@ handleFileDelete:
|
||||
; What a name means on the disk is about to change, so the remembered file goes.
|
||||
CALL fileForget
|
||||
CALL sbfsDelete
|
||||
MVQA
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
STA.2
|
||||
RETI
|
||||
SRET
|
||||
|
||||
; DP0 is the name it has, DP1 the name it should have. Q is zero if it moved.
|
||||
handleFileRename:
|
||||
@@ -1854,18 +1831,14 @@ handleFileRename:
|
||||
; What a name means on the disk is about to change, so the remembered file goes.
|
||||
CALL fileForget
|
||||
CALL sbfsRename
|
||||
MVQA
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
STA.2
|
||||
RETI
|
||||
SRET
|
||||
|
||||
serviceNoDisk:
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
INIA 0d1
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; A is the answer, so Q becomes it.
|
||||
SRET
|
||||
|
||||
; ---- Reading a file that will not fit ----
|
||||
;
|
||||
@@ -1978,31 +1951,32 @@ handleFileInfo:
|
||||
INCD.2
|
||||
LDB.2
|
||||
|
||||
MVSD.2
|
||||
DPUP.2 0d05 ; The saved DP3, high byte first.
|
||||
STA.2
|
||||
INCD.2
|
||||
STB.2
|
||||
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
; A two byte answer, and DP3 is where a routine hands one back. Built with the
|
||||
; Stack rather than written into the frame: SRET leaves DP3 alone, so there is
|
||||
; nothing to reach into. PSHA then PSHB puts the high byte above the low one,
|
||||
; which is the order POPD reads them in.
|
||||
PSHA
|
||||
PSHB
|
||||
POPD.3
|
||||
RSTA
|
||||
STA.2 ; And the saved Q: it is there.
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Q is zero: it is there.
|
||||
SRET
|
||||
|
||||
fileInfoNoDisk:
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
INIA 0d1
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; A is the answer, so Q becomes it.
|
||||
SRET
|
||||
|
||||
fileInfoMissing:
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
INIA 0d2
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
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
|
||||
; 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
|
||||
|
||||
fileBlockAnswer:
|
||||
MVSD.2
|
||||
DPUP.2 0d05
|
||||
STA.2
|
||||
INCD.2
|
||||
STB.2
|
||||
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
; A two byte answer, and DP3 is where a routine hands one back. Built with the
|
||||
; Stack rather than written into the frame: SRET leaves DP3 alone, so there is
|
||||
; nothing to reach into. PSHA then PSHB puts the high byte above the low one,
|
||||
; which is the order POPD reads them in.
|
||||
PSHA
|
||||
PSHB
|
||||
POPD.3
|
||||
RSTA
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Q is zero: it is there.
|
||||
SRET
|
||||
|
||||
fileBlockNoDisk:
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
INIA 0d1
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; A is the answer, so Q becomes it.
|
||||
SRET
|
||||
|
||||
fileBlockMissing:
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
INIA 0d2
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; A is the answer, so Q becomes it.
|
||||
SRET
|
||||
|
||||
fileBlockPastEnd:
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
INIA 0d3
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; A is the answer, so Q becomes it.
|
||||
SRET
|
||||
|
||||
fileBlockFailed:
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
INIA 0d4
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
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,
|
||||
; and returns as though nothing happened.
|
||||
@@ -2291,18 +2266,18 @@ handleBootState:
|
||||
BNQ bootStateNone
|
||||
SETD.2 SbfsStateWas
|
||||
LDA.2
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; A is the answer, so Q becomes it.
|
||||
SRET
|
||||
|
||||
bootStateNone:
|
||||
; No disk, or one that would not answer. Nothing there to be unsettled about.
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
RSTA
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; A is the answer, so Q becomes it.
|
||||
SRET
|
||||
|
||||
handleBootSettle:
|
||||
SETD.2 DiskReady
|
||||
@@ -2312,18 +2287,18 @@ handleBootSettle:
|
||||
RSTA
|
||||
CALL sbfsSetBootState
|
||||
BNQ bootSettleNo
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
RSTA
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; A is the answer, so Q becomes it.
|
||||
SRET
|
||||
|
||||
bootSettleNo:
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
INIA 0d1
|
||||
STA.2
|
||||
RETI
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; A is the answer, so Q becomes it.
|
||||
SRET
|
||||
|
||||
handleExit:
|
||||
SETD.1 SystemStack
|
||||
@@ -3829,7 +3804,7 @@ ShapeLength:
|
||||
0d1 0d3 0d2 0d2 0d3 0d4 0d3
|
||||
|
||||
InstructionCount:
|
||||
0d71
|
||||
0d72
|
||||
|
||||
; ---- The instruction table ----
|
||||
;
|
||||
@@ -3862,6 +3837,7 @@ Instructions:
|
||||
0x73 0d0 "RETI"
|
||||
0x74 0d0 "RRET"
|
||||
0x75 0d0 "RET "
|
||||
0x76 0d0 "SRET"
|
||||
0x20 0d0 "RSTA"
|
||||
0x21 0d0 "RSTB"
|
||||
0x22 0d0 "INCA"
|
||||
|
||||
@@ -26,6 +26,21 @@
|
||||
; quiet: 7 a service that says nothing leaves Q as it found it
|
||||
; answer: 42 one that does, does not
|
||||
; 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
|
||||
|
||||
@@ -59,6 +74,39 @@ start:
|
||||
POPD.0
|
||||
CALL printString
|
||||
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
|
||||
|
||||
; Says nothing, so whatever the caller had in Q is still there afterwards.
|
||||
@@ -88,6 +136,31 @@ pointer:
|
||||
STA.1
|
||||
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
|
||||
|
||||
QuietText:
|
||||
@@ -96,6 +169,12 @@ AnswerText:
|
||||
"answer: "
|
||||
PointerText:
|
||||
"pointer: "
|
||||
SretText:
|
||||
"sret: "
|
||||
SretPtrText:
|
||||
"sretptr: "
|
||||
SretKeptText:
|
||||
"sretkept: "
|
||||
Letters:
|
||||
"ABC"
|
||||
|
||||
@@ -105,3 +184,6 @@ Letters:
|
||||
quiet quiet
|
||||
answer answer
|
||||
pointer pointer
|
||||
sretAnswer sretAnswer
|
||||
sretPointer sretPointer
|
||||
sretTrample sretTrample
|
||||
|
||||
Reference in New Issue
Block a user