From 1f2f797cb9967645f5a5714431eebacf5d289edf Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Tue, 21 Jul 2020 19:53:40 +0200 Subject: [PATCH] Add section fragments Fixes #517, and hopefully enables RGBDS as a SDCC back-end --- include/asm/section.h | 5 +- include/link/section.h | 3 +- include/linkdefs.h | 10 +++- src/asm/asmy.y | 12 +++-- src/asm/globlex.c | 1 + src/asm/output.c | 5 +- src/asm/rgbasm.5 | 46 ++++++++++++++++- src/asm/section.c | 29 +++++------ src/link/object.c | 26 +++++++--- src/link/patch.c | 10 ++-- src/link/section.c | 47 +++++++++++++----- src/linkdefs.c | 6 +++ test/asm/section-union.err | 4 +- test/link/section-union/fragments/a.asm | 7 +++ test/link/section-union/fragments/b.asm | 7 +++ test/link/section-union/fragments/ref.out.bin | Bin 0 -> 4 bytes test/link/test.sh | 6 +++ 17 files changed, 174 insertions(+), 50 deletions(-) create mode 100644 test/link/section-union/fragments/a.asm create mode 100644 test/link/section-union/fragments/b.asm create mode 100644 test/link/section-union/fragments/ref.out.bin diff --git a/include/asm/section.h b/include/asm/section.h index 6a7f42bd..8fe16ca7 100644 --- a/include/asm/section.h +++ b/include/asm/section.h @@ -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); diff --git a/include/link/section.h b/include/link/section.h index af3804dc..24280412 100644 --- a/include/link/section.h +++ b/include/link/section.h @@ -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; diff --git a/include/linkdefs.h b/include/linkdefs.h index c7f16b75..029501ca 100644 --- a/include/linkdefs.h +++ b/include/linkdefs.h @@ -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. diff --git a/src/asm/asmy.y b/src/asm/asmy.y index 8693ac4c..da6b7b4c 100644 --- a/src/asm/asmy.y +++ b/src/asm/asmy.y @@ -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 sectunion +%type sectmod %type 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; } diff --git a/src/asm/globlex.c b/src/asm/globlex.c index dc56ddf1..43fc68a3 100644 --- a/src/asm/globlex.c +++ b/src/asm/globlex.c @@ -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}, diff --git a/src/asm/output.c b/src/asm/output.c index 646efb9f..7954fdf1 100644 --- a/src/asm/output.c +++ b/src/asm/output.c @@ -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); diff --git a/src/asm/rgbasm.5 b/src/asm/rgbasm.5 index e8393cda..7c44f35c 100644 --- a/src/asm/rgbasm.5 +++ b/src/asm/rgbasm.5 @@ -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: diff --git a/src/asm/section.c b/src/asm/section.c index 784bb5a8..4961a8e3 100644 --- a/src/asm/section.c +++ b/src/asm/section.c @@ -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; } diff --git a/src/link/object.c b/src/link/object.c index 43c928ae..49a29293 100644 --- a/src/link/object.c +++ b/src/link/object.c @@ -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; } } diff --git a/src/link/patch.c b/src/link/patch.c index 09198e47..6b951017 100644 --- a/src/link/patch.c +++ b/src/link/patch.c @@ -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); } diff --git a/src/link/section.c b/src/link/section.c index fb344e6d..e3938187 100644 --- a/src/link/section.c +++ b/src/link/section.c @@ -8,6 +8,8 @@ #include #include +#include +#include #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) } } - if (other->size > target->size) - target->size = other->size; + 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 { diff --git a/src/linkdefs.c b/src/linkdefs.c index c60221fa..d71247eb 100644 --- a/src/linkdefs.c +++ b/src/linkdefs.c @@ -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", +}; diff --git a/test/asm/section-union.err b/test/asm/section-union.err index 871b97db..e50f5ceb 100644 --- a/test/asm/section-union.err +++ b/test/asm/section-union.err @@ -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): diff --git a/test/link/section-union/fragments/a.asm b/test/link/section-union/fragments/a.asm new file mode 100644 index 00000000..e81e162c --- /dev/null +++ b/test/link/section-union/fragments/a.asm @@ -0,0 +1,7 @@ + +SECTION FRAGMENT "output", ROM0 +X: + db X + db 1 + +assert WARN, X == 0 diff --git a/test/link/section-union/fragments/b.asm b/test/link/section-union/fragments/b.asm new file mode 100644 index 00000000..aee478c8 --- /dev/null +++ b/test/link/section-union/fragments/b.asm @@ -0,0 +1,7 @@ + +SECTION FRAGMENT "output", ROM0 +Y: + db Y + db 3 + +assert WARN, Y == 2 diff --git a/test/link/section-union/fragments/ref.out.bin b/test/link/section-union/fragments/ref.out.bin new file mode 100644 index 0000000000000000000000000000000000000000..eaf36c1daccfdf325514461cd1a2ffbc139b5464 GIT binary patch literal 4 LcmZQzWMT#Y01f~L literal 0 HcmV?d00001 diff --git a/test/link/test.sh b/test/link/test.sh index e3751791..ddd0112e 100755 --- a/test/link/test.sh +++ b/test/link/test.sh @@ -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