From 19ab36a201216bbdd98244920e569399ea49dde5 Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Tue, 25 Aug 2026 18:15:50 -0400 Subject: [PATCH] The assembler can say where everything ended up -S writes every label and the address it was given, in address order. Nothing else knows that. A program on the disk is bytes; the monitor can disassemble it but has no idea what any of it is called. So counting which addresses a program calls says a great deal and names nothing - the answer arrives as a column of numbers and somebody works out by hand which routine each one is inside. It was deferred when the native assembler was planned, as a listing and symbol dump nobody needed yet. Finding out where the assembler spends its time is what needed it: the top six call targets were addresses until this existed and are numStep, numCompare, tokGet, srcNext, numAddByte and clsSameName with it. Sorted by address rather than by name, because the question asked of it is always "what is at this address". Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW --- README.md | 6 +++++ Source/Assembler/Assembler.c | 13 ++++++++++- Source/Assembler/secondPass.c | 42 +++++++++++++++++++++++++++++++++++ Source/Assembler/secondPass.h | 4 ++++ 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cd12e6e..8f42c39 100644 --- a/README.md +++ b/README.md @@ -105,8 +105,14 @@ If the CPU reads a byte that is not an instruction, it goes to the fault handler | `-o ` | Write the output to this path. | | `-I ` | Look in this directory for included files. May be given more than once. | | `-M ` | Write out which source files the output depends on, as a make rule. | +| `-S ` | Write every label and the address it was given, in address order. | | `-h`, `--help` | Show help and usage information. | +`-S` is the only thing that knows what a program's addresses are called. A program on the +disk is bytes; the machine's own monitor can disassemble it but has no idea what any of it +is named. So a count of which addresses get called says a great deal and names nothing, and +this is what turns such a count into a list of routine names. + Without `-o` the output takes the source file's name, in the directory you called the assembler from, with the extension the format asks for: `.bin` for a boot image and `.sbx` for a loadable program. Included files are looked for beside the file that includes them, and then along the directories given with `-I`. ## Managing Disks: SplitDisk diff --git a/Source/Assembler/Assembler.c b/Source/Assembler/Assembler.c index 9753ea0..a1d446d 100644 --- a/Source/Assembler/Assembler.c +++ b/Source/Assembler/Assembler.c @@ -102,6 +102,7 @@ void printUsage(const char *programName) { printf(" -o Write the output to this path instead of alongside the source.\n"); printf(" -I Look in this directory for included files. May be given more than once.\n"); printf(" -M Write the source files this output depends on, as a make rule.\n"); + printf(" -S Write every label and the address it was given, in address order.\n"); printf(" -h, --help Display this help message.\n"); } @@ -171,15 +172,17 @@ int main(int argc, char *argv[]) { {"output", required_argument, 0, 'o'}, {"include", required_argument, 0, 'I'}, {"depend", required_argument, 0, 'M'}, + {"symbols", required_argument, 0, 'S'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0 } }; char *outputFileName = NULL; char *dependencyFileName = NULL; + char *symbolFileName = NULL; int option_index = 0; int opt; - while ((opt = getopt_long(argc, argv, "o:I:M:h", long_options, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "o:I:M:S:h", long_options, &option_index)) != -1) { switch (opt) { case 'o': outputFileName = strdup(optarg); @@ -190,6 +193,9 @@ int main(int argc, char *argv[]) { case 'M': dependencyFileName = strdup(optarg); break; + case 'S': + symbolFileName = strdup(optarg); + break; case 'h': printUsage(argv[0]); return 0; @@ -249,6 +255,11 @@ int main(int argc, char *argv[]) { writeDependencyFile(dependencyFileName, outputFileName); free(dependencyFileName); } + // Before the cleanup, which is what frees the label table this reads. + if (symbolFileName) { + writeSymbolFile(symbolFileName); + free(symbolFileName); + } assemblerCleanup(intermediateArray, index, outputFileName); return 0; } diff --git a/Source/Assembler/secondPass.c b/Source/Assembler/secondPass.c index 365a775..38a124a 100644 --- a/Source/Assembler/secondPass.c +++ b/Source/Assembler/secondPass.c @@ -21,6 +21,48 @@ int debugSecondPass = 0; Label labelArray[MAX_LABELS]; int labelCount = 0; +// ---- Where everything ended up ---- +// +// Every label and the address it was given, in address order. The assembler knows this and +// nothing else does: a program on the disk is bytes, and the machine's own monitor can +// disassemble it but has no idea what any of it is called. +// +// WHAT IT IS FOR is telling where a program spends its time. Counting which addresses get +// called says a great deal and names nothing, so the answer arrives as a list of numbers +// and somebody has to work out by hand which routine each one is inside. With this, a +// tally of call targets becomes a list of routine names. +// +// Sorted by address rather than by name, because the question asked of it is always "what +// is at this address", and a label table is small enough that sorting it is free. +static int byAddress(const void *left, const void *right) { + const Label *a = left, *b = right; + if (a->address != b->address) { + return a->address < b->address ? -1 : 1; + } + return strcmp(a->label, b->label); +} + +void writeSymbolFile(const char *path) { + FILE *file = fopen(path, "w"); + if (!file) { + 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) { + 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), byAddress); + for (int i = 0; i < labelCount; i++) { + fprintf(file, "%04X %s\n", sorted[i].address, sorted[i].label); + } + free(sorted); + fclose(file); +} + void freeLabelList() { for (int i = 0; i < labelCount; i++) { if (labelArray[i].label) { diff --git a/Source/Assembler/secondPass.h b/Source/Assembler/secondPass.h index 6f4e07a..f689349 100644 --- a/Source/Assembler/secondPass.h +++ b/Source/Assembler/secondPass.h @@ -24,6 +24,10 @@ typedef struct { int type; } 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. +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.