Programs can now list and share vectors.

This commit is contained in:
Anachronaut
2026-08-17 21:39:27 -04:00
parent a842c884e8
commit 1d1a14318c
18 changed files with 537 additions and 27 deletions
+101
View File
@@ -0,0 +1,101 @@
; A loaded program that is interrupted by the console rather than asking it for anything.
;
; Until the loadable program format could carry vectors, this could not be written. A
; handler has to be an address in the vector table, and a program that is not the one the
; machine booted from had no way to say what its vectors were, so interrupts belonged to
; boot images and every loaded program had to poll. Snake polls for exactly that reason.
;
; What this shows is the whole path: the assembler writes the vectors into the file, the
; system installs them when the program is run, the console raises its line, the handler
; runs, and the system takes the vectors back out again when the program gives the machine
; back. Nothing in the waiting loop below looks at the console at all.
;
; The vector is named by port, because a device interrupts on the port it is plugged into
; and the console is on port 0x00.
#Include services.asm
#Program
#Base 0x2000
start:
CIF ; Nothing arrives until there is something to catch it.
; Set rather than trusted to be zero. Running a program a second time does not load it
; again, so its Data Segment is exactly as the last run left it - and the last thing the
; last run did was set this.
RSTA
SETD.0 Stopping
STA.0
SETD.0 Banner
CALL printString
CALL newLine
; Key mode and interrupt on input, in one write, since the two control bits are
; independent of each other.
INIA 0x03
OUTA 0x02
SIF
wait:
; This loop is the point. It never touches the console, so every character that appears
; below was put there by something that interrupted it.
SETD.3 Stopping
LDA.3
RSTB
OR
BRQ wait
CALL newLine
RSTA
OUTA 0x02 ; Line mode and no interrupts, the way it was found.
SETD.0 DoneText
CALL printString
CALL newLine
SWI osExit
; Entered because the console had something to say. Never called.
keyHandler:
INA 0x01
INIB 0x01 ; READY: is there a byte, as opposed to the end of input?
AND
BRQ keyNoByte
INA 0x00
INIB 0x71 ; q, which is how this program is stopped.
XOR
BRQ keyStop
OUTA 0x00 ; Nothing echoes in key mode, so the handler does it.
RETI
keyNoByte:
; The end of input raises the line once as well, so a program driven entirely by
; interrupts is told when nothing more is coming instead of waiting for ever.
keyStop:
SETD.3 Stopping
INIA 0x01
STA.3
RETI
#Data
#Base 0x1000
Banner:
"keys, by interrupt. q stops."
DoneText:
"the console has been handed back"
; The only thing the handler and the loop it interrupts have to say to each other.
Stopping:
0x00
#Vectors
Boot start
Device 0x00 keyHandler
#Include console.asm
+9 -7
View File
@@ -9,13 +9,15 @@
; console holds the next key until it is asked, so nothing typed between frames is lost, ; console holds the next key until it is asked, so nothing typed between frames is lost,
; and a script of moves plays back one move to a frame. ; and a script of moves plays back one move to a frame.
; ;
; WHY IT POLLS RATHER THAN INTERRUPTS. The console can raise an interrupt line when a byte ; WHY IT POLLS RATHER THAN INTERRUPTS. When this was written a loaded program could not be
; arrives, which is the better shape for a game: the loop would never look at the console ; interrupted at all: installing a handler means putting an address in the vector table,
; at all. A loaded program cannot use it. Installing a handler means putting an address in ; and the loadable format carried only code and data, so a program that was not the one the
; the vector table, and the loadable program format carries only code and data - a program ; machine booted from had no way to say what its vectors were. That is no longer true - the
; that is not the one the machine booted from has no way to say what its vectors are. So ; format carries them now, and Keys.asm is the program that shows it.
; interrupts belong to boot images for now, and this asks once a frame, which is what the ;
; machines this one is pretending to be did anyway. ; This still polls, and now by choice. Asking once a frame is what the machines this one is
; pretending to be actually did, it is the shape a game with a frame loop wants anyway, and
; having one of each in the same Apps directory is worth more than having two the same.
; ;
; THE BOARD IS A PAGE, and that is the whole trick this program turns on. Sixteen by ; THE BOARD IS A PAGE, and that is the whole trick this program turns on. Sixteen by
; sixteen is 256 squares, so a square number is a byte, and the board is aligned so that ; sixteen is 256 squares, so a square number is a byte, and the board is aligned so that
+233
View File
@@ -279,12 +279,24 @@ loadMagicLoop:
STA.1 STA.1
BNA loadMagicLoop BNA loadMagicLoop
; Version one is code and data. Version two also brings vectors, which is a thing a
; loader has to know how to do rather than a detail it can skip: a program whose handlers
; were quietly dropped would run and then go wrong somewhere with nothing to connect it
; back to here. Anything else is refused.
SETD.0 0x80 0x00 SETD.0 0x80 0x00
DPUP.0 0d04 DPUP.0 0d04
LDA.0 LDA.0
SETD.1 LoadVersion
STA.1
INIB 0d1 INIB 0d1
XOR XOR
BRQ loadVersionKnown
SETD.1 LoadVersion
LDA.1
INIB 0d2
XOR
BNQ loadWrongVersion BNQ loadWrongVersion
loadVersionKnown:
; The code. It comes from the staging area just past the sixteen byte header, and goes ; The code. It comes from the staging area just past the sixteen byte header, and goes
; wherever the header says, in Program Memory, which the instruction set cannot write ; wherever the header says, in Program Memory, which the instruction set cannot write
@@ -340,6 +352,75 @@ loadMagicLoop:
INIA 0x01 INIA 0x01
OUTA 0xE8 ; Blit. OUTA 0xE8 ; Blit.
; ---- The vectors it brought ----
;
; Kept here rather than installed. A vector points into a program, so it has no business
; being in the table while that program is only loaded and not running: run puts them in
; and exit takes them out again, so the window they are live in is exactly the run.
; Keeping our own copy is also what lets a program be run more than once, since the
; staging area it came in on is fair game for the program's own use.
;
; Where to read them from is not worked out. The data blit left the controller's source
; address on the first byte after the data, which is where they are, so it is read back.
SETD.0 VectorSource
INA 0xE1
STA.0
INCD.0
INA 0xE2
STA.0
SETD.0 0x80 0x00
DPUP.0 0d05
LDA.0
SETD.1 LoadedVectorCount
STA.1
BRA loadVectorsCopied
; More than there is room for is refused rather than half taken. Half a program's
; handlers is not a smaller version of that program.
INIB 0d17
CCF
SUB
BNC loadTooManyVectors
SETD.0 VectorSource
LDD.2.0 ; DP2 walks the entries where they are staged.
SETD.3 LoadedVectors ; DP3 walks our own copy of them.
SETD.1 LoadedVectorCount
LDA.1
SETD.1 VectorsLeft
STA.1
loadVectorCopy:
; Four bytes: where it goes, then what goes there. The two bytes for what was there
; before are left alone until something is actually put in.
LDA.2
STA.3
INCD.2
INCD.3
LDA.2
STA.3
INCD.2
INCD.3
LDA.2
STA.3
INCD.2
INCD.3
LDA.2
STA.3
INCD.2
INCD.3
INCD.3
INCD.3
SETD.1 VectorsLeft
LDA.1
DECA
STA.1
BNA loadVectorCopy
loadVectorsCopied:
; Where it starts. Written out by hand rather than through a routine, because a routine ; Where it starts. Written out by hand rather than through a routine, because a routine
; could not hand two bytes back: CALL puts A, B and the first three pointers back the ; could not hand two bytes back: CALL puts A, B and the first three pointers back the
; way it found them. ; way it found them.
@@ -370,6 +451,9 @@ loadNoDisk:
loadNothingNamed: loadNothingNamed:
SETD.0 LoadWhat SETD.0 LoadWhat
BRI loadComplain BRI loadComplain
loadTooManyVectors:
SETD.0 TooManyVectors
BRI loadComplain
loadMissing: loadMissing:
SETD.0 NoSuchFile SETD.0 NoSuchFile
BRI loadComplain BRI loadComplain
@@ -401,6 +485,8 @@ doRun:
SETD.1 SystemStack SETD.1 SystemStack
STD.0.1 STD.0.1
CALL installVectors
; The entry address is a number until BRD makes it a place. DP3 is the one to build it ; The entry address is a number until BRD makes it a place. DP3 is the one to build it
; in, because it is the pointer nothing puts back. ; in, because it is the pointer nothing puts back.
SETD.1 LoadedEntry SETD.1 LoadedEntry
@@ -413,6 +499,112 @@ runNothing:
CALL newLine CALL newLine
BRI prompt BRI prompt
; ---- Putting a program's vectors in, and taking them out again ----
;
; The vector table lives in Program Memory, which no instruction can write, so both of
; these go through the memory controller. Port 0xE9 reads a byte from the source and writes
; a byte to the destination, stepping the address on either way, so a two byte entry is two
; reads or two writes and no address arithmetic in between.
;
; What was in the slot is kept before anything replaces it, and put back afterwards, rather
; than the slot being cleared. Clearing would be wrong wherever a program has installed a
; handler over one the system was already using: the program is allowed to do that, and
; when it goes, what it covered up has to come back rather than becoming a hole.
installVectors:
SETD.0 LoadedVectorCount
LDA.0
BRA installDone
SETD.1 VectorsLeft
STA.1
SETD.3 LoadedVectors
installOne:
; DP3 walks one six byte entry: where it goes, what goes there, and room for what was
; there before. Reading and writing the same slot, so the controller is pointed at it
; from both ends at once and the address is only worked out once.
RSTA
OUTA 0xE0 ; SourceBank: Program Memory.
OUTA 0xE3 ; DestBank: the same.
LDA.3
OUTA 0xE1
OUTA 0xE4
INCD.3
LDA.3
OUTA 0xE2
OUTA 0xE5
INCD.3 ; On the handler.
; What is there now, before anything replaces it.
INA 0xE9
PSHA
INA 0xE9
PSHA
; And the handler in its place.
LDA.3
OUTA 0xE9
INCD.3
LDA.3
OUTA 0xE9
INCD.3 ; On the two bytes kept for what was there before.
; The Stack gives them back in the reverse of the order they went on, so the low byte
; arrives first and is written to the second of the two. Getting this the natural way
; round instead put the low byte where the high one goes and the high byte over the
; handler, which the first run of a program survives - the table is already written by
; then - and the second run does not.
POPA
INCD.3
STA.3
DECD.3
POPA
STA.3
INCD.3
INCD.3
SETD.1 VectorsLeft
LDA.1
DECA
STA.1
BNA installOne
installDone:
RET
removeVectors:
SETD.0 LoadedVectorCount
LDA.0
BRA removeDone
SETD.1 VectorsLeft
STA.1
SETD.3 LoadedVectors
removeOne:
RSTA
OUTA 0xE3 ; DestBank: Program Memory.
LDA.3
OUTA 0xE4
INCD.3
LDA.3
OUTA 0xE5
INCD.3
INCD.3
INCD.3 ; Past the handler, to what was underneath it.
LDA.3
OUTA 0xE9
INCD.3
LDA.3
OUTA 0xE9
INCD.3
SETD.1 VectorsLeft
LDA.1
DECA
STA.1
BNA removeOne
removeDone:
RET
; ---- The services ---- ; ---- The services ----
; ;
; These are what a loaded program is allowed to ask for. The names and their numbers come ; These are what a loaded program is allowed to ask for. The names and their numbers come
@@ -422,6 +614,19 @@ runNothing:
; A handler arrives with the caller's registers exactly as they were: an interrupt frame ; A handler arrives with the caller's registers exactly as they were: an interrupt frame
; is pushed, not cleared. So the pointer a program put in DP0 is still there to be used. ; is pushed, not cleared. So the pointer a program put in DP0 is still there to be used.
; The disk finishing, acknowledged and ignored.
;
; The system drives the disk by asking its status port and waiting, so it has no use for
; the line. But the disk raises one after every operation whether anybody wants it or not,
; and a line goes on waiting while the Interrupt Flag is down rather than being lost. The
; shell keeps the flag down, so the line from the last disk read was still standing when
; the first program to enable interrupts ran, and it arrived there - a fault, in a program
; that had never heard of the disk, blamed on the innocent instruction that let it through.
;
; Answering a line is what takes it down, so this is one instruction and that is the point.
diskDone:
RETI
handlePrintString: handlePrintString:
CALL printString CALL printString
RETI RETI
@@ -441,6 +646,11 @@ handleExit:
LDD.0.1 LDD.0.1
MVDS.0 MVDS.0
; Whatever the program put in the vector table comes out again. A vector points into the
; program that supplied it, and the program is gone, so anything left installed would aim
; an interrupt at whatever those addresses hold next.
CALL removeVectors
; The console goes back to how the shell wants it, whatever the program left it in: line ; The console goes back to how the shell wants it, whatever the program left it in: line
; mode, and not interrupting. A program that wanted either is expected to put it back ; mode, and not interrupting. A program that wanted either is expected to put it back
; itself, but one that stopped early, or forgot, would otherwise hand back a shell with ; itself, but one that stopped early, or forgot, would otherwise hand back a shell with
@@ -713,6 +923,8 @@ LoadWhat:
"load what?" "load what?"
NoSuchFile: NoSuchFile:
"no such file" "no such file"
TooManyVectors:
"that program wants more vectors than there is room for"
Unreadable: Unreadable:
"could not read it" "could not read it"
NotProgram: NotProgram:
@@ -745,6 +957,26 @@ LoadedEntry:
0x00 0x00 0x00 0x00
LoadCount: LoadCount:
0x00 0x00
LoadVersion:
0x00
; ---- The vectors a loaded program brought with it ----
;
; Six bytes each: where it goes, what goes there, and what was there before. The last two
; are filled in when the program runs and read back when it exits, so what a program covers
; up comes back rather than becoming a hole.
;
; Sixteen is a limit rather than a considered number. It is far more than anything written
; so far wants, and a program asking for more is refused at load rather than having some of
; its handlers installed and the rest dropped.
VectorSource:
0x00 0x00
VectorsLeft:
0x00
LoadedVectorCount:
0x00
LoadedVectors:
#Reserve 0d96
; Where the monitor is looking, so that a bare 'dump' can carry on from it. ; Where the monitor is looking, so that a bare 'dump' can carry on from it.
DumpBank: DumpBank:
@@ -781,3 +1013,4 @@ CommandLine:
osPrintString handlePrintString osPrintString handlePrintString
osReadLine handleReadLine osReadLine handleReadLine
osExit handleExit osExit handleExit
Device 0x20 diskDone
+64 -8
View File
@@ -57,7 +57,10 @@ void addLabel(char *labelName, uint16_t address, int type, const char *fileName,
if (debugSecondPass) printf("Added label %s with address %04X\n", labelName, labelArray[labelCount].address); if (debugSecondPass) printf("Added label %s with address %04X\n", labelName, labelArray[labelCount].address);
labelCount++; labelCount++;
} else { } else {
fprintf(stderr, "Error: Too many labels defined.\n"); fprintf(stderr, RED "Error: Too many labels. This program and everything it\n"
" includes may define %d between them, and \"%s\" is one too many.\n" RESET,
MAX_LABELS, labelName);
printf("File: %s at line %d.\n", fileName, lineNumber);
exit(1); exit(1);
} }
} }
@@ -564,16 +567,43 @@ static void writeLoadable(const char *outputFileName, uint8_t *Program, int prog
fprintf(stderr, RED "Error: Could not open file \"%s\" for writing.\n" RESET, outputFileName); fprintf(stderr, RED "Error: Could not open file \"%s\" for writing.\n" RESET, outputFileName);
exit(1); exit(1);
} }
// Boot says where a program begins, which is what the entry field holds, so in a
// loadable program that line fills it in. It is NOT installed as vector 0: that entry
// is where the whole machine starts, and a program being loaded into a running system
// has no business saying anything about that.
//
// Without a Boot line the entry is the first byte of the code, which is where a
// program begins if it does not say otherwise.
uint16_t entry = codeBase;
int installed = 0;
for (int i = 0; i < vectorArrayCount; i++) {
if (vectorArray[i].declaredOnly) {
continue;
}
if (vectorArray[i].base == SOFTWARE_VECTOR_BASE
&& vectorArray[i].index == VECTOR_BOOT) {
entry = vectorArray[i].handler;
continue;
}
installed++;
}
if (installed > 255) {
fprintf(stderr, RED "Error: A loadable program may bring at most 255 vectors.\n" RESET);
exit(1);
}
uint8_t header[SBEX_HEADER_BYTES]; uint8_t header[SBEX_HEADER_BYTES];
memset(header, 0, sizeof(header)); memset(header, 0, sizeof(header));
memcpy(header, SBEX_MAGIC, SBEX_MAGIC_BYTES); memcpy(header, SBEX_MAGIC, SBEX_MAGIC_BYTES);
header[SBEX_VERSION_AT] = SBEX_VERSION; // A program that brings vectors needs something of its loader that a version one
// loader does not know how to give, so it says so, and an older one refuses it rather
// than running it without them.
header[SBEX_VERSION_AT] = installed > 0 ? SBEX_VERSION_VECTORS : SBEX_VERSION;
header[SBEX_VECTORS_AT] = (uint8_t)installed;
header[SBEX_CODE_AT] = (uint8_t)(codeBase >> 8); header[SBEX_CODE_AT] = (uint8_t)(codeBase >> 8);
header[SBEX_CODE_AT + 1] = (uint8_t)(codeBase & 0xFF); header[SBEX_CODE_AT + 1] = (uint8_t)(codeBase & 0xFF);
// Where it starts is where it begins. A program that wants otherwise puts a branch header[SBEX_ENTRY_AT] = (uint8_t)(entry >> 8);
// at its first instruction, which costs three bytes and needs no format for it. header[SBEX_ENTRY_AT + 1] = (uint8_t)(entry & 0xFF);
header[SBEX_ENTRY_AT] = (uint8_t)(codeBase >> 8);
header[SBEX_ENTRY_AT + 1] = (uint8_t)(codeBase & 0xFF);
header[SBEX_CODE_LEN_AT] = (uint8_t)(codeLength >> 8); header[SBEX_CODE_LEN_AT] = (uint8_t)(codeLength >> 8);
header[SBEX_CODE_LEN_AT + 1] = (uint8_t)(codeLength & 0xFF); header[SBEX_CODE_LEN_AT + 1] = (uint8_t)(codeLength & 0xFF);
header[SBEX_DATA_AT] = (uint8_t)(dataBase >> 8); header[SBEX_DATA_AT] = (uint8_t)(dataBase >> 8);
@@ -583,10 +613,36 @@ static void writeLoadable(const char *outputFileName, uint8_t *Program, int prog
fwrite(header, 1, sizeof(header), outputFile); fwrite(header, 1, sizeof(header), outputFile);
fwrite(Program + codeBase, 1, (size_t)codeLength, outputFile); fwrite(Program + codeBase, 1, (size_t)codeLength, outputFile);
fwrite(Data + dataBase, 1, (size_t)dataLength, outputFile); fwrite(Data + dataBase, 1, (size_t)dataLength, outputFile);
// The vectors, last, so that everything before them sits where a version one loader
// already expects to find it.
for (int i = 0; i < vectorArrayCount; i++) {
if (vectorArray[i].declaredOnly) {
continue;
}
if (vectorArray[i].base == SOFTWARE_VECTOR_BASE
&& vectorArray[i].index == VECTOR_BOOT) {
continue;
}
uint16_t slot = vectorArray[i].base + (uint16_t)vectorArray[i].index * VECTOR_ENTRY_BYTES;
fputc((slot >> 8) & 0xFF, outputFile);
fputc(slot & 0xFF, outputFile);
fputc((vectorArray[i].handler >> 8) & 0xFF, outputFile);
fputc(vectorArray[i].handler & 0xFF, outputFile);
}
fclose(outputFile); fclose(outputFile);
printf("Successfully wrote SplitBit loadable program to \"%s\".\n", outputFileName); printf("Successfully wrote SplitBit loadable program to \"%s\".\n", outputFileName);
printf(GREEN " Code: %d bytes at 0x%04X.\n Data: %d bytes at 0x%04X.\n Total size: %d bytes.\n" RESET, printf(GREEN " Code: %d bytes at 0x%04X.\n Data: %d bytes at 0x%04X.\n" RESET,
codeLength, codeBase, dataLength, dataBase, SBEX_HEADER_BYTES + codeLength + dataLength); codeLength, codeBase, dataLength, dataBase);
if (entry != codeBase) {
printf(GREEN " Starts at 0x%04X.\n" RESET, entry);
}
if (installed > 0) {
printf(GREEN " Vectors: %d. Version 2, so a loader that cannot install them will say so.\n" RESET,
installed);
}
printf(GREEN " Total size: %d bytes.\n" RESET,
SBEX_HEADER_BYTES + codeLength + dataLength + installed * SBEX_VECTOR_ENTRY_BYTES);
} }
void writeOutputFile(const char *outputFileName, uint8_t *Program, int programCount, uint8_t *Data, int dataCount) { void writeOutputFile(const char *outputFileName, uint8_t *Program, int programCount, uint8_t *Data, int dataCount) {
+5 -1
View File
@@ -11,7 +11,11 @@
#include <ctype.h> #include <ctype.h>
#include "Assm-util.h" #include "Assm-util.h"
#define MAX_LABELS 256 // Every label in a program and in everything it includes shares one table, because they
// share one namespace: a name may only be defined once across the whole assembly. So this
// is not the size of one file but the size of a program and its libraries together, and
// CosmOS with its four libraries went past 256 while still being a small system.
#define MAX_LABELS 1024
#define MAX_VECTORS 256 #define MAX_VECTORS 256
typedef struct { typedef struct {
+35 -1
View File
@@ -146,7 +146,41 @@ Message:
Every label inside is then already the address it will have once the program is loaded, so a branch or a SETD written in it points at the right place. Giving either segment a base makes the whole program a loadable one, and the assembler writes it out with a header saying where its two pieces go, followed by the pieces themselves. The space below each base is not in the file: the header says where the bytes belong and the loader puts them there. Every label inside is then already the address it will have once the program is loaded, so a branch or a SETD written in it points at the right place. Giving either segment a base makes the whole program a loadable one, and the assembler writes it out with a header saying where its two pieces go, followed by the pieces themselves. The space below each base is not in the file: the header says where the bytes belong and the loader puts them there.
A program starts at its code base. One that wants to begin somewhere else puts a branch at its first instruction, which costs three bytes and needs nothing from the format. A program starts at its code base unless it says otherwise, and `Boot` in its Vector Segment is how it says otherwise:
```
#Program
#Base 0x2000
helpers:
...
start:
...
#Vectors
Boot start ; Which is where this program begins.
```
That fills in the entry point in the header. It is not installed as vector 0 of the machine, which is where everything begins at power on and no business of a program being loaded into a system that is already running.
### Vectors In A Loadable Program:
Everything else in a Vector Segment is carried in the file and installed by whatever loads the program. That is what lets a loaded program be interrupted: a handler is an address in the vector table, and until the format could carry one, a program that was not the one the machine booted from had no way to ask for it.
```
#Vectors
Boot start
Device 0x00 keyHandler ; The console, which now interrupts this program.
```
A program carrying vectors is written out as version two of the format, and the assembler says so:
```
Vectors: 1. Version 2, so a loader that cannot install them will say so.
```
A program carrying none stays version one and loads anywhere. The difference matters because a loader that does not understand version two refuses the file rather than running a program with its handlers missing, which would work until the moment it was supposed to be interrupted and then fail somewhere with nothing pointing back at the cause.
Whoever loads the program is expected to take the vectors out again when it finishes. See the Programming Manual.
The address a program is assembled for has to be the address it is loaded at. Nothing checks that, and nothing can fix it: a program put anywhere else has every branch and every SETD inside it pointing somewhere wrong. The address a program is assembled for has to be the address it is loaded at. Nothing checks that, and nothing can fix it: a program put anywhere else has every branch and every SETD inside it pointing somewhere wrong.
+15 -3
View File
@@ -526,19 +526,31 @@ A program that was not the one the machine booted from carries sixteen bytes in
| Offset | Size | Holds | | Offset | Size | Holds |
| --- | --- | --- | | --- | --- | --- |
| 0 | 4 | SBEX | | 0 | 4 | SBEX |
| 4 | 1 | Version. One. | | 4 | 1 | Version. One, or two if it brings vectors. |
| 5 | 1 | Reserved. | | 5 | 1 | How many vectors follow the data. Zero in a version one file. |
| 6 | 2 | Where the code goes in Program Memory. | | 6 | 2 | Where the code goes in Program Memory. |
| 8 | 2 | Where to start running. | | 8 | 2 | Where to start running. |
| 10 | 2 | How many bytes of code there are. | | 10 | 2 | How many bytes of code there are. |
| 12 | 2 | Where the data goes in Data Memory. | | 12 | 2 | Where the data goes in Data Memory. |
| 14 | 2 | How many bytes of data there are. | | 14 | 2 | How many bytes of data there are. |
| 16 | | The code, and then the data. | | 16 | | The code, then the data, then the vectors. |
Programs/loader.asm reads one off a disk, puts the two pieces where the header asks, and jumps to the entry with BRD. Every part of that already existed: the filesystem finds the file, the memory controller writes Program Memory, and BRD turns an address worked out at run time into somewhere to go. The header is the only new thing. Programs/CosmOS does the same as one of its commands, and then takes the machine back afterwards, which the standalone loader has no way to do. Programs/loader.asm reads one off a disk, puts the two pieces where the header asks, and jumps to the entry with BRD. Every part of that already existed: the filesystem finds the file, the memory controller writes Program Memory, and BRD turns an address worked out at run time into somewhere to go. The header is the only new thing. Programs/CosmOS does the same as one of its commands, and then takes the machine back afterwards, which the standalone loader has no way to do.
The magic matters for the same reason it does everywhere else on this machine. Without it, loading a text file would put nonsense into Program Memory and then jump into it. The magic matters for the same reason it does everywhere else on this machine. Without it, loading a text file would put nonsense into Program Memory and then jump into it.
### Bringing Vectors:
A program that only wants to be run needs nothing here and says version one. A program that wants a handler installed needs something of whoever loads it, and says version two.
Each vector is four bytes: the address of the slot in the vector table, then the address to put in it, both most significant byte first. Naming the slot rather than the vector number means the loader does no arithmetic and does not have to know where either vector table begins, and one entry can be a software or a hardware vector without saying which it is.
**A version two file is refused by a loader that cannot install them.** That is the point of the version rather than an inconvenience of it. A program whose handlers were quietly dropped would load, run, and then go wrong somewhere with nothing to connect the failure back to loading — a game waiting for keys that no longer arrive. Failing once, at load, with a reason, is worth more than running.
**Whoever installs them takes them back.** A vector points into the program that supplied it, so one left in the table after that program has gone aims an interrupt at whatever occupies those addresses next. CosmOS keeps its own copy of what a program brought, puts them in when the program is run and not when it is loaded, and restores what was underneath them when the program gives the machine back. Restoring, rather than clearing: a program is allowed to install a handler over one the system was already using, and when it goes, what it covered up has to come back rather than become a hole.
`Boot` in a loadable program fills in the entry field, since that is what it means, and is not installed as vector 0 — where the machine starts is not a loaded program's business. Without one, a program begins at the first byte of its code.
### Where A Program Says It Lives: ### Where A Program Says It Lives:
**Nothing relocates anything.** A program is put exactly where its header asks, and that has to be the address it was assembled for, or every branch and every SETD inside it points somewhere wrong. **Nothing relocates anything.** A program is put exactly where its header asks, and that has to be the address it was assembled for, or every branch and every SETD inside it points somewhere wrong.
+29
View File
@@ -131,6 +131,35 @@ else:
problems.append("the Assembler Manual does not say the automatic vectors start" problems.append("the Assembler Manual does not say the automatic vectors start"
" at %d" % autoFrom) " at %d" % autoFrom)
# ---- The loadable header table matches the offsets the assembler writes ----
#
# The Programming Manual prints the header field by field, which is the description two
# implementations work from. sbex.h is where the offsets actually are, so a field moved
# there and not here would leave the manual describing a format nobody writes.
sbex = read("Source/Assembler/sbex.h")
offsets = {name: int(value)
for name, value in re.findall(r'^#define (SBEX_[A-Z_]+_AT)\s+(\d+)$', sbex, re.M)}
if "## Loading A Program From A Disk:" not in pm:
problems.append("the Programming Manual has lost its loadable program section")
else:
loading = pm.split("## Loading A Program From A Disk:")[1].split("\n## ")[0]
listed = [int(m) for m in re.findall(r'^\| (\d+) \| \d* \|', loading, re.M)]
for name, offset in sorted(offsets.items(), key=lambda pair: pair[1]):
if offset not in listed:
problems.append("%s is at offset %d and the header table has no row for it"
% (name, offset))
# Spelled as a word, the way these manuals write small numbers in prose.
asWord = {1: "one", 2: "two", 3: "three", 4: "four", 5: "five"}
for version in ("SBEX_VERSION", "SBEX_VERSION_VECTORS"):
number = re.search(r'^#define %s\s+(\d+)$' % version, sbex, re.M)
if not number:
problems.append("%s is gone from sbex.h" % version)
continue
said = asWord.get(int(number.group(1)))
if said is None or said not in loading.lower():
problems.append("the loadable program section does not mention version %s (%s)"
% (number.group(1), said))
# ---- Every console status bit is described ---- # ---- Every console status bit is described ----
# #
# The status port is read by writing a mask and testing it, so a program can only use a bit # The status port is read by writing a mask and testing it, so a program can only use a bit
+1 -1
View File
@@ -19,5 +19,5 @@ CosmOS
0020 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................ 0020 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................
0030 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................ 0030 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................
> halted > halted
Execution halted after 23498 cycles. Execution halted after 23512 cycles.
[exit 0] [exit 0]
+1 -1
View File
@@ -5,5 +5,5 @@ finished
> Hello, World! > Hello, World!
finished finished
> halted > halted
Execution halted after 2668 cycles. Execution halted after 2702 cycles.
[exit 0] [exit 0]
+17
View File
@@ -0,0 +1,17 @@
CosmOS
> loaded, starting at 2000
> keys, by interrupt. q stops.
ab
the console has been handed back
finished
> > keys, by interrupt. q stops.
cd
the console has been handed back
finished
> > FE00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
FE10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
FE20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
FE30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
> halted
Execution halted after 10055 cycles.
[exit 0]
+1 -1
View File
@@ -869,5 +869,5 @@ the board has settled
finished finished
> >
halted halted
Execution halted after 11671206 cycles. Execution halted after 11671230 cycles.
[exit 0] [exit 0]
+1 -1
View File
@@ -21,5 +21,5 @@ stopped
finished finished
> > > >
halted halted
Execution halted after 26219 cycles. Execution halted after 26243 cycles.
[exit 0] [exit 0]
+3 -2
View File
@@ -4,8 +4,9 @@ CosmOS
hello.sbx 52 hello.sbx 52
Life.sbx 1411 Life.sbx 1411
Snake.sbx 2175 Snake.sbx 2175
Keys.sbx 663
notes.txt 21 notes.txt 21
5 files 6 files
> load what? > load what?
> no such file > no such file
> not a program > not a program
@@ -17,5 +18,5 @@ finished
what should I call you? hello, Claude. that is all I do. what should I call you? hello, Claude. that is all I do.
finished finished
> halted > halted
Execution halted after 12778 cycles. Execution halted after 14083 cycles.
[exit 0] [exit 0]
+1 -1
View File
@@ -289,5 +289,5 @@ you ran into something
finished finished
> >
halted halted
Execution halted after 1910669 cycles. Execution halted after 1910693 cycles.
[exit 0] [exit 0]
+7
View File
@@ -0,0 +1,7 @@
load Keys.sbx
run
abq
run
cdq
dump program fe00
exit
+6
View File
@@ -80,6 +80,12 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Snake.asm" -o "$WORK/Snake.sbx" >/dev/null "$ROOT/Programs/CosmOS/Apps/Snake.asm" -o "$WORK/Snake.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Snake.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Snake.sbx" >/dev/null
# Keys.sbx brings a vector of its own, which is what the version two format exists for. It
# is the only program here the system has to install anything for, so it is what says the
# whole path works: written into the file, installed at run, taken back out at exit.
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Keys.asm" -o "$WORK/Keys.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Keys.sbx" >/dev/null
printf 'this is not a program' > notes.txt printf 'this is not a program' > notes.txt
"$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null "$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null
+8
View File
@@ -231,12 +231,20 @@ cosmosLifeKey | CosmOS/Source/cosmos.asm | run | cosmosLif
# twelve moves at once: six turn it down the board and six take it left onto the food. So # twelve moves at once: six turn it down the board and six take it left onto the food. So
# what is recorded is a turn, a meal, a longer snake, and then a wall. # what is recorded is a turn, a meal, a longer snake, and then a wall.
cosmosSnake | CosmOS/Source/cosmos.asm | run | cosmosSnake.in | - | disks/cosmos.img cosmosSnake | CosmOS/Source/cosmos.asm | run | cosmosSnake.in | - | disks/cosmos.img
# A loaded program that brings a vector of its own, which nothing could do until the
# loadable format could carry one. It is run TWICE on purpose: installing has to be exactly
# undone by removing, and the way that fails is asymmetrically, so the second run is the one
# that catches it. The dump afterwards is the proof that the table was put back - the
# console's slot at FE00 is zero again, and CosmOS's own disk handler further along is
# untouched by a program having installed over the top of it.
cosmosKeys | CosmOS/Source/cosmos.asm | run | cosmosKeys.in | - | disks/cosmos.img
# The programs CosmOS loads, checked on their own so that a failure here reads as "the app # The programs CosmOS loads, checked on their own so that a failure here reads as "the app
# does not assemble" rather than as a broken disk image. # does not assemble" rather than as a broken disk image.
app-greet | CosmOS/Apps/greet.asm | assemble | - | - app-greet | CosmOS/Apps/greet.asm | assemble | - | -
app-hello | CosmOS/Apps/hello.asm | assemble | - | - app-hello | CosmOS/Apps/hello.asm | assemble | - | -
app-Life | CosmOS/Apps/Life.asm | assemble | - | - app-Life | CosmOS/Apps/Life.asm | assemble | - | -
app-Snake | CosmOS/Apps/Snake.asm | assemble | - | - app-Snake | CosmOS/Apps/Snake.asm | assemble | - | -
app-Keys | CosmOS/Apps/Keys.asm | assemble | - | -
# ---- Programs driven by console input ---- # ---- Programs driven by console input ----
inputTest | inputTest.asm | run | inputTest.in | - inputTest | inputTest.asm | run | inputTest.in | -