shithub: rgbds

Download patch

ref: 1f2f797cb9967645f5a5714431eebacf5d289edf
parent: aca00e4fce98e0020941efe08a971eed398bd82c
author: ISSOtm <[email protected]>
date: Tue Jul 21 15:53:40 EDT 2020

Add section fragments

Fixes #517, and hopefully enables RGBDS as a SDCC back-end

--- a/include/asm/section.h
+++ b/include/asm/section.h
@@ -19,7 +19,7 @@
 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 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);
--- a/include/link/section.h
+++ b/include/link/section.h
@@ -42,8 +42,9 @@
 	/* 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;
--- 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,
@@ -72,6 +72,14 @@
 
 	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,
--- a/src/asm/asmy.y
+++ b/src/asm/asmy.y
@@ -496,6 +496,7 @@
 	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 @@
 %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 @@
 %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 @@
 		}
 ;
 
-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; }
--- a/src/asm/globlex.c
+++ b/src/asm/globlex.c
@@ -442,6 +442,7 @@
 
 	{"def", T_OP_DEF},
 
+	{"fragment", T_POP_FRAGMENT},
 	{"bank", T_OP_BANK},
 	{"align", T_OP_ALIGN},
 
--- a/src/asm/output.c
+++ b/src/asm/output.c
@@ -176,7 +176,10 @@
 
 	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);
--- a/src/asm/rgbasm.5
+++ b/src/asm/rgbasm.5
@@ -367,7 +367,6 @@
 .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 @@
 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 @@
 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:
--- a/src/asm/section.c
+++ b/src/asm/section.c
@@ -88,7 +88,7 @@
  */
 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 @@
 			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 @@
 			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 @@
 		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 @@
  * 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;
 }
 
--- a/src/link/object.c
+++ b/src/link/object.c
@@ -266,10 +266,16 @@
 		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 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 @@
 		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;
 		}
 	}
 
--- a/src/link/patch.c
+++ b/src/link/patch.c
@@ -421,7 +421,7 @@
  * @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 @@
 		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 @@
 			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 @@
 				      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 applyPatches(struct Section *section, void *arg)
 {
 	(void)arg;
+	struct Section *dataSection = section;
 
 	do {
-		applyFilePatches(section);
+		applyFilePatches(section, dataSection);
 		section = section->nextu;
 	} while (section);
 }
--- a/src/link/section.c
+++ b/src/link/section.c
@@ -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 @@
 	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 @@
 		}
 	}
 
-	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 @@
 	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 {
--- a/src/linkdefs.c
+++ b/src/linkdefs.c
@@ -44,3 +44,9 @@
 	[SECTTYPE_OAM]   = "OAM",
 	[SECTTYPE_HRAM]  = "HRAM"
 };
+
+char const * const sectionModNames[] = {
+	[SECTION_NORMAL]   = "regular",
+	[SECTION_UNION]    = "union",
+	[SECTION_FRAGMENT] = "fragment",
+};
--- 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):
--- /dev/null
+++ b/test/link/section-union/fragments/a.asm
@@ -1,0 +1,7 @@
+
+SECTION FRAGMENT "output", ROM0
+X:
+	db X
+	db 1
+
+assert WARN, X == 0
--- /dev/null
+++ b/test/link/section-union/fragments/b.asm
@@ -1,0 +1,7 @@
+
+SECTION FRAGMENT "output", ROM0
+Y:
+	db Y
+	db 3
+
+assert WARN, Y == 2
--- /dev/null
+++ b/test/link/section-union/fragments/ref.out.bin
@@ -1,0 +1,1 @@
+
\ No newline at end of file
--- a/test/link/test.sh
+++ b/test/link/test.sh
@@ -92,6 +92,12 @@
 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