shithub: rgbds

Download patch

ref: baeec2315fd4acab9f3f1021ba98a731ba0de2b9
parent: 46a402f7d76dd90b668bc79c9d11b0f9f0c98e44
parent: 92134d7684dd4cf4753eac184ad940f80acee943
author: Eldred Habert <[email protected]>
date: Sun Mar 22 07:21:24 EDT 2020

Merge pull request #495 from ISSOtm/sectunion

Implement unionized sections

--- a/include/asm/asm.h
+++ b/include/asm/asm.h
@@ -27,7 +27,6 @@
 
 extern int32_t nLineNo;
 extern uint32_t nTotalLines;
-extern uint32_t nPC;
 extern uint32_t nIFDepth;
 extern bool skipElif;
 extern uint32_t nUnionDepth;
--- a/include/asm/main.h
+++ b/include/asm/main.h
@@ -31,6 +31,8 @@
 extern int32_t nGBGfxID;
 extern int32_t nBinaryID;
 
+extern uint32_t curOffset; /* Offset into the current section */
+
 extern struct sOptions DefaultOptions;
 extern struct sOptions CurrentOptions;
 
--- a/include/asm/output.h
+++ b/include/asm/output.h
@@ -19,9 +19,10 @@
 extern struct Section *pSectionList, *pCurrentSection;
 
 void out_SetFileName(char *s);
-void out_CreatePatch(uint32_t type, struct Expression const *expr);
+void out_CreatePatch(uint32_t type, struct Expression const *expr,
+		     uint32_t ofs);
 bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
-		      char const *message);
+		      char const *message, uint32_t ofs);
 void out_WriteObject(void);
 
 #endif /* RGBDS_ASM_OUTPUT_H */
--- a/include/asm/section.h
+++ b/include/asm/section.h
@@ -18,7 +18,8 @@
 struct Section {
 	char *pzName;
 	enum SectionType nType;
-	uint32_t nPC;
+	bool isUnion;
+	uint32_t size;
 	uint32_t nOrg;
 	uint32_t nBank;
 	uint32_t nAlign;
@@ -28,18 +29,19 @@
 };
 
 struct SectionSpec {
-	int32_t bank;
-	int32_t alignment;
+	uint32_t bank;
+	uint32_t alignment;
 };
 
 struct Section *out_FindSectionByName(const char *pzName);
