mirror of
https://github.com/gbdev/rgbds.git
synced 2026-09-22 13:47:05 +00:00
Refactor and fix sect packing's bank iteration
Moving both pieces of related code together, as it were. This also ends up changing the behaviour of scrambling, where sections could “leak” out of the scrambling's bank pool and resume being placed normally. We have discussed it offline, and decided that this was a bug. The test suite has been updated accordingly, which even gives the occasion to move this test from a special case to the main generic loop!
This commit is contained in:
+83
-58
@@ -23,11 +23,6 @@
|
||||
#include "link/symbol.hpp"
|
||||
#include "link/warning.hpp"
|
||||
|
||||
struct MemoryLocation {
|
||||
uint16_t address;
|
||||
uint32_t bank;
|
||||
};
|
||||
|
||||
struct FreeSpace {
|
||||
uint16_t address;
|
||||
uint16_t size;
|
||||
@@ -80,6 +75,71 @@ struct Scrambling {
|
||||
};
|
||||
static Scrambling scrambling;
|
||||
|
||||
struct MemoryLocation {
|
||||
uint16_t address;
|
||||
uint32_t bank;
|
||||
|
||||
static MemoryLocation initFor(Section const §ion) {
|
||||
MemoryLocation location;
|
||||
|
||||
if (section.isAddressFixed) { // This will never change.
|
||||
location.address = section.org;
|
||||
}
|
||||
|
||||
if (section.isBankFixed) {
|
||||
location.bank = section.bank;
|
||||
} else {
|
||||
location.bank = section.typeInfo().firstBank;
|
||||
|
||||
if (auto info = scrambling.getInfoFor(section.type);
|
||||
info.has_value() && info->maxOfs != 0) { // If scrambling is enabled...
|
||||
// ...then we will begin the search from a different offset for each section.
|
||||
// Go to the next offset (backwards), wrapping around. (Thus, no underflow!)
|
||||
info->curOfs = (info->curOfs != 0 ? info->curOfs : info->maxOfs) - 1;
|
||||
location.bank += info->curOfs;
|
||||
}
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
// Try again in the next bank, if one is available.
|
||||
[[nodiscard("This returns whether iteration can be continued")]]
|
||||
bool goToNextApplicableBankFor(Section const §ion) {
|
||||
assume(bank >= section.typeInfo().firstBank);
|
||||
assume(bank <= section.typeInfo().lastBank);
|
||||
|
||||
if (section.isBankFixed) {
|
||||
// We have already tried the only possible bank.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try scrambled banks in descending order until no bank in the scrambled range is
|
||||
// available.
|
||||
if (auto info = scrambling.getInfoFor(section.type);
|
||||
info.has_value() && info->maxOfs != 0) {
|
||||
// All floating sections within a scrambled region should be
|
||||
// within the scrambled bank pool.
|
||||
assume(bank < info->maxOfs + section.typeInfo().firstBank);
|
||||
|
||||
uint16_t ofsWithinPool = bank - section.typeInfo().firstBank;
|
||||
// Go to the next bank (backwards), wrapping around. (Thus, no overflow!)
|
||||
ofsWithinPool = (ofsWithinPool != 0 ? ofsWithinPool : info->maxOfs) - 1;
|
||||
|
||||
bank = ofsWithinPool + section.typeInfo().firstBank;
|
||||
// Keep iterating unless we have wrapped back around to the start offset.
|
||||
return ofsWithinPool != info->curOfs;
|
||||
}
|
||||
|
||||
// Otherwise, try in ascending order.
|
||||
if (bank == section.typeInfo().lastBank) {
|
||||
return false;
|
||||
}
|
||||
++bank;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Checks whether a given location is suitable for placing a given section
|
||||
// This checks not only that the location has enough room for the section, but
|
||||
// also that the constraints (alignment...) are respected.
|
||||
@@ -101,31 +161,12 @@ static bool isLocationSuitable(
|
||||
return location.address + section.size <= freeSpace.address + freeSpace.size;
|
||||
}
|
||||
|
||||
static MemoryLocation getStartLocation(Section const §ion) {
|
||||
MemoryLocation location;
|
||||
|
||||
// Determine which bank we should start searching in
|
||||
if (section.isBankFixed) {
|
||||
location.bank = section.bank;
|
||||
} else {
|
||||
location.bank = section.typeInfo().firstBank;
|
||||
|
||||
if (auto info = scrambling.getInfoFor(section.type);
|
||||
info.has_value() && info->maxOfs != 0) {
|
||||
info->curOfs = (info->curOfs != 0 ? info->curOfs : info->maxOfs) - 1;
|
||||
location.bank += info->curOfs;
|
||||
}
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
// Returns a suitable free space index into `memory[section->type]` at which to place the given
|
||||
// section, or `std::nullopt` if none was found.
|
||||
static std::optional<size_t> getPlacement(Section const §ion, MemoryLocation &location) {
|
||||
SectionTypeInfo const &typeInfo = section.typeInfo();
|
||||
|
||||
for (;;) {
|
||||
do {
|
||||
if (location.bank < typeInfo.firstBank
|
||||
|| location.bank >= memory[section.type].size() + typeInfo.firstBank) {
|
||||
fatal(
|
||||
@@ -191,29 +232,9 @@ static std::optional<size_t> getPlacement(Section const §ion, MemoryLocation
|
||||
// Try again with the new location/free space combo
|
||||
}
|
||||
|
||||
// Try again in the next bank, if one is available.
|
||||
// Try scrambled banks in descending order until no bank in the scrambled range is
|
||||
// available. Otherwise, try in ascending order.
|
||||
if (section.isBankFixed) {
|
||||
return std::nullopt;
|
||||
} else if (
|
||||
auto info = scrambling.getInfoFor(section.type); info.has_value() && info->maxOfs != 0
|
||||
) {
|
||||
if (location.bank > typeInfo.firstBank) {
|
||||
--location.bank;
|
||||
} else if (info->maxOfs < typeInfo.lastBank) {
|
||||
location.bank = info->maxOfs + 1;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
} else if (location.bank < typeInfo.lastBank) {
|
||||
++location.bank;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Try again in the next iteration.
|
||||
}
|
||||
} while (location.goToNextApplicableBankFor(section));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::string getSectionDescription(Section const §ion) {
|
||||
@@ -245,7 +266,15 @@ static std::string getSectionDescription(Section const §ion) {
|
||||
} else {
|
||||
description = description + "anywhere";
|
||||
}
|
||||
|
||||
if (auto info = scrambling.getInfoFor(section.type);
|
||||
info.has_value() && info->maxOfs != 0) { // Only mention scrambling if it is enabled.
|
||||
char size[6];
|
||||
snprintf(size, sizeof(size), "%" PRIu16, info->maxOfs);
|
||||
description = description + " within the " + size + " scrambled banks";
|
||||
}
|
||||
}
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
@@ -263,16 +292,13 @@ static void assignSection(Section §ion, MemoryLocation const &location) {
|
||||
// Places a section in a suitable location, or error out if it fails to.
|
||||
// Due to the implemented algorithm, this should be called with sections of decreasing size!
|
||||
static void placeSection(Section §ion) {
|
||||
SectionTypeInfo const &typeInfo = section.typeInfo();
|
||||
MemoryLocation location = MemoryLocation::initFor(section);
|
||||
|
||||
// Specially handle 0-byte SECTIONs, as they can't overlap anything
|
||||
if (section.size == 0) {
|
||||
// Unless the SECTION has a fixed address or non-zero alignment, the starting
|
||||
// Unless the SECTION has a fixed address or non-zero alignment offset, the starting
|
||||
// address is fine for any alignment, as checked in `sect_DoSanityChecks`.
|
||||
MemoryLocation location = {
|
||||
.address = section.isAddressFixed ? section.org : typeInfo.startAddr,
|
||||
.bank = section.isBankFixed ? section.bank : typeInfo.firstBank,
|
||||
};
|
||||
location.address = section.isAddressFixed ? section.org : section.typeInfo().startAddr;
|
||||
if (section.isAlignFixed && !section.isAddressFixed) {
|
||||
if (uint16_t offset = (location.address - section.alignOfs) & section.alignMask;
|
||||
offset != 0) {
|
||||
@@ -285,9 +311,9 @@ static void placeSection(Section §ion) {
|
||||
|
||||
// Place section using first-fit decreasing algorithm
|
||||
// https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm
|
||||
MemoryLocation location = getStartLocation(section);
|
||||
if (std::optional<size_t> spaceIdx = getPlacement(section, location); spaceIdx) {
|
||||
std::deque<FreeSpace> &bankMem = memory[section.type][location.bank - typeInfo.firstBank];
|
||||
std::deque<FreeSpace> &bankMem =
|
||||
memory[section.type][location.bank - section.typeInfo().firstBank];
|
||||
FreeSpace &freeSpace = bankMem[*spaceIdx];
|
||||
|
||||
assignSection(section, location);
|
||||
@@ -324,9 +350,8 @@ static void placeSection(Section §ion) {
|
||||
if (!section.isBankFixed || !section.isAddressFixed) {
|
||||
// If a section failed to go to several places, nothing we can report
|
||||
fatal("Unable to place %s", getSectionDescription(section).c_str());
|
||||
} else if (
|
||||
uint16_t onePastEnd = typeInfo.endAddr() + 1; section.org + section.size > onePastEnd
|
||||
) {
|
||||
} else if (uint16_t onePastEnd = section.typeInfo().endAddr() + 1;
|
||||
section.org + section.size > onePastEnd) {
|
||||
// If the section just can't fit the bank, report that
|
||||
fatal(
|
||||
"Unable to place %s: section runs past end of region ($%04x > $%04x)",
|
||||
|
||||
Reference in New Issue
Block a user