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
83 lines
3.5 KiB
C
83 lines
3.5 KiB
C
// secondPass.c
|
|
// Functions for the 'second pass' of the SplitBit Assembler.
|
|
// Written by Anachronaut
|
|
// 10/25/2024
|
|
|
|
#ifndef SECONDPASS_H
|
|
#define SECONDPASS_H
|
|
|
|
|
|
#include <stdint.h>
|
|
#include <ctype.h>
|
|
#include "Assm-util.h"
|
|
|
|
// 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.
|
|
//
|
|
// AND PAST 1024 the day the shell learned to finish a word somebody had started. Doubled
|
|
// rather than nudged: a ceiling reached once is a ceiling that will be reached again, and
|
|
// the table is pointers into source that is already in memory - 2048 of them is sixteen
|
|
// kilobytes on a host with gigabytes of it.
|
|
#define MAX_LABELS 2048
|
|
#define MAX_VECTORS 256
|
|
|
|
typedef struct {
|
|
char* label;
|
|
uint16_t address;
|
|
int type;
|
|
// Where the name was written. Only the symbol file reads these, and it is the source
|
|
// of the whole feature: an address tells you a routine exists and a file and line tell
|
|
// you where to go and read it.
|
|
//
|
|
// NEITHER IS OWNED HERE. fileName points at the copy the include list keeps, the same
|
|
// storage every intermediateElement points at, and the include list outlives the label
|
|
// table - assemblerCleanup frees the labels first. So freeLabelList must not free it.
|
|
const char* fileName;
|
|
int lineNumber;
|
|
} Label;
|
|
|
|
// 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.
|
|
typedef struct {
|
|
char* name; // What it was called, or NULL for a device, which is named by its port.
|
|
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.
|
|
// 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();
|
|
|
|
void freeVectorList();
|
|
|
|
// Reads the Vector Segment: allocates a number to every named vector, works out which
|
|
// vector each device line means, and resolves the handlers. Runs after the labels are
|
|
// known, because a handler is named by its label.
|
|
void populateVectorTable(intermediateElement *intermediateArray, int arraySize);
|
|
|
|
// Turns each vector name used as an operand of SWI into the number it was given.
|
|
void fillInVectorReferences(intermediateElement *intermediateArray, int arraySize);
|
|
|
|
int vectorCount();
|
|
|
|
void populateLabelTable(intermediateElement *intermediateArray, int arraySize);
|
|
|
|
void fillInLabelAddresses(intermediateElement *intermediateArray, int arraySize);
|
|
|
|
void populateOutputBuffers(intermediateElement *intermediateArray, int arraySize, 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);
|
|
|
|
#endif
|