joeylib2/tests/gate/iigsProfile.c

422 lines
15 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// iigsProfile.c - name the IIgs profiler's PC samples, for
// scripts/profile-iigs.sh.
//
// iigsProfile SAMPLES MAP TOP
//
// SAMPLES holds the MAME script's lines: "S ADDRESS COUNT" (a sampled
// 24-bit pc and how often), "F BANK BYTES" (the first bytes of a bank as
// it ran, in hex), and "T TOTAL". MAP is link816's map: its global and
// local symbols by link address (a global wins an address both name);
// beside it MAP's base name with ".json" (the segments' link banks, else
// one segment at bank 0) and ".segN.bin" (each segment's image).
//
// GS/OS loads segments wherever it likes, so each running bank is matched
// to the segment whose image its first bytes best agree with (85% at
// least); a sample in such a bank is named by the symbol below it in that
// segment, else as segN+OFFSET, and one elsewhere by the bank it is in
// (ROM, the firmware and vectors, or GS/OS, the tools, and NTP). Prints
// the matches and the TOP places most sampled.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gateArg.h"
#include "toolFile.h"
#include "toolJson.h"
#define TOOL_NAME "iigsProfile"
#define LINE_MAX_CHARS 8192u
#define NAME_MAX_CHARS 128u
#define WHERE_CHARS 160u
#define PATH_CHARS 4096u
// Room in a path for what is added to the map's base name.
#define SUFFIX_ROOM 32u
#define BASE_CHARS (PATH_CHARS - SUFFIX_ROOM)
#define BANKS 256u
#define BANK_SHIFT 16u
#define OFFSET_MASK 0xFFFFu
#define ADDRESS_DIGITS 6u
#define MATCH_BYTES 16u
#define MATCH_SCORE 0.85
#define PERCENT 100.0
#define ROM_FIRST 0xFCu
#define FIRMWARE_FIRST 0xE0u
#define FIRMWARE_LAST 0xE1u
#define HEX 16
#define START_COUNT 256u
#define MAP_SUFFIX ".map"
typedef struct SymbolT {
uint32_t address;
char name[NAME_MAX_CHARS];
bool global;
} SymbolT;
typedef struct SegmentT {
uint32_t number;
uint32_t linkBank;
uint8_t *image;
size_t len;
} SegmentT;
// A place samples were counted at, in the order first met.
typedef struct PlaceT {
char where[WHERE_CHARS];
uint32_t count;
} PlaceT;
// The samples: each address's count (the last line for it wins, in the
// place its first line had), the banks' fingerprints, and the total.
typedef struct SamplesT {
uint32_t *addresses;
uint32_t *counts;
size_t count;
size_t cap;
uint8_t *prints[BANKS];
size_t printLen[BANKS];
uint32_t total;
} SamplesT;
// What a running bank was matched to.
typedef struct BankT {
bool matched;
uint32_t segment;
uint32_t linkBank;
double score;
} BankT;
static int bySymbol(const void *a, const void *b);
static void placeAdd(PlaceT **places, size_t *count, const char *where, uint32_t n);
static void samplesRead(const char *path, SamplesT *samples);
static void segmentsRead(const char *base, SegmentT **segments, size_t *count);
static void symbolsRead(const char *path, SymbolT **symbols, size_t *count);
static void whereOf(uint32_t address, const BankT *banks, const SymbolT *symbols, size_t symbolCount, const SegmentT *segments, char *where);
static void *xrealloc(void *p, size_t bytes);
static int bySymbol(const void *a, const void *b) {
const SymbolT *x = (const SymbolT *)a;
const SymbolT *y = (const SymbolT *)b;
return x->address < y->address ? -1 : (x->address > y->address ? 1 : 0);
}
// One sample count to its place's total (a new place goes last).
static void placeAdd(PlaceT **places, size_t *count, const char *where, uint32_t n) {
size_t i;
for (i = 0; i < *count && strcmp((*places)[i].where, where) != 0; i++) {
}
if (i == *count) {
*places = (PlaceT *)xrealloc(*places, (*count + 1u) * sizeof(PlaceT));
snprintf((*places)[i].where, WHERE_CHARS, "%s", where);
(*places)[i].count = 0;
(*count)++;
}
(*places)[i].count += n;
}
static void samplesRead(const char *path, SamplesT *samples) {
FILE *f;
char line[LINE_MAX_CHARS];
char *at;
char *stop;
uint32_t address;
uint32_t n;
uint32_t bank;
size_t len;
size_t i;
memset(samples, 0, sizeof(*samples));
f = fopen(path, "rb");
if (f == NULL) {
toolFail("cannot read %s", path);
}
while (fgets(line, sizeof(line), f) != NULL) {
if (line[0] == 'S' && line[1] == ' ') {
address = (uint32_t)strtoul(line + 2, &stop, HEX);
n = (uint32_t)strtoul(stop, NULL, 10);
for (i = 0; i < samples->count && samples->addresses[i] != address; i++) {
}
if (i == samples->count) {
if (samples->count == samples->cap) {
samples->cap = samples->cap == 0 ? START_COUNT : samples->cap * 2u;
samples->addresses = (uint32_t *)xrealloc(samples->addresses, samples->cap * sizeof(uint32_t));
samples->counts = (uint32_t *)xrealloc(samples->counts, samples->cap * sizeof(uint32_t));
}
samples->addresses[samples->count++] = address;
}
samples->counts[i] = n;
} else if (line[0] == 'F' && line[1] == ' ') {
bank = (uint32_t)strtoul(line + 2, &stop, HEX) & (BANKS - 1u);
for (at = stop; *at == ' '; at++) {
}
len = strspn(at, "0123456789abcdefABCDEF") / 2u;
free(samples->prints[bank]);
samples->prints[bank] = (uint8_t *)xrealloc(NULL, len + 1u);
samples->printLen[bank] = len;
for (i = 0; i < len; i++) {
samples->prints[bank][i] = (uint8_t)strtoul((char[]){ at[2u * i], at[2u * i + 1u], 0 }, NULL, HEX);
}
} else if (line[0] == 'T' && line[1] == ' ') {
samples->total = (uint32_t)strtoul(line + 2, NULL, 10);
}
}
fclose(f);
}
// The segments the manifest names (base.json), with their images
// (base.segN.bin); without a manifest, one segment at bank 0.
static void segmentsRead(const char *base, SegmentT **segments, size_t *count) {
char path[PATH_CHARS];
char err[TOOL_JSON_ERROR_BYTES];
uint8_t *text;
size_t len;
size_t i;
ToolJsonT *doc;
const ToolJsonT *list;
const ToolJsonT *num;
const ToolJsonT *at;
*segments = NULL;
*count = 0;
snprintf(path, sizeof(path), "%s.json", base);
doc = NULL;
if (toolReadFile(path, &text, &len)) {
doc = toolJsonParse((const char *)text, len, err);
if (doc == NULL) {
toolFail("%s: %s", path, err);
}
free(text);
}
list = doc == NULL ? NULL : toolJsonGet(doc, "segments");
if (doc == NULL) {
*segments = (SegmentT *)xrealloc(NULL, sizeof(SegmentT));
(*segments)[0].number = 1;
(*segments)[0].linkBank = 0;
*count = 1;
} else {
*segments = (SegmentT *)xrealloc(NULL, (list == NULL ? 0u : list->count) * sizeof(SegmentT) + 1u);
for (i = 0; list != NULL && i < list->count; i++) {
num = toolJsonGet(list->items[i], "num");
at = toolJsonGet(list->items[i], "base");
if (num == NULL || at == NULL || at->type != TOOL_JSON_STRING) {
toolFail("%s: a segment without num and base", path);
}
(*segments)[*count].number = (uint32_t)num->number;
(*segments)[*count].linkBank = (uint32_t)(strtoul(at->string, NULL, HEX) >> BANK_SHIFT);
(*count)++;
}
toolJsonFree(doc);
}
for (i = 0; i < *count; i++) {
snprintf(path, sizeof(path), "%s.seg%u.bin", base, (*segments)[i].number);
if (!toolReadFile(path, &(*segments)[i].image, &(*segments)[i].len)) {
toolFail("cannot read %s", path);
}
}
}
// The map's symbols, sorted by address: the globals, and the locals where
// no global names the address.
static void symbolsRead(const char *path, SymbolT **symbols, size_t *count) {
FILE *f;
char line[LINE_MAX_CHARS];
char name[NAME_MAX_CHARS];
char *at;
char *end;
size_t cap;
size_t i;
size_t n;
uint32_t address;
int section;
*symbols = NULL;
*count = 0;
cap = 0;
section = 0;
f = fopen(path, "rb");
if (f == NULL) {
toolFail("cannot read %s", path);
}
while (fgets(line, sizeof(line), f) != NULL) {
if (line[0] == '#') {
section = strncmp(line, "# global symbols", strlen("# global symbols")) == 0 ? 1 : (strncmp(line, "# local symbols", strlen("# local symbols")) == 0 ? 2 : 0);
continue;
}
for (at = line; *at == ' ' || *at == '\t'; at++) {
}
for (end = at + strlen(at); end > at && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\r' || end[-1] == '\n'); end--) {
}
*end = 0;
if (section == 0 || at[0] != '0' || at[1] != 'x' || strspn(at + 2, "0123456789abcdefABCDEF") != ADDRESS_DIGITS || (at[2 + ADDRESS_DIGITS] != ' ' && at[2 + ADDRESS_DIGITS] != '\t')) {
continue;
}
address = (uint32_t)strtoul(at + 2, NULL, HEX);
for (at += 2 + ADDRESS_DIGITS; *at == ' ' || *at == '\t'; at++) {
}
n = strcspn(at, " \t");
if (n == 0 || at[n] != 0 || n >= NAME_MAX_CHARS) {
continue;
}
memcpy(name, at, n + 1u);
for (i = 0; i < *count && (*symbols)[i].address != address; i++) {
}
if (i == *count) {
if (*count == cap) {
cap = cap == 0 ? START_COUNT : cap * 2u;
*symbols = (SymbolT *)xrealloc(*symbols, cap * sizeof(SymbolT));
}
(*count)++;
} else if (section != 1) {
continue;
}
(*symbols)[i].address = address;
memcpy((*symbols)[i].name, name, n + 1u);
}
fclose(f);
qsort(*symbols, *count, sizeof(SymbolT), bySymbol);
}
// Where an address is: a matched bank's symbol (the one at or below it in
// the same link bank) or segment offset, else the kind of bank.
static void whereOf(uint32_t address, const BankT *banks, const SymbolT *symbols, size_t symbolCount, const SegmentT *segments, char *where) {
const BankT *bank;
uint32_t link;
size_t lo;
size_t hi;
size_t mid;
bank = &banks[address >> BANK_SHIFT];
if (bank->matched) {
link = (bank->linkBank << BANK_SHIFT) | (address & OFFSET_MASK);
lo = 0;
hi = symbolCount;
while (lo < hi) {
mid = (lo + hi) / 2u;
if (symbols[mid].address <= link) {
lo = mid + 1u;
} else {
hi = mid;
}
}
if (lo > 0 && (symbols[lo - 1u].address >> BANK_SHIFT) == (link >> BANK_SHIFT)) {
snprintf(where, WHERE_CHARS, "%s", symbols[lo - 1u].name);
return;
}
snprintf(where, WHERE_CHARS, "seg%u+%04X", segments[bank->segment].number, address & OFFSET_MASK);
return;
}
if ((address >> BANK_SHIFT) >= ROM_FIRST) {
snprintf(where, WHERE_CHARS, "ROM $%02X", address >> BANK_SHIFT);
} else if ((address >> BANK_SHIFT) >= FIRMWARE_FIRST && (address >> BANK_SHIFT) <= FIRMWARE_LAST) {
snprintf(where, WHERE_CHARS, "bank $%02X (firmware/vectors)", address >> BANK_SHIFT);
} else {
snprintf(where, WHERE_CHARS, "bank $%02X (GS/OS, tools, NTP)", address >> BANK_SHIFT);
}
}
static void *xrealloc(void *p, size_t bytes) {
p = realloc(p, bytes == 0 ? 1u : bytes);
if (p == NULL) {
toolFail("out of memory");
}
return p;
}
int main(int argc, char **argv) {
SamplesT samples;
SegmentT *segments;
SymbolT *symbols;
PlaceT *places;
PlaceT held;
BankT banks[BANKS];
char base[BASE_CHARS];
char where[WHERE_CHARS];
size_t segmentCount;
size_t symbolCount;
size_t placeCount;
size_t len;
size_t same;
size_t i;
size_t j;
uint32_t bank;
long top;
double score;
toolFileName(TOOL_NAME);
if (argc != 4) {
fprintf(stderr, "usage: %s SAMPLES MAP TOP\n", TOOL_NAME);
return GATE_USAGE_EXIT;
}
top = gateArgLong("top", argv[3]);
len = strlen(argv[2]);
snprintf(base, sizeof(base), "%.*s", (int)(len >= strlen(MAP_SUFFIX) && strcmp(argv[2] + len - strlen(MAP_SUFFIX), MAP_SUFFIX) == 0 ? len - strlen(MAP_SUFFIX) : len), argv[2]);
segmentsRead(base, &segments, &segmentCount);
symbolsRead(argv[2], &symbols, &symbolCount);
samplesRead(argv[1], &samples);
// Each running bank to the segment its first bytes best match.
memset(banks, 0, sizeof(banks));
for (bank = 0; bank < BANKS; bank++) {
if (samples.prints[bank] == NULL) {
continue;
}
for (i = 0; i < segmentCount; i++) {
len = samples.printLen[bank] < segments[i].len ? samples.printLen[bank] : segments[i].len;
if (len < MATCH_BYTES) {
continue;
}
same = 0;
for (j = 0; j < len; j++) {
same += samples.prints[bank][j] == segments[i].image[j] ? 1u : 0u;
}
score = (double)same / (double)len;
if (!banks[bank].matched || score > banks[bank].score) {
banks[bank].matched = true;
banks[bank].segment = (uint32_t)i;
banks[bank].linkBank = segments[i].linkBank;
banks[bank].score = score;
}
}
banks[bank].matched = banks[bank].matched && banks[bank].score >= MATCH_SCORE;
}
places = NULL;
placeCount = 0;
for (i = 0; i < samples.count; i++) {
whereOf(samples.addresses[i], banks, symbols, symbolCount, segments, where);
placeAdd(&places, &placeCount, where, samples.counts[i]);
}
// Most samples first; equal ones in the order first met.
for (i = 1; i < placeCount; i++) {
held = places[i];
for (j = i; j > 0 && places[j - 1u].count < held.count; j--) {
places[j] = places[j - 1u];
}
places[j] = held;
}
printf("profile-iigs: %u samples\n", samples.total);
for (bank = 0; bank < BANKS; bank++) {
if (banks[bank].matched) {
printf(" bank $%02X = segment %u (link bank $%02X, %.0f%% of %zu bytes match)\n", bank, segments[banks[bank].segment].number, banks[bank].linkBank, banks[bank].score * PERCENT, samples.printLen[bank]);
}
}
printf("%7s %6s %s\n", "samples", "pct", "where");
for (i = 0; i < placeCount && (long)i < top; i++) {
printf("%7u %5.1f%% %s\n", places[i].count, PERCENT * places[i].count / (samples.total > 0 ? samples.total : 1u), places[i].where);
}
return 0;
}