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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user