shithub: rgbds

Download patch

ref: 76446e6d00eb86ed8a55e5bcbf1eb40ce47bb59c
parent: 6623b1dc458633d344c83a4ba8f0310f9c6a3090
author: ISSOtm <[email protected]>
date: Thu Jan 21 14:41:04 EST 2021

Change behavior of merging FRAGMENTs to constrain each fragment individually

Additionally, remove the deprecated merging of non-fragment SECTIONs

--- a/src/asm/parser.y
+++ b/src/asm/parser.y
@@ -1487,22 +1487,11 @@
 			$$.bank = -1;
 		}
 		| sectattrs T_COMMA T_OP_ALIGN T_LBRACK uconst T_RBRACK {
-			if ($5 > 16)
-				error("Alignment must be between 0 and 16, not %u\n", $5);
-			else
-				$$.alignment = $5;
+			$$.alignment = $5;
 		}
 		| sectattrs T_COMMA T_OP_ALIGN T_LBRACK uconst T_COMMA uconst T_RBRACK {
-			if ($5 > 16) {
-				error("Alignment must be between 0 and 16, not %u\n", $5);
-			} else {
-				$$.alignment = $5;
-				if ($7 >= 1 << $$.alignment)
-					error("Alignment offset must not be greater than alignment (%u < %u)\n",
-						$7, 1 << $$.alignment);
-				else
-					$$.alignOfs = $7;
-			}
+			$$.alignment = $5;
+			$$.alignOfs = $7;
 		}
 		| sectattrs T_COMMA T_OP_BANK T_LBRACK uconst T_RBRACK {
 			/* We cannot check the validity of this now */
--- a/src/asm/section.c
+++ b/src/asm/section.c
@@ -1,4 +1,5 @@
 
+#include <assert.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <stdbool.h>
@@ -98,18 +99,167 @@
 	return NULL;
 }
 
+#define mask(align) ((1U << (align)) - 1)
+#define fail(...) \
+do { \
+	error(__VA_ARGS__); \
+	nbSectErrors++; \
+} while (0)
+
+static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type, uint32_t org,
+				   uint8_t alignment, uint16_t alignOffset)
+{
+	assert(alignment < 16); // Should be ensured by the caller
+	unsigned int nbSectErrors = 0;
+
+	/*
+	 * Unionized sections only need "compatible" constraints, and they end up with the strictest
+	 * combination of both.
+	 */
+	if (sect_HasData(type))
+		fail("Cannot declare ROM sections as UNION\n");
+
+	if (org != -1) {
+		/* If both are fixed, they must be the same */
+		if (sect->org != -1 && sect->org != org)
+			fail("Section already declared as fixed at different address $%04"
+			     PRIx32 "\n", sect->org);
+		else if (sect->align != 0 && (mask(sect->align) & (org - sect->alignOfs)))
+			fail("Section already declared as aligned to %u bytes (offset %"
+				   PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
+		else
+			/* Otherwise, just override */
+			sect->org = org;
+
+	} else if (alignment != 0) {
+		/* Make sure any fixed address given is compatible */
+		if (sect->org != -1) {
+			if ((sect->org - alignOffset) & mask(alignment))
+				fail("Section already declared as fixed at incompatible address $%04"
+				     PRIx32 "\n", sect->org);
+		/* Check if alignment offsets are compatible */
+		} else if ((alignOffset & mask(sect->align))
+			   != (sect->alignOfs & mask(alignment))) {
+			fail("Section already declared with incompatible %" PRIu8
+			     "-byte alignment (offset %" PRIu16 ")\n",
+			     sect->align, sect->alignOfs);
+		} else if (alignment > sect->align) {
+			// If the section is not fixed, its alignment is the largest of both
+			sect->align = alignment;
+			sect->alignOfs = alignOffset;
+		}
+	}
+
+	return nbSectErrors;
+}
+
+static unsigned int mergeFragments(struct Section *sect, enum SectionType type, uint32_t org,
+				   uint8_t alignment, uint16_t alignOffset)
+{
+	(void)type;
+	assert(alignment < 16); // Should be ensured by the caller
+	unsigned int nbSectErrors = 0;
+
+	/*
+	 * Fragments only need "compatible" constraints, and they end up with the strictest
+	 * combination of both.
+	 * The merging is however performed at the *end* of the original section!
+	 */
+	if (org != -1) {
+		uint16_t curOrg = org - sect->size;
+
+		/* If both are fixed, they must be the same */
+		if (sect->org != -1 && sect->org != curOrg)
+			fail("Section already declared as fixed at incompatible address $%04"
+			     PRIx32 " (cur addr = %04" PRIx32 ")\n",
+			     sect->org, sect->org + sect->size);
+		else if (sect->align != 0 && (mask(sect->align) & (curOrg - sect->alignOfs)))
+			fail("Section already declared as aligned to %u bytes (offset %"
+			     PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
+		else
+			/* Otherwise, just override */
+			sect->org = curOrg;
+
+	} else if (alignment != 0) {
+		int32_t curOfs = (alignOffset - sect->size) % (1U << alignment);
+
+		if (curOfs < 0)
+			curOfs += 1U << alignment;
+
+		/* Make sure any fixed address given is compatible */
+		if (sect->org != -1) {
+			if ((sect->org - curOfs) & mask(alignment))
+				fail("Section already declared as fixed at incompatible address $%04"
+				     PRIx32 "\n", sect->org);
+		/* Check if alignment offsets are compatible */
+		} else if ((curOfs & mask(sect->align)) != (sect->alignOfs & mask(alignment))) {
+			fail("Section already declared with incompatible %" PRIu8
+			     "-byte alignment (offset %" PRIu16 ")\n",
+			     sect->align, sect->alignOfs);
+		} else if (alignment > sect->align) {
+			// If the section is not fixed, its alignment is the largest of both
+			sect->align = alignment;
+			sect->alignOfs = curOfs;
+		}
+	}
+
+	return nbSectErrors;
+}
+
+static void mergeSections(struct Section *sect, enum SectionType type, uint32_t org, uint32_t bank,
+			  uint8_t alignment, uint16_t alignOffset, enum SectionModifier mod)
+{
+	unsigned int nbSectErrors = 0;
+
+	if (type != sect->type)
+		fail("Section already exists but with type %s\n", typeNames[sect->type]);
+
+	if (sect->modifier != mod) {
+		fail("Section already declared as %s section\n", sectionModNames[sect->modifier]);
+	} else {
+		switch (mod) {
+		case SECTION_UNION:
+		case SECTION_FRAGMENT:
+			nbSectErrors += (mod == SECTION_UNION ? mergeSectUnion : mergeFragments)
+						(sect, type, org, alignment, alignOffset);
+
+			// Common checks
+
+			/* If the section's bank is unspecified, override it */
+			if (sect->bank == -1)
+				sect->bank = bank;
+			/* If both specify a bank, it must be the same one */
+			else if (bank != -1 && sect->bank != bank)
+				fail("Section already declared with different bank %" PRIu32 "\n",
+				     sect->bank);
+			break;
+
+		case SECTION_NORMAL:
+			// TODO: this should report where the section was defined
+			fail("Section already defined previously\n");
+			break;
+		}
+	}
+
+	if (nbSectErrors)
+		fatalerror("Cannot create section \"%s\" (%u error%s)\n",
+			   sect->name, nbSectErrors, nbSectErrors == 1 ? "" : "s");
+}
+
+#undef fail
+
 /*
  * Find a section by name and type. If it doesn't exist, create it
  */
-static struct Section *getSection(char const *name, enum SectionType type,
-				  uint32_t org, struct SectionSpec const *attrs,
-				  enum SectionModifier mod)
+static struct Section *getSection(char const *name, enum SectionType type, uint32_t org,
+				  struct SectionSpec const *attrs, enum SectionModifier mod)
 {
-#define mask(align) ((1 << (align)) - 1)
 	uint32_t bank = attrs->bank;
 	uint8_t alignment = attrs->alignment;
 	uint16_t alignOffset = attrs->alignOfs;
 
+	// First, validate parameters, and normalize them if applicable
+
 	if (bank != -1) {
 		if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM
 		 && type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX)
@@ -116,18 +266,32 @@
 			error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
 		else if (bank < bankranges[type][0]
 		      || bank > bankranges[type][1])
-			error("%s bank value $%" PRIx32 " out of range ($%" PRIx32 " to $%"
+			error("%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04"
 				PRIx32 ")\n", typeNames[type], bank,
 				bankranges[type][0], bankranges[type][1]);
+	} else if (nbbanks(type) == 1) {
+		// If the section type only has a single bank, implicitly force it
+		bank = bankranges[type][0];
 	}
 
 	if (alignOffset >= 1 << alignment) {
-		error("Alignment offset must not be greater than alignment (%" PRIu16 " < %u)\n",
-			alignOffset, 1U << alignment);
+		error("Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)\n",
+		      alignOffset, 1U << alignment);
 		alignOffset = 0;
 	}
 
+	if (org != -1) {
+		if (org < startaddr[type] || org > endaddr(type))
+			error("Section \"%s\"'s fixed address %#" PRIx32
+				" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
+				name, org, startaddr[type], endaddr(type));
+	}
+
 	if (alignment != 0) {
+		if (alignment > 16) {
+			error("Alignment must be between 0 and 16, not %u\n", alignment);
+			alignment = 16;
+		}
 		/* It doesn't make sense to have both alignment and org set */
 		uint32_t mask = mask(alignment);
 
@@ -139,128 +303,20 @@
 		} else if (startaddr[type] & mask) {
 			error("Section \"%s\"'s alignment cannot be attained in %s\n",
 				name, typeNames[type]);
+		} else if (alignment == 16) {
+			// Treat an alignment of 16 as being fixed at address 0
+			alignment = 0;
+			org = 0;
+			// The address is known to be valid, since the alignment is
 		}
 	}
 
-	if (org != -1) {
-		if (org < startaddr[type] || org > endaddr(type))
-			error("Section \"%s\"'s fixed address %#" PRIx32
-				" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
-				name, org, startaddr[type], endaddr(type));
-	}
+	// Check if another section exists with the same name; merge if yes, otherwise create one
 
-	if (nbbanks(type) == 1)
-		bank = bankranges[type][0];
-
 	struct Section *sect = out_FindSectionByName(name);
 
 	if (sect) {
-		unsigned int nbSectErrors = 0;
-#define fail(...) \
-	do { \
-		error(__VA_ARGS__); \
-		nbSectErrors++; \
-	} while (0)
-
-		if (type != sect->type)
-			fail("Section \"%s\" already exists but with type %s\n",
-			     sect->name, typeNames[sect->type]);
-
-		if (sect->modifier != mod)
-			fail("Section \"%s\" already declared as %s section\n",
-			     sect->name, sectionModNames[sect->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 (mod == SECTION_UNION) {
-			/*
-			 * WARNING: see comment about assumption in
-			 * `EndLoadSection` if modifying the following check!
-			 */
-			if (sect_HasData(type))
-				fail("Cannot declare ROM sections as UNION\n");
-			if (org != -1) {
-				/* If both are fixed, they must be the same */
-				if (sect->org != -1 && sect->org != org)
-					fail("Section \"%s\" already declared as fixed at different address $%"
-					     PRIx32 "\n",
-					     sect->name, sect->org);
-				else if (sect->align != 0
-				      && (mask(sect->align)
-						& (org - sect->alignOfs)))
-					fail("Section \"%s\" already declared as aligned to %u bytes (offset %"
-					     PRIu16 ")\n", sect->name, 1U << sect->align, sect->alignOfs);
-				else
-					/* Otherwise, just override */
-					sect->org = org;
-			} else if (alignment != 0) {
-				/* Make sure any fixed address is compatible */
-				if (sect->org != -1) {
-					if ((sect->org - alignOffset)
-							& mask(alignment))
-						fail("Section \"%s\" already declared as fixed at incompatible address $%"
-						     PRIx32 "\n", sect->name, sect->org);
-				/* Check if alignment offsets are compatible */
-				} else if ((alignOffset & mask(sect->align))
-					!= (sect->alignOfs
-							& mask(alignment))) {
-					fail("Section \"%s\" already declared with incompatible %"
-					     PRIu8 "-byte alignment (offset %" PRIu16 ")\n",
-					     sect->name, sect->align, sect->alignOfs);
-				} else if (alignment > sect->align) {
-					/*
-					 * If the section is not fixed,
-					 * its alignment is the largest of both
-					 */
-					sect->align = alignment;
-					sect->alignOfs = alignOffset;
-				}
-			}
-			/* If the section's bank is unspecified, override it */
-			if (sect->bank == -1)
-				sect->bank = bank;
-			/* If both specify a bank, it must be the same one */
-			else if (bank != -1 && sect->bank != bank)
-				fail("Section \"%s\" already declared with different bank %"
-				     PRIu32 "\n", sect->name, sect->bank);
-		} else { /* Section fragments are handled identically in RGBASM */
-			/* However, concaternating non-fragments will be made an error */
-			if (sect->modifier != SECTION_FRAGMENT || mod != SECTION_FRAGMENT)
-				warning(WARNING_OBSOLETE,
-					"Concatenation of non-fragment sections is deprecated\n");
-
-			if (org != sect->org) {
-				if (sect->org == -1)
-					fail("Section \"%s\" already declared as floating\n",
-					     sect->name);
-				else
-					fail("Section \"%s\" already declared as fixed at $%"
-					     PRIx32 "\n", sect->name, sect->org);
-			}
-			if (bank != sect->bank) {
-				if (sect->bank == -1)
-					fail("Section \"%s\" already declared as floating bank\n",
-					     sect->name);
-				else
-					fail("Section \"%s\" already declared as fixed at bank %"
-					     PRIu32 "\n", sect->name, sect->bank);
-			}
-			if (alignment != sect->align) {
-				if (sect->align == 0)
-					fail("Section \"%s\" already declared as unaligned\n",
-					     sect->name);
-				else
-					fail("Section \"%s\" already declared as aligned to %u bytes\n",
-					     sect->name, 1U << sect->align);
-			}
-		}
-
-		if (nbSectErrors)
-			fatalerror("Cannot create section \"%s\" (%u errors)\n",
-				   sect->name, nbSectErrors);
-#undef fail
+		mergeSections(sect, type, org, bank, alignment, alignOffset, mod);
 		return sect;
 	}
 
@@ -279,7 +335,6 @@
 	sect->bank = bank;
 	sect->align = alignment;
 	sect->alignOfs = alignOffset;
-	sect->next = pSectionList;
 	sect->patches = NULL;
 
 	/* It is only needed to allocate memory for ROM sections. */
@@ -294,14 +349,11 @@
 		sect->data = NULL;
 	}
 
-	/*
-	 * Add the new section to the list
-	 * at the beginning because order doesn't matter
-	 */
+	// Add the new section to the list (order doesn't matter)
+	sect->next = pSectionList;
 	pSectionList = sect;
 
 	return sect;
-#undef mask
 }
 
 /*
--- a/src/link/object.c
+++ b/src/link/object.c
@@ -349,6 +349,8 @@
 	section->bank = tmp;
 	tryGetc(byte, file, "%s: Cannot read \"%s\"'s alignment: %s",
 		fileName, section->name);
+	if (byte > 16)
+		byte = 16;
 	section->isAlignFixed = byte != 0;
 	section->alignMask = (1 << byte) - 1;
 	tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s alignment offset: %s",
--- a/src/link/section.c
+++ b/src/link/section.c
@@ -39,62 +39,123 @@
 	hash_ForEach(sections, forEach, &callbackArg);
 }
 
-static void mergeSections(struct Section *target, struct Section *other, enum SectionModifier mod)
+static void checkSectUnionCompat(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 $%" PRIx16 " and $%" PRIx16,
+				errx(1, "Section \"%s\" is defined with conflicting addresses $%04"
+				     PRIx16 " and $%04" PRIx16,
 				     other->name, target->org, other->org);
 		} else if (target->isAlignFixed) {
 			if ((other->org - target->alignOfs) & target->alignMask)
-				errx(1, "Section \"%s\" is defined with conflicting %" PRIu16 "-byte alignment (offset %" PRIu16 ") and address $%" PRIx16,
+				errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
+				     "-byte alignment (offset %" PRIu16 ") and address $%04" PRIx16,
 				     other->name, target->alignMask + 1,
 				     target->alignOfs, other->org);
 		}
 		target->isAddressFixed = true;
 		target->org = other->org;
+
 	} else if (other->isAlignFixed) {
 		if (target->isAddressFixed) {
 			if ((target->org - other->alignOfs) & other->alignMask)
-				errx(1, "Section \"%s\" is defined with conflicting address $%" PRIx16 " and %" PRIu16 "-byte alignment (offset %" PRIu16 ")",
+				errx(1, "Section \"%s\" is defined with conflicting address $%04"
+				     PRIx16 " and %" PRIu16 "-byte alignment (offset %" PRIu16 ")",
 				     other->name, target->org,
 				     other->alignMask + 1, other->alignOfs);
 		} else if (target->isAlignFixed
 			&& (other->alignMask & target->alignOfs)
 				 != (target->alignMask & other->alignOfs)) {
-			errx(1, "Section \"%s\" is defined with conflicting %" PRIu16 "-byte alignment (offset %" PRIu16 ") and %" PRIu16 "-byte alignment (offset %" PRIu16 ")",
-			     other->name, target->alignMask + 1,
-			     target->alignOfs, other->alignMask + 1,
-			     other->alignOfs);
-		} else if (!target->isAlignFixed
-			|| (other->alignMask > target->alignMask)) {
+			errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
+			     "-byte alignment (offset %" PRIu16 ") and %" PRIu16
+			     "-byte alignment (offset %" PRIu16 ")",
+			     other->name, target->alignMask + 1, target->alignOfs,
+			     other->alignMask + 1, other->alignOfs);
+		} else if (!target->isAlignFixed || (other->alignMask > target->alignMask)) {
 			target->isAlignFixed = true;
 			target->alignMask = other->alignMask;
 		}
 	}
+}
 
+static void checkFragmentCompat(struct Section *target, struct Section *other)
+{
+	if (other->isAddressFixed) {
+		uint16_t org = other->org - target->size;
+
+		if (target->isAddressFixed) {
+			if (target->org != org)
+				errx(1, "Section \"%s\" is defined with conflicting addresses $%04"
+				     PRIx16 " and $%04" PRIx16,
+				     other->name, target->org, other->org);
+
+		} else if (target->isAlignFixed) {
+			if ((org - target->alignOfs) & target->alignMask)
+				errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
+				     "-byte alignment (offset %" PRIu16 ") and address $%04" PRIx16,
+				     other->name, target->alignMask + 1,
+				     target->alignOfs, other->org);
+		}
+		target->isAddressFixed = true;
+		target->org = org;
+
+	} else if (other->isAlignFixed) {
+		int32_t ofs = (other->alignOfs - target->size) % (other->alignMask + 1);
+
+		if (ofs < 0)
+			ofs += other->alignMask + 1;
+
+		if (target->isAddressFixed) {
+			if ((target->org - ofs) & other->alignMask)
+				errx(1, "Section \"%s\" is defined with conflicting address $%04"
+				     PRIx16 " and %" PRIu16 "-byte alignment (offset %" PRIu16 ")",
+				     other->name, target->org,
+				     other->alignMask + 1, other->alignOfs);
+
+		} else if (target->isAlignFixed
+			&& (other->alignMask & target->alignOfs) != (target->alignMask & ofs)) {
+			errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
+			     "-byte alignment (offset %" PRIu16 ") and %" PRIu16
+			     "-byte alignment (offset %" PRIu16 ")",
+			     other->name, target->alignMask + 1, target->alignOfs,
+			     other->alignMask + 1, other->alignOfs);
+
+		} else if (!target->isAlignFixed || (other->alignMask > target->alignMask)) {
+			target->isAlignFixed = true;
+			target->alignMask = other->alignMask;
+			target->alignOfs = ofs;
+		}
+	}
+}
+
+static void mergeSections(struct Section *target, struct Section *other, enum SectionModifier mod)
+{
+	// Common checks
+
+	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->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 %" PRIu32 " and %" PRIu32,
-			     other->name, target->bank, other->bank);
+			errx(1, "Section \"%s\" is defined with conflicting banks %" PRIu32 " and %"
+			     PRIu32, other->name, target->bank, other->bank);
 		}
 	}
 
 	switch (mod) {
 	case SECTION_UNION:
+		checkSectUnionCompat(target, other);
 		if (other->size > target->size)
 			target->size = other->size;
 		break;
 
 	case SECTION_FRAGMENT:
+		checkFragmentCompat(target, other);
 		target->size += other->size;
 		other->offset = target->size - other->size;
 		if (sect_HasData(target->type)) {
@@ -167,7 +228,7 @@
 		fail("Section \"%s\" has an invalid type.", section->name);
 	if (is32kMode && section->type == SECTTYPE_ROMX) {
 		if (section->isBankFixed && section->bank != 1)
-			fail("%s: ROMX sections must be in bank 1 with option -t.",
+			fail("%s: ROMX sections must be in bank 1 (if any) with option -t",
 			     section->name);
 		else
 			section->type = SECTTYPE_ROM0;
@@ -174,13 +235,13 @@
 	}
 	if (isWRA0Mode && section->type == SECTTYPE_WRAMX) {
 		if (section->isBankFixed && section->bank != 1)
-			fail("%s: WRAMX sections must be in bank 1 with options -w or -d.",
+			fail("%s: WRAMX sections must be in bank 1 with options -w or -d",
 			     section->name);
 		else
 			section->type = SECTTYPE_WRAMX;
 	}
 	if (isDmgMode && section->type == SECTTYPE_VRAM && section->bank == 1)
-		fail("%s: VRAM bank 1 can't be used with option -d.",
+		fail("%s: VRAM bank 1 can't be used with option -d",
 		     section->name);
 
 	/*
@@ -191,17 +252,13 @@
 		section->isAlignFixed = false;
 
 	/* Too large an alignment may not be satisfiable */
-	if (section->isAlignFixed
-	 && (section->alignMask & startaddr[section->type]))
-		fail("%s: %s sections cannot be aligned to $%" PRIx16 " bytes",
-		     section->name, typeNames[section->type],
-		     section->alignMask + 1);
+	if (section->isAlignFixed && (section->alignMask & startaddr[section->type]))
+		fail("%s: %s sections cannot be aligned to $%04" PRIx16 " bytes",
+		     section->name, typeNames[section->type], section->alignMask + 1);
 
-	uint32_t minbank = bankranges[section->type][0],
-		 maxbank = bankranges[section->type][1];
+	uint32_t minbank = bankranges[section->type][0], maxbank = bankranges[section->type][1];
 
-	if (section->isBankFixed && section->bank < minbank
-				 && section->bank > maxbank)
+	if (section->isBankFixed && section->bank < minbank && section->bank > maxbank)
 		fail(minbank == maxbank
 			? "Cannot place section \"%s\" in bank %" PRIu32 ", it must be %" PRIu32
 			: "Cannot place section \"%s\" in bank %" PRIu32 ", it must be between %" PRIu32 " and %" PRIu32,
@@ -219,35 +276,26 @@
 		section->isBankFixed = true;
 	}
 
-	if (section->isAlignFixed) {
-		enum SectionType type = section->type;
-
+	if (section->isAddressFixed) {
 		/* It doesn't make sense to have both org and alignment set */
-		if (section->isAddressFixed) {
-			if (section->org & section->alignMask)
+		if (section->isAlignFixed) {
+			if ((section->org & section->alignMask) != section->alignOfs)
 				fail("Section \"%s\"'s fixed address doesn't match its alignment",
 				     section->name);
 			section->isAlignFixed = false;
-		} else if ((endaddr(type) & section->alignMask)
-			   == startaddr[type]) {
-			section->org = startaddr[type];
-			section->isAlignFixed = false;
-			section->isAddressFixed = true;
 		}
-	}
 
-	if (section->isAddressFixed) {
 		/* Ensure the target address is valid */
 		if (section->org < startaddr[section->type]
 		 || section->org > endaddr(section->type))
-			fail("Section \"%s\"'s fixed address %#" PRIx16 " is outside of range [%#" PRIx16 "; %#" PRIx16 "]",
-			     section->name, section->org,
+			fail("Section \"%s\"'s fixed address %#" PRIx16 " is outside of range [%#"
+			     PRIx16 "; %#" PRIx16 "]", section->name, section->org,
 			     startaddr[section->type], endaddr(section->type));
 
 		if (section->org + section->size > endaddr(section->type) + 1)
-			fail("Section \"%s\"'s end address %#" PRIx16 " is greater than last address %#" PRIx16,
-			     section->name, section->org + section->size,
-			     endaddr(section->type) + 1);
+			fail("Section \"%s\"'s end address %#" PRIx16
+			     " is greater than last address %#" PRIx16, section->name,
+			     section->org + section->size, endaddr(section->type) + 1);
 	}
 
 #undef fail
--- /dev/null
+++ b/test/asm/align-16.asm
@@ -1,0 +1,8 @@
+
+SECTION "Byte", ROM0
+
+	db 2
+
+SECTION "ROM0", ROM0, ALIGN[16]
+
+	db 1
--- /dev/null
+++ b/test/asm/align-16.out.bin
@@ -1,0 +1,1 @@
+
\ No newline at end of file
--- /dev/null
+++ b/test/asm/align-large-ofs.asm
@@ -1,0 +1,2 @@
+
+SECTION "Tesst", ROM0, ALIGN[1,2]
--- /dev/null
+++ b/test/asm/align-large-ofs.err
@@ -1,0 +1,3 @@
+ERROR: align-large-ofs.asm(2):
+    Alignment offset (2) must be smaller than alignment size (2)
+error: Assembly aborted (1 errors)!
--- /dev/null
+++ b/test/asm/align-large.asm
@@ -1,0 +1,2 @@
+
+SECTION "You lost the game", ROM0[17]
--- /dev/null
+++ b/test/asm/fragment-align-org-rev.asm
@@ -1,0 +1,27 @@
+
+SECTION "Word", ROM0/*[5]*/
+
+	dw $78df
+
+SECTION FRAGMENT "Frag", ROM0/*[1]*/
+
+	db $40
+
+SECTION FRAGMENT "Frag", ROM0/*[2]*/
+
+	db $2e
+
+SECTION FRAGMENT "Frag", ROM0/*[3]*/,ALIGN[1,1]
+
+	db $1f
+
+SECTION "Byte", ROM0/*[0]*/
+
+	db $44
+
+SECTION FRAGMENT "Frag", ROM0/*[4]*/
+
+	db $7b
+	align 2, 1
+
+SECTION FRAGMENT "Frag", ROM0[5]
--- /dev/null
+++ b/test/asm/fragment-align-org-rev.out.bin
@@ -1,0 +1,1 @@
+D@.{�x
\ No newline at end of file
--- /dev/null
+++ b/test/asm/fragment-align-org.asm
@@ -1,0 +1,25 @@
+
+SECTION FRAGMENT "Frag", ROM0[1]
+
+	db $40
+
+SECTION "Word", ROM0/*[6]*/
+
+	dw $78d5
+
+SECTION FRAGMENT "Frag", ROM0/*[2]*/,ALIGN[1]
+
+	db $2e
+
+SECTION FRAGMENT "Frag", ROM0/*[3]*/
+
+	db $1f
+
+SECTION "Byte", ROM0/*[0]*/
+
+	db $44
+
+SECTION FRAGMENT "Frag", ROM0/*[4]*/
+
+	db $7b
+	align 2, 1
--- /dev/null
+++ b/test/asm/fragment-align-org.out.bin
@@ -1,0 +1,1 @@
+D@.{�x
\ No newline at end of file
--- /dev/null
+++ b/test/asm/fragment-align.asm
@@ -1,0 +1,25 @@
+
+SECTION FRAGMENT "Frag", ROM0/*[1]*/
+
+	db $40
+
+SECTION "Word", ROM0/*[6]*/
+
+	dw $78d5
+
+SECTION FRAGMENT "Frag", ROM0/*[2]*/,ALIGN[1]
+
+	db $2e
+
+SECTION FRAGMENT "Frag", ROM0/*[3]*/
+
+	db $1f
+
+SECTION "Byte", ROM0/*[0]*/
+
+	db $44
+
+SECTION FRAGMENT "Frag", ROM0/*[4]*/
+
+	db $7b
+	align 2 ; Uh oh
--- /dev/null
+++ b/test/asm/fragment-align.err
@@ -1,0 +1,3 @@
+ERROR: fragment-align.asm(25):
+    Section's alignment fails required alignment (offset from section start = $0004)
+error: Assembly aborted (1 errors)!
--- a/test/asm/section-union.err
+++ b/test/asm/section-union.err
@@ -1,10 +1,4 @@
 ERROR: section-union.asm(37):
-    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):
-    Section "test" already declared as aligned to 256 bytes
+    Section already declared as union section
 FATAL: section-union.asm(37):
-    Cannot create section "test" (3 errors)
+    Cannot create section "test" (1 error)
--- /dev/null
+++ b/test/link/fragment-align/base-bad/a.asm
@@ -1,0 +1,20 @@
+
+SECTION FRAGMENT "Frag", ROM0/*[1]*/
+
+	db $40
+
+SECTION "Word", ROM0/*[6]*/
+
+	dw $78d5
+
+SECTION FRAGMENT "Frag", ROM0/*[2]*/,ALIGN[1]
+
+	db $2e
+
+SECTION FRAGMENT "Frag", ROM0/*[3]*/
+
+	db $1f
+
+SECTION "Byte", ROM0/*[0]*/
+
+	db $44
--- /dev/null
+++ b/test/link/fragment-align/base-bad/b.asm
@@ -1,0 +1,5 @@
+
+SECTION FRAGMENT "Frag", ROM0/*[4]*/
+
+	db $7b
+	align 2 ; Uh oh
--- /dev/null
+++ b/test/link/fragment-align/base-bad/out.err
@@ -1,0 +1,1 @@
+error: Section "Frag" is defined with conflicting 2-byte alignment (offset 1) and 4-byte alignment (offset 3)
--- /dev/null
+++ b/test/link/fragment-align/base/a.asm
@@ -1,0 +1,20 @@
+
+SECTION FRAGMENT "Frag", ROM0/*[1]*/
+
+	db $40
+
+SECTION "Word", ROM0/*[5]*/
+
+	dw $78d5
+
+SECTION FRAGMENT "Frag", ROM0/*[2]*/,ALIGN[1]
+
+	db $2e
+
+SECTION FRAGMENT "Frag", ROM0/*[3]*/
+
+	db $1f
+
+SECTION "Byte", ROM0/*[0]*/
+
+	db $44
--- /dev/null
+++ b/test/link/fragment-align/base/b.asm
@@ -1,0 +1,5 @@
+
+SECTION FRAGMENT "Frag", ROM0/*[4]*/
+
+	db $7b
+	align 2, 1
--- /dev/null
+++ b/test/link/fragment-align/base/out.gb
@@ -1,0 +1,1 @@
+D@.{�x
\ No newline at end of file
--- /dev/null
+++ b/test/link/fragment-align/org-bad/a.asm
@@ -1,0 +1,8 @@
+
+SECTION FRAGMENT "Frag", ROM0[0] ; Uh oh
+
+	db $40
+
+SECTION "Word", ROM0/*[6]*/
+
+	dw $78d5
--- /dev/null
+++ b/test/link/fragment-align/org-bad/b.asm
@@ -1,0 +1,17 @@
+
+SECTION FRAGMENT "Frag", ROM0/*[2]*/,ALIGN[1]
+
+	db $2e
+
+SECTION FRAGMENT "Frag", ROM0/*[3]*/
+
+	db $1f
+
+SECTION "Byte", ROM0/*[0]*/
+
+	db $44
+
+SECTION FRAGMENT "Frag", ROM0/*[4]*/
+
+	db $7b
+	align 2, 1
--- /dev/null
+++ b/test/link/fragment-align/org-bad/out.err
@@ -1,0 +1,1 @@
+error: Section "Frag" is defined with conflicting address $0000 and 4-byte alignment (offset 2)
--- /dev/null
+++ b/test/link/fragment-align/org-rev-bad/a.asm
@@ -1,0 +1,25 @@
+
+SECTION "Word", ROM0/*[5]*/
+
+	dw $78df
+
+SECTION FRAGMENT "Frag", ROM0/*[1]*/
+
+	db $40
+
+SECTION FRAGMENT "Frag", ROM0/*[2]*/
+
+	db $2e
+
+SECTION FRAGMENT "Frag", ROM0/*[3]*/,ALIGN[1,1]
+
+	db $1f
+
+SECTION "Byte", ROM0/*[0]*/
+
+	db $44
+
+SECTION FRAGMENT "Frag", ROM0/*[4]*/
+
+	db $7b
+	align 2, 1
--- /dev/null
+++ b/test/link/fragment-align/org-rev-bad/b.asm
@@ -1,0 +1,2 @@
+
+SECTION FRAGMENT "Frag", ROM0[6] ; Uh oh
--- /dev/null
+++ b/test/link/fragment-align/org-rev-bad/out.err
@@ -1,0 +1,1 @@
+error: Section "Frag" is defined with conflicting 4-byte alignment (offset 1) and address $0006
--- /dev/null
+++ b/test/link/fragment-align/org-rev/a.asm
@@ -1,0 +1,25 @@
+
+SECTION "Word", ROM0/*[5]*/
+
+	dw $78df
+
+SECTION FRAGMENT "Frag", ROM0/*[1]*/
+
+	db $40
+
+SECTION FRAGMENT "Frag", ROM0/*[2]*/
+
+	db $2e
+
+SECTION FRAGMENT "Frag", ROM0/*[3]*/,ALIGN[1,1]
+
+	db $1f
+
+SECTION "Byte", ROM0/*[0]*/
+
+	db $44
+
+SECTION FRAGMENT "Frag", ROM0/*[4]*/
+
+	db $7b
+	align 2, 1
--- /dev/null
+++ b/test/link/fragment-align/org-rev/b.asm
@@ -1,0 +1,2 @@
+
+SECTION FRAGMENT "Frag", ROM0[5]
--- /dev/null
+++ b/test/link/fragment-align/org-rev/out.gb
@@ -1,0 +1,1 @@
+D@.{�x
\ No newline at end of file
--- /dev/null
+++ b/test/link/fragment-align/org/a.asm
@@ -1,0 +1,8 @@
+
+SECTION FRAGMENT "Frag", ROM0[1]
+
+	db $40
+
+SECTION "Word", ROM0/*[6]*/
+
+	dw $78d5
--- /dev/null
+++ b/test/link/fragment-align/org/b.asm
@@ -1,0 +1,17 @@
+
+SECTION FRAGMENT "Frag", ROM0/*[2]*/,ALIGN[1]
+
+	db $2e
+
+SECTION FRAGMENT "Frag", ROM0/*[3]*/
+
+	db $1f
+
+SECTION "Byte", ROM0/*[0]*/
+
+	db $44
+
+SECTION FRAGMENT "Frag", ROM0/*[4]*/
+
+	db $7b
+	align 2, 1
--- /dev/null
+++ b/test/link/fragment-align/org/out.gb
@@ -1,0 +1,1 @@
+D@.{�x
\ No newline at end of file
--- a/test/link/section-union/align-conflict.out
+++ b/test/link/section-union/align-conflict.out
@@ -1,6 +1,6 @@
 error: Section "conflicting alignment" is defined with conflicting 4-byte alignment (offset 0) and address $cafe
 ---
 ERROR: <stdin>(18):
-    Section "conflicting alignment" already declared as aligned to 4 bytes (offset 0)
+    Section already declared as aligned to 4 bytes (offset 0)
 FATAL: <stdin>(18):
-    Cannot create section "conflicting alignment" (1 errors)
+    Cannot create section "conflicting alignment" (1 error)
--- a/test/link/section-union/align-ofs-conflict.out
+++ b/test/link/section-union/align-ofs-conflict.out
@@ -1,6 +1,6 @@
 error: Section "conflicting alignment" is defined with conflicting 8-byte alignment (offset 7) and 16-byte alignment (offset 14)
 ---
 ERROR: <stdin>(18):
-    Section "conflicting alignment" already declared with incompatible 3-byte alignment (offset 7)
+    Section already declared with incompatible 3-byte alignment (offset 7)
 FATAL: <stdin>(18):
-    Cannot create section "conflicting alignment" (1 errors)
+    Cannot create section "conflicting alignment" (1 error)
--- a/test/link/section-union/bad-types.out
+++ b/test/link/section-union/bad-types.out
@@ -1,6 +1,6 @@
 error: Section "conflicting types" is defined with conflicting types HRAM and WRAM0
 ---
 ERROR: <stdin>(18):
-    Section "conflicting types" already exists but with type HRAM
+    Section already exists but with type HRAM
 FATAL: <stdin>(18):
-    Cannot create section "conflicting types" (1 errors)
+    Cannot create section "conflicting types" (1 error)
--- a/test/link/section-union/bank-conflict.out
+++ b/test/link/section-union/bank-conflict.out
@@ -1,6 +1,6 @@
 error: Section "conflicting banks" is defined with conflicting banks 4 and 1
 ---
 ERROR: <stdin>(14):
-    Section "conflicting banks" already declared with different bank 4
+    Section already declared with different bank 4
 FATAL: <stdin>(14):
-    Cannot create section "conflicting banks" (1 errors)
+    Cannot create section "conflicting banks" (1 error)
--- a/test/link/section-union/data-overlay.out
+++ b/test/link/section-union/data-overlay.out
@@ -3,4 +3,4 @@
 ERROR: <stdin>(18):
     Cannot declare ROM sections as UNION
 FATAL: <stdin>(18):
-    Cannot create section "overlaid data" (1 errors)
+    Cannot create section "overlaid data" (1 error)
--- a/test/link/section-union/different-data.out
+++ b/test/link/section-union/different-data.out
@@ -3,4 +3,4 @@
 ERROR: <stdin>(16):
     Cannot declare ROM sections as UNION
 FATAL: <stdin>(16):
-    Cannot create section "different data" (1 errors)
+    Cannot create section "different data" (1 error)
--- a/test/link/section-union/different-ofs.out
+++ b/test/link/section-union/different-ofs.out
@@ -1,6 +1,6 @@
 error: Section "conflicting alignment" is defined with conflicting 8-byte alignment (offset 7) and 8-byte alignment (offset 6)
 ---
 ERROR: <stdin>(18):
-    Section "conflicting alignment" already declared with incompatible 3-byte alignment (offset 7)
+    Section already declared with incompatible 3-byte alignment (offset 7)
 FATAL: <stdin>(18):
-    Cannot create section "conflicting alignment" (1 errors)
+    Cannot create section "conflicting alignment" (1 error)
--- a/test/link/section-union/different-size.out
+++ b/test/link/section-union/different-size.out
@@ -3,4 +3,4 @@
 ERROR: <stdin>(16):
     Cannot declare ROM sections as UNION
 FATAL: <stdin>(16):
-    Cannot create section "different section sizes" (1 errors)
+    Cannot create section "different section sizes" (1 error)
--- a/test/link/section-union/different-syntaxes.out
+++ b/test/link/section-union/different-syntaxes.out
@@ -3,4 +3,4 @@
 ERROR: <stdin>(18):
     Cannot declare ROM sections as UNION
 FATAL: <stdin>(18):
-    Cannot create section "different syntaxes" (1 errors)
+    Cannot create section "different syntaxes" (1 error)
--- a/test/link/section-union/org-conflict.out
+++ b/test/link/section-union/org-conflict.out
@@ -1,6 +1,6 @@
 error: Section "conflicting address" is defined with conflicting addresses $beef and $babe
 ---
 ERROR: <stdin>(16):
-    Section "conflicting address" already declared as fixed at different address $beef
+    Section already declared as fixed at different address $beef
 FATAL: <stdin>(16):
-    Cannot create section "conflicting address" (1 errors)
+    Cannot create section "conflicting address" (1 error)
--- a/test/link/section-union/split-data.out
+++ b/test/link/section-union/split-data.out
@@ -3,4 +3,4 @@
 ERROR: <stdin>(18):
     Cannot declare ROM sections as UNION
 FATAL: <stdin>(18):
-    Cannot create section "mutually-overlaid data" (1 errors)
+    Cannot create section "mutually-overlaid data" (1 error)
--- a/test/link/test.sh
+++ b/test/link/test.sh
@@ -1,5 +1,6 @@
-#!/bin/sh
+#!/bin/bash
 export LC_ALL=C
+set -o pipefail
 
 otemp=$(mktemp)
 gbtemp=$(mktemp)
@@ -13,16 +14,31 @@
 resbold=$(tput sgr0)
 red=$(tput setaf 1)
 rescolors=$(tput op)
+
 tryDiff () {
-	diff -u --strip-trailing-cr $1 $2 || (echo "${bold}${red}${i%.asm}.out mismatch!${rescolors}${resbold}"; false)
+	if ! diff -u --strip-trailing-cr $1 $2; then
+		echo "${bold}${red}${i%.asm}.out mismatch!${rescolors}${resbold}"
+		false
+	fi
 }
 
 tryCmp () {
-	cmp $1 $2 || (../../contrib/gbdiff.bash $1 $2; echo "${bold}${red}${i%.asm}.out.bin mismatch!${rescolors}${resbold}"; false)
+	if ! cmp $1 $2; then
+		../../contrib/gbdiff.bash $1 $2
+		echo "${bold}${red}${i%.asm}.out.bin mismatch!${rescolors}${resbold}"
+		false
+	fi
 }
 
 RGBASM=../../rgbasm
 RGBLINK=../../rgblink
+rgblink() {
+	out="$(env $RGBLINK "$@")" || return $?
+	if [[ -n "$out" ]]; then
+		echo "$bold${red}Linking shouldn't produce anything on stdout!$rescolors$resbold"
+		false
+	fi
+}
 
 for i in *.asm; do
 	$RGBASM -o $otemp $i
@@ -31,13 +47,13 @@
 	ran_flag=
 	for flag in '-d' '-t' '-w'; do
 		if [ -f ${i%.asm}-no${flag}.out ]; then
-			$RGBLINK -o $gbtemp $otemp > $outtemp 2>&1
+			rgblink -o $gbtemp $otemp > $outtemp 2>&1
 			tryDiff ${i%.asm}-no${flag}.out $outtemp
 			rc=$(($? || $rc))
 			ran_flag=1
 		fi
 		if [ -f ${i%.asm}${flag}.out ]; then
-			$RGBLINK ${flag} -o $gbtemp $otemp > $outtemp 2>&1
+			rgblink ${flag} -o $gbtemp $otemp > $outtemp 2>&1
 			tryDiff ${i%.asm}${flag}.out $outtemp
 			rc=$(($? || $rc))
 			ran_flag=1
@@ -49,7 +65,7 @@
 
 	# Other tests have several linker scripts
 	find . -name "${i%.asm}*.link" | while read script; do
-		$RGBLINK -l $script -o $gbtemp $otemp > $outtemp 2>&1
+		rgblink -l $script -o $gbtemp $otemp > $outtemp 2>&1
 		tryDiff ${script%.link}.out $outtemp
 		rc=$(($? || $rc))
 		ran_flag=1
@@ -59,7 +75,7 @@
 	fi
 
 	# The rest of the tests just links a file, and maybe checks the binary
-	$RGBLINK -o $gbtemp $otemp > $outtemp 2>&1
+	rgblink -o $gbtemp $otemp > $outtemp 2>&1
 	if [ -f ${i%.asm}.out ]; then
 		tryDiff ${i%.asm}.out $outtemp
 		rc=$(($? || $rc))
@@ -75,28 +91,41 @@
 
 # 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" tryCmp $gbtemp $gbtemp2
-rc=$(($? || $rc))
-
 $RGBASM -o $otemp bank-const/a.asm
 $RGBASM -o $gbtemp2 bank-const/b.asm
-$RGBLINK -o $gbtemp $gbtemp2 $otemp > $outtemp 2>&1
+rgblink -o $gbtemp $gbtemp2 $otemp > $outtemp 2>&1
 i="bank-const.asm" tryDiff bank-const/err.out $outtemp
 rc=$(($? || $rc))
 
+for i in fragment-align/*; do
+	$RGBASM -o $otemp $i/a.asm
+	$RGBASM -o $gbtemp2 $i/b.asm
+	rgblink -o $gbtemp $otemp $gbtemp2 2>$outtemp
+	tryDiff $i/out.err $outtemp
+	rc=$(($? || $rc))
+	if [[ -f $i/out.gb ]]; then
+		dd if=$gbtemp count=1 bs=$(printf %s $(wc -c < $i/out.gb)) > $otemp 2>/dev/null
+		tryCmp $i/out.gb $otemp
+		rc=$(($? || $rc))
+	fi
+done
+
+$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" 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
+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
+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))
@@ -103,13 +132,13 @@
 for i in section-union/*.asm; do
 	$RGBASM -o $otemp   $i
 	$RGBASM -o $gbtemp2 $i -DSECOND
-	if $RGBLINK $otemp $gbtemp2 > $outtemp 2>&1; then
+	if rgblink $otemp $gbtemp2 2>$outtemp; 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
+	cat $i - $i <<<'SECOND equs "1"' | $RGBASM - 2>> $outtemp
 	tryDiff ${i%.asm}.out $outtemp
 	rc=$(($? || $rc))
 done
--- a/test/link/vram-fixed-dmg-mode-d.out
+++ b/test/link/vram-fixed-dmg-mode-d.out
@@ -1,2 +1,2 @@
-warning: v1: VRAM bank 1 can't be used with option -d.
+warning: v1: VRAM bank 1 can't be used with option -d
 error: Sanity checks failed