-void out_NewSection(char const *pzName, uint32_t secttype, int32_t org,
-		    struct SectionSpec const *attributes);
-void out_SetLoadSection(char const *name, uint32_t secttype, int32_t org,
+void out_NewSection(char const *pzName, uint32_t secttype, uint32_t org,
+		    struct SectionSpec const *attributes, bool isUnion);
+void out_SetLoadSection(char const *name, uint32_t secttype, uint32_t org,
 			struct SectionSpec const *attributes);
 void out_EndLoadSection(void);
 
 struct Section *sect_GetSymbolSection(void);
+uint32_t sect_GetOutputOffset(void);
 
 void out_AbsByte(uint8_t b);
 void out_AbsByteGroup(uint8_t const *s, int32_t length);
@@ -46,7 +48,7 @@
 void out_Skip(int32_t skip);
 void out_String(char const *s);
 void out_RelByte(struct Expression *expr);
-void out_RelBytes(struct Expression *expr, int32_t n);
+void out_RelBytes(struct Expression *expr, uint32_t n);
 void out_RelWord(struct Expression *expr);
 void out_RelLong(struct Expression *expr);
 void out_PCRelByte(struct Expression *expr);
--- a/include/asm/symbol.h
+++ b/include/asm/symbol.h
@@ -13,6 +13,8 @@
 #include <stdint.h>
 #include <string.h>
 
+#include "asm/section.h"
+
 #include "types.h"
 
 #define HASHSIZE	(1 << 16)
@@ -30,7 +32,6 @@
 struct sSymbol {
 	char tzName[MAXSYMLEN + 1];
 	enum SymbolType type;
-	bool isConstant; /* Whether the symbol's value is currently known */
 	bool isExported; /* Whether the symbol is to be exported */
 	bool isBuiltin;  /* Whether the symbol is a built-in */
 	bool isReferenced; /* Whether the symbol is referenced in a RPN expr */
@@ -52,7 +53,9 @@
 
 static inline bool sym_IsConstant(struct sSymbol const *sym)
 {
-	return sym->isConstant;
+	return sym->type == SYM_EQU || sym->type == SYM_SET
+				|| (sym->type == SYM_LABEL && sym->pSection
+				    && sym->pSection->nOrg != -1);
 }
 
 static inline bool sym_IsNumeric(struct sSymbol const *sym)
--- a/include/link/section.h
+++ b/include/link/section.h
@@ -37,6 +37,7 @@
 	char *name;
 	uint16_t size;
 	enum SectionType type;
+	bool isUnion;
 	bool isAddressFixed;
 	uint16_t org;
 	bool isBankFixed;
@@ -50,6 +51,7 @@
 	struct Symbol **fileSymbols;
 	uint32_t nbSymbols;
 	struct Symbol const **symbols;
+	struct Section *nextu; /* The next "component" of this unionized sect */
 };
 
 /*
--- a/include/linkdefs.h
+++ b/include/linkdefs.h
@@ -14,7 +14,7 @@
 
 #define RGBDS_OBJECT_VERSION_STRING "RGB%1hhu"
 #define RGBDS_OBJECT_VERSION_NUMBER (uint8_t)9
-#define RGBDS_OBJECT_REV 2
+#define RGBDS_OBJECT_REV 3
 
 enum AssertionType {
 	ASSERT_WARN,
--- a/src/asm/asmy.y
+++ b/src/asm/asmy.y
@@ -393,7 +393,7 @@
 	if (nUnionDepth > MAXUNIONS)
 		fatalerror("Too many nested UNIONs");
 
-	unionStart[unionIndex] = nPC;
+	unionStart[unionIndex] = curOffset;
 	unionSize[unionIndex] = 0;
 }
 
@@ -400,13 +400,12 @@
 static void updateUnion(void)
 {
 	uint32_t unionIndex = nUnionDepth - 1;
-	uint32_t size = nPC - unionStart[unionIndex];
+	uint32_t size = curOffset - unionStart[unionIndex];
 
 	if (size > unionSize[unionIndex])
 		unionSize[unionIndex] = size;
 
-	nPC = unionStart[unionIndex];
-	pCurrentSection->nPC = unionStart[unionIndex];
+	curOffset = unionStart[unionIndex];
 }
 
 static size_t strlenUTF8(const char *s)
@@ -601,7 +600,8 @@
 %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	<macroArg> macroargs;
+%type	<nConstValue> sectunion
+%type	<macroArg> macroargs
 
 %token	T_Z80_ADC T_Z80_ADD T_Z80_AND
 %token	T_Z80_BIT
@@ -815,7 +815,8 @@
 assert		: T_POP_ASSERT assert_type relocexpr
 		{
 			if (!rpn_isKnown(&$3)) {
-				if (!out_CreateAssert($2, &$3, ""))
+				if (!out_CreateAssert($2, &$3, "",
+						      sect_GetOutputOffset()))
 					yyerror("Assertion creation failed: %s",
 						strerror(errno));
 			} else if ($3.nVal == 0) {
@@ -836,7 +837,8 @@
 		| T_POP_ASSERT assert_type relocexpr ',' string
 		{
 			if (!rpn_isKnown(&$3)) {
-				if (!out_CreateAssert($2, &$3, $5))
+				if (!out_CreateAssert($2, &$3, $5,
+						      sect_GetOutputOffset()))
 					yyerror("Assertion creation failed: %s",
 						strerror(errno));
 			} else if ($3.nVal == 0) {
@@ -968,8 +970,7 @@
 			updateUnion();
 
 			nUnionDepth--;
-			nPC = unionStart[nUnionDepth] + unionSize[nUnionDepth];
-			pCurrentSection->nPC = nPC;
+			curOffset = unionStart[nUnionDepth] + unionSize[nUnionDepth];
 		}
 ;
 
@@ -1418,10 +1419,13 @@
 		}
 ;
 
-section		: T_POP_SECTION string ',' sectiontype sectorg sectattrs {
-			out_NewSection($2, $4, $5, &$6);
+section		: T_POP_SECTION sectunion string ',' sectiontype sectorg sectattrs {
+			out_NewSection($3, $5, $6, &$7, $2);
 		}
 ;
+
+sectunion	: /* empty */	{ $$ = false; }
+		| T_POP_UNION	{ $$ = true; }
 
 sectiontype	: T_SECT_WRAM0	{ $$ = SECTTYPE_WRAM0; }
 		| T_SECT_VRAM	{ $$ = SECTTYPE_VRAM; }
--- a/src/asm/main.c
+++ b/src/asm/main.c
@@ -40,11 +40,13 @@
 char **cldefines;
 
 clock_t nStartClock, nEndClock;
-int32_t nLineNo;
-uint32_t nTotalLines, nPC, nIFDepth, nUnionDepth;
+uint32_t nTotalLines, nIFDepth, nUnionDepth;
 bool skipElif;
 uint32_t unionStart[128], unionSize[128];
 
+int32_t nLineNo;
+uint32_t curOffset;
+
 #if defined(YYDEBUG) && YYDEBUG
 extern int yydebug;
 #endif
@@ -525,7 +527,6 @@
 	nIFDepth = 0;
 	skipElif = true;
 	nUnionDepth = 0;
-	nPC = 0;
 	sym_Init();
 	sym_SetExportAll(CurrentOptions.exportall);
 	fstk_Init(tzMainfile);
--- a/src/asm/output.c
+++ b/src/asm/output.c
@@ -187,9 +187,9 @@
 {
 	fputstring(pSect->pzName, f);
 
-	fputlong(pSect->nPC, f);
+	fputlong(pSect->size, f);
 
-	fputc(pSect->nType, f);
+	fputc(pSect->nType | pSect->isUnion << 7, f);
 
 	fputlong(pSect->nOrg, f);
 	fputlong(pSect->nBank, f);
@@ -198,7 +198,7 @@
 	if (sect_HasData(pSect->nType)) {
 		struct Patch *pPatch;
 
-		fwrite(pSect->tData, 1, pSect->nPC, f);
+		fwrite(pSect->tData, 1, pSect->size, f);
 		fputlong(countpatches(pSect), f);
 
 		pPatch = pSect->pPatches;
@@ -402,7 +402,8 @@
 /*
  * Allocate a new patch structure and link it into the list
  */
-static struct Patch *allocpatch(uint32_t type, struct Expression const *expr)
+static struct Patch *allocpatch(uint32_t type, struct Expression const *expr,
+				uint32_t ofs)
 {
 	struct Patch *pPatch;
 
@@ -418,8 +419,8 @@
 
 	pPatch->nRPNSize = 0;
 	pPatch->nType = type;
-	pPatch->nOffset = pCurrentSection->nPC;
 	fstk_DumpToStr(pPatch->tzFilename, sizeof(pPatch->tzFilename));
+	pPatch->nOffset = ofs;
 
 	writerpn(pPatch->pRPN, &pPatch->nRPNSize, expr->tRPN, expr->nRPNLength);
 	assert(pPatch->nRPNSize == expr->nRPNPatchSize);
@@ -430,9 +431,9 @@
 /*
  * Create a new patch (includes the rpn expr)
  */
-void out_CreatePatch(uint32_t type, struct Expression const *expr)
+void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs)
 {
-	struct Patch *pPatch = allocpatch(type, expr);
+	struct Patch *pPatch = allocpatch(type, expr, ofs);
 
 	pPatch->pNext = pCurrentSection->pPatches;
 	pCurrentSection->pPatches = pPatch;
@@ -442,7 +443,7 @@
  * Creates an assert that will be written to the object file
  */
 bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
-		      char const *message)
+		      char const *message, uint32_t ofs)
 {
 	struct Assertion *assertion = malloc(sizeof(*assertion));
 
@@ -449,7 +450,7 @@
 	if (!assertion)
 		return false;
 
-	assertion->patch = allocpatch(type, expr);
+	assertion->patch = allocpatch(type, expr, ofs);
 	assertion->section = pCurrentSection;
 	assertion->message = strdup(message);
 	if (!assertion->message) {
--- a/src/asm/section.c
+++ b/src/asm/section.c
@@ -16,16 +16,18 @@
 struct SectionStackEntry {
 	struct Section *pSection;
 	struct sSymbol *pScope; /* Section's symbol scope */
+	uint32_t offset;
 	struct SectionStackEntry *pNext;
 };
 
 struct SectionStackEntry *pSectionStack;
 static struct Section *currentLoadSection = NULL;
+uint32_t loadOffset = 0; /* The offset of the LOAD section within its parent */
 
 /*
  * A quick check to see if we have an initialized section
  */
-static void checksection(void)
+static inline void checksection(void)
 {
 	if (pCurrentSection == NULL)
 		fatalerror("Code generation before SECTION directive");
@@ -35,7 +37,7 @@
  * A quick check to see if we have an initialized section that can contain
  * this much initialized data
  */
-static void checkcodesection(void)
+static inline void checkcodesection(void)
 {
 	checksection();
 
@@ -49,22 +51,20 @@
 /*
  * Check if the section has grown too much.
  */
-static void checksectionoverflow(uint32_t delta_size)
+static void reserveSpace(uint32_t delta_size)
 {
 	uint32_t maxSize = maxsize[pCurrentSection->nType];
-	uint32_t newSize = pCurrentSection->nPC + delta_size;
+	uint32_t newSize = curOffset + delta_size;
 
-	if (newSize > maxSize) {
-		/*
-		 * This check is here to trap broken code that generates
-		 * sections that are too big and to prevent the assembler from
-		 * generating huge object files or trying to allocate too much
-		 * memory.
-		 * The real check must be done at the linking stage.
-		 */
+	/*
+	 * This check is here to trap broken code that generates sections that
+	 * are too big and to prevent the assembler from generating huge object
+	 * files or trying to allocate too much memory.
+	 * A check at the linking stage is still necessary.
+	 */
+	if (newSize > maxSize)
 		fatalerror("Section '%s' is too big (max size = 0x%X bytes, reached 0x%X).",
 			   pCurrentSection->pzName, maxSize, newSize);
-	}
 }
 
 struct Section *out_FindSectionByName(const char *pzName)
@@ -85,7 +85,8 @@
  * Find a section by name and type. If it doesn't exist, create it
  */
 static struct Section *getSection(char const *pzName, enum SectionType type,
-				  int32_t org, int32_t bank, int32_t alignment)
+				  uint32_t org, uint32_t bank,
+				  uint32_t alignment, bool isUnion)
 {
 	if (bank != -1) {
 		if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM
@@ -123,13 +124,103 @@
 	struct Section *pSect = out_FindSectionByName(pzName);
 
 	if (pSect) {
-		if (type == pSect->nType
-			&& ((uint32_t)org) == pSect->nOrg
-			&& ((uint32_t)bank) == pSect->nBank
-			&& ((uint32_t)alignment == pSect->nAlign)) {
-			return pSect;
+		unsigned int nbSectErrors = 0;
+#define fail(...) \
+	do { \
+		yyerror(__VA_ARGS__); \
+		nbSectErrors++; \
+	} while (0)
+
+		if (type != pSect->nType)
+			fail("Section \"%s\" already exists but with type %s",
+			     pSect->pzName, typeNames[pSect->nType]);
+
+		/*
+		 * 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);
+			/*
+			 * WARNING: see comment abount assumption in
+			 * `EndLoadSection` if modifying the following check!
+			 */
+			if (sect_HasData(type))
+				fail("Cannot declare ROM sections as UNION");
+			if (org != -1) {
+				/* If neither is fixed, they must be the same */
+				if (pSect->nOrg != -1 && pSect->nOrg != org)
+					fail("Section \"%s\" already declared as fixed at different address $%x",
+					     pSect->pzName, pSect->nOrg);
+				else if (pSect->nAlign != 0
+				      && ((pSect->nAlign - 1) & org))
+					fail("Section \"%s\" already declared as aligned to %u bytes",
+					     pSect->pzName, pSect->nAlign);
+				else
+					/* Otherwise, just override */
+					pSect->nOrg = org;
+			} else if (alignment != 0) {
+				/* Make sure any fixed address is compatible */
+				if (pSect->nOrg != -1) {
+					uint32_t mask = alignment - 1;
+
+					if (pSect->nOrg & mask)
+						fail("Section \"%s\" already declared as fixed at incompatible address $%x",
+						     pSect->pzName,
+						     pSect->nOrg);
+				} else if (alignment > pSect->nAlign) {
+					/*
+					 * If the section is not fixed,
+					 * its alignment is the largest of both
+					 */
+					pSect->nAlign = alignment;
+				}
+			}
+			/* If the section's bank is unspecified, override it */
+			if (pSect->nBank == -1)
+				pSect->nBank = bank;
+			/* If both specify a bank, it must be the same one */
+			else if (bank != -1 && pSect->nBank != bank)
+				fail("Section \"%s\" already declared with different bank %u",
+				     pSect->pzName, pSect->nBank);
+		} else {
+			if (pSect->isUnion)
+				fail("Section \"%s\" already declared as union",
+				     pSect->pzName);
+			if (org != pSect->nOrg) {
+				if (pSect->nOrg == -1)
+					fail("Section \"%s\" already declared as floating",
+					     pSect->pzName);
+				else
+					fail("Section \"%s\" already declared as fixed at $%x",
+					     pSect->pzName, pSect->nOrg);
+			}
+			if (bank != pSect->nBank) {
+				if (pSect->nBank == -1)
+					fail("Section \"%s\" already declared as floating bank",
+					     pSect->pzName);
+				else
+					fail("Section \"%s\" already declared as fixed at bank %u",
+					     pSect->pzName, pSect->nBank);
+			}
+			if (alignment != pSect->nAlign) {
+				if (pSect->nAlign == 0)
+					fail("Section \"%s\" already declared as unaligned",
+					     pSect->pzName);
+				else
+					fail("Section \"%s\" already declared as aligned to %u bytes",
+					     pSect->pzName, pSect->nAlign);
+			}
 		}
-		fatalerror("Section already exists but with a different type");
+
+		if (nbSectErrors)
+			fatalerror("Cannot create section \"%s\" (%u errors)",
+				   pSect->pzName, nbSectErrors);
+#undef fail
+		return pSect;
 	}
 
 	pSect = malloc(sizeof(*pSect));
@@ -141,7 +232,8 @@
 		fatalerror("Not enough memory for sectionname");
 
 	pSect->nType = type;
-	pSect->nPC = 0;
+	pSect->isUnion = isUnion;
+	pSect->size = 0;
 	pSect->nOrg = org;
 	pSect->nBank = bank;
 	pSect->nAlign = alignment;
@@ -177,10 +269,7 @@
 	if (nUnionDepth > 0)
 		fatalerror("Cannot change the section within a UNION");
 
-	nPC = (pSect != NULL) ? pSect->nPC : 0;
-
 	pPCSymbol->pSection = pSect;
-	pPCSymbol->isConstant = pSect && pSect->nOrg != -1;
 
 	sym_SetCurrentSymbolScope(NULL);
 }
@@ -188,17 +277,17 @@
 /*
  * Set the current section by name and type
  */
-void out_NewSection(char const *pzName, uint32_t type, int32_t org,
-		    struct SectionSpec const *attributes)
+void out_NewSection(char const *pzName, uint32_t type, uint32_t org,
+		    struct SectionSpec const *attributes, bool isUnion)
 {
 	if (currentLoadSection)
 		fatalerror("Cannot change the section within a `LOAD` block");
 
 	struct Section *pSect = getSection(pzName, type, org, attributes->bank,
-					   1 << attributes->alignment);
+					   1 << attributes->alignment, isUnion);
 
-	nPC = pSect->nPC;
 	setSection(pSect);
+	curOffset = isUnion ? 0 : pSect->size;
 	pCurrentSection = pSect;
 }
 
@@ -205,7 +294,7 @@
 /*
  * Set the current section by name and type
  */
-void out_SetLoadSection(char const *name, uint32_t type, int32_t org,
+void out_SetLoadSection(char const *name, uint32_t type, uint32_t org,
 			struct SectionSpec const *attributes)
 {
 	checkcodesection();
@@ -214,9 +303,10 @@
 		fatalerror("`LOAD` blocks cannot be nested");
 
 	struct Section *pSect = getSection(name, type, org, attributes->bank,
-					   1 << attributes->alignment);
+					   1 << attributes->alignment, false);
 
-	nPC = pSect->nPC;
+	loadOffset = curOffset;
+	curOffset = 0; /* curOffset -= loadOffset; */
 	setSection(pSect);
 	currentLoadSection = pSect;
 }
@@ -227,8 +317,9 @@
 		yyerror("Found `ENDL` outside of a `LOAD` block");
 	currentLoadSection = NULL;
 
-	nPC = pCurrentSection->nPC;
 	setSection(pCurrentSection);
+	curOffset += loadOffset;
+	loadOffset = 0;
 }
 
 struct Section *sect_GetSymbolSection(void)
@@ -236,17 +327,46 @@
 	return currentLoadSection ? currentLoadSection : pCurrentSection;
 }
 
-/*
- * Output an absolute byte (bypassing ROM/union checks)
- */
-static void absByteBypassCheck(uint8_t b)
+uint32_t sect_GetOutputOffset(void)
 {
-	pCurrentSection->tData[pCurrentSection->nPC++] = b;
-	if (currentLoadSection)
-		currentLoadSection->nPC++;
-	nPC++;
+	return curOffset + loadOffset;
 }
 
+static inline void growSection(uint32_t growth)
+{
+	curOffset += growth;
+	if (curOffset > pCurrentSection->size)
+		pCurrentSection->size = curOffset;
+	if (currentLoadSection && curOffset > currentLoadSection->size)
+		currentLoadSection->size = curOffset;
+}
+
+static inline void writebyte(uint8_t byte)
+{
+	pCurrentSection->tData[sect_GetOutputOffset()] = byte;
+	growSection(1);
+}
+
+static inline void writeword(uint16_t b)
+{
+	writebyte(b & 0xFF);
+	writebyte(b >> 8);
+}
+
+static inline void writelong(uint32_t b)
+{
+	writebyte(b & 0xFF);
+	writebyte(b >> 8);
+	writebyte(b >> 16);
+	writebyte(b >> 24);
+}
+
+static inline void createPatch(enum PatchType type,
+			       struct Expression const *expr)
+{
+	out_CreatePatch(type, expr, sect_GetOutputOffset());
+}
+
 /*
  * Output an absolute byte
  */
@@ -253,16 +373,18 @@
 void out_AbsByte(uint8_t b)
 {
 	checkcodesection();
-	checksectionoverflow(1);
-	absByteBypassCheck(b);
+	reserveSpace(1);
+
+	writebyte(b);
 }
 
 void out_AbsByteGroup(uint8_t const *s, int32_t length)
 {
 	checkcodesection();
-	checksectionoverflow(length);
+	reserveSpace(length);
+
 	while (length--)
-		absByteBypassCheck(*s++);
+		writebyte(*s++);
 }
 
 /*
@@ -271,19 +393,17 @@
 void out_Skip(int32_t skip)
 {
 	checksection();
-	checksectionoverflow(skip);
+	reserveSpace(skip);
+
 	if (!sect_HasData(pCurrentSection->nType)) {
-		pCurrentSection->nPC += skip;
-		if (currentLoadSection)
-			currentLoadSection->nPC += skip;
-		nPC += skip;
+		growSection(skip);
 	} else if (nUnionDepth > 0) {
 		while (skip--)
-			absByteBypassCheck(CurrentOptions.fillchar);
+			writebyte(CurrentOptions.fillchar);
 	} else {
 		checkcodesection();
 		while (skip--)
-			absByteBypassCheck(CurrentOptions.fillchar);
+			writebyte(CurrentOptions.fillchar);
 	}
 }
 
@@ -293,21 +413,12 @@
 void out_String(char const *s)
 {
 	checkcodesection();
-	checksectionoverflow(strlen(s));
+	reserveSpace(strlen(s));
+
 	while (*s)
-		absByteBypassCheck(*s++);
+		writebyte(*s++);
 }
 
-static void outputExpression(struct Expression const *expr)
-{
-	if (!rpn_isKnown(expr)) {
-		out_CreatePatch(PATCHTYPE_BYTE, expr);
-		out_AbsByte(0);
-	} else {
-		out_AbsByte(expr->nVal);
-	}
-}
-
 /*
  * Output a relocatable byte. Checking will be done to see if it
  * is an absolute value in disguise.
@@ -314,7 +425,15 @@
  */
 void out_RelByte(struct Expression *expr)
 {
-	outputExpression(expr);
+	checkcodesection();
+	reserveSpace(1);
+
+	if (!rpn_isKnown(expr)) {
+		createPatch(PATCHTYPE_BYTE, expr);
+		writebyte(0);
+	} else {
+		writebyte(expr->nVal);
+	}
 	rpn_Free(expr);
 }
 
@@ -322,69 +441,54 @@
  * Output several copies of a relocatable byte. Checking will be done to see if
  * it is an absolute value in disguise.
  */
-void out_RelBytes(struct Expression *expr, int32_t n)
+void out_RelBytes(struct Expression *expr, uint32_t n)
 {
-	while (n--)
-		outputExpression(expr);
+	checkcodesection();
+	reserveSpace(n);
+
+	while (n--) {
+		if (!rpn_isKnown(expr)) {
+			createPatch(PATCHTYPE_BYTE, expr);
+			writebyte(0);
+		} else {
+			writebyte(expr->nVal);
+		}
+	}
 	rpn_Free(expr);
 }
 
 /*
- * Output an absolute word
- */
-static void absWord(uint16_t b)
-{
-	checkcodesection();
-	checksectionoverflow(2);
-	pCurrentSection->tData[pCurrentSection->nPC++] = b & 0xFF;
-	pCurrentSection->tData[pCurrentSection->nPC++] = b >> 8;
-	if (currentLoadSection)
-		currentLoadSection->nPC += 2;
-	nPC += 2;
-}
-
-/*
  * Output a relocatable word. Checking will be done to see if
  * it's an absolute value in disguise.
  */
 void out_RelWord(struct Expression *expr)
 {
+	checkcodesection();
+	reserveSpace(2);
+
 	if (!rpn_isKnown(expr)) {
-		out_CreatePatch(PATCHTYPE_WORD, expr);
-		absWord(0);
+		createPatch(PATCHTYPE_WORD, expr);
+		writeword(0);
 	} else {
-		absWord(expr->nVal);
+		writeword(expr->nVal);
 	}
 	rpn_Free(expr);
 }
 
 /*
- * Output an absolute longword
- */
-static void absLong(uint32_t b)
-{
-	checkcodesection();
-	checksectionoverflow(4);
-	pCurrentSection->tData[pCurrentSection->nPC++] = b & 0xFF;
-	pCurrentSection->tData[pCurrentSection->nPC++] = b >> 8;
-	pCurrentSection->tData[pCurrentSection->nPC++] = b >> 16;
-	pCurrentSection->tData[pCurrentSection->nPC++] = b >> 24;
-	if (currentLoadSection)
-		currentLoadSection->nPC += 4;
-	nPC += 4;
-}
-
-/*
  * Output a relocatable longword. Checking will be done to see if
  * is an absolute value in disguise.
  */
 void out_RelLong(struct Expression *expr)
 {
+	checkcodesection();
+	reserveSpace(2);
+
 	if (!rpn_isKnown(expr)) {
-		out_CreatePatch(PATCHTYPE_LONG, expr);
-		absLong(0);
+		createPatch(PATCHTYPE_LONG, expr);
+		writelong(0);
 	} else {
-		absLong(expr->nVal);
+		writelong(expr->nVal);
 	}
 	rpn_Free(expr);
 }
@@ -396,13 +500,11 @@
 void out_PCRelByte(struct Expression *expr)
 {
 	checkcodesection();
-	checksectionoverflow(1);
+	reserveSpace(1);
+
 	if (!rpn_isKnown(expr) || pCurrentSection->nOrg == -1) {
-		out_CreatePatch(PATCHTYPE_JR, expr);
-		pCurrentSection->tData[pCurrentSection->nPC++] = 0;
-		if (currentLoadSection)
-			currentLoadSection->nPC++;
-		nPC++;
+		createPatch(PATCHTYPE_JR, expr);
+		writebyte(0);
 	} else {
 		/* Target is relative to the byte *after* the operand */
 		uint16_t address = sym_GetValue(pPCSymbol) + 1;
@@ -412,9 +514,9 @@
 		if (offset < -128 || offset > 127) {
 			yyerror("jr target out of reach (expected -129 < %d < 128)",
 				offset);
-			out_AbsByte(0);
+			writebyte(0);
 		} else {
-			out_AbsByte(offset);
+			writebyte(offset);
 		}
 	}
 	rpn_Free(expr);
@@ -444,7 +546,7 @@
 		fsize = ftell(f);
 		rewind(f);
 
-		checksectionoverflow(fsize);
+		reserveSpace(fsize);
 	} else if (errno != ESPIPE) {
 		yyerror("Error determining size of INCBIN file '%s': %s", s,
 			strerror(errno));
@@ -452,11 +554,8 @@
 
 	while ((byte = fgetc(f)) != EOF) {
 		if (fsize == -1)
-			checksectionoverflow(1);
-		pCurrentSection->tData[pCurrentSection->nPC++] = byte;
-		if (currentLoadSection)
-			currentLoadSection->nPC++;
-		nPC++;
+			growSection(1);
+		writebyte(byte);
 	}
 
 	if (ferror(f))
@@ -493,7 +592,7 @@
 	}
 
 	checkcodesection();
-	checksectionoverflow(length);
+	reserveSpace(length);
 
 	int32_t fsize;
 
@@ -524,10 +623,7 @@
 		int byte = fgetc(f);
 
 		if (byte != EOF) {
-			pCurrentSection->tData[pCurrentSection->nPC++] = byte;
-			if (currentLoadSection)
-				currentLoadSection->nPC++;
-			nPC++;
+			writebyte(byte);
 		} else if (ferror(f)) {
 			yyerror("Error reading INCBIN file '%s': %s", s,
 				strerror(errno));
@@ -553,6 +649,7 @@
 
 	pSect->pSection = pCurrentSection;
 	pSect->pScope = sym_GetCurrentSymbolScope();
+	pSect->offset = curOffset;
 	pSect->pNext = pSectionStack;
 	pSectionStack = pSect;
 }
@@ -571,6 +668,8 @@
 	setSection(pSect->pSection);
 	pCurrentSection = pSect->pSection;
 	sym_SetCurrentSymbolScope(pSect->pScope);
+	curOffset = pSect->offset;
+
 	pSectionStack = pSect->pNext;
 	free(pSect);
 }
--- a/src/asm/symbol.c
+++ b/src/asm/symbol.c
@@ -62,7 +62,7 @@
 
 static int32_t CallbackPC(struct sSymbol const *self)
 {
-	return self->pSection ? self->pSection->nOrg + self->pSection->nPC : 0;
+	return self->pSection ? self->pSection->nOrg + curOffset : 0;
 }
 
 /*
@@ -124,7 +124,6 @@
 	if (snprintf((*ppsym)->tzName, MAXSYMLEN + 1, "%s", s) > MAXSYMLEN)
 		warning(WARNING_LONG_STR, "Symbol name is too long: '%s'", s);
 
-	(*ppsym)->isConstant = false;
 	(*ppsym)->isExported = false;
 	(*ppsym)->isBuiltin = false;
 	(*ppsym)->isReferenced = false;
@@ -349,7 +348,6 @@
 
 	nsym->nValue = value;
 	nsym->type = SYM_EQU;
-	nsym->isConstant = true;
 	nsym->pScope = NULL;
 	updateSymbolFilename(nsym);
 
@@ -417,7 +415,6 @@
 
 	nsym->nValue = value;
 	nsym->type = SYM_SET;
-	nsym->isConstant = true;
 	nsym->pScope = NULL;
 	updateSymbolFilename(nsym);
 
@@ -479,9 +476,8 @@
 			nsym->tzFileName, nsym->nFileLine);
 	/* If the symbol already exists as a ref, just "take over" it */
 
-	nsym->nValue = nPC;
+	nsym->nValue = curOffset;
 	nsym->type = SYM_LABEL;
-	nsym->isConstant = pCurrentSection && pCurrentSection->nOrg != -1;
 
 	if (exportall)
 		nsym->isExported = true;
--- a/src/link/object.c
+++ b/src/link/object.c
@@ -245,6 +245,7 @@
 			char const *fileName)
 {
 	int32_t tmp;
+	uint8_t type;
 
 	tryReadstr(section->name, file, "%s: Cannot read section name: %s",
 		   fileName);
@@ -254,8 +255,10 @@
 		errx(1, "\"%s\"'s section size (%d) is invalid", section->name,
 		     tmp);
 	section->size = tmp;
-	tryGetc(section->type, file, "%s: Cannot read \"%s\"'s type: %s",
+	tryGetc(type, file, "%s: Cannot read \"%s\"'s type: %s",
 		fileName, section->name);
+	section->type = type & 0x7F;
+	section->isUnion = type >> 7;
 	tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s org: %s",
 		    fileName, section->name);
 	section->isAddressFixed = tmp >= 0;
@@ -358,6 +361,14 @@
 		   fileName);
 }
 
