Rewrite RGBLINK entirely

The goal was to improve readability, but along the way a few things were
gained.
- Sorted sym and map files
- Infrastructure for supporting multiple .o versions
- Valgrind-proof, as far as my testing goes anyways
- Improved verbosity messages
- Added error checking
- Performance improvements, see end of commit message

The readability improvement was spurred while trying to make sense of the
old code while trying to implement features such as sorted sym and map
files.
I also did my best to remove hardcoded logic, such that modifications
should be doable; for example, "RAM loading" sections, which are linked
against a different location than the one they're stored at.

Some work remains to be done, see the "TODO:" and "FIXME:" comments.
Further, while regression tests pass, this new linker should be tested on
different codebases (ideally while instrumented with `make develop` and
under valgrind).
The few errors spotted in the man pages (alignment) need to be corrected.
Finally, documentation comments need to be written, I have written a lot of
them but not all.

This also provides a significant performance boost (benchmarked with a
51994-symbol project):

Current master RGBLINK:
2.02user 0.03system 0:02.06elapsed 99%CPU (0avgtext+0avgdata 84336maxresident)k
0inputs+11584outputs (0major+20729minor)pagefaults 0swaps

Rewritten RGBLINK:
0.19user 0.06system 0:00.63elapsed 40%CPU (0avgtext+0avgdata 32460maxresident)k
23784inputs+11576outputs (0major+7672minor)pagefaults 0swaps
This commit is contained in:
ISSOtm
2019-09-19 14:27:37 +02:00
parent 696feae32e
commit 0e24adcafd
39 changed files with 2507 additions and 2630 deletions

View File

