Add section fragments

Fixes #517, and hopefully enables RGBDS as a SDCC back-end
This commit is contained in:
ISSOtm
2020-07-21 19:53:40 +02:00
parent aca00e4fce
commit 1f2f797cb9
17 changed files with 174 additions and 50 deletions

View File

@@ -19,7 +19,7 @@ struct Expression;
struct Section {
char *pzName;
enum SectionType nType;
bool isUnion;
enum SectionModifier modifier;
uint32_t size;
uint32_t nOrg;
uint32_t nBank;
@@ -38,7 +38,8 @@ struct SectionSpec {
struct Section *out_FindSectionByName(const char *pzName);
void out_NewSection(char const *pzName, uint32_t secttype, uint32_t org,
struct SectionSpec const *attributes, bool isUnion);
struct SectionSpec const *attributes,
enum SectionModifier mod);
void out_SetLoadSection(char const *name, uint32_t secttype, uint32_t org,
struct SectionSpec const *attributes);
void out_EndLoadSection(void);

View File

@@ -42,8 +42,9 @@ struct Section {
/* Info contained in the object files */
char *name;
uint16_t size;
uint16_t offset;
enum SectionType type;
bool isUnion;
enum SectionModifier modifier;
bool isAddressFixed;
uint16_t org;
bool isBankFixed;

View File

@@ -14,7 +14,7 @@
#define RGBDS_OBJECT_VERSION_STRING "RGB%1u"
#define RGBDS_OBJECT_VERSION_NUMBER 9U
#define RGBDS_OBJECT_REV 4U
#define RGBDS_OBJECT_REV 5U
enum AssertionType {
ASSERT_WARN,
@@ -73,6 +73,14 @@ enum SectionType {
SECTTYPE_INVALID
};
enum SectionModifier {
SECTION_NORMAL,
SECTION_UNION,
SECTION_FRAGMENT
};
extern char const * const sectionModNames[];
/**
* Tells whether a section has data in its object file definition,
* depending on type.

View File

@@ -496,6 +496,7 @@ static void strsubUTF8(char *dest, const char *src, uint32_t pos, uint32_t len)
char tzString[MAXSTRLEN + 1];
struct Expression sVal;
int32_t nConstValue;
enum SectionModifier sectMod;
struct SectionSpec sectSpec;
struct MacroArgs *macroArg;
enum AssertionType assertType;
@@ -570,7 +571,7 @@ static void strsubUTF8(char *dest, const char *src, uint32_t pos, uint32_t len)
%token T_POP_IF T_POP_ELIF T_POP_ELSE T_POP_ENDC
%token T_POP_EXPORT T_POP_GLOBAL T_POP_XDEF
%token T_POP_DB T_POP_DS T_POP_DW T_POP_DL
%token T_POP_SECTION
%token T_POP_SECTION T_POP_FRAGMENT
%token T_POP_RB
%token T_POP_RW
%token T_POP_RL
@@ -600,7 +601,7 @@ static void strsubUTF8(char *dest, const char *src, uint32_t pos, uint32_t len)
%token T_SECT_WRAM0 T_SECT_VRAM T_SECT_ROMX T_SECT_ROM0 T_SECT_HRAM
%token T_SECT_WRAMX T_SECT_SRAM T_SECT_OAM
%type <nConstValue> sectunion
%type <sectMod> sectmod
%type <macroArg> macroargs
%token T_Z80_ADC T_Z80_ADD T_Z80_AND
@@ -1438,13 +1439,14 @@ string : T_STRING {
}
;
section : T_POP_SECTION sectunion string ',' sectiontype sectorg sectattrs {
section : T_POP_SECTION sectmod string ',' sectiontype sectorg sectattrs {
out_NewSection($3, $5, $6, &$7, $2);
}
;
sectunion : /* empty */ { $$ = false; }
| T_POP_UNION { $$ = true; }
sectmod : /* empty */ { $$ = SECTION_NORMAL; }
| T_POP_UNION { $$ = SECTION_UNION; }
| T_POP_FRAGMENT{ $$ = SECTION_FRAGMENT; }
;
sectiontype : T_SECT_WRAM0 { $$ = SECTTYPE_WRAM0; }

View File

@@ -442,6 +442,7 @@ const struct sLexInitString lexer_strings[] = {
{"def", T_OP_DEF},
{"fragment", T_POP_FRAGMENT},
{"bank", T_OP_BANK},
{"align", T_OP_ALIGN},

View File

@@ -176,7 +176,10 @@ static void writesection(struct Section const *pSect, FILE *f)
fputlong(pSect->size, f);
fputc(pSect->nType | pSect->isUnion << 7, f);
bool isUnion = pSect->modifier == SECTION_UNION;
bool isFragment = pSect->modifier == SECTION_FRAGMENT;
fputc(pSect->nType | isUnion << 7 | isFragment << 6, f);
fputlong(pSect->nOrg, f);
fputlong(pSect->nBank, f);

View File

@@ -367,7 +367,6 @@ This tells the assembler what kind of information follows and, if it is code, wh
.Pp
.Ar name
is a string enclosed in double quotes, and can be a new name or the name of an existing section.
All sections assembled at the same time that have the same name are considered to be the same section, and their code is put together in the object file generated by the assembler.
If the type doesn't match, an error occurs.
All other sections must have a unique name, even in different source files, or the linker will treat it as an error.
.Pp
@@ -655,7 +654,7 @@ The same unionized section (= having the same name) can be declared several time
invocation, and across several invocations.
Different declarations are treated and merged identically whether within the same invocation, or different ones.
.It
A section cannot be declared both as unionized or non-unionized.
If one section has been declared as unionized, all sections with the same name must be declared unionized as well.
.It
All declarations must have the same type.
For example, even if
@@ -679,6 +678,49 @@ or
Different declarations of the same unionized section are not appended, but instead overlaid on top of eachother, just like
.Sx Unions .
Similarly, the size of an unionized section is the largest of all its declarations.
.Ss Section Fragments
Section fragments are sections with a small twist: when several of the same name are encountered, they are concatenated instead of producing an error.
This works within the same file (paralleling the behavior "plain" sections has in previous versions), but also across object files.
However, similarly to
.Sx Unionized Sections ,
some rules must be followed:
.Bl -bullet -offset indent
.It
If one section has been declared as fragment, all sections with the same name must be declared fragments as well.
.It
All declarations must have the same type.
For example, even if
.Xr rgblink 1 Ap s
.Fl w
flag is used,
.Ic WRAM0
and
.Ic WRAMX
types are still considered different.
.It
Different constraints (alignment, bank, etc.) can be specified for each unionized section declaration, but they must all be compatible.
For example, alignment must be compatible with any fixed address, all specified banks must be the same, etc.
.It
A section fragment may not be unionized; after all, that wouldn't make much sense.
.El
.Pp
When RGBASM merges two fragments, the one encountered later is appended to the one encountered earlier.
.Pp
When RGBLINK merges two fragments, the one whose file was specified last is appended to the one whose file was specified first.
For example, assuming
.Ql bar.o ,
.Ql baz.o ,
and
.Ql foo.o
all contain a fragment with the same name, the command
.Dl rgblink -o rom.gb baz.o foo.o bar.o
would produce the fragment from
.Ql baz.o
first, followed by the one from
.Ql foo.o ,
and the one from
.Ql bar.o
last.
.Sh SYMBOLS
.Pp
RGBDS supports several types of symbols:

View File

@@ -88,7 +88,7 @@ struct Section *out_FindSectionByName(const char *pzName)
*/
static struct Section *getSection(char const *pzName, enum SectionType type,
uint32_t org, struct SectionSpec const *attrs,
bool isUnion)
enum SectionModifier mod)
{
#define mask(align) ((1 << (align)) - 1)
uint32_t bank = attrs->bank;
@@ -150,17 +150,17 @@ static struct Section *getSection(char const *pzName, enum SectionType type,
fail("Section \"%s\" already exists but with type %s",
pSect->pzName, typeNames[pSect->nType]);
if (pSect->modifier != mod)
fail("Section \"%s\" already declared as %s section",
pSect->pzName, sectionModNames[pSect->modifier]);
/*
* Normal sections need to have exactly identical constraints;
* but unionized sections only need "compatible" constraints,
* and they end up with the strictest combination of both
*/
if (isUnion) {
if (!pSect->isUnion)
fail("Section \"%s\" already declared as non-union",
pSect->pzName);
if (mod == SECTION_UNION) {
/*
* WARNING: see comment abount assumption in
* WARNING: see comment about assumption in
* `EndLoadSection` if modifying the following check!
*/
if (sect_HasData(type))
@@ -210,10 +210,11 @@ static struct Section *getSection(char const *pzName, enum SectionType type,
else if (bank != -1 && pSect->nBank != bank)
fail("Section \"%s\" already declared with different bank %" PRIu32,
pSect->pzName, pSect->nBank);
} else {
if (pSect->isUnion)
fail("Section \"%s\" already declared as union",
pSect->pzName);
} else { /* Section fragments are handled identically in RGBASM */
/* However, concaternating non-fragments will be made an error */
if (pSect->modifier != SECTION_FRAGMENT || mod != SECTION_FRAGMENT)
warning(WARNING_OBSOLETE, "Concatenation of non-fragment sections is deprecated");
if (org != pSect->nOrg) {
if (pSect->nOrg == -1)
fail("Section \"%s\" already declared as floating",
@@ -257,7 +258,7 @@ static struct Section *getSection(char const *pzName, enum SectionType type,
fatalerror("Not enough memory for sectionname");
pSect->nType = type;
pSect->isUnion = isUnion;
pSect->modifier = mod;
pSect->size = 0;
pSect->nOrg = org;
pSect->nBank = bank;
@@ -303,15 +304,15 @@ static void changeSection(void)
* Set the current section by name and type
*/
void out_NewSection(char const *pzName, uint32_t type, uint32_t org,
struct SectionSpec const *attribs, bool isUnion)
struct SectionSpec const *attribs, enum SectionModifier mod)
{
if (currentLoadSection)
fatalerror("Cannot change the section within a `LOAD` block");
struct Section *pSect = getSection(pzName, type, org, attribs, isUnion);
struct Section *pSect = getSection(pzName, type, org, attribs, mod);
changeSection();
curOffset = isUnion ? 0 : pSect->size;
curOffset = mod == SECTION_UNION ? 0 : pSect->size;
pCurrentSection = pSect;
}

View File

@@ -266,10 +266,16 @@ static void readSection(FILE *file, struct Section *section,
errx(1, "\"%s\"'s section size (%" PRId32 ") is invalid",
section->name, tmp);
section->size = tmp;
section->offset = 0;
tryGetc(byte, file, "%s: Cannot read \"%s\"'s type: %s",
fileName, section->name);
section->type = byte & 0x7F;
section->isUnion = byte >> 7;
section->type = byte & 0x3F;
if (byte >> 7)
section->modifier = SECTION_UNION;
else if (byte >> 6)
section->modifier = SECTION_FRAGMENT;
else
section->modifier = SECTION_NORMAL;
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s org: %s",
fileName, section->name);
section->isAddressFixed = tmp >= 0;
@@ -382,7 +388,7 @@ static void readAssertion(FILE *file, struct Assertion *assert,
static inline struct Section *getMainSection(struct Section *section)
{
if (section->isUnion)
if (section->modifier != SECTION_NORMAL)
section = sect_GetSection(section->name);
return section;
@@ -502,10 +508,18 @@ void obj_ReadFile(char const *fileName)
if (sectionID == -1) {
fileSymbols[i]->section = NULL;
} else {
struct Section *section = fileSections[sectionID];
/* Give the section a pointer to the symbol as well */
linkSymToSect(fileSymbols[i], fileSections[sectionID]);
fileSymbols[i]->section =
getMainSection(fileSections[sectionID]);
linkSymToSect(fileSymbols[i], section);
if (section->modifier != SECTION_NORMAL) {
if (section->modifier == SECTION_FRAGMENT)
/* Add the fragment's offset to the symbol's */
fileSymbols[i]->offset += section->offset;
section = getMainSection(section);
}
fileSymbols[i]->section = section;
}
}

View File

@@ -421,7 +421,7 @@ void patch_CheckAssertions(struct Assertion *assert)
* @param section The section to patch
* @param arg Ignored callback arg
*/
static void applyFilePatches(struct Section *section)
static void applyFilePatches(struct Section *section, struct Section *dataSection)
{
if (!sect_HasData(section->type))
return;
@@ -432,6 +432,7 @@ static void applyFilePatches(struct Section *section)
int32_t value = computeRPNExpr(patch,
(struct Symbol const * const *)
section->fileSymbols);
uint16_t offset = patch->offset + section->offset;
/* `jr` is quite unlike the others... */
if (patch->type == PATCHTYPE_JR) {
@@ -443,7 +444,7 @@ static void applyFilePatches(struct Section *section)
if (offset < -128 || offset > 127)
error("%s: jr target out of reach (expected -129 < %" PRId16 " < 128)",
patch->fileName, offset);
section->data[patch->offset] = offset & 0xFF;
dataSection->data[offset] = offset & 0xFF;
} else {
/* Patch a certain number of bytes */
struct {
@@ -463,7 +464,7 @@ static void applyFilePatches(struct Section *section)
value < 0 ? " (maybe negative?)" : "",
types[patch->type].size * 8U);
for (uint8_t i = 0; i < types[patch->type].size; i++) {
section->data[patch->offset + i] = value & 0xFF;
dataSection->data[offset + i] = value & 0xFF;
value >>= 8;
}
}
@@ -479,9 +480,10 @@ static void applyFilePatches(struct Section *section)
static void applyPatches(struct Section *section, void *arg)
{
(void)arg;
struct Section *dataSection = section;
do {
applyFilePatches(section);
applyFilePatches(section, dataSection);
section = section->nextu;
} while (section);
}

View File

@@ -8,6 +8,8 @@
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "link/main.h"
#include "link/section.h"
@@ -37,7 +39,7 @@ void sect_ForEach(void (*callback)(struct Section *, void *), void *arg)
hash_ForEach(sections, forEach, &callbackArg);
}
static void mergeSections(struct Section *target, struct Section *other)
static void mergeSections(struct Section *target, struct Section *other, enum SectionModifier mod)
{
if (target->type != other->type)
errx(1, "Section \"%s\" is defined with conflicting types %s and %s",
@@ -86,9 +88,30 @@ static void mergeSections(struct Section *target, struct Section *other)
}
}
switch (mod) {
case SECTION_UNION:
if (other->size > target->size)
target->size = other->size;
break;
case SECTION_FRAGMENT:
target->size += other->size;
other->offset = target->size - other->size;
if (sect_HasData(target->type)) {
/* Ensure we're not allocating 0 bytes */
target->data = realloc(target->data,
sizeof(*target->data) * target->size + 1);
if (!target->data)
errx(1, "Failed to concatenate \"%s\"'s fragments", target->name);
memcpy(target->data + target->size - other->size, other->data, other->size);
}
break;
case SECTION_NORMAL:
trap_;
}
other->nextu = target->nextu;
target->nextu = other;
}
@@ -98,16 +121,14 @@ void sect_AddSection(struct Section *section)
struct Section *other = hash_GetElement(sections, section->name);
if (other) {
if (other->isUnion && section->isUnion) {
mergeSections(other, section);
} else if (section->isUnion || other->isUnion) {
errx(1, "Section \"%s\" defined as both unionized and not",
section->name);
} else {
errx(1, "Section name \"%s\" is already in use",
section->name);
}
} else if (section->isUnion && sect_HasData(section->type)) {
if (section->modifier != other->modifier)
errx(1, "Section \"%s\" defined as %s and %s", section->name,
sectionModNames[section->modifier], sectionModNames[other->modifier]);
else if (section->modifier == SECTION_NORMAL)
errx(1, "Section name \"%s\" is already in use", section->name);
else
mergeSections(other, section, section->modifier);
} else if (section->modifier == SECTION_UNION && sect_HasData(section->type)) {
errx(1, "Section \"%s\" is of type %s, which cannot be unionized",
section->name, typeNames[section->type]);
} else {

View File

@@ -44,3 +44,9 @@ char const * const typeNames[] = {
[SECTTYPE_OAM] = "OAM",
[SECTTYPE_HRAM] = "HRAM"
};
char const * const sectionModNames[] = {
[SECTION_NORMAL] = "regular",
[SECTION_UNION] = "union",
[SECTION_FRAGMENT] = "fragment",
};

View File

@@ -1,5 +1,7 @@
ERROR: section-union.asm(37):
Section "test" already declared as union
Section "test" already declared as union section
warning: section-union.asm(37): [-Wobsolete]
Concatenation of non-fragment sections is deprecated
ERROR: section-union.asm(37):
Section "test" already declared as fixed at $c000
ERROR: section-union.asm(37):

View File

@@ -0,0 +1,7 @@
SECTION FRAGMENT "output", ROM0
X:
db X
db 1
assert WARN, X == 0

View File

@@ -0,0 +1,7 @@
SECTION FRAGMENT "output", ROM0
Y:
db Y
db 3
assert WARN, Y == 2

Binary file not shown.

View File

@@ -92,6 +92,12 @@ $RGBLINK -o $gbtemp -l section-union/good/script.link $otemp $gbtemp2
dd if=$gbtemp count=1 bs=$(printf %s $(wc -c < section-union/good/ref.out.bin)) > $otemp 2>/dev/null
i="section-union/good.asm" tryCmp section-union/good/ref.out.bin $otemp
rc=$(($? || $rc))
$RGBASM -o $otemp section-union/fragments/a.asm
$RGBASM -o $gbtemp2 section-union/fragments/b.asm
$RGBLINK -o $gbtemp $otemp $gbtemp2
dd if=$gbtemp count=1 bs=$(printf %s $(wc -c < section-union/fragments/ref.out.bin)) > $otemp 2>/dev/null
i="section-union/fragments.asm" tryCmp section-union/fragments/ref.out.bin $otemp
rc=$(($? || $rc))
for i in section-union/*.asm; do
$RGBASM -o $otemp $i
$RGBASM -o $gbtemp2 $i -DSECOND