Programs can now pin specific routines to specific vectors in SplitBit assembly. Added snake game.
This commit is contained in:
@@ -68,8 +68,25 @@
|
||||
#define VECTOR_BANK_FAULT 4
|
||||
// Vectors 5 to 15 are held back for faults that do not exist yet, so that each cause
|
||||
// can have an entry of its own rather than sharing one and needing a cause register to
|
||||
// tell them apart. Everything from 16 up belongs to programs.
|
||||
#define VECTOR_FIRST_FREE 16
|
||||
// tell them apart. Everything from 16 up belongs to programs, in two halves.
|
||||
//
|
||||
// PINNED, 16 to 63. Numbers in here are never handed out by the assembler; a program that
|
||||
// wants one writes it down. This is where anything TWO SEPARATELY ASSEMBLED PROGRAMS have
|
||||
// to agree about lives - the system's services, and any library that stays resident and is
|
||||
// called through the vector table.
|
||||
//
|
||||
// AUTOMATIC, 64 up. Numbered by the assembler in the order they are written. These belong
|
||||
// to one program and nothing outside it can name them, so what number they get does not
|
||||
// matter as long as it is not somebody else's.
|
||||
//
|
||||
// THE SPLIT IS THE POINT. With one range for both, what number a program's own traps got
|
||||
// depended on what it had included: adding a line that included the system's service names
|
||||
// silently pushed every trap after it along by three. Worse, a program that did NOT include
|
||||
// them was given 16, which is osPrintString, so installing its own handler would have
|
||||
// replaced a system service by accident. Numbers that must agree are now written out loud,
|
||||
// and numbers that need not agree are drawn from somewhere nobody else is looking.
|
||||
#define VECTOR_FIRST_PINNED 16
|
||||
#define VECTOR_FIRST_AUTO 64
|
||||
|
||||
// The first address the vector table occupies, and so the first address that program
|
||||
// text may not use.
|
||||
|
||||
@@ -239,11 +239,22 @@ static uint16_t resolveHandler(intermediateElement *intermediateArray, int at, c
|
||||
return (uint16_t)address;
|
||||
}
|
||||
|
||||
// Is anything already using this software vector number?
|
||||
static int vectorNumberTaken(uint8_t index) {
|
||||
for (int i = 0; i < vectorArrayCount; i++) {
|
||||
if (vectorArray[i].base == SOFTWARE_VECTOR_BASE && vectorArray[i].index == index) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void populateVectorTable(intermediateElement *intermediateArray, int arraySize) {
|
||||
// Software vectors a program names for itself are numbered in the order they are
|
||||
// written, starting above the block held back for faults. A programmer never types
|
||||
// one, so there is no way to land on a reserved vector by accident.
|
||||
int nextFreeVector = VECTOR_FIRST_FREE;
|
||||
// Software vectors a program names for itself and does not pin are numbered in the
|
||||
// order they are written, from the automatic range. A programmer never types one of
|
||||
// these, so there is no way to land on a reserved vector or on somebody else's
|
||||
// agreed number by accident.
|
||||
int nextFreeVector = VECTOR_FIRST_AUTO;
|
||||
|
||||
int i = nextVectorToken(intermediateArray, arraySize, 0);
|
||||
while (i >= 0) {
|
||||
@@ -264,13 +275,31 @@ void populateVectorTable(intermediateElement *intermediateArray, int arraySize)
|
||||
continue;
|
||||
}
|
||||
|
||||
int handlerToken = nextVectorToken(intermediateArray, arraySize, i + 1);
|
||||
// A number on the same line, between the name and any handler, PINS the vector.
|
||||
// Nothing else can appear there: a label may not begin with a digit, so a value in
|
||||
// this position is a number and can be nothing else.
|
||||
int after = nextVectorToken(intermediateArray, arraySize, i + 1);
|
||||
int pinned = -1;
|
||||
if (sameLine(intermediateArray, i, after) && intermediateArray[after].type == VALUE) {
|
||||
pinned = intermediateArray[after].byteValue;
|
||||
after = nextVectorToken(intermediateArray, arraySize, after + 1);
|
||||
}
|
||||
int handlerToken = after;
|
||||
int hasHandler = sameLine(intermediateArray, i, handlerToken);
|
||||
int already = findVector(token);
|
||||
|
||||
if (already >= 0) {
|
||||
// Met before. A handler now is somebody implementing what was declared
|
||||
// earlier, which is how one shared file can serve both sides.
|
||||
//
|
||||
// A number here has to be the one it was declared with. Saying it again is
|
||||
// allowed, because an implementer repeating what it is implementing is
|
||||
// harmless; saying a different one means the two sides disagree about which
|
||||
// vector this is, which is the whole thing pinning exists to prevent.
|
||||
if (pinned >= 0 && pinned != vectorArray[already].index) {
|
||||
vectorError("That vector was already given a different number.",
|
||||
&intermediateArray[i]);
|
||||
}
|
||||
if (!hasHandler) {
|
||||
vectorError("That vector is declared more than once.", &intermediateArray[i]);
|
||||
}
|
||||
@@ -292,7 +321,29 @@ void populateVectorTable(intermediateElement *intermediateArray, int arraySize)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!reserved) {
|
||||
if (reserved && pinned >= 0) {
|
||||
// Boot, SoftReset and BadOpcode are where the machine looks, not where a
|
||||
// program says to look, so their numbers are not anybody's to choose.
|
||||
vectorError("That vector's number is fixed by the machine and cannot be given.",
|
||||
&intermediateArray[i]);
|
||||
}
|
||||
if (!reserved && pinned >= 0) {
|
||||
if (pinned < VECTOR_FIRST_PINNED || pinned >= VECTOR_FIRST_AUTO) {
|
||||
fprintf(stderr, RED "Error: %d is not a vector number that can be given.\n"
|
||||
" Numbers below %d belong to the machine, and %d and up are\n"
|
||||
" handed out by the assembler. A vector two programs have to\n"
|
||||
" agree about takes one from %d to %d.\n" RESET,
|
||||
pinned, VECTOR_FIRST_PINNED, VECTOR_FIRST_AUTO,
|
||||
VECTOR_FIRST_PINNED, VECTOR_FIRST_AUTO - 1);
|
||||
printf("File: %s at line %d.\n",
|
||||
intermediateArray[i].fileName, intermediateArray[i].lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
if (vectorNumberTaken((uint8_t)pinned)) {
|
||||
vectorError("Another vector already has that number.", &intermediateArray[i]);
|
||||
}
|
||||
index = (uint8_t)pinned;
|
||||
} else if (!reserved) {
|
||||
if (nextFreeVector > 255) {
|
||||
vectorError("There are no software vectors left to give this one.", &intermediateArray[i]);
|
||||
}
|
||||
|
||||
+92
-19
@@ -28,13 +28,36 @@ static int consoleEnded = 0;
|
||||
static int consolePushback = -1; // A byte already taken from the host, or -1.
|
||||
static int consoleInterrupts = 0; // Whether an arriving byte puts the line up.
|
||||
static struct termios consoleSavedTerminal;
|
||||
static int consoleTerminalSaved = 0;
|
||||
static int consoleTerminalSaved = 0; // There is a copy of how the terminal was found.
|
||||
static int consoleTerminalRaw = 0; // The terminal is currently in this machine's mode.
|
||||
static int consoleGuardsInstalled = 0; // The handlers below are in place.
|
||||
|
||||
// Hands the terminal back exactly as it was found, WITHOUT forgetting what the program
|
||||
// asked for. Separate from consoleRestore because the two are wanted in different places:
|
||||
// a machine that is stopping wants both, and a machine that is being suspended wants only
|
||||
// this, since it is going to carry on wanting keys when it is resumed.
|
||||
static void consoleReleaseTerminal(void) {
|
||||
if (consoleTerminalRaw) {
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &consoleSavedTerminal);
|
||||
consoleTerminalRaw = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void consoleTakeTerminal(void) {
|
||||
if (consoleTerminalRaw || !consoleTerminalSaved) {
|
||||
return;
|
||||
}
|
||||
struct termios raw = consoleSavedTerminal;
|
||||
raw.c_lflag &= (tcflag_t)~(ICANON | ECHO);
|
||||
raw.c_cc[VMIN] = 1;
|
||||
raw.c_cc[VTIME] = 0;
|
||||
if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == 0) {
|
||||
consoleTerminalRaw = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void consoleRestore(void) {
|
||||
if (consoleTerminalSaved) {
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &consoleSavedTerminal);
|
||||
consoleTerminalSaved = 0;
|
||||
}
|
||||
consoleReleaseTerminal();
|
||||
consoleKeyMode = 0;
|
||||
// Whatever the console was in the middle of asking for is withdrawn along with the
|
||||
// mode. A line left standing here would be answered by whatever ran next, which had
|
||||
@@ -43,14 +66,72 @@ void consoleRestore(void) {
|
||||
clearInterrupt(PORT_CONSOLE);
|
||||
}
|
||||
|
||||
// Restores the terminal and then dies the way it would have died anyway, so that the
|
||||
// shell sees the signal it was expecting rather than a machine that exited quietly.
|
||||
static void consoleSignalHandler(int signalNumber) {
|
||||
consoleRestore();
|
||||
// ---- Giving the terminal back whatever happens ----
|
||||
//
|
||||
// A machine that stops in key mode and does not undo it leaves the shell that started it
|
||||
// with no echo and no line editing, which is a far worse failure than anything the program
|
||||
// was doing, and one the user has no obvious way to connect to this program.
|
||||
//
|
||||
// atexit covers stopping on purpose and nothing else. It does NOT run when a process is
|
||||
// killed by a signal, so every way of dying that matters has to be caught and undone by
|
||||
// hand. The list below is every signal whose default action ends the process and which can
|
||||
// be caught at all - SIGKILL and SIGSTOP cannot, and nothing can be done about those.
|
||||
//
|
||||
// SIGHUP is on the list for a specific reason, learned the hard way: it is what arrives
|
||||
// when the terminal or the session that started this machine goes away, which is exactly
|
||||
// what happens when whatever launched it crashes. Handling INT and TERM and stopping there
|
||||
// covers the polite endings and misses the one that actually leaves a broken terminal
|
||||
// behind.
|
||||
|
||||
// Puts the terminal back and then dies the way it would have died anyway, so that whatever
|
||||
// is waiting sees the signal it expected rather than a machine that exited quietly.
|
||||
static void consoleFatalSignal(int signalNumber) {
|
||||
consoleReleaseTerminal();
|
||||
signal(signalNumber, SIG_DFL);
|
||||
raise(signalNumber);
|
||||
}
|
||||
|
||||
static void consoleContinueSignal(int signalNumber);
|
||||
|
||||
// Suspending is not dying, so the terminal goes back but the mode is remembered. Whoever
|
||||
// gets the terminal next is entitled to find it as they left it, and this machine is
|
||||
// entitled to have its keys again when it is resumed.
|
||||
static void consoleStopSignal(int signalNumber) {
|
||||
consoleReleaseTerminal();
|
||||
signal(SIGCONT, consoleContinueSignal);
|
||||
signal(signalNumber, SIG_DFL);
|
||||
raise(signalNumber);
|
||||
}
|
||||
|
||||
static void consoleContinueSignal(int signalNumber) {
|
||||
(void)signalNumber;
|
||||
signal(SIGTSTP, consoleStopSignal);
|
||||
signal(SIGCONT, consoleContinueSignal);
|
||||
if (consoleKeyMode) {
|
||||
consoleTakeTerminal();
|
||||
}
|
||||
}
|
||||
|
||||
static void consoleInstallGuards(void) {
|
||||
if (consoleGuardsInstalled) {
|
||||
return;
|
||||
}
|
||||
consoleGuardsInstalled = 1;
|
||||
// Installed on the first use of key mode rather than at startup, so a run that never
|
||||
// asks for it installs nothing at all.
|
||||
atexit(consoleRestore);
|
||||
|
||||
static const int fatal[] = {
|
||||
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE,
|
||||
SIGBUS, SIGSEGV, SIGPIPE, SIGALRM, SIGTERM
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(fatal) / sizeof(fatal[0]); i++) {
|
||||
signal(fatal[i], consoleFatalSignal);
|
||||
}
|
||||
signal(SIGTSTP, consoleStopSignal);
|
||||
signal(SIGCONT, consoleContinueSignal);
|
||||
}
|
||||
|
||||
static void consoleSetMode(int wantKeys) {
|
||||
if (wantKeys == consoleKeyMode) {
|
||||
return;
|
||||
@@ -71,17 +152,9 @@ static void consoleSetMode(int wantKeys) {
|
||||
return;
|
||||
}
|
||||
consoleTerminalSaved = 1;
|
||||
// Registered on the first use rather than at startup, so a run that never asks
|
||||
// for key mode installs nothing at all.
|
||||
atexit(consoleRestore);
|
||||
signal(SIGINT, consoleSignalHandler);
|
||||
signal(SIGTERM, consoleSignalHandler);
|
||||
}
|
||||
struct termios raw = consoleSavedTerminal;
|
||||
raw.c_lflag &= (tcflag_t)~(ICANON | ECHO);
|
||||
raw.c_cc[VMIN] = 1;
|
||||
raw.c_cc[VTIME] = 0;
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
|
||||
consoleInstallGuards();
|
||||
consoleTakeTerminal();
|
||||
}
|
||||
|
||||
// Puts the line up if the console has something to say and has been asked to say it.
|
||||
|
||||
@@ -95,10 +95,13 @@
|
||||
// ask the console to be, it can also ask the console what it currently is.
|
||||
#define CONSOLE_STATUS_INTERRUPT 0x08
|
||||
|
||||
// Puts the terminal back the way it was found. Registered with atexit and called from the
|
||||
// signal handlers, because a machine that stops in key mode and does not undo it leaves
|
||||
// the shell that started it unusable, which is a far worse failure than anything the
|
||||
// program was doing.
|
||||
// Puts the terminal back the way it was found. Registered with atexit and called from a
|
||||
// handler for every signal that can end this process and be caught, because a machine that
|
||||
// stops in key mode and does not undo it leaves the shell that started it unusable, which
|
||||
// is a far worse failure than anything the program was doing. atexit alone is not enough:
|
||||
// it does not run when a process is killed, and the ending that matters most is SIGHUP,
|
||||
// which is what arrives when whatever launched this machine dies and takes the terminal
|
||||
// with it.
|
||||
void consoleRestore(void);
|
||||
|
||||
// One byte from the console, waiting if it has to. Everything that reads standard input
|
||||
|
||||
Reference in New Issue
Block a user