@@ -1,174 +1,344 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "link/output.h"
#include "link/main.h"
#include "link/section.h"
#include "link/symbol.h"
#include "extern/err.h"
#include "link/mylink.h"
#include "link/mapfile.h"
#include "link/main.h"
#include "link/assign.h"
struct SortedSection {
struct Section const *section;
struct SortedSection *next;
};
char *tzOutname;
char *tzOverlayname;
static struct {
uint32_t nbBanks;
struct SortedSections {
struct SortedSection *sections;
struct SortedSection *zeroLenSections;
} *banks;
} sections[SECTTYPE_INVALID];
int32_t MaxOverlayBank;
void writehome(FILE *f, FILE *f_overlay)
void out_AddSection(struct Section const *section)
{
const struct sSection *pSect;
uint8_t *mem;
static uint32_t maxNbBanks[] = {
[SECTTYPE_ROM0] = 1,
[SECTTYPE_ROMX] = UINT32_MAX,
[SECTTYPE_VRAM] = 2,
[SECTTYPE_SRAM] = UINT32_MAX,
[SECTTYPE_WRAM0] = 1,
[SECTTYPE_WRAMX] = 7,
[SECTTYPE_OAM] = 1,
[SECTTYPE_HRAM] = 1
};
mem = malloc(MaxAvail[BANK_INDEX_ROM0]);
if (!mem)
uint32_t targetBank = section->bank - bankranges[section->type][0];
uint32_t minNbBanks = targetBank + 1;
if (minNbBanks > maxNbBanks[section->type])
errx(1, "Section \"%s\" has invalid bank range (%u > %u)",
section->name, section->bank,
maxNbBanks[section->type] - 1);
if (minNbBanks > sections[section->type].nbBanks) {
sections[section->type].banks =
realloc(sections[section->type].banks,
sizeof(*sections[0].banks) * minNbBanks);
for (uint32_t i = sections[section->type].nbBanks;
i < minNbBanks; i++) {
sections[section->type].banks[i].sections = NULL;
sections[section->type].banks[i].zeroLenSections = NULL;
}
sections[section->type].nbBanks = minNbBanks;
}
if (!sections[section->type].banks)
err(1, "Failed to realloc banks");
struct SortedSection *newSection = malloc(sizeof(*newSection));
struct SortedSection **ptr = section->size
? &sections[section->type].banks[targetBank].sections
: &sections[section->type].banks[targetBank].zeroLenSections;
if (!newSection)
err(1, "Failed to add new section \"%s\"", section->name);
newSection->section = section;
while (*ptr && (*ptr)->section->org < section->org)
ptr = &(*ptr)->next;
newSection->next = *ptr;
*ptr = newSection;
}
static void checkOverlay(void)
{
if (!overlayFile)
return;
if (f_overlay != NULL) {
fseek(f_overlay, 0L, SEEK_SET);
if (fread(mem, 1, MaxAvail[BANK_INDEX_ROM0], f_overlay) !=
MaxAvail[BANK_INDEX_ROM0]) {
warnx("Failed to read data from overlay file.");
}
} else {
memset(mem, fillchar, MaxAvail[BANK_INDEX_ROM0]);
}
MapfileInitBank(0);
pSect = pSections;
while (pSect) {
if (pSect->Type == SECT_ROM0) {
memcpy(mem + pSect->nOrg, pSect->pData,
pSect->nByteSize);
MapfileWriteSection(pSect);
}
pSect = pSect->pNext;
if (fseek(overlayFile, 0, SEEK_END) != 0) {
warnx("Overlay file is not seekable, cannot check if properly formed");
return;
}
MapfileCloseBank(area_Avail(0));
long overlaySize = ftell(overlayFile);
fwrite(mem, 1, MaxAvail[BANK_INDEX_ROM0], f);
free(mem);
if (overlaySize % 0x4000)
errx(1, "Overlay file must have a size multiple of 0x4000");
uint32_t nbOverlayBanks = overlaySize / 0x4000 - 1;
if (nbOverlayBanks < 1)
errx(1, "Overlay must be at least 0x8000 bytes large");
if (nbOverlayBanks > sections[SECTTYPE_ROMX].nbBanks) {
sections[SECTTYPE_ROMX].banks =
realloc(sections[SECTTYPE_ROMX].banks,
sizeof(*sections[SECTTYPE_ROMX].banks) *
nbOverlayBanks);
if (!sections[SECTTYPE_ROMX].banks)
err(1, "Failed to realloc banks for overlay");
sections[SECTTYPE_ROMX].nbBanks = nbOverlayBanks;
}
}
void writebank(FILE *f, FILE *f_overlay, int32_t bank)
static void writeBank(struct SortedSection *bankSections, uint16_t baseOffset,
uint16_t size)
{
const struct sSection *pSect;
uint8_t *mem;
uint16_t offset = 0;
mem = malloc(MaxAvail[bank]);
if (!mem)
while (bankSections) {
struct Section const *section = bankSections->section;
/* Output padding up to the next SECTION */
while (offset + baseOffset < section->org) {
putc_unlocked(overlayFile ? getc_unlocked(overlayFile)
: padValue,
outputFile);
offset++;
}
/* Output the section itself */
fwrite(section->data, sizeof(*section->data), section->size,
outputFile);
if (overlayFile) {
/* Skip bytes even with pipes */
for (uint16_t i = 0; i < section->size; i++)
getc_unlocked(overlayFile);
}
offset += section->size;
bankSections = bankSections->next;
}
while (offset < size) {
putc_unlocked(overlayFile ? getc_unlocked(overlayFile)
: padValue,
outputFile);
offset++;
}
}
static void writeROM(void)
{
checkOverlay();
if (outputFile) {
flockfile(outputFile);
if (overlayFile)
flockfile(overlayFile);
if (sections[SECTTYPE_ROM0].nbBanks > 0)
writeBank(sections[SECTTYPE_ROM0].banks[0].sections,
0x0000, 0x4000);
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++)
writeBank(sections[SECTTYPE_ROMX].banks[i].sections,
0x4000, 0x4000);
if (overlayFile)
funlockfile(overlayFile);
funlockfile(outputFile);
}
}
static struct SortedSection const **nextSection(struct SortedSection const **s1,
struct SortedSection const **s2)
{
if (!*s1)
return s2;
if (!*s2)
return s1;
return (*s1)->section->org < (*s2)->section->org ? s1 : s2;
}
static void writeSymBank(struct SortedSections const *bankSections)
{
if (!symFile)
return;
if (f_overlay != NULL && bank <= MaxOverlayBank) {
fseek(f_overlay, bank * 0x4000, SEEK_SET);
if (fread(mem, 1, MaxAvail[bank], f_overlay) != MaxAvail[bank])
warnx("Failed to read data from overlay file.");
} else {
memset(mem, fillchar, MaxAvail[bank]);
}
MapfileInitBank(bank);
struct {
struct SortedSection const *sections;
#define sect sections->section /* Fake member as a shortcut */
uint32_t i;
struct Symbol const *sym;
uint16_t addr;
} sectList = { .sections = bankSections->sections, .i = 0 },
zlSectList = { .sections = bankSections->zeroLenSections, .i = 0 },
*minSectList;
pSect = pSections;
while (pSect) {
if (pSect->Type == SECT_ROMX && pSect->nBank == bank) {
memcpy(mem + pSect->nOrg - 0x4000, pSect->pData,
pSect->nByteSize);
MapfileWriteSection(pSect);
for (;;) {
while (sectList.sections
&& sectList.i == sectList.sect->nbSymbols) {
sectList.sections = sectList.sections->next;
sectList.i = 0;
}
pSect = pSect->pNext;
}
MapfileCloseBank(area_Avail(bank));
fwrite(mem, 1, MaxAvail[bank], f);
free(mem);
}
void out_Setname(char *tzOutputfile)
{
tzOutname = tzOutputfile;
}
void out_SetOverlayname(char *tzOverlayfile)
{
tzOverlayname = tzOverlayfile;
}
void Output(void)
{
int32_t i;
FILE *f;
FILE *f_overlay = NULL;
/*
* Load overlay
*/
if (tzOverlayname) {
f_overlay = fopen(tzOverlayname, "rb");
if (!f_overlay) {
errx(1, "Failed to open overlay file %s\n",
tzOverlayname);
while (zlSectList.sections
&& zlSectList.i == zlSectList.sect->nbSymbols) {
zlSectList.sections = zlSectList.sections->next;
zlSectList.i = 0;
}
fseek(f_overlay, 0, SEEK_END);
if (!sectList.sections && !zlSectList.sections) {
break;
} else if (sectList.sections && zlSectList.sections) {
sectList.sym = sectList.sect->symbols[sectList.i];
zlSectList.sym = zlSectList.sect->symbols[zlSectList.i];
sectList.addr =
sectList.sym->offset + sectList.sect->org;
zlSectList.addr =
zlSectList.sym->offset + zlSectList.sect->org;
if (ftell(f_overlay) % 0x4000 != 0)
errx(1, "Overlay file must be aligned to 0x4000 bytes.");
minSectList = sectList.addr < zlSectList.addr
? &sectList
: &zlSectList;
} else if (sectList.sect) {
sectList.sym = sectList.sect->symbols[sectList.i];
sectList.addr =
sectList.sym->offset + sectList.sect->org;
MaxOverlayBank = (ftell(f_overlay) / 0x4000) - 1;
minSectList = &sectList;
} else {
zlSectList.sym = zlSectList.sect->symbols[zlSectList.i];
zlSectList.addr =
zlSectList.sym->offset + zlSectList.sect->org;
if (MaxOverlayBank < 1)
errx(1, "Overlay file must be at least 0x8000 bytes.");
if (MaxOverlayBank > MaxBankUsed)
MaxBankUsed = MaxOverlayBank;
}
/*
* Write ROM.
*/
f = fopen(tzOutname, "wb");
if (f != NULL) {
writehome(f, f_overlay);
for (i = 1; i <= MaxBankUsed; i += 1)
writebank(f, f_overlay, i);
fclose(f);
}
/*
* Close overlay
*/
if (tzOverlayname)
fclose(f_overlay);
/*
* Add regular sections to map and sym files.
*/
for (i = BANK_INDEX_WRAM0; i < BANK_INDEX_MAX; i++) {
const struct sSection *pSect;
MapfileInitBank(i);
pSect = pSections;
while (pSect) {
if (pSect->nBank == i)
MapfileWriteSection(pSect);
pSect = pSect->pNext;
minSectList = &zlSectList;
}
fprintf(symFile, "%02x:%04x %s\n",
minSectList->sect->bank, minSectList->addr,
minSectList->sym->name);
minSectList->i++;
}
#undef sect
}
static void writeMapBank(struct SortedSections const *sectList,
enum SectionType type, uint32_t bank)
{
if (!mapFile)
return;
struct SortedSection const *section = sectList->sections;
struct SortedSection const *zeroLenSection = sectList->zeroLenSections;
fprintf(mapFile, "%s bank #%u:\n", typeNames[type],
bank + bankranges[type][0]);
uint16_t slack = maxsize[type];
while (section || zeroLenSection) {
struct SortedSection const **pickedSection =
nextSection(&section, &zeroLenSection);
struct Section const *sect = (*pickedSection)->section;
slack -= sect->size;
fprintf(mapFile, " SECTION: $%04x-$%04x ($%04x byte%s) [\"%s\"]\n",
sect->org, sect->org + sect->size - 1, sect->size,
sect->size == 1 ? "" : "s", sect->name);
for (size_t i = 0; i < sect->nbSymbols; i++)
fprintf(mapFile, " $%04x = %s\n",
sect->symbols[i]->offset + sect->org,
sect->symbols[i]->name);
*pickedSection = (*pickedSection)->next;
}
if (slack == maxsize[type])
fputs(" EMPTY\n\n", mapFile);
else
fprintf(mapFile, " SLACK: $%04x byte%s\n\n", slack,
slack == 1 ? "" : "s");
}
static void writeSymAndMap(void)
{
if (!symFile && !mapFile)
return;
enum SectionType typeMap[SECTTYPE_INVALID] = {
SECTTYPE_ROM0,
SECTTYPE_ROMX,
SECTTYPE_VRAM,
SECTTYPE_SRAM,
SECTTYPE_WRAM0,
SECTTYPE_WRAMX,
SECTTYPE_OAM,
SECTTYPE_HRAM
};
if (symFile)
fputs("; File generated by rgblink\n", symFile);
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
enum SectionType type = typeMap[i];
if (sections[type].nbBanks > 0) {
for (uint32_t bank = 0; bank < sections[type].nbBanks;
bank++) {
writeSymBank(&sections[type].banks[bank]);
writeMapBank(&sections[type].banks[bank],
type, bank);
}
}
MapfileCloseBank(area_Avail(i));
}
}
static void cleanupSections(struct SortedSection *section)
{
while (section) {
struct SortedSection *next = section->next;
free(section);
section = next;
}
}
static void cleanup(void)
{
for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) {
if (sections[type].nbBanks > 0) {
for (uint32_t i = 0; i < sections[type].nbBanks; i++) {
struct SortedSections *bank =
&sections[type].banks[i];
cleanupSections(bank->sections);
cleanupSections(bank->zeroLenSections);
}
free(sections[type].banks);
}
}
}
void out_WriteFiles(void)
{
writeROM();
writeSymAndMap();
cleanup();
}