CosmOS pre-alpha and launchable application versions of old programs.

This commit is contained in:
Anachronaut
2026-08-17 15:31:49 -04:00
parent eff6902bcf
commit 91c9d49d1b
66 changed files with 5612 additions and 160 deletions
+47
View File
@@ -119,6 +119,45 @@ void writeDependencyFile(const char *dependencyPath, const char *outputPath) {
fclose(file);
}
// A program that bases one segment and not the other is a mistake the assembler is the
// last place to catch. Nothing relocates, so the unbased half keeps the addresses it was
// given, which are addresses from zero up, and the loader puts it there: on top of
// whatever the system keeps at the bottom of memory. It does not fail at load time and it
// does not fail at the jump. It fails later, somewhere else, as corruption.
//
// Only a segment with something in it can land on anything, so an empty one says nothing.
// A base of zero that was actually asked for is left alone, which is how a program says
// it meant it.
void checkSegmentBases(const char *fileName) {
if (!programIsLoadable()) {
return; // A boot image. Both segments begin at zero because that is where they go.
}
const char *segmentName[3];
segmentName[PROGRAM] = "Program";
segmentName[DATA] = "Data";
int segmentEnd[3];
segmentEnd[PROGRAM] = programLength;
segmentEnd[DATA] = dataLength;
const int segments[2] = { PROGRAM, DATA };
for (int i = 0; i < 2; i++) {
int mine = segments[i], other = segments[1 - i];
int content = segmentEnd[mine] - segmentBase(mine);
if (segmentBaseWasGiven(mine) || content <= 0 || !segmentBaseWasGiven(other)) {
continue;
}
fprintf(stderr, RED "Error: The %s Segment is based at 0x%04X, but the %s Segment\n"
" has %d byte%s at 0x0000 and was never given a #Base.\n"
" Half a program loaded at zero lands on whatever is already there.\n" RESET,
segmentName[other], segmentBase(other), segmentName[mine],
content, content == 1 ? "" : "s");
printf(" File: %s\n", fileName);
printf(" Say \"#Base 0x0000\" in the %s Segment if that is what you meant.\n", segmentName[mine]);
exit(1);
}
}
int main(int argc, char *argv[]) {
static struct option long_options[] = {
{"output", required_argument, 0, 'o'},
@@ -185,7 +224,15 @@ int main(int argc, char *argv[]) {
// the buffers are filled, because SWI needs the number its vector was given.
populateVectorTable(intermediateArray, index);
fillInVectorReferences(intermediateArray, index);
// The buffers are filled from wherever each segment is based, so that a byte's place
// in the buffer is the address it will have. For a boot image both bases are zero and
// this changes nothing.
programLength = segmentBase(PROGRAM);
dataLength = segmentBase(DATA);
populateOutputBuffers(intermediateArray, index, Program, &programLength, Data, &dataLength);
// After the buffers, because how much a segment actually holds is not known until it
// has been filled, and an empty segment is not a mistake.
checkSegmentBases(fileName);
if (!outputFileName) {
outputFileName = createOutputFileName(fileName);
}
+50 -4
View File
@@ -13,6 +13,30 @@
int debug = 0;
static uint16_t segmentBases[3];
static int basesGiven = 0;
// Per segment, because a base of zero that was asked for and a base of zero that was
// never mentioned are different things, and only the second one is likely a mistake.
static int baseGiven[3];
void setSegmentBase(int segment, uint16_t base) {
segmentBases[segment] = base;
baseGiven[segment] = 1;
basesGiven = 1;
}
uint16_t segmentBase(int segment) {
return segmentBases[segment];
}
int segmentBaseWasGiven(int segment) {
return baseGiven[segment];
}
int programIsLoadable(void) {
return basesGiven;
}
void toUppercase(char *str) {
for (int i = 0; str[i]; i++) {
str[i] = toupper(str[i]);
@@ -37,6 +61,10 @@ int checkIfKeyword(intermediateElement *currentElement) {
// the number that follows. The file that needs the boundary is then the
// file that asks for it, rather than relying on whatever came before.
return KEYWORD_ALIGN;
} else if (strcmp(currentElement->token, "#Base") == 0) {
// Says where this segment is loaded, which makes the program a loadable one
// rather than a boot image.
return KEYWORD_BASE;
} else if (strcmp(currentElement->token, "#Reserve") == 0) {
// Puts down the number of zero bytes that follows, so that a label can
// stand for a region rather than just its first byte.
@@ -172,7 +200,9 @@ int checkIfLiteralValue(intermediateElement *currentElement) {
return 1;
}
uint16_t readCount(intermediateElement *currentElement, const char *what) {
// Shared by readCount and readAddress, which differ only in whether zero is an answer.
// A count of nothing is a typo; an address of zero is the bottom of memory.
static uint16_t readNumber(intermediateElement *currentElement, const char *what, long least) {
const char *token = currentElement->token;
int base;
const char *baseName;
@@ -202,14 +232,22 @@ uint16_t readCount(intermediateElement *currentElement, const char *what) {
}
}
long value = strtol(digits, NULL, base);
if (value < 1 || value > 0xFFFF) {
fprintf(stderr, RED "Error: %s was given \"%s\". It has to be at least 1 and no more than 0xFFFF.\n" RESET, what, token);
if (value < least || value > 0xFFFF) {
fprintf(stderr, RED "Error: %s was given \"%s\". It has to be at least %ld and no more than 0xFFFF.\n" RESET, what, token, least);
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
exit(1);
}
return (uint16_t)value;
}
uint16_t readCount(intermediateElement *currentElement, const char *what) {
return readNumber(currentElement, what, 1);
}
uint16_t readAddress(intermediateElement *currentElement, const char *what) {
return readNumber(currentElement, what, 0);
}
int checkIfLabel(intermediateElement *currentElement) {
char *token = currentElement->token;
int length = strlen(token);
@@ -261,7 +299,15 @@ int readToken(intermediateElement *currentElement, FILE *file, int *lineNumber)
if (i < (int)(sizeof(buffer) - 1)) {
buffer[i++] = c;
} else {
fprintf(stderr, "Error: String literal too long.\n");
// Say the limit and where it was met. A string long enough to reach this
// is usually several lines of help text, and "too long" on its own leaves
// somebody counting characters to find out by how much.
fprintf(stderr, RED "Error: String literal longer than %d characters.\n"
" Every string carries its own zero byte, so two written in a row"
" are two strings\n rather than one long one. Give each its own"
" label and print them one after another.\n" RESET,
(int)(sizeof(buffer) - 1));
printf(" File: %s at line %d.\n", currentElement->fileName, *lineNumber);
exit(1);
}
}
+28
View File
@@ -40,6 +40,7 @@
#define KEYWORD_VECTORS 4
#define KEYWORD_ALIGN 5
#define KEYWORD_RESERVE 6
#define KEYWORD_BASE 7
// Destination values.
#define NOWHERE 0
@@ -86,4 +87,31 @@ int readToken(intermediateElement *currentElement, FILE *file, int *lineNumber);
// ever emitted as a byte, so there is no reason to hold them to a byte's range.
uint16_t readCount(intermediateElement *currentElement, const char *what);
// The same, but zero is allowed. #Base takes one of these: a segment deliberately based
// at the bottom of memory is a thing a program is entitled to say, and saying it out loud
// is how it is told apart from a segment nobody based at all.
uint16_t readAddress(intermediateElement *currentElement, const char *what);
// ---- Where a segment is based ----
//
// A program that says nothing about this is a boot image: both its segments begin at
// zero, and the machine loads them there. A program that gives either segment a base is
// meant to be loaded somewhere else, so it is written out as a loadable program instead,
// with its addresses in front of it and none of the space below them in the file.
//
// Nothing relocates anything, so the base a program is assembled for has to be the one it
// is loaded at.
void setSegmentBase(int segment, uint16_t base);
uint16_t segmentBase(int segment);
// Whether this particular segment was given one. A segment left at zero because nobody
// said otherwise cannot be told from one deliberately based at zero by its value alone,
// and the difference is what the mismatch check below is about.
int segmentBaseWasGiven(int segment);
// Whether either segment was given one, which is what decides the kind of file written.
int programIsLoadable(void);
#endif
+2
View File
@@ -76,6 +76,7 @@ Instruction instruction_set[] = {
{0x4A, "LDD"},
{0x4B, "STD"},
{0x4C, "MVSD"},
{0x4D, "MVDS"},
// Output Operations:
{0xD0, "OUTQ"},
{0xD1, "OUTA"},
@@ -121,6 +122,7 @@ int dataPointerOperands(uint8_t opcode) {
case 0x48: // DPUP
case 0x49: // DPDN
case 0x4C: // MVSD
case 0x4D: // MVDS
return 1;
default:
return 0;
+40 -2
View File
@@ -155,6 +155,9 @@ int loadFile(intermediateElement **intermediateArray, char *fileName, int *inter
// fileName is a path already resolved and recorded by the caller, and the copy
// the include list owns, so element fileNames can safely point at it.
int status = NOWHERE;
// A base has to come before anything else in its segment, so this remembers whether
// that segment has had anything put in it yet.
static int segmentUsed[3] = {0, 0, 0};
int lineNumber = 1; // Line numbers start at 1.
// Open the file.
FILE *file = fopen(fileName, "r");
@@ -229,6 +232,34 @@ int loadFile(intermediateElement **intermediateArray, char *fileName, int *inter
// Set the state to DATA so we mark additional tokens for inclusion into Data Memory.
status = DATA;
break;
case KEYWORD_BASE: {
if (status != PROGRAM && status != DATA) {
fprintf(stderr, RED "Error: #Base outside the Program or Data Segment.\n There is no segment for it to be the base of.\n" RESET);
printf(" File: %s at line %d.\n", fileName, lineNumber);
exit(1);
}
if (segmentUsed[status]) {
fprintf(stderr, RED "Error: #Base after something is already in the segment.\n"
" A base says where the whole segment begins, so it has to come first.\n" RESET);
printf(" File: %s at line %d.\n", fileName, lineNumber);
exit(1);
}
(*intermediateIndex)++;
if (!readToken(&(*intermediateArray)[*intermediateIndex], file, &lineNumber)) {
fprintf(stderr, RED "Error: #Base without an address.\n" RESET);
exit(1);
}
(*intermediateArray)[*intermediateIndex].fileName = fileName;
(*intermediateArray)[*intermediateIndex].lineNumber = lineNumber;
setSegmentBase(status, readAddress(&(*intermediateArray)[*intermediateIndex], "#Base"));
(*intermediateArray)[*intermediateIndex].type = KEYWORD;
(*intermediateArray)[*intermediateIndex].byteLength = 0;
(*intermediateArray)[*intermediateIndex].destination = NOWHERE;
(*intermediateArray)[*intermediateIndex - 1].destination = NOWHERE;
(*intermediateArray)[*intermediateIndex - 1].byteLength = 0;
(*intermediateIndex)++;
continue;
}
case KEYWORD_ALIGN:
case KEYWORD_RESERVE: {
// Both take a count, and both only make sense somewhere that has a
@@ -288,8 +319,11 @@ int loadFile(intermediateElement **intermediateArray, char *fileName, int *inter
printf(" File: %s at line %d.\n", fileName, lineNumber);
exit(1);
}
// Next, check if it's a literal value.
} else if (checkIfLiteralValue(&(*intermediateArray)[*intermediateIndex])) {
// Next, check if it's a literal value. A string is never one, however it begins:
// the quotes are gone by now, so a string starting with a zero looks exactly like
// a malformed literal and used to be rejected as one.
} else if ((*intermediateArray)[*intermediateIndex].type != STRING
&& checkIfLiteralValue(&(*intermediateArray)[*intermediateIndex])) {
// We should check to make sure we have a destination for it.
if (status == NOWHERE) {
fprintf(stderr, RED "Error: Attempting to write a value to nowhere!\n Did you forget to use the #Program or #Data keyword?\n" RESET);
@@ -320,6 +354,10 @@ int loadFile(intermediateElement **intermediateArray, char *fileName, int *inter
}
}
(*intermediateArray)[*intermediateIndex].destination = status;
if ((status == PROGRAM || status == DATA)
&& (*intermediateArray)[*intermediateIndex].type != KEYWORD) {
segmentUsed[status] = 1;
}
(*intermediateIndex)++;
}
return 0;
+129 -12
View File
@@ -14,6 +14,7 @@
#include "secondPass.h"
#include "Assm-util.h"
#include "assembly.h"
#include "sbex.h"
int debugSecondPass = 0;
@@ -62,8 +63,11 @@ void addLabel(char *labelName, uint16_t address, int type, const char *fileName,
}
void populateLabelTable(intermediateElement *intermediateArray, int arraySize) {
int programCount = 0;
int dataCount = 0;
// Counting starts at the base, so a label in a program built to live somewhere else
// already holds the address it will have once it is there. Nothing relocates
// anything, which is exactly why this has to be right at assembly time.
int programCount = segmentBase(PROGRAM);
int dataCount = segmentBase(DATA);
// Loop through the array, if there's a label definition, add it to the label list.
for (int i = 0; i < arraySize ; i++) {
// How many zeroes an #Align comes to depends on where the cursor has reached,
@@ -159,6 +163,16 @@ static void vectorError(const char *message, intermediateElement *element) {
exit(1);
}
// Whether the token at b is written on the same line as the one at a, and so belongs to
// the same entry. A line is what tells a name with a handler apart from a name on its own.
static int sameLine(intermediateElement *intermediateArray, int a, int b) {
if (a < 0 || b < 0) {
return 0;
}
return intermediateArray[a].lineNumber == intermediateArray[b].lineNumber
&& intermediateArray[a].fileName == intermediateArray[b].fileName;
}
// The next token belonging to the Vector Segment, or -1 if the segment has run out.
static int nextVectorToken(intermediateElement *intermediateArray, int arraySize, int from) {
for (int i = from; i < arraySize; i++) {
@@ -169,12 +183,28 @@ static int nextVectorToken(intermediateElement *intermediateArray, int arraySize
return -1;
}
static void addVector(char *name, uint8_t index, uint16_t base, uint16_t handler, intermediateElement *element) {
// A declared vector has a name and a number but no handler, so nothing goes into the
// table for it. It exists so that a program can name a service it calls without claiming
// to implement it, which is what lets one file be included by both sides.
// Where a vector of this name already is, or -1. A name can be met twice: once where it
// is declared and once where somebody supplies its handler.
static int findVector(const char *name) {
for (int i = 0; i < vectorArrayCount; i++) {
if (vectorArray[i].name && strcmp(vectorArray[i].name, name) == 0) {
return i;
}
}
return -1;
}
static void addVector(char *name, uint8_t index, uint16_t base, uint16_t handler,
int declaredOnly, intermediateElement *element) {
if (vectorArrayCount >= MAX_VECTORS) {
vectorError("Too many vectors defined.", element);
}
for (int i = 0; i < vectorArrayCount; i++) {
if (vectorArray[i].index == index && vectorArray[i].base == base) {
if (vectorArray[i].index == index && vectorArray[i].base == base
&& !vectorArray[i].declaredOnly && !declaredOnly) {
fprintf(stderr, RED "Error: That vector already has a handler.\n" RESET);
printf("File: %s at line %d.\n", element->fileName, element->lineNumber);
exit(1);
@@ -189,6 +219,7 @@ static void addVector(char *name, uint8_t index, uint16_t base, uint16_t handler
vectorArray[vectorArrayCount].index = index;
vectorArray[vectorArrayCount].base = base;
vectorArray[vectorArrayCount].handler = handler;
vectorArray[vectorArrayCount].declaredOnly = declaredOnly;
vectorArrayCount++;
}
@@ -228,7 +259,26 @@ void populateVectorTable(intermediateElement *intermediateArray, int arraySize)
int handlerToken = nextVectorToken(intermediateArray, arraySize, portToken + 1);
uint16_t handler = resolveHandler(intermediateArray, handlerToken, "Device");
addVector(NULL, intermediateArray[portToken].byteValue, HARDWARE_VECTOR_BASE,
handler, &intermediateArray[i]);
handler, 0, &intermediateArray[i]);
i = nextVectorToken(intermediateArray, arraySize, handlerToken + 1);
continue;
}
int handlerToken = nextVectorToken(intermediateArray, arraySize, i + 1);
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.
if (!hasHandler) {
vectorError("That vector is declared more than once.", &intermediateArray[i]);
}
if (!vectorArray[already].declaredOnly) {
vectorError("That vector already has a handler.", &intermediateArray[i]);
}
vectorArray[already].handler = resolveHandler(intermediateArray, handlerToken, token);
vectorArray[already].declaredOnly = 0;
i = nextVectorToken(intermediateArray, arraySize, handlerToken + 1);
continue;
}
@@ -250,9 +300,15 @@ void populateVectorTable(intermediateElement *intermediateArray, int arraySize)
nextFreeVector++;
}
int handlerToken = nextVectorToken(intermediateArray, arraySize, i + 1);
if (!hasHandler) {
// Nothing follows it on the line, so this says what the vector is called and
// what number it has, and leaves implementing it to somebody else.
addVector(token, index, SOFTWARE_VECTOR_BASE, 0, 1, &intermediateArray[i]);
i = handlerToken;
continue;
}
uint16_t handler = resolveHandler(intermediateArray, handlerToken, token);
addVector(token, index, SOFTWARE_VECTOR_BASE, handler, &intermediateArray[i]);
addVector(token, index, SOFTWARE_VECTOR_BASE, handler, 0, &intermediateArray[i]);
i = nextVectorToken(intermediateArray, arraySize, handlerToken + 1);
}
}
@@ -440,6 +496,48 @@ void populateOutputBuffers(intermediateElement *intermediateArray, int arraySize
}
}
// A loadable program: sixteen bytes saying where it belongs, then the code and the data.
// The space below each base is not written out, because nothing needs to carry it: the
// header says where the bytes go and the loader puts them there.
static void writeLoadable(const char *outputFileName, uint8_t *Program, int programCount,
uint8_t *Data, int dataCount) {
uint16_t codeBase = segmentBase(PROGRAM);
uint16_t dataBase = segmentBase(DATA);
int codeLength = programCount - codeBase;
int dataLength = dataCount - dataBase;
if (codeLength < 0) codeLength = 0;
if (dataLength < 0) dataLength = 0;
FILE *outputFile = fopen(outputFileName, "wb");
if (!outputFile) {
fprintf(stderr, RED "Error: Could not open file \"%s\" for writing.\n" RESET, outputFileName);
exit(1);
}
uint8_t header[SBEX_HEADER_BYTES];
memset(header, 0, sizeof(header));
memcpy(header, SBEX_MAGIC, SBEX_MAGIC_BYTES);
header[SBEX_VERSION_AT] = SBEX_VERSION;
header[SBEX_CODE_AT] = (uint8_t)(codeBase >> 8);
header[SBEX_CODE_AT + 1] = (uint8_t)(codeBase & 0xFF);
// Where it starts is where it begins. A program that wants otherwise puts a branch
// at its first instruction, which costs three bytes and needs no format for it.
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 + 1] = (uint8_t)(codeLength & 0xFF);
header[SBEX_DATA_AT] = (uint8_t)(dataBase >> 8);
header[SBEX_DATA_AT + 1] = (uint8_t)(dataBase & 0xFF);
header[SBEX_DATA_LEN_AT] = (uint8_t)(dataLength >> 8);
header[SBEX_DATA_LEN_AT + 1] = (uint8_t)(dataLength & 0xFF);
fwrite(header, 1, sizeof(header), outputFile);
fwrite(Program + codeBase, 1, (size_t)codeLength, outputFile);
fwrite(Data + dataBase, 1, (size_t)dataLength, outputFile);
fclose(outputFile);
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,
codeLength, codeBase, dataLength, dataBase, SBEX_HEADER_BYTES + codeLength + dataLength);
}
void writeOutputFile(const char *outputFileName, uint8_t *Program, int programCount, uint8_t *Data, int dataCount) {
FILE *outputFile = fopen(outputFileName, "wb");
if (!outputFile) {
@@ -487,16 +585,35 @@ void writeOutputFile(const char *outputFileName, uint8_t *Program, int programCo
exit(1);
}
// A program with a base is one meant to be loaded, so it is written out with its
// addresses in front of it and nothing below them. A boot image carries the padding
// because the machine loads it at zero; a loadable one would only be carrying space
// it does not use.
if (programIsLoadable()) {
fclose(outputFile);
writeLoadable(outputFileName, Program, programCount, Data, dataCount);
return;
}
// The Vector Segment, only if the program named any. Leaving it out entirely is
// what lets a binary written before vectors existed still load: the reader treats
// the end of the file as an empty table rather than a missing one.
int installed = 0;
for (int i = 0; i < vectorArrayCount; i++) {
if (!vectorArray[i].declaredOnly) {
installed++;
}
}
int vectorBytes = 0;
if (vectorArrayCount > 0) {
if (installed > 0) {
fwrite("VEC", sizeof(char), SEGMENT_MARKER_LENGTH, outputFile);
vectorBytes = vectorArrayCount * VECTOR_ENTRY_FILE_BYTES;
vectorBytes = installed * VECTOR_ENTRY_FILE_BYTES;
fputc((vectorBytes >> 8) & 0xFF, outputFile);
fputc(vectorBytes & 0xFF, outputFile);
for (int i = 0; i < vectorArrayCount; i++) {
if (vectorArray[i].declaredOnly) {
continue;
}
uint16_t slot = vectorArray[i].base + (uint16_t)vectorArray[i].index * VECTOR_ENTRY_BYTES;
fputc((slot >> 8) & 0xFF, outputFile);
fputc(slot & 0xFF, outputFile);
@@ -508,10 +625,10 @@ void writeOutputFile(const char *outputFileName, uint8_t *Program, int programCo
fclose(outputFile);
printf("Successfully wrote SplitBit binary to \"%s\".\n", outputFileName);
printf(GREEN " Program Segment size: %d bytes.\n Data Segment size: %d bytes.\n" RESET, programCount, dataCount);
if (vectorArrayCount > 0) {
printf(GREEN " Vectors: %d.\n" RESET, vectorArrayCount);
if (installed > 0) {
printf(GREEN " Vectors: %d.\n" RESET, installed);
}
printf(GREEN " Total size: %d bytes.\n" RESET,
(programCount + dataCount + SPLITBIT_HEADER_BYTES
+ (vectorArrayCount > 0 ? SEGMENT_MARKER_LENGTH + SEGMENT_LENGTH_BYTES + vectorBytes : 0)));
+ (installed > 0 ? SEGMENT_MARKER_LENGTH + SEGMENT_LENGTH_BYTES + vectorBytes : 0)));
}
+1
View File
@@ -26,6 +26,7 @@ typedef struct {
uint8_t index; // Which vector in its table.
uint16_t base; // Which table: software or hardware.
uint16_t handler; // Where the handler ended up.
int declaredOnly; // Named and numbered, with nobody implementing it here.
} VectorEntry;
void freeLabelList();