+static inline struct Section *getMainSection(struct Section *section)
+{
+	if (section->isUnion)
+		section = sect_GetSection(section->name);
+
+	return section;
+}
+
 /**
  * Reads an object file of any supported format
  * @param fileName The filename to report for errors
@@ -448,6 +459,7 @@
 
 		if (!section)
 			err(1, "%s: Couldn't create new section", fileName);
+		section->nextu = NULL;
 		readSection(file, section, fileName);
 		section->fileSymbols = fileSymbols;
 
@@ -472,9 +484,10 @@
 		if (sectionID == -1) {
 			fileSymbols[i]->section = NULL;
 		} else {
-			fileSymbols[i]->section = fileSections[sectionID];
 			/* Give the section a pointer to the symbol as well */
 			linkSymToSect(fileSymbols[i], fileSections[sectionID]);
+			fileSymbols[i]->section =
+					getMainSection(fileSections[sectionID]);
 		}
 	}
 
--- a/src/link/patch.c
+++ b/src/link/patch.c
@@ -117,6 +117,24 @@
 	return *(*expression)++;
 }
 
+static struct Symbol const *getSymbol(struct Symbol ** const symbolList,
+				      uint32_t index, char const *fileName)
+{
+	struct Symbol const *symbol = symbolList[index];
+
+	/* If the symbol is defined elsewhere... */
+	if (symbol->type == SYMTYPE_IMPORT) {
+		struct Symbol const *symbolDefinition =
+						sym_GetSymbol(symbol->name);
+		if (!symbolDefinition)
+			errx(1, "%s: Unknown symbol \"%s\"", fileName,
+			     symbol->name);
+		symbol = symbolDefinition;
+	}
+
+	return symbol;
+}
+
 /**
  * Compute a patch's value from its RPN string.
  * @param patch The patch to compute the value of
@@ -238,19 +256,8 @@
 				value |= getRPNByte(&expression, &size,
 						    patch->fileName) << shift;
 
-			symbol = section->fileSymbols[value];
-
-			/* If the symbol is defined elsewhere... */
-			if (symbol->type == SYMTYPE_IMPORT) {
-				struct Symbol const *symbolDefinition =
-						sym_GetSymbol(symbol->name);
-				if (!symbolDefinition)
-					errx(1, "%s: Unknown symbol \"%s\"",
-					     patch->fileName, symbol->name);
-				symbol = symbolDefinition;
-			}
-
-			value = symbol->section->bank;
+			value = getSymbol(section->fileSymbols, value,
+					  patch->fileName)->section->bank;
 			break;
 
 		case RPN_BANK_SECT:
