Vectors in the symbol table, with both of the numbers they have

A vector is the one thing about a program that nothing else can tell
you. A pinned vector has its number in the source that pinned it, but a
vector the assembler numbered has that number nowhere at all - not in
the source, not in the binary in any form a reader can find. Until now
there was no way to learn that a vector became number 64.

It also cost two hops to follow by hand. The name in "SWI osPrintString"
is not the name of the routine that implements it, so finding the code
meant searching for the vector, reading the handler's name off the
Vector Segment, and searching again. A vector row now names the handler
and gives the line the two were tied together on.

Both numbers, at the user's asking, because neither can be worked out
from the other without knowing which table the vector is in: the Number
is what a program writes and the machine dispatches on, the Address is
where the handler's address is stored, base plus twice the number. The
slot is computed with the same expression the loader is given, so what
the table says and what gets written there cannot drift apart. A vector
a program only declares is listed too - that is how a program says which
vectors it calls, and how two programs can be checked against each other
for agreeing about a number.

A device has no name of its own, being named by the port it is plugged
into, so it is listed under its handler.

The first field is now Kind rather than Memory, because Vector and
Device are not memories. Sorted Program, Data, Vector, Device.

docs.sh checks the six fields against the manual and against real dumps
of two programs - Keys, a loadable program with all four kinds, and
cosmos, a boot image whose segments both start at zero. It now also
checks that a row's name really appears on the line the row names, which
is what catches the string-newline bug fixed in ca6c8ca coming back.
Verified with break.sh four ways: wrong slot arithmetic, vectors
dropped, a field renamed in the manual, and that bug reintroduced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-09-05 10:51:50 -04:00
co-authored by Claude Opus 5
parent ca6c8ca6ad
commit f3d8985bc4
4 changed files with 240 additions and 65 deletions
+113 -30
View File
@@ -21,6 +21,11 @@ int debugSecondPass = 0;
Label labelArray[MAX_LABELS];
int labelCount = 0;
// Beside the labels rather than beside the code that fills it, because the symbol file
// below reads both tables and this one would otherwise not be declared yet.
VectorEntry vectorArray[MAX_VECTORS];
int vectorArrayCount = 0;
// ---- Where everything ended up ----
//
// Every label with the memory and address it was given and the place it was written, one
@@ -40,25 +45,57 @@ int labelCount = 0;
// -F'\t', sort -k1,2 - and so that a name never has to be quoted. No header line, for the
// same reason: nothing that reads it should have to know to skip one.
//
// THE MEMORY IS THE FIRST FIELD BECAUSE THIS IS A HARVARD MACHINE. Program and Data are
// THE KIND IS THE FIRST FIELD BECAUSE THIS IS A HARVARD MACHINE. Program and Data are
// separate address spaces, so 0x3000 is two different places and an address alone does not
// say which. That is invisible in a loadable program, where the two segments are based
// somewhere apart, and immediate in a boot image, where both start at zero and every
// address in the file appears twice.
//
// Sorted by memory and then address rather than by name, because the question asked of it
// is always "what is at this address"; sorting by address alone would interleave two
// address spaces and make the first column flicker between them. A label table is small
// enough that sorting it is free.
// VECTORS ARE IN HERE TOO, and they are the part nothing else can tell you. A pinned vector
// has its number written in the source that pins it, but an automatic one is handed a number
// by this assembler and that number appears nowhere at all - not in the source, not in the
// binary in any form a reader can find. A vector also costs two hops to follow by hand: the
// name in "SWI osFileRead" is not the name of the routine that implements it, so finding the
// code means grepping for the vector, reading the handler's name off the Vector Segment, and
// grepping again. A vector row gives its number, the handler's name, and the line the two
// were tied together on.
//
// A VECTOR CARRIES BOTH ITS NUMBERS, which is why there is a sixth field. Its number is what
// a program writes and what the machine dispatches on; its slot is where the handler's
// address is stored in Program Memory, base + number * 2, which is what the loader writes
// and what a memory dump shows. Neither can be worked out from the other without knowing
// which table it is in, so the file says both.
//
// Sorted by kind and then address rather than by name, because the question asked of it is
// always "what is at this address"; sorting by address alone would interleave separate
// address spaces and make the first column flicker between them. The table is small enough
// that sorting it is free.
// The four kinds, in the order they are listed. Program and Data are the two memories, then
// the two vector tables.
#define ROW_PROGRAM 0
#define ROW_DATA 1
#define ROW_VECTOR 2
#define ROW_DEVICE 3
typedef struct {
int kind;
uint16_t address; // Where it lives: a label's address, or a vector's slot.
const char *name;
const char *fileName;
int lineNumber;
int number; // The vector or port number, or -1 for a label, which has none.
} SymbolRow;
static int byPlace(const void *left, const void *right) {
const Label *a = left, *b = right;
if (a->type != b->type) {
return a->type < b->type ? -1 : 1; // PROGRAM is 1 and DATA is 2, so code first.
const SymbolRow *a = left, *b = right;
if (a->kind != b->kind) {
return a->kind < b->kind ? -1 : 1;
}
if (a->address != b->address) {
return a->address < b->address ? -1 : 1;
}
return strcmp(a->label, b->label);
return strcmp(a->name, b->name);
}
void writeSymbolFile(const char *path) {
@@ -67,27 +104,61 @@ void writeSymbolFile(const char *path) {
fprintf(stderr, RED "Error: Could not open file \"%s\" for writing.\n" RESET, path);
exit(1);
}
Label *sorted = malloc((size_t)labelCount * sizeof(Label));
if (!sorted) {
int rowCount = labelCount + vectorArrayCount;
SymbolRow *rows = malloc((size_t)rowCount * sizeof(SymbolRow) + 1);
if (!rows) {
fprintf(stderr, RED "Error: Out of memory writing the symbol file.\n" RESET);
fclose(file);
exit(1);
}
memcpy(sorted, labelArray, (size_t)labelCount * sizeof(Label));
qsort(sorted, (size_t)labelCount, sizeof(Label), byPlace);
int n = 0;
for (int i = 0; i < labelCount; i++) {
// A label is put in one of the two segments by populateLabelTable and there is no
// third, but the name is printed from the type rather than assumed, so a label that
// somehow arrived as neither says so instead of being filed under Data.
const char *memory = sorted[i].type == PROGRAM ? "Program"
: sorted[i].type == DATA ? "Data"
: "?";
fprintf(file, "%s\t%04X\t%s\t%s\t%d\n",
memory, sorted[i].address, sorted[i].label,
sorted[i].fileName ? sorted[i].fileName : "?",
sorted[i].lineNumber);
// third, but the kind is taken from the type rather than assumed, so a label that
// somehow arrived as neither is not quietly filed under Data.
rows[n].kind = labelArray[i].type == PROGRAM ? ROW_PROGRAM : ROW_DATA;
rows[n].address = labelArray[i].address;
rows[n].name = labelArray[i].label;
rows[n].fileName = labelArray[i].fileName;
rows[n].lineNumber = labelArray[i].lineNumber;
rows[n].number = -1;
n++;
}
free(sorted);
for (int i = 0; i < vectorArrayCount; i++) {
int hardware = vectorArray[i].base == HARDWARE_VECTOR_BASE;
rows[n].kind = hardware ? ROW_DEVICE : ROW_VECTOR;
// The same arithmetic the loader is given, so that what this says a vector's slot is
// and what actually gets written there cannot drift apart.
rows[n].address = vectorArray[i].base
+ (uint16_t)vectorArray[i].index * VECTOR_ENTRY_BYTES;
// A device has no name of its own - it is named by the port it is plugged into - so
// it is listed under its handler, which is the only name it has.
rows[n].name = vectorArray[i].name ? vectorArray[i].name
: vectorArray[i].handlerName ? vectorArray[i].handlerName
: "?";
rows[n].fileName = vectorArray[i].fileName;
rows[n].lineNumber = vectorArray[i].lineNumber;
rows[n].number = vectorArray[i].index;
n++;
}
qsort(rows, (size_t)n, sizeof(SymbolRow), byPlace);
static const char *kindName[4] = { "Program", "Data", "Vector", "Device" };
for (int i = 0; i < n; i++) {
char number[12]; // Wide enough for any int, which is more than a vector needs.
if (rows[i].number < 0) {
// A LABEL HAS NO NUMBER, and the field says so rather than being left empty:
// a run of tabs with nothing between them is the one thing a reader of this
// file, human or otherwise, can miscount.
snprintf(number, sizeof(number), "-");
} else {
snprintf(number, sizeof(number), "%d", rows[i].number);
}
fprintf(file, "%s\t%04X\t%s\t%s\t%d\t%s\n",
kindName[rows[i].kind], rows[i].address, rows[i].name,
rows[i].fileName ? rows[i].fileName : "?",
rows[i].lineNumber, number);
}
free(rows);
fclose(file);
}
@@ -215,9 +286,6 @@ void populateLabelTable(intermediateElement *intermediateArray, int arraySize) {
int findLabelAddress(const char *labelName);
VectorEntry vectorArray[MAX_VECTORS];
int vectorArrayCount = 0;
int vectorCount() {
return vectorArrayCount;
}
@@ -227,6 +295,9 @@ void freeVectorList() {
if (vectorArray[i].name) {
free(vectorArray[i].name);
}
if (vectorArray[i].handlerName) {
free(vectorArray[i].handlerName);
}
}
vectorArrayCount = 0;
}
@@ -303,7 +374,8 @@ static int findVector(const char *name) {
}
static void addVector(char *name, uint8_t index, uint16_t base, uint16_t handler,
int declaredOnly, intermediateElement *element) {
int declaredOnly, intermediateElement *element,
const char *handlerName) {
if (vectorArrayCount >= MAX_VECTORS) {
vectorError("Too many vectors defined.", element);
}
@@ -325,6 +397,9 @@ static void addVector(char *name, uint8_t index, uint16_t base, uint16_t handler
vectorArray[vectorArrayCount].base = base;
vectorArray[vectorArrayCount].handler = handler;
vectorArray[vectorArrayCount].declaredOnly = declaredOnly;
vectorArray[vectorArrayCount].handlerName = handlerName ? strdup(handlerName) : NULL;
vectorArray[vectorArrayCount].fileName = element->fileName;
vectorArray[vectorArrayCount].lineNumber = element->lineNumber;
vectorArrayCount++;
}
@@ -375,7 +450,8 @@ 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, 0, &intermediateArray[i]);
handler, 0, &intermediateArray[i],
intermediateArray[handlerToken].token);
i = nextVectorToken(intermediateArray, arraySize, handlerToken + 1);
continue;
}
@@ -413,6 +489,12 @@ void populateVectorTable(intermediateElement *intermediateArray, int arraySize)
}
vectorArray[already].handler = resolveHandler(intermediateArray, handlerToken, token);
vectorArray[already].declaredOnly = 0;
// The declaration said what it is called; this says where it was implemented,
// which is the more useful of the two places to be sent.
free(vectorArray[already].handlerName);
vectorArray[already].handlerName = strdup(intermediateArray[handlerToken].token);
vectorArray[already].fileName = intermediateArray[i].fileName;
vectorArray[already].lineNumber = intermediateArray[i].lineNumber;
i = nextVectorToken(intermediateArray, arraySize, handlerToken + 1);
continue;
}
@@ -459,12 +541,13 @@ void populateVectorTable(intermediateElement *intermediateArray, int arraySize)
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]);
addVector(token, index, SOFTWARE_VECTOR_BASE, 0, 1, &intermediateArray[i], NULL);
i = handlerToken;
continue;
}
uint16_t handler = resolveHandler(intermediateArray, handlerToken, token);
addVector(token, index, SOFTWARE_VECTOR_BASE, handler, 0, &intermediateArray[i]);
addVector(token, index, SOFTWARE_VECTOR_BASE, handler, 0, &intermediateArray[i],
intermediateArray[handlerToken].token);
i = nextVectorToken(intermediateArray, arraySize, handlerToken + 1);
}
}
+8 -2
View File
@@ -38,8 +38,8 @@ typedef struct {
int lineNumber;
} Label;
// Every label and the address it was given, in address order, so that a tally of
// addresses can be turned back into a list of routine names.
// Every label and every vector, with where it lives and where it was written, so that a
// tally of addresses can be turned back into a list of routine names.
void writeSymbolFile(const char *path);
// One line of the Vector Segment, once it has been worked out.
@@ -49,6 +49,12 @@ typedef struct {
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.
// For the symbol file, which is the only thing that reads these. A device has no name
// of its own, so the handler's is the only name it can be listed under. fileName is
// borrowed from the include list the way a Label's is; handlerName is owned, like name.
char* handlerName;
const char* fileName;
int lineNumber;
} VectorEntry;
void freeLabelList();