mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
417 lines
9.9 KiB
C++
417 lines
9.9 KiB
C++
/* SPDX-License-Identifier: MIT */
|
|
|
|
// Outputs an objectfile
|
|
|
|
#include <algorithm>
|
|
#include <assert.h>
|
|
#include <deque>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string>
|
|
#include <string.h>
|
|
#include <vector>
|
|
|
|
#include "asm/charmap.hpp"
|
|
#include "asm/fstack.hpp"
|
|
#include "asm/main.hpp"
|
|
#include "asm/output.hpp"
|
|
#include "asm/rpn.hpp"
|
|
#include "asm/section.hpp"
|
|
#include "asm/symbol.hpp"
|
|
#include "asm/warning.hpp"
|
|
|
|
#include "error.hpp"
|
|
#include "linkdefs.hpp"
|
|
#include "platform.hpp" // strdup
|
|
|
|
struct Assertion {
|
|
struct Patch patch;
|
|
struct Section *section;
|
|
std::string message;
|
|
};
|
|
|
|
const char *objectName;
|
|
|
|
// List of symbols to put in the object file
|
|
static std::vector<struct Symbol *> objectSymbols;
|
|
|
|
static std::deque<struct Assertion> assertions;
|
|
|
|
static std::deque<struct FileStackNode *> fileStackNodes;
|
|
|
|
// Write a long to a file (little-endian)
|
|
static void putlong(uint32_t i, FILE *f)
|
|
{
|
|
putc(i, f);
|
|
putc(i >> 8, f);
|
|
putc(i >> 16, f);
|
|
putc(i >> 24, f);
|
|
}
|
|
|
|
// Write a NULL-terminated string to a file
|
|
static void putstring(char const *s, FILE *f)
|
|
{
|
|
while (*s)
|
|
putc(*s++, f);
|
|
putc(0, f);
|
|
}
|
|
|
|
void out_RegisterNode(struct FileStackNode *node)
|
|
{
|
|
// If node is not already registered, register it (and parents), and give it a unique ID
|
|
while (node->ID == (uint32_t)-1) {
|
|
node->ID = fileStackNodes.size();
|
|
fileStackNodes.push_front(node);
|
|
|
|
// Also register the node's parents
|
|
node = node->parent;
|
|
if (!node)
|
|
break;
|
|
}
|
|
}
|
|
|
|
void out_ReplaceNode(struct FileStackNode * /* node */)
|
|
{
|
|
#if 0
|
|
This is code intended to replace a node, which is pretty useless until ref counting is added...
|
|
|
|
auto search = std::find(RANGE(fileStackNodes), node);
|
|
assert(search != fileStackNodes.end());
|
|
// The list is supposed to have decrementing IDs; catch inconsistencies early
|
|
assert(search->ID == node->ID);
|
|
assert(search + 1 == fileStackNodes.end() || (search + 1)->ID == node->ID - 1);
|
|
|
|
// TODO: unreference the node
|
|
*search = node;
|
|
#endif
|
|
}
|
|
|
|
// Return a section's ID, or -1 if the section is not in the list
|
|
static uint32_t getSectIDIfAny(struct Section *sect)
|
|
{
|
|
if (!sect)
|
|
return (uint32_t)-1;
|
|
|
|
for (auto it = sectionList.begin(); it != sectionList.end(); it++) {
|
|
if (&*it == sect)
|
|
return it - sectionList.begin();
|
|
}
|
|
|
|
fatalerror("Unknown section '%s'\n", sect->name);
|
|
}
|
|
|
|
// Write a patch to a file
|
|
static void writepatch(struct Patch const &patch, FILE *f)
|
|
{
|
|
assert(patch.src->ID != (uint32_t)-1);
|
|
putlong(patch.src->ID, f);
|
|
putlong(patch.lineNo, f);
|
|
putlong(patch.offset, f);
|
|
putlong(getSectIDIfAny(patch.pcSection), f);
|
|
putlong(patch.pcOffset, f);
|
|
putc(patch.type, f);
|
|
putlong(patch.rpn.size(), f);
|
|
fwrite(patch.rpn.data(), 1, patch.rpn.size(), f);
|
|
}
|
|
|
|
// Write a section to a file
|
|
static void writesection(struct Section const §, FILE *f)
|
|
{
|
|
putstring(sect.name, f);
|
|
|
|
putlong(sect.size, f);
|
|
|
|
bool isUnion = sect.modifier == SECTION_UNION;
|
|
bool isFragment = sect.modifier == SECTION_FRAGMENT;
|
|
|
|
putc(sect.type | isUnion << 7 | isFragment << 6, f);
|
|
|
|
putlong(sect.org, f);
|
|
putlong(sect.bank, f);
|
|
putc(sect.align, f);
|
|
putlong(sect.alignOfs, f);
|
|
|
|
if (sect_HasData(sect.type)) {
|
|
fwrite(sect.data.data(), 1, sect.size, f);
|
|
putlong(sect.patches.size(), f);
|
|
|
|
for (struct Patch const &patch : sect.patches)
|
|
writepatch(patch, f);
|
|
}
|
|
}
|
|
|
|
// Write a symbol to a file
|
|
static void writesymbol(struct Symbol const *sym, FILE *f)
|
|
{
|
|
putstring(sym->name, f);
|
|
if (!sym_IsDefined(sym)) {
|
|
putc(SYMTYPE_IMPORT, f);
|
|
} else {
|
|
assert(sym->src->ID != (uint32_t)-1);
|
|
|
|
putc(sym->isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, f);
|
|
putlong(sym->src->ID, f);
|
|
putlong(sym->fileLine, f);
|
|
putlong(getSectIDIfAny(sym_GetSection(sym)), f);
|
|
putlong(sym->value, f);
|
|
}
|
|
}
|
|
|
|
static void registerSymbol(struct Symbol *sym)
|
|
{
|
|
sym->ID = objectSymbols.size();
|
|
objectSymbols.push_back(sym);
|
|
out_RegisterNode(sym->src);
|
|
}
|
|
|
|
// 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 == (uint32_t)-1 && !sym_IsPC(sym))
|
|
registerSymbol(sym);
|
|
return sym->ID;
|
|
}
|
|
|
|
static void writerpn(std::vector<uint8_t> &rpnexpr, const std::vector<uint8_t> &rpn)
|
|
{
|
|
char symName[512];
|
|
size_t rpnptr = 0;
|
|
|
|
for (size_t offset = 0; offset < rpn.size(); ) {
|
|
#define popbyte() rpn[offset++]
|
|
#define writebyte(byte) rpnexpr[rpnptr++] = byte
|
|
uint8_t rpndata = popbyte();
|
|
|
|
switch (rpndata) {
|
|
struct Symbol *sym;
|
|
uint32_t value;
|
|
uint8_t b;
|
|
size_t i;
|
|
|
|
case RPN_CONST:
|
|
writebyte(RPN_CONST);
|
|
writebyte(popbyte());
|
|
writebyte(popbyte());
|
|
writebyte(popbyte());
|
|
writebyte(popbyte());
|
|
break;
|
|
|
|
case RPN_SYM:
|
|
i = 0;
|
|
do {
|
|
symName[i] = popbyte();
|
|
} while (symName[i++]);
|
|
|
|
// The symbol name is always written expanded
|
|
sym = sym_FindExactSymbol(symName);
|
|
if (sym_IsConstant(sym)) {
|
|
writebyte(RPN_CONST);
|
|
value = sym_GetConstantValue(symName);
|
|
} else {
|
|
writebyte(RPN_SYM);
|
|
value = getSymbolID(sym);
|
|
}
|
|
|
|
writebyte(value & 0xFF);
|
|
writebyte(value >> 8);
|
|
writebyte(value >> 16);
|
|
writebyte(value >> 24);
|
|
break;
|
|
|
|
case RPN_BANK_SYM:
|
|
i = 0;
|
|
do {
|
|
symName[i] = popbyte();
|
|
} while (symName[i++]);
|
|
|
|
// The symbol name is always written expanded
|
|
sym = sym_FindExactSymbol(symName);
|
|
value = getSymbolID(sym);
|
|
|
|
writebyte(RPN_BANK_SYM);
|
|
writebyte(value & 0xFF);
|
|
writebyte(value >> 8);
|
|
writebyte(value >> 16);
|
|
writebyte(value >> 24);
|
|
break;
|
|
|
|
case RPN_BANK_SECT:
|
|
writebyte(RPN_BANK_SECT);
|
|
do {
|
|
b = popbyte();
|
|
writebyte(b);
|
|
} while (b != 0);
|
|
break;
|
|
|
|
case RPN_SIZEOF_SECT:
|
|
writebyte(RPN_SIZEOF_SECT);
|
|
do {
|
|
b = popbyte();
|
|
writebyte(b);
|
|
} while (b != 0);
|
|
break;
|
|
|
|
case RPN_STARTOF_SECT:
|
|
writebyte(RPN_STARTOF_SECT);
|
|
do {
|
|
b = popbyte();
|
|
writebyte(b);
|
|
} while (b != 0);
|
|
break;
|
|
|
|
default:
|
|
writebyte(rpndata);
|
|
break;
|
|
}
|
|
#undef popbyte
|
|
#undef writebyte
|
|
}
|
|
}
|
|
|
|
static void initpatch(struct Patch &patch, uint32_t type, struct Expression const *expr, uint32_t ofs)
|
|
{
|
|
uint32_t rpnSize = rpn_isKnown(expr) ? 5 : expr->rpnPatchSize;
|
|
struct FileStackNode *node = fstk_GetFileStack();
|
|
|
|
patch.rpn.resize(rpnSize);
|
|
patch.type = type;
|
|
patch.src = node;
|
|
// All patches are assumed to eventually be written, so the file stack node is registered
|
|
out_RegisterNode(node);
|
|
patch.lineNo = lexer_GetLineNo();
|
|
patch.offset = ofs;
|
|
patch.pcSection = sect_GetSymbolSection();
|
|
patch.pcOffset = sect_GetSymbolOffset();
|
|
|
|
// If the rpnSize's value is known, output a constant RPN rpnSize directly
|
|
if (rpn_isKnown(expr)) {
|
|
// Make sure to update `rpnSize` above if modifying this!
|
|
patch.rpn[0] = RPN_CONST;
|
|
patch.rpn[1] = (uint32_t)(expr->val) & 0xFF;
|
|
patch.rpn[2] = (uint32_t)(expr->val) >> 8;
|
|
patch.rpn[3] = (uint32_t)(expr->val) >> 16;
|
|
patch.rpn[4] = (uint32_t)(expr->val) >> 24;
|
|
} else {
|
|
writerpn(patch.rpn, *expr->rpn);
|
|
}
|
|
}
|
|
|
|
// Create a new patch (includes the rpn expr)
|
|
void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, uint32_t pcShift)
|
|
{
|
|
// Add the patch to the list
|
|
struct Patch &patch = currentSection->patches.emplace_front();
|
|
|
|
initpatch(patch, type, expr, ofs);
|
|
|
|
// If the patch had a quantity of bytes output before it,
|
|
// PC is not at the patch's location, but at the location
|
|
// before those bytes.
|
|
patch.pcOffset -= pcShift;
|
|
}
|
|
|
|
// Creates an assert that will be written to the object file
|
|
void out_CreateAssert(enum AssertionType type, struct Expression const *expr,
|
|
char const *message, uint32_t ofs)
|
|
{
|
|
struct Assertion &assertion = assertions.emplace_front();
|
|
|
|
initpatch(assertion.patch, type, expr, ofs);
|
|
assertion.message = message;
|
|
}
|
|
|
|
static void writeassert(struct Assertion &assert, FILE *f)
|
|
{
|
|
writepatch(assert.patch, f);
|
|
putstring(assert.message.c_str(), f);
|
|
}
|
|
|
|
static void writeFileStackNode(struct FileStackNode const *node, FILE *f)
|
|
{
|
|
putlong(node->parent ? node->parent->ID : (uint32_t)-1, f);
|
|
putlong(node->lineNo, f);
|
|
putc(node->type, f);
|
|
if (node->type != NODE_REPT) {
|
|
putstring(((struct FileStackNamedNode const *)node)->name.c_str(), f);
|
|
} else {
|
|
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
|
|
|
|
putlong(reptNode->iters.size(), f);
|
|
// Iters are stored by decreasing depth, so reverse the order for output
|
|
for (uint32_t i = reptNode->iters.size(); i--; )
|
|
putlong(reptNode->iters[i], f);
|
|
}
|
|
}
|
|
|
|
static void registerUnregisteredSymbol(struct Symbol *symbol)
|
|
{
|
|
// Check for symbol->src, to skip any built-in symbol from rgbasm
|
|
if (symbol->src && symbol->ID == (uint32_t)-1) {
|
|
registerSymbol(symbol);
|
|
}
|
|
}
|
|
|
|
// Write an objectfile
|
|
void out_WriteObject(void)
|
|
{
|
|
FILE *f;
|
|
|
|
if (strcmp(objectName, "-")) {
|
|
f = fopen(objectName, "wb");
|
|
} else {
|
|
objectName = "<stdout>";
|
|
f = fdopen(STDOUT_FILENO, "wb");
|
|
}
|
|
if (!f)
|
|
err("Failed to open object file '%s'", objectName);
|
|
|
|
// Also write symbols that weren't written above
|
|
sym_ForEach(registerUnregisteredSymbol);
|
|
|
|
fprintf(f, RGBDS_OBJECT_VERSION_STRING);
|
|
putlong(RGBDS_OBJECT_REV, f);
|
|
|
|
putlong(objectSymbols.size(), f);
|
|
putlong(sectionList.size(), f);
|
|
|
|
putlong(fileStackNodes.size(), f);
|
|
for (auto it = fileStackNodes.begin(); it != fileStackNodes.end(); it++) {
|
|
struct FileStackNode const *node = *it;
|
|
|
|
writeFileStackNode(node, f);
|
|
|
|
// The list is supposed to have decrementing IDs
|
|
if (it + 1 != fileStackNodes.end() && it[1]->ID != node->ID - 1)
|
|
fatalerror("Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
|
". Please report this to the developers!\n",
|
|
it[1]->ID, node->ID);
|
|
}
|
|
|
|
for (struct Symbol const *sym : objectSymbols)
|
|
writesymbol(sym, f);
|
|
|
|
for (struct Section § : sectionList)
|
|
writesection(sect, f);
|
|
|
|
putlong(assertions.size(), f);
|
|
|
|
for (struct Assertion &assert : assertions)
|
|
writeassert(assert, f);
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
// Set the objectfilename
|
|
void out_SetFileName(char *s)
|
|
{
|
|
if (objectName)
|
|
warnx("Overriding output filename %s", objectName);
|
|
objectName = s;
|
|
if (verbose)
|
|
printf("Output filename %s\n", objectName);
|
|
}
|