@@ -305,18 +312,9 @@
 				value |= getRPNByte(&expression, &size,
 						    patch->fileName) << shift;
 
-			symbol = section->fileSymbols[value];
+			symbol = getSymbol(section->fileSymbols, value,
+					   patch->fileName);
 
-			/* If the symbol is defined elsewhere... */
-			if (symbol->type == SYMTYPE_IMPORT) {
-				struct Symbol const *symbolDefinition =
-						sym_GetSymbol(symbol->name);
-				if (!symbolDefinition)
-					errx(1, "%s: Unknown symbol \"%s\"",
-					     patch->fileName, symbol->name);
-				symbol = symbolDefinition;
-			}
-
 			if (!strcmp(symbol->name, "@")) {
 				value = section->org + patch->offset;
 			} else {
@@ -387,10 +385,8 @@
  * @param section The section to patch
  * @param arg Ignored callback arg
  */
-static void applyPatches(struct Section *section, void *arg)
+static void applyFilePatches(struct Section *section)
 {
-	(void)arg;
-
 	if (!sect_HasData(section->type))
 		return;
 
@@ -433,6 +429,22 @@
 			}
 		}
 	}
+}
+
+/**
+ * Applies all of a section's patches, iterating over "components" of
+ * unionized sections
+ * @param section The section to patch
+ * @param arg Ignored callback arg
+ */
+static void applyPatches(struct Section *section, void *arg)
+{
+	(void)arg;
+
+	do {
+		applyFilePatches(section);
+		section = section->nextu;
+	} while (section);
 }
 
 void patch_ApplyPatches(void)
--- a/src/link/section.c
+++ b/src/link/section.c
@@ -36,17 +36,80 @@
 	hash_ForEach(sections, forEach, &callbackArg);
 }
 
+static void mergeSections(struct Section *target, struct Section *other)
+{
+	if (target->type != other->type)
+		errx(1, "Section \"%s\" is defined with conflicting types %s and %s",
+		     other->name,
+		     typeNames[target->type], typeNames[other->type]);
+	if (other->isAddressFixed) {
+		if (target->isAddressFixed) {
+			if (target->org != other->org)
+				errx(1, "Section \"%s\" is defined with conflicting addresses $%x and $%x",
+				     other->name, target->org, other->org);
+		} else if (target->isAlignFixed) {
+			if (other->org & target->alignMask)
+				errx(1, "Section \"%s\" is defined with conflicting %u-byte alignment and address $%x",
+				     other->name, target->alignMask + 1,
+				     other->org);
+		}
+		target->isAddressFixed = true;
+		target->org = other->org;
+	} else if (other->isAlignFixed) {
+		if (target->isAddressFixed) {
+			if (target->org & other->alignMask)
+				errx(1, "Section \"%s\" is defined with conflicting address $%x and %u-byte alignment",
+				     other->name, target->org,
+				     other->alignMask + 1);
+		} else if (!target->isAlignFixed
+			|| other->alignMask > target->alignMask) {
+			target->isAlignFixed = true;
+			target->alignMask = other->alignMask;
+		}
+	}
+
+	if (other->isBankFixed) {
+		if (!target->isBankFixed) {
+			target->isBankFixed = true;
+			target->bank = other->bank;
+		} else if (target->bank != other->bank) {
+			errx(1, "Section \"%s\" is defined with conflicting banks %u and %u",
+			     other->name, target->bank, other->bank);
+		}
+	}
+
+	if (other->size > target->size)
+		target->size = other->size;
+
+	target->nextu = other;
+}
+
 void sect_AddSection(struct Section *section)
 {
 	/* Check if the section already exists */
-	if (hash_GetElement(sections, section->name))
-		errx(1, "Section name \"%s\" is already in use", section->name);
+	struct Section *other = hash_GetElement(sections, section->name);
 
-	/* If not, add it */
-	bool collided = hash_AddElement(sections, section->name, section);
+	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)) {
+		errx(1, "Section \"%s\" is of type %s, which cannot be unionized",
+		     section->name, typeNames[section->type]);
+	} else {
+		/* If not, add it */
+		bool collided = hash_AddElement(sections, section->name,
+						section);
 
-	if (beVerbose && collided)
-		warnx("Section hashmap collision occurred!");
+		if (beVerbose && collided)
+			warnx("Section hashmap collision occurred!");
+	}
 }
 
 struct Section *sect_GetSection(char const *name)
--- a/src/rgbds.5
+++ b/src/rgbds.5
@@ -48,8 +48,10 @@
     BYTE    Type          ; 0 = LOCAL symbol only used in this file.
                           ; 1 = IMPORT this symbol from elsewhere
                           ; 2 = EXPORT this symbol to other objects.
+                          ; Bit 7 is independent from the above value, and
+                          ; encodes whether the section is unionized
 
-    IF      Type != 1     ; If symbol is defined in this object file.
+    IF (Type & 0x7F) != 1 ; If symbol is defined in this object file.
 
         STRING  FileName  ; File where the symbol is defined.
 
--- /dev/null
+++ b/test/asm/section-union.asm
@@ -1,0 +1,37 @@
+SECTION UNION "test", WRAM0
+Base:
+	ds $1000
+
+SECTION UNION "test", WRAM0,ALIGN[8]
+	ds 42
+Plus42:
+
+SECTION UNION "test", WRAM0,ALIGN[4]
+
+SECTION UNION "test", WRAM0[$C000]
+; Since the section's base address is known, the labels are constant now
+	ds $1000 ; This shouldn't overflow
+End:
+
+SECTION UNION "test", WRAM0,ALIGN[9]
+
+
+check_label: MACRO
+EXPECTED equ \2
+	IF \1 == EXPECTED
+RESULT equs "OK!"
+	ELSE
+RESULT equs "expected {EXPECTED}"
+	ENDC
+	PURGE EXPECTED
+
+	PRINTT "\1 is at {\1} ({RESULT})\n"
+	PURGE RESULT
+ENDM
+
+	check_label Base, $C000
+	check_label Plus42, $C000 + 42
+	check_label End, $D000
+
+
+SECTION "test", WRAM0 ; Don't allow creating a section that's not a union!
--- /dev/null
+++ b/test/asm/section-union.err
@@ -1,0 +1,8 @@
+ERROR: section-union.asm(37):
+    Section "test" already declared as union
+ERROR: section-union.asm(37):
+    Section "test" already declared as fixed at $c000
+ERROR: section-union.asm(37):
+    Section "test" already declared as aligned to 256 bytes
+ERROR: section-union.asm(37):
+    Cannot create section "test" (3 errors)
--- /dev/null
+++ b/test/asm/section-union.out
@@ -1,0 +1,3 @@
+Base is at $C000 (OK!)
+Plus42 is at $C02A (OK!)
+End is at $D000 (OK!)
--- /dev/null
+++ b/test/link/section-union/align-conflict.asm
@@ -1,0 +1,10 @@
+IF !DEF(SECOND)
+ATTRS equs ",ALIGN[2]"
+ELSE
+ATTRS equs "[$CAFE]"
+ENDC
+
+SECTION UNION "conflicting alignment", WRAM0 ATTRS
+	db
+
+	PURGE ATTRS
--- /dev/null
+++ b/test/link/section-union/align-conflict.out
@@ -1,0 +1,6 @@
+error: Section "conflicting alignment" is defined with conflicting 4-byte alignment and address $cafe
+---
+ERROR: -(18):
+    Section "conflicting alignment" already declared as aligned to 4 bytes
+ERROR: -(18):
+    Cannot create section "conflicting alignment" (1 errors)
--- /dev/null
+++ b/test/link/section-union/assert.asm
@@ -1,0 +1,18 @@
+IF !DEF(SECOND)
+OFS = 42
+ELSE
+OFS = 69
+ENDC
+
+BASE = $C0DE
+
+SECTION UNION "assertions in unions", WRAM0
+IF DEF(SECOND)
+	assert @ != BASE, "Force failing the build" ; Force failure in RGBLINK, though
+ENDC
+	ds OFS
+	assert @ == BASE + OFS, "This assertion should not trigger"
+
+; Only make RGBASM aware of the section's location *after* it sees the assertion
+; This forces it to pass it to RGBLINK
+SECTION UNION "assertions in unions", WRAM0[BASE]
--- /dev/null
+++ b/test/link/section-union/assert.out
@@ -1,0 +1,6 @@
+section-union/assert.asm(11): Force failing the build
+error: 1 assertions failed!
+---
+ERROR: -(30):
+    Assertion failed: Force failing the build
+error: Assembly aborted (1 errors)!
--- /dev/null
+++ b/test/link/section-union/bad-types.asm
@@ -1,0 +1,10 @@
+IF !DEF(SECOND)
+TYPE equs "HRAM"
+ELSE
+TYPE equs "WRAM0"
+ENDC
+
+SECTION UNION "conflicting types", TYPE
+	db
+
+	PURGE TYPE
--- /dev/null
+++ b/test/link/section-union/bad-types.out
@@ -1,0 +1,6 @@
+error: Section "conflicting types" is defined with conflicting types HRAM and WRAM0
+---
+ERROR: -(18):
+    Section "conflicting types" already exists but with type HRAM
+ERROR: -(18):
+    Cannot create section "conflicting types" (1 errors)
--- /dev/null
+++ b/test/link/section-union/bank-conflict.asm
@@ -1,0 +1,8 @@
+IF !DEF(SECOND)
+SECOND equs "4"
+ENDC
+
+SECTION UNION "conflicting banks", WRAMX, BANK[SECOND]
+	db
+
+	PURGE SECOND
--- /dev/null
+++ b/test/link/section-union/bank-conflict.out
@@ -1,0 +1,6 @@
+error: Section "conflicting banks" is defined with conflicting banks 4 and 1
+---
+ERROR: -(14):
+    Section "conflicting banks" already declared with different bank 4
+ERROR: -(14):
+    Cannot create section "conflicting banks" (1 errors)
--- /dev/null
+++ b/test/link/section-union/data-overlay.asm
@@ -1,0 +1,10 @@
+IF !DEF(SECOND)
+DATA equs "ds 4"
+ELSE
+DATA equs "db $aa, $bb, $cc, $dd"
+ENDC
+
+SECTION UNION "overlaid data", ROM0
+	DATA
+
+	PURGE DATA
--- /dev/null
+++ b/test/link/section-union/data-overlay.out
@@ -1,0 +1,6 @@
+error: Section "overlaid data" is of type ROM0, which cannot be unionized
+---
+ERROR: -(18):
+    Cannot declare ROM sections as UNION
+ERROR: -(18):
+    Cannot create section "overlaid data" (1 errors)
--- /dev/null
+++ b/test/link/section-union/different-data.asm
@@ -1,0 +1,8 @@
+IF !DEF(SECOND)
+DATA = 1
+ELSE
+DATA = 2
+ENDC
+
+SECTION UNION "different data", ROM0
+	db DATA
--- /dev/null
+++ b/test/link/section-union/different-data.out
@@ -1,0 +1,6 @@
+error: Section "different data" is of type ROM0, which cannot be unionized
+---
+ERROR: -(16):
+    Cannot declare ROM sections as UNION
+ERROR: -(16):
+    Cannot create section "different data" (1 errors)
--- /dev/null
+++ b/test/link/section-union/different-size.asm
@@ -1,0 +1,8 @@
+IF !DEF(SECOND)
+SIZE = 69
+ELSE
+SIZE = 420
+ENDC
+
+SECTION UNION "different section sizes", ROM0
+	ds SIZE
--- /dev/null
+++ b/test/link/section-union/different-size.out
@@ -1,0 +1,6 @@
+error: Section "different section sizes" is of type ROM0, which cannot be unionized
+---
+ERROR: -(16):
+    Cannot declare ROM sections as UNION
+ERROR: -(16):
+    Cannot create section "different section sizes" (1 errors)
--- /dev/null
+++ b/test/link/section-union/different-syntaxes.asm
@@ -1,0 +1,10 @@
+IF !DEF(SECOND)
+INSTR equs "sbc a"
+ELSE
+INSTR equs "db $9f"
+ENDC
+
+SECTION UNION "different syntaxes", ROM0
+	INSTR
+
+	PURGE INSTR
--- /dev/null
+++ b/test/link/section-union/different-syntaxes.out
@@ -1,0 +1,6 @@
+error: Section "different syntaxes" is of type ROM0, which cannot be unionized
+---
+ERROR: -(18):
+    Cannot declare ROM sections as UNION
+ERROR: -(18):
+    Cannot create section "different syntaxes" (1 errors)
--- /dev/null
+++ b/test/link/section-union/good/a.asm
@@ -1,0 +1,21 @@
+SECTION UNION "a", WRAM0
+	ds 10
+a1:
+
+SECTION UNION "b", WRAMX,ALIGN[4]
+banked::
+	ds 10
+b1:
+
+SECTION UNION "c", HRAM[$FFC0]
+	ds 5
+c1::
+
+
+SECTION "output 1", ROM0
+	dw a1,a2 ; $C00A, $C02A
+	dw b1,b2 ; $DABA, $DAB2
+	dw c1,c2 ; $FFC5, $FFC5
+
+SECTION "output 3", ROM0
+	db BANK(banked)
--- /dev/null
+++ b/test/link/section-union/good/b.asm
@@ -1,0 +1,18 @@
+SECTION UNION "a", WRAM0
+a1: ; This is here to check that the two `a1` don't conflict
+	ds 42
+a2::
+
+SECTION UNION "b", WRAMX[$DAB0]
+	ds 2
+b2::
+
+SECTION UNION "c", HRAM[$FFC0]
+b1: ; Same but in different sections now
+	ds 5
+c2::
+
+SECTION "output 2", ROM0
+	dw a1,a2
+	dw b1,b2
+	dw c1,c2
binary files /dev/null b/test/link/section-union/good/ref.out.bin differ
--- /dev/null
+++ b/test/link/section-union/good/script.link
@@ -1,0 +1,9 @@
+ROM0
+	"output 1"
+	"output 2"
+	"output 3"
+WRAM0
+	"a" ; $C000
+WRAMX 1
+	ORG $DAB0
+	"b"
--- /dev/null
+++ b/test/link/section-union/no-room.asm
@@ -1,0 +1,12 @@
+; NB: the error is that there is no room to place the unionized section in,
+;     so RGBASM can't possibly error out.
+IF !DEF(SECOND)
+	SECTION "bloat", WRAMX[$d000], BANK[2]
+		ds $fe0
+
+	SECTION UNION "test", WRAMX, BANK[2]
+		db
+ELSE
+	SECTION UNION "test", WRAMX, ALIGN[6]
+		db
+ENDC
--- /dev/null
+++ b/test/link/section-union/no-room.out
@@ -1,0 +1,2 @@
+error: Unable to place "test" (WRAMX section) in bank $02 with align mask ffffffc0
+---
--- /dev/null
+++ b/test/link/section-union/org-conflict.asm
@@ -1,0 +1,8 @@
+IF !DEF(SECOND)
+ADDR = $BEEF
+ELSE
+ADDR = $BABE
+ENDC
+
+SECTION UNION "conflicting address", SRAM[ADDR]
+	db
--- /dev/null
+++ b/test/link/section-union/org-conflict.out
@@ -1,0 +1,6 @@
+error: Section "conflicting address" is defined with conflicting addresses $beef and $babe
+---
+ERROR: -(16):
+    Section "conflicting address" already declared as fixed at different address $beef
+ERROR: -(16):
+    Cannot create section "conflicting address" (1 errors)
--- /dev/null
+++ b/test/link/section-union/split-data.asm
@@ -1,0 +1,10 @@
+IF !DEF(SECOND)
+DATA equs "ds 1\ndb $aa"
+ELSE
+DATA equs "db $bb\nds 1"
+ENDC
+
+SECTION UNION "mutually-overlaid data", ROM0
+	DATA
+
+	PURGE DATA
--- /dev/null
+++ b/test/link/section-union/split-data.out
@@ -1,0 +1,6 @@
+error: Section "mutually-overlaid data" is of type ROM0, which cannot be unionized
+---
+ERROR: -(18):
+    Cannot declare ROM sections as UNION
+ERROR: -(18):
+    Cannot create section "mutually-overlaid data" (1 errors)
--- a/test/link/test.sh
+++ b/test/link/test.sh
@@ -71,13 +71,34 @@
 	fi
 done
 
-# This test does its own thing
+# These tests do their own thing
+
 $RGBASM -o $otemp high-low/a.asm
 $RGBLINK -o $gbtemp $otemp
 $RGBASM -o $otemp high-low/b.asm
 $RGBLINK -o $gbtemp2 $otemp
-i="high-low.asm" tryDiff $gbtemp $gbtemp2
+i="high-low.asm" tryCmp $gbtemp $gbtemp2
 rc=$(($? || $rc))
+
+$RGBASM -o $otemp section-union/good/a.asm
+$RGBASM -o $gbtemp2 section-union/good/b.asm
+$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))
+for i in section-union/*.asm; do
+	$RGBASM -o $otemp   $i
+	$RGBASM -o $gbtemp2 $i -DSECOND
+	if $RGBLINK $otemp $gbtemp2 > $outtemp 2>&1; then
+		echo -e "${bold}${red}$i didn't fail to link!${rescolors}${resbold}"
+		rc=1
+	fi
+	echo --- >> $outtemp
+	# Ensure RGBASM also errors out
+	echo 'SECOND equs "1"' | cat $i - $i | $RGBASM - 2>> $outtemp
+	tryDiff ${i%.asm}.out $outtemp
+	rc=$(($? || $rc))
+done
 
 rm -f $otemp $gbtemp $gbtemp2 $outtemp
 exit $rc