shithub: rgbds

ref: 03e20138d37722ba2a9c9a22d36f45262adf0348
dir: /src/asm/output.c/

View raw version
/*
 * This file is part of RGBDS.
 *
 * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
 *
 * SPDX-License-Identifier: MIT
 */

/*
 * Outputs an objectfile
 */

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "asm/asm.h"
#include "asm/charmap.h"
#include "asm/fstack.h"
#include "asm/main.h"
#include "asm/output.h"
#include "asm/rpn.h"
#include "asm/section.h"
#include "asm/symbol.h"
#include "asm/warning.h"

#include "extern/err.h"

#include "linkdefs.h"
#include "platform.h" // strdup

struct Patch {
	char tzFilename[_MAX_PATH + 1];
	uint32_t nOffset;
	struct Section *pcSection;
	uint32_t pcOffset;
	uint8_t type;
	uint32_t nRPNSize;
	uint8_t *pRPN;
	struct Patch *next;
};

struct Assertion {
	struct Patch *patch;
	struct Section *section;
	char *message;
	struct Assertion *next;
};

char *tzObjectname;

/* TODO: shouldn't `pCurrentSection` be somewhere else? */
struct Section *pSectionList, *pCurrentSection;

/* Linked list of symbols to put in the object file */
static struct Symbol *objectSymbols = NULL;
static struct Symbol **objectSymbolsTail = &objectSymbols;
static uint32_t nbSymbols = 0; /* Length of the above list */

static struct Assertion *assertions = NULL;

/*
 * Count the number of sections used in this object
 */
static uint32_t countsections(void)
{
	struct Section *sect;
	uint32_t count = 0;

	sect = pSectionList;
	while (sect) {
		count++;
		sect = sect->next;
	}

	return count;
}

/*
 * Count the number of patches used in this object
 */
static uint32_t countpatches(struct Section const *sect)
{
	uint32_t r = 0;

	for (struct Patch const *patch = sect->patches; patch != NULL;
	     patch = patch->next)
		r++;

	return r;
}

/**
 * Count the number of assertions used in this object
 */
static uint32_t countasserts(void)
{
	struct Assertion *assert = assertions;
	uint32_t count = 0;

	while (assert) {
		count++;
		assert = assert->next;
	}
	return count;
}

/*
 * Write a long to a file (little-endian)
 */
static void fputlong(uint32_t i, FILE *f)
{
	fputc(i, f);
	fputc(i >> 8, f);
	fputc(i >> 16, f);
	fputc(i >> 24, f);
}

/*
 * Write a NULL-terminated string to a file
 */
static void fputstring(char const *s, FILE *f)
{
	while (*s)
		fputc(*s++, f);
	fputc(0, f);
}

/*
 * Return a section's ID
 */
static uint32_t getsectid(struct Section const *sect)
{
	struct Section const *sec;
	uint32_t ID = 0;

	sec = pSectionList;

	while (sec) {
		if (sec == sect)
			return ID;
		ID++;
		sec = sec->next;
	}

	fatalerror("Unknown section '%s'\n", sect->name);
}

static uint32_t getSectIDIfAny(struct Section const *sect)
{
	return sect ? getsectid(sect) : -1;
}

/*
 * Write a patch to a file
 */
static void writepatch(struct Patch const *patch, FILE *f)
{
	fputstring(patch->tzFilename, f);
	fputlong(patch->nOffset, f);
	fputlong(getSectIDIfAny(patch->pcSection), f);
	fputlong(patch->pcOffset, f);
	fputc(patch->type, f);
	fputlong(patch->nRPNSize, f);
	fwrite(patch->pRPN, 1, patch->nRPNSize, f);
}

/*
 * Write a section to a file
 */
static void writesection(struct Section const *sect, FILE *f)
{
	fputstring(sect->name, f);

	fputlong(sect->size, f);

	bool isUnion = sect->modifier == SECTION_UNION;
	bool isFragment = sect->modifier == SECTION_FRAGMENT;

	fputc(sect->type | isUnion << 7 | isFragment << 6, f);

	fputlong(sect->org, f);
	fputlong(sect->bank, f);
	fputc(sect->align, f);
	fputlong(sect->alignOfs, f);

	if (sect_HasData(sect->type)) {
		fwrite(sect->data, 1, sect->size, f);
		fputlong(countpatches(sect), f);

		for (struct Patch const *patch = sect->patches; patch != NULL;
		     patch = patch->next)
			writepatch(patch, f);
	}
}

/*
 * Write a symbol to a file
 */
static void writesymbol(struct Symbol const *sym, FILE *f)
{
	fputstring(sym->name, f);
	if (!sym_IsDefined(sym)) {
		fputc(SYMTYPE_IMPORT, f);
	} else {
		fputc(sym->isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, f);
		fputstring(sym->fileName, f);
		fputlong(sym->fileLine, f);
		fputlong(getSectIDIfAny(sym_GetSection(sym)), f);
		fputlong(sym->value, f);
	}
}

/*
 * Returns a symbol's ID within the object file
 * If the symbol does not have one, one is assigned by registering the symbol
 */
static uint32_t getSymbolID(struct Symbol *sym)
{
	if (sym->ID == -1) {
		sym->ID = nbSymbols++;

		*objectSymbolsTail = sym;
		objectSymbolsTail = &sym->next;
	}
	return sym->ID;
}

static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
		     uint32_t rpnlen)
{
	char tzSym[512];

	for (size_t offset = 0; offset < rpnlen; ) {
#define popbyte() rpn[offset++]
#define writebyte(byte)	rpnexpr[(*rpnptr)++] = byte
		uint8_t rpndata = popbyte();

		switch (rpndata) {
		case RPN_CONST:
			writebyte(RPN_CONST);
			writebyte(popbyte());
			writebyte(popbyte());
			writebyte(popbyte());
			writebyte(popbyte());
			break;
		case RPN_SYM:
		{
			for (unsigned int i = -1; (tzSym[++i] = popbyte()); )
				;
			struct Symbol *sym = sym_FindSymbol(tzSym);
			uint32_t value;

			if (sym_IsConstant(sym)) {
				writebyte(RPN_CONST);
				value = sym_GetConstantValue(tzSym);
			} else {
				writebyte(RPN_SYM);
				value = getSymbolID(sym);
			}
			writebyte(value & 0xFF);
			writebyte(value >> 8);
			writebyte(value >> 16);
			writebyte(value >> 24);
			break;
		}
		case RPN_BANK_SYM:
		{
			for (unsigned int i = -1; (tzSym[++i] = popbyte()); )
				;
			struct Symbol *sym = sym_FindSymbol(tzSym);
			uint32_t value = getSymbolID(sym);

			writebyte(RPN_BANK_SYM);
			writebyte(value & 0xFF);
			writebyte(value >> 8);
			writebyte(value >> 16);
			writebyte(value >> 24);
			break;
		}
		case RPN_BANK_SECT:
		{
			uint8_t b;

			writebyte(RPN_BANK_SECT);
			do {
				b = popbyte();
				writebyte(b);
			} while (b != 0);
			break;
		}
		default:
			writebyte(rpndata);
			break;
		}
#undef popbyte
#undef writebyte
	}
}

/*
 * Allocate a new patch structure and link it into the list
 */
static struct Patch *allocpatch(uint32_t type, struct Expression const *expr,
				uint32_t ofs)
{
	struct Patch *patch = malloc(sizeof(struct Patch));
	uint32_t rpnSize = expr->isKnown ? 5 : expr->nRPNPatchSize;

	if (!patch)
		fatalerror("No memory for patch: %s\n", strerror(errno));
	patch->pRPN = malloc(sizeof(*patch->pRPN) * rpnSize);

	if (!patch->pRPN)
		fatalerror("No memory for patch's RPN expression: %s\n", strerror(errno));

	patch->type = type;
	fstk_DumpToStr(patch->tzFilename, sizeof(patch->tzFilename));
	patch->nOffset = ofs;
	patch->pcSection = sect_GetSymbolSection();
	patch->pcOffset = sect_GetSymbolOffset();

	/* If the expression's value is known, output a constant RPN expression directly */
	if (expr->isKnown) {
		patch->nRPNSize = rpnSize;
		/* Make sure to update `rpnSize` above if modifying this! */
		patch->pRPN[0] = RPN_CONST;
		patch->pRPN[1] = (uint32_t)(expr->nVal) & 0xFF;
		patch->pRPN[2] = (uint32_t)(expr->nVal) >> 8;
		patch->pRPN[3] = (uint32_t)(expr->nVal) >> 16;
		patch->pRPN[4] = (uint32_t)(expr->nVal) >> 24;
	} else {
		patch->nRPNSize = 0;
		writerpn(patch->pRPN, &patch->nRPNSize, expr->tRPN, expr->nRPNLength);
	}
	assert(patch->nRPNSize == rpnSize);

	return patch;
}

/*
 * Create a new patch (includes the rpn expr)
 */
void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs)
{
	struct Patch *patch = allocpatch(type, expr, ofs);

	patch->next = pCurrentSection->patches;
	pCurrentSection->patches = patch;
}

/**
 * Creates an assert that will be written to the object file
 */
bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
		      char const *message, uint32_t ofs)
{
	struct Assertion *assertion = malloc(sizeof(*assertion));

	if (!assertion)
		return false;

	assertion->patch = allocpatch(type, expr, ofs);
	assertion->message = strdup(message);
	if (!assertion->message) {
		free(assertion);
		return false;
	}

	assertion->next = assertions;
	assertions = assertion;

	return true;
}

static void writeassert(struct Assertion *assert, FILE *f)
{
	writepatch(assert->patch, f);
	fputstring(assert->message, f);
}

static void registerExportedSymbol(struct Symbol *symbol, void *arg)
{
	(void)arg;
	if (sym_IsExported(symbol) && symbol->ID == -1) {
		*objectSymbolsTail = symbol;
		objectSymbolsTail = &symbol->next;
		nbSymbols++;
	}
}

/*
 * Write an objectfile
 */
void out_WriteObject(void)
{
	FILE *f = fopen(tzObjectname, "wb");

	if (!f)
		err(1, "Couldn't write file '%s'", tzObjectname);

	/* Also write exported symbols that weren't written above */
	sym_ForEach(registerExportedSymbol, NULL);

	fprintf(f, RGBDS_OBJECT_VERSION_STRING, RGBDS_OBJECT_VERSION_NUMBER);
	fputlong(RGBDS_OBJECT_REV, f);

	fputlong(nbSymbols, f);
	fputlong(countsections(), f);

	for (struct Symbol const *sym = objectSymbols; sym; sym = sym->next)
		writesymbol(sym, f);

	for (struct Section *sect = pSectionList; sect; sect = sect->next)
		writesection(sect, f);

	fputlong(countasserts(), f);
	for (struct Assertion *assert = assertions; assert;
	     assert = assert->next)
		writeassert(assert, f);

	fclose(f);
}

/*
 * Set the objectfilename
 */
void out_SetFileName(char *s)
{
	tzObjectname = s;
	if (verbose)
		printf("Output filename %s\n", s);
}