mirror of
https://github.com/gbdev/rgbds.git
synced 2026-08-20 22:04:32 +00:00
339 lines
9.6 KiB
C++
339 lines
9.6 KiB
C++
// SPDX-License-Identifier: MIT
|
|
|
|
#include "link/layout.hpp"
|
|
|
|
#include <array>
|
|
#include <bit>
|
|
#include <inttypes.h>
|
|
#include <stdint.h>
|
|
#include <vector>
|
|
|
|
#include "helpers.hpp"
|
|
#include "linkdefs.hpp"
|
|
|
|
#include "link/section.hpp"
|
|
#include "link/warning.hpp"
|
|
|
|
static std::array<std::vector<uint16_t>, SECTTYPE_INVALID> curAddr;
|
|
static SectionType activeType = SECTTYPE_INVALID; // Index into curAddr
|
|
static uint32_t activeBankIdx; // Index into curAddr[activeType]
|
|
static bool isPcFloating;
|
|
static uint16_t floatingAlignMask;
|
|
static uint16_t floatingAlignOffset;
|
|
|
|
static void setActiveTypeAndIdx(SectionType type, uint32_t idx) {
|
|
activeType = type;
|
|
activeBankIdx = idx;
|
|
isPcFloating = false;
|
|
if (curAddr[activeType].size() <= activeBankIdx) {
|
|
curAddr[activeType].resize(activeBankIdx + 1, sectionTypeInfo[type].startAddr);
|
|
}
|
|
}
|
|
|
|
void layout_SetFloatingSectionType(SectionType type) {
|
|
if (sectTypeBanks(type) == 1) {
|
|
// There is only a single bank anyway, so just set the index to 0.
|
|
setActiveTypeAndIdx(type, 0);
|
|
} else {
|
|
activeType = type;
|
|
activeBankIdx = UINT32_MAX;
|
|
// Force PC to be floating for this kind of section.
|
|
// Because we wouldn't know how to index into `curAddr[activeType]`!
|
|
isPcFloating = true;
|
|
floatingAlignMask = 0;
|
|
floatingAlignOffset = 0;
|
|
}
|
|
}
|
|
|
|
void layout_SetSectionType(SectionType type) {
|
|
if (sectTypeBanks(type) != 1) {
|
|
scriptError("A bank number must be specified for %s", sectionTypeInfo[type].name.c_str());
|
|
// Keep going with a default value for the bank index.
|
|
}
|
|
|
|
setActiveTypeAndIdx(type, 0); // There is only a single bank anyway, so just set the index to 0.
|
|
}
|
|
|
|
void layout_SetSectionType(SectionType type, uint32_t bank) {
|
|
SectionTypeInfo const &typeInfo = sectionTypeInfo[type];
|
|
|
|
if (bank < typeInfo.firstBank) {
|
|
scriptError(
|
|
"%s bank %" PRIu32 " does not exist (the minimum is %" PRIu32 ")",
|
|
typeInfo.name.c_str(),
|
|
bank,
|
|
typeInfo.firstBank
|
|
);
|
|
bank = typeInfo.firstBank;
|
|
} else if (bank > typeInfo.lastBank) {
|
|
scriptError(
|
|
"%s bank %" PRIu32 " does not exist (the maximum is %" PRIu32 ")",
|
|
typeInfo.name.c_str(),
|
|
bank,
|
|
typeInfo.lastBank
|
|
);
|
|
}
|
|
|
|
setActiveTypeAndIdx(type, bank - typeInfo.firstBank);
|
|
}
|
|
|
|
void layout_SetAddr(uint32_t addr) {
|
|
if (activeType == SECTTYPE_INVALID) {
|
|
scriptError("Cannot set the current address: no memory region is active");
|
|
return;
|
|
}
|
|
if (activeBankIdx == UINT32_MAX) {
|
|
scriptError("Cannot set the current address: the bank is floating");
|
|
return;
|
|
}
|
|
|
|
uint16_t &pc = curAddr[activeType][activeBankIdx];
|
|
SectionTypeInfo const &typeInfo = sectionTypeInfo[activeType];
|
|
|
|
if (addr < pc) {
|
|
scriptError("Cannot decrease the current address (from $%04x to $%04x)", pc, addr);
|
|
} else if (addr > sectTypeEndAddr(activeType)) { // Allow "one past the end" sections.
|
|
scriptError(
|
|
"Cannot set the current address to $%04" PRIx32 ": %s ends at $%04" PRIx16,
|
|
addr,
|
|
typeInfo.name.c_str(),
|
|
sectTypeEndAddr(activeType)
|
|
);
|
|
pc = sectTypeEndAddr(activeType);
|
|
} else {
|
|
pc = addr;
|
|
}
|
|
isPcFloating = false;
|
|
}
|
|
|
|
void layout_MakeAddrFloating() {
|
|
if (activeType == SECTTYPE_INVALID) {
|
|
scriptError("Cannot make the current address floating: no memory region is active");
|
|
return;
|
|
}
|
|
|
|
isPcFloating = true;
|
|
floatingAlignMask = 0;
|
|
floatingAlignOffset = 0;
|
|
}
|
|
|
|
void layout_AlignTo(uint32_t alignment, uint32_t alignOfs) {
|
|
if (activeType == SECTTYPE_INVALID) {
|
|
scriptError("Cannot align: no memory region is active");
|
|
return;
|
|
}
|
|
|
|
if (isPcFloating) {
|
|
if (alignment >= 16) {
|
|
layout_SetAddr(floatingAlignOffset);
|
|
} else {
|
|
uint32_t alignSize = 1u << alignment;
|
|
uint32_t alignMask = alignSize - 1;
|
|
|
|
if (alignOfs >= alignSize) {
|
|
scriptError(
|
|
"Cannot align: The alignment offset (%" PRIu32
|
|
") must be less than alignment size (%" PRIu32 ")",
|
|
alignOfs,
|
|
alignSize
|
|
);
|
|
return;
|
|
}
|
|
|
|
floatingAlignMask = alignMask;
|
|
floatingAlignOffset = alignOfs & alignMask;
|
|
}
|
|
return;
|
|
}
|
|
|
|
SectionTypeInfo const &typeInfo = sectionTypeInfo[activeType];
|
|
uint16_t &pc = curAddr[activeType][activeBankIdx];
|
|
|
|
if (alignment > 16) {
|
|
scriptError("Cannot align: The alignment (%" PRIu32 ") must be less than 16", alignment);
|
|
return;
|
|
}
|
|
|
|
// Let it wrap around, this'll trip the final check if alignment == 16.
|
|
uint16_t length = alignOfs - pc;
|
|
|
|
if (alignment < 16) {
|
|
uint32_t alignSize = 1u << alignment;
|
|
uint32_t alignMask = alignSize - 1;
|
|
|
|
if (alignOfs >= alignSize) {
|
|
scriptError(
|
|
"Cannot align: The alignment offset (%" PRIu32
|
|
") must be less than alignment size (%" PRIu32 ")",
|
|
alignOfs,
|
|
alignSize
|
|
);
|
|
return;
|
|
}
|
|
|
|
assume(pc >= typeInfo.startAddr);
|
|
length &= alignMask;
|
|
}
|
|
|
|
if (uint16_t offset = pc - typeInfo.startAddr; length > typeInfo.size - offset) {
|
|
scriptError(
|
|
"Cannot align: the next suitable address after $%04" PRIx16 " is $%04" PRIx16
|
|
", past $%04" PRIx16,
|
|
pc,
|
|
static_cast<uint16_t>(pc + length),
|
|
static_cast<uint16_t>(sectTypeEndAddr(activeType) + 1)
|
|
);
|
|
return;
|
|
}
|
|
|
|
pc += length;
|
|
}
|
|
|
|
void layout_Pad(uint32_t length) {
|
|
if (activeType == SECTTYPE_INVALID) {
|
|
scriptError("Cannot increase the current address: no memory region is active");
|
|
return;
|
|
}
|
|
|
|
if (isPcFloating) {
|
|
floatingAlignOffset = (floatingAlignOffset + length) & floatingAlignMask;
|
|
return;
|
|
}
|
|
|
|
SectionTypeInfo const &typeInfo = sectionTypeInfo[activeType];
|
|
uint16_t &pc = curAddr[activeType][activeBankIdx];
|
|
|
|
assume(pc >= typeInfo.startAddr);
|
|
if (uint16_t offset = pc - typeInfo.startAddr; length + offset > typeInfo.size) {
|
|
scriptError(
|
|
"Cannot increase the current address by %u bytes: only %u bytes to $%04" PRIx16,
|
|
length,
|
|
typeInfo.size - offset,
|
|
static_cast<uint16_t>(sectTypeEndAddr(activeType) + 1)
|
|
);
|
|
} else {
|
|
pc += length;
|
|
}
|
|
}
|
|
|
|
void layout_PlaceSection(std::string const &name, bool isOptional) {
|
|
if (activeType == SECTTYPE_INVALID) {
|
|
scriptError("No memory region has been specified to place section \"%s\" in", name.c_str());
|
|
return;
|
|
}
|
|
|
|
Section *section = sect_GetSection(name.c_str());
|
|
if (!section) {
|
|
if (!isOptional) {
|
|
scriptError("Undefined section \"%s\"", name.c_str());
|
|
}
|
|
return;
|
|
}
|
|
|
|
SectionTypeInfo const &typeInfo = sectionTypeInfo[activeType];
|
|
assume(section->offset == 0);
|
|
// Check that the linker script doesn't contradict what the code says.
|
|
if (section->type == SECTTYPE_INVALID) {
|
|
// A section that has data must get assigned a type that requires data.
|
|
if (!sectTypeHasData(activeType) && !section->data.empty()) {
|
|
scriptError(
|
|
"\"%s\" is specified to be a %s section, but it contains data",
|
|
name.c_str(),
|
|
typeInfo.name.c_str()
|
|
);
|
|
} else if (sectTypeHasData(activeType) && section->data.empty() && section->size != 0) {
|
|
// A section that lacks data can only be assigned to a type that requires data
|
|
// if it's empty.
|
|
scriptError(
|
|
"\"%s\" is specified to be a %s section, but it does not contain data",
|
|
name.c_str(),
|
|
typeInfo.name.c_str()
|
|
);
|
|
} else {
|
|
// SDCC areas don't have a type assigned yet, so the linker script gives them one.
|
|
for (Section &piece : section->pieces()) {
|
|
piece.type = activeType;
|
|
}
|
|
}
|
|
} else if (section->type != activeType) {
|
|
scriptError(
|
|
"\"%s\" is specified to be a %s section, but it is already a %s section",
|
|
name.c_str(),
|
|
typeInfo.name.c_str(),
|
|
sectionTypeInfo[section->type].name.c_str()
|
|
);
|
|
}
|
|
|
|
if (activeBankIdx == UINT32_MAX) {
|
|
section->isBankFixed = false;
|
|
} else {
|
|
uint32_t bank = activeBankIdx + typeInfo.firstBank;
|
|
if (section->isBankFixed && bank != section->bank) {
|
|
scriptError(
|
|
"The linker script places section \"%s\" in %s bank %" PRIu32
|
|
", but it was already defined in bank %" PRIu32,
|
|
name.c_str(),
|
|
sectionTypeInfo[section->type].name.c_str(),
|
|
bank,
|
|
section->bank
|
|
);
|
|
}
|
|
section->isBankFixed = true;
|
|
section->bank = bank;
|
|
}
|
|
|
|
if (!isPcFloating) {
|
|
uint16_t &org = curAddr[activeType][activeBankIdx];
|
|
if (section->isAddressFixed && org != section->org) {
|
|
scriptError(
|
|
"The linker script assigns section \"%s\" to address $%04" PRIx16
|
|
", but it was already at $%04" PRIx16,
|
|
name.c_str(),
|
|
org,
|
|
section->org
|
|
);
|
|
} else if (section->isAlignFixed && (org & section->alignMask) != section->alignOfs) {
|
|
uint8_t alignment = std::countr_one(section->alignMask);
|
|
scriptError(
|
|
"The linker script assigns section \"%s\" to address $%04" PRIx16
|
|
", but that would be ALIGN[%" PRIu8 ", %" PRIu16
|
|
"] instead of the requested ALIGN[%" PRIu8 ", %" PRIu16 "]",
|
|
name.c_str(),
|
|
org,
|
|
alignment,
|
|
static_cast<uint16_t>(org & section->alignMask),
|
|
alignment,
|
|
section->alignOfs
|
|
);
|
|
}
|
|
section->isAddressFixed = true;
|
|
section->isAlignFixed = false; // This can't be set when the above is.
|
|
section->org = org;
|
|
|
|
uint16_t curOfs = org - typeInfo.startAddr;
|
|
if (section->size > typeInfo.size - curOfs) {
|
|
uint16_t overflowSize = section->size - (typeInfo.size - curOfs);
|
|
scriptError(
|
|
"The linker script assigns section \"%s\" to address $%04" PRIx16
|
|
", but then it would overflow %s by %" PRIu16 " byte%s",
|
|
name.c_str(),
|
|
org,
|
|
typeInfo.name.c_str(),
|
|
overflowSize,
|
|
overflowSize == 1 ? "" : "s"
|
|
);
|
|
// Fill as much as possible without going out of bounds.
|
|
org = typeInfo.startAddr + typeInfo.size;
|
|
} else {
|
|
org += section->size;
|
|
}
|
|
} else {
|
|
section->isAddressFixed = false;
|
|
section->isAlignFixed = floatingAlignMask != 0;
|
|
section->alignMask = floatingAlignMask;
|
|
section->alignOfs = floatingAlignOffset;
|
|
|
|
floatingAlignOffset = (floatingAlignOffset + section->size) & floatingAlignMask;
|
|
}
|
|
}
|