diff --git a/Makefile b/Makefile index 81f8f95d..a55d0001 100644 --- a/Makefile +++ b/Makefile @@ -80,6 +80,7 @@ src/asm/lexer.o src/asm/main.o: src/asm/parser.hpp rgblink_obj := \ ${common_obj} \ src/link/assign.o \ + src/link/layout.o \ src/link/main.o \ src/link/object.o \ src/link/output.o \ @@ -94,7 +95,7 @@ rgblink_obj := \ src/opmath.o \ src/util.o -src/link/main.o: src/link/script.hpp +src/link/layout.o src/link/main.o: src/link/script.hpp rgbfix_obj := \ ${common_obj} \ diff --git a/contrib/checkdiff.bash b/contrib/checkdiff.bash index 57cf8e99..50097914 100755 --- a/contrib/checkdiff.bash +++ b/contrib/checkdiff.bash @@ -26,11 +26,23 @@ dependency include/linkdefs.hpp man/rgbds.5 \ dependency src/asm/parser.y man/rgbasm.5 \ "Was the rgbasm grammar changed?" +dependency src/asm/actions.cpp man/rgbasm.5 \ + "Was the rgbasm grammar changed?" + dependency src/link/script.y man/rgblink.5 \ "Was the linker script grammar changed?" -dependency include/asm/warning.hpp man/rgbasm.1 \ +dependency src/link/layout.cpp man/rgblink.5 \ + "Was the linker script grammar changed?" + +dependency include/asm/warning.hpp man/rgbasm.1 \ "Were the rgbasm warnings changed?" +dependency include/link/warning.hpp man/rgblink.1 \ + "Were the rgblink warnings changed?" +dependency include/fix/warning.hpp man/rgbfix.1 \ + "Were the rgbfix warnings changed?" +dependency include/gfx/warning.hpp man/rgbgfx.1 \ + "Were the rgbgfx warnings changed?" dependency src/asm/object.cpp include/linkdefs.hpp \ "Should the object file revision be bumped?" diff --git a/include/asm/lexer.hpp b/include/asm/lexer.hpp index 5d4b0d0c..0e58fd50 100644 --- a/include/asm/lexer.hpp +++ b/include/asm/lexer.hpp @@ -121,7 +121,6 @@ void lexer_SetGfxDigits(char const digits[4]); bool lexer_AtTopLevel(); void lexer_RestartRept(uint32_t lineNo); -void lexer_Init(); void lexer_SetMode(LexerMode mode); void lexer_ToggleStringExpansion(bool enable); diff --git a/include/asm/symbol.hpp b/include/asm/symbol.hpp index 4730bf5f..aa831b8f 100644 --- a/include/asm/symbol.hpp +++ b/include/asm/symbol.hpp @@ -24,8 +24,8 @@ enum SymbolType { SYM_REF // Forward reference to a label }; -struct Symbol; // For the `sym_IsPC` forward declaration -bool sym_IsPC(Symbol const *sym); // For the inline `getSection` method +struct Symbol; // Forward declaration for `sym_IsPC` +bool sym_IsPC(Symbol const *sym); // Forward declaration for `getSection` struct Symbol { std::string name; diff --git a/include/gfx/main.hpp b/include/gfx/main.hpp index 9879142e..bdcedfd3 100644 --- a/include/gfx/main.hpp +++ b/include/gfx/main.hpp @@ -80,18 +80,6 @@ struct Options { extern Options options; -// Prints the error count, and exits with failure -[[noreturn]] -void giveUp(); -// If any error has been emitted thus far, calls `giveUp()`. -void requireZeroErrors(); -// Prints an error, and increments the error count -[[gnu::format(printf, 1, 2)]] -void error(char const *fmt, ...); -// Prints a fatal error, increments the error count, and gives up -[[gnu::format(printf, 1, 2), noreturn]] -void fatal(char const *fmt, ...); - struct Palette { // An array of 4 GBC-native (RGB555) colors std::array colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX}; diff --git a/include/link/layout.hpp b/include/link/layout.hpp new file mode 100644 index 00000000..88f38ce8 --- /dev/null +++ b/include/link/layout.hpp @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT + +#ifndef RGBDS_LINK_LAYOUT_HPP +#define RGBDS_LINK_LAYOUT_HPP + +#include +#include +#include + +#include "linkdefs.hpp" + +struct LexerStackEntry { + std::filebuf file; + std::string path; + uint32_t lineNo; + + explicit LexerStackEntry(std::string &&path_) : file(), path(path_), lineNo(1) {} +}; + +#define scriptError(context, fmt, ...) \ + ::error( \ + "%s(%" PRIu32 "): " fmt, context.path.c_str(), context.lineNo __VA_OPT__(, ) __VA_ARGS__ \ + ) + +LexerStackEntry &lexer_Context(); +void lexer_IncludeFile(std::string &&path); +void lexer_IncLineNo(); +bool lexer_Init(char const *linkerScriptName); + +void layout_SetFloatingSectionType(SectionType type); +void layout_SetSectionType(SectionType type); +void layout_SetSectionType(SectionType type, uint32_t bank); +void layout_SetAddr(uint32_t addr); +void layout_MakeAddrFloating(); +void layout_AlignTo(uint32_t alignment, uint32_t offset); +void layout_Pad(uint32_t length); +void layout_PlaceSection(std::string const &name, bool isOptional); + +#endif // RGBDS_LINK_LAYOUT_HPP diff --git a/include/link/main.hpp b/include/link/main.hpp index 83a6c630..82406e59 100644 --- a/include/link/main.hpp +++ b/include/link/main.hpp @@ -13,7 +13,6 @@ struct Options { bool isDmgMode; // -d - char const *linkerScriptName; // -l char const *mapFileName; // -m bool noSymInMap; // -M char const *symFileName; // -n @@ -22,9 +21,9 @@ struct Options { uint8_t padValue; // -p bool hasPadValue = false; // Setting these three to 0 disables the functionality - uint16_t scrambleROMX = 0; // -S - uint8_t scrambleWRAMX = 0; - uint8_t scrambleSRAM = 0; + uint16_t scrambleROMX; // -S + uint8_t scrambleWRAMX; + uint8_t scrambleSRAM; bool is32kMode; // -t bool beVerbose; // -v bool isWRAM0Mode; // -w diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 945aa7a1..19cc4021 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,7 @@ set(rgbasm_src set(rgblink_src "${BISON_LINKER_SCRIPT_PARSER_OUTPUT_SOURCE}" "link/assign.cpp" + "link/layout.cpp" "link/main.cpp" "link/object.cpp" "link/output.cpp" diff --git a/src/asm/actions.cpp b/src/asm/actions.cpp index dd026109..b67871fb 100644 --- a/src/asm/actions.cpp +++ b/src/asm/actions.cpp @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: MIT + #include "asm/actions.hpp" #include diff --git a/src/asm/lexer.cpp b/src/asm/lexer.cpp index d7685f0b..f285d38c 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -595,7 +595,7 @@ static bool isMacroChar(char c) { return c == '@' || c == '#' || c == '<' || (c >= '1' && c <= '9'); } -// forward declarations for readBracketedMacroArgNum +// Forward declarations for `readBracketedMacroArgNum` static int peek(); static void shiftChar(); static int bumpChar(); @@ -795,7 +795,7 @@ int LexerState::peekCharAhead() { return EOF; } -// forward declarations for peek +// Forward declarations for `peek` static std::shared_ptr readInterpolation(size_t depth); static int peek() { @@ -1622,7 +1622,7 @@ static void readCharacter(std::string &str) { // Lexer core -static Token yylex_SKIP_TO_ENDC(); // forward declaration for yylex_NORMAL +static Token yylex_SKIP_TO_ENDC(); // Forward declaration for `yylex_NORMAL` // Must stay in sync with the `switch` in `yylex_NORMAL`! static bool isGarbageCharacter(int c) { diff --git a/src/asm/main.cpp b/src/asm/main.cpp index 1f13eb91..bf634057 100644 --- a/src/asm/main.cpp +++ b/src/asm/main.cpp @@ -14,7 +14,7 @@ #include "diagnostics.hpp" #include "extern/getopt.hpp" #include "helpers.hpp" -#include "parser.hpp" +#include "parser.hpp" // Generated from parser.y #include "version.hpp" #include "asm/charmap.hpp" diff --git a/src/asm/parser.y b/src/asm/parser.y index 602c1af2..d22f1557 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -2719,7 +2719,7 @@ hl_ind_dec: %% -/******************** Semantic actions ********************/ +/******************** Error handler ********************/ void yy::parser::error(std::string const &str) { ::error("%s", str.c_str()); diff --git a/src/link/layout.cpp b/src/link/layout.cpp new file mode 100644 index 00000000..770cd1c3 --- /dev/null +++ b/src/link/layout.cpp @@ -0,0 +1,704 @@ +// SPDX-License-Identifier: MIT + +#include "link/layout.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "helpers.hpp" +#include "itertools.hpp" +#include "util.hpp" + +#include "link/section.hpp" +#include "link/warning.hpp" +// Include this last so it gets all type & constant definitions +#include "script.hpp" // For token definitions, generated from script.y + +/******************** Lexer ********************/ + +static std::vector lexerStack; +static bool atEof = false; + +LexerStackEntry &lexer_Context() { + return lexerStack.back(); +} + +void lexer_IncludeFile(std::string &&path) { + // `emplace_back` can invalidate references to the stack's elements! + // This is why `newContext` must be gotten before `prevContext`. + LexerStackEntry &newContext = lexerStack.emplace_back(std::move(path)); + LexerStackEntry &prevContext = lexerStack[lexerStack.size() - 2]; + + if (!newContext.file.open(newContext.path, std::ios_base::in)) { + // The order is important: report the error, increment the line number, modify the stack! + scriptError( + prevContext, "Failed to open included linker script \"%s\"", newContext.path.c_str() + ); + ++prevContext.lineNo; + lexerStack.pop_back(); + } else { + // The lexer will use the new entry to lex the next token. + ++prevContext.lineNo; + } +} + +void lexer_IncLineNo() { + ++lexerStack.back().lineNo; +} + +static bool isWhiteSpace(int c) { + return c == ' ' || c == '\t'; +} + +static bool isNewline(int c) { + return c == '\r' || c == '\n'; +} + +yy::parser::symbol_type yylex(); // Forward declaration for `yywrap` + +static yy::parser::symbol_type yywrap() { + if (lexerStack.size() != 1) { + if (!atEof) { + // Inject a newline at EOF to simplify parsing. + atEof = true; + return yy::parser::make_newline(); + } + lexerStack.pop_back(); + return yylex(); + } + if (!atEof) { + // Inject a newline at EOF to simplify parsing. + atEof = true; + return yy::parser::make_newline(); + } + return yy::parser::make_YYEOF(); +} + +static bool isIdentChar(int c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); +} + +static std::string readIdent(int c) { + LexerStackEntry &context = lexerStack.back(); + std::string ident; + ident.push_back(c); + for (c = context.file.sgetc(); isIdentChar(c); c = context.file.snextc()) { + ident.push_back(c); + } + return ident; +} + +static bool isDecDigit(int c) { + return c >= '0' && c <= '9'; +} + +static yy::parser::symbol_type parseDecNumber(int c) { + LexerStackEntry &context = lexerStack.back(); + uint32_t number = c - '0'; + for (c = context.file.sgetc(); isDecDigit(c) || c == '_'; c = context.file.sgetc()) { + if (c != '_') { + number = number * 10 + (c - '0'); + } + context.file.sbumpc(); + } + return yy::parser::make_number(number); +} + +static bool isBinDigit(int c) { + return c >= '0' && c <= '1'; +} + +static yy::parser::symbol_type parseBinNumber(char const *prefix) { + LexerStackEntry &context = lexerStack.back(); + int c = context.file.sgetc(); + if (!isBinDigit(c)) { + scriptError(context, "No binary digits found after '%s'", prefix); + return yy::parser::make_number(0); + } + + uint32_t number = c - '0'; + context.file.sbumpc(); + for (c = context.file.sgetc(); isBinDigit(c) || c == '_'; c = context.file.sgetc()) { + if (c != '_') { + number = number * 2 + (c - '0'); + } + context.file.sbumpc(); + } + return yy::parser::make_number(number); +} + +static bool isOctDigit(int c) { + return c >= '0' && c <= '7'; +} + +static yy::parser::symbol_type parseOctNumber(char const *prefix) { + LexerStackEntry &context = lexerStack.back(); + int c = context.file.sgetc(); + if (!isOctDigit(c)) { + scriptError(context, "No octal digits found after '%s'", prefix); + return yy::parser::make_number(0); + } + + uint32_t number = c - '0'; + context.file.sbumpc(); + for (c = context.file.sgetc(); isOctDigit(c) || c == '_'; c = context.file.sgetc()) { + if (c != '_') { + number = number * 8 + (c - '0'); + } + context.file.sbumpc(); + } + return yy::parser::make_number(number); +} + +static bool isHexDigit(int c) { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); +} + +static uint8_t parseHexDigit(int c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } else { + unreachable_(); // LCOV_EXCL_LINE + } +} + +static yy::parser::symbol_type parseHexNumber(char const *prefix) { + LexerStackEntry &context = lexerStack.back(); + int c = context.file.sgetc(); + if (!isHexDigit(c)) { + scriptError(context, "No hexadecimal digits found after '%s'", prefix); + return yy::parser::make_number(0); + } + + uint32_t number = parseHexDigit(c); + context.file.sbumpc(); + for (c = context.file.sgetc(); isHexDigit(c) || c == '_'; c = context.file.sgetc()) { + if (c != '_') { + number = number * 16 + parseHexDigit(c); + } + context.file.sbumpc(); + } + return yy::parser::make_number(number); +} + +static yy::parser::symbol_type parseNumber(int c) { + LexerStackEntry &context = lexerStack.back(); + if (c == '0') { + switch (context.file.sgetc()) { + case 'x': + context.file.sbumpc(); + return parseHexNumber("0x"); + case 'X': + context.file.sbumpc(); + return parseHexNumber("0X"); + case 'o': + context.file.sbumpc(); + return parseOctNumber("0o"); + case 'O': + context.file.sbumpc(); + return parseOctNumber("0O"); + case 'b': + context.file.sbumpc(); + return parseBinNumber("0b"); + case 'B': + context.file.sbumpc(); + return parseBinNumber("0B"); + } + } + return parseDecNumber(c); +} + +static yy::parser::symbol_type parseString() { + LexerStackEntry &context = lexerStack.back(); + int c = context.file.sgetc(); + std::string str; + for (; c != '"'; c = context.file.sgetc()) { + if (c == EOF || isNewline(c)) { + scriptError(context, "Unterminated string"); + break; + } + context.file.sbumpc(); + if (c == '\\') { + c = context.file.sgetc(); + if (c == EOF || isNewline(c)) { + scriptError(context, "Unterminated string"); + break; + } else if (c == 'n') { + c = '\n'; + } else if (c == 'r') { + c = '\r'; + } else if (c == 't') { + c = '\t'; + } else if (c == '0') { + c = '\0'; + } else if (c != '\\' && c != '"' && c != '\'') { + scriptError(context, "Cannot escape character %s", printChar(c)); + } + context.file.sbumpc(); + } + str.push_back(c); + } + if (c == '"') { + context.file.sbumpc(); + } + return yy::parser::make_string(std::move(str)); +} + +struct Keyword { + std::string_view name; + yy::parser::symbol_type (*tokenGen)(); +}; + +using namespace std::literals; + +static std::array keywords{ + Keyword{"ORG"sv, yy::parser::make_ORG }, + Keyword{"FLOATING"sv, yy::parser::make_FLOATING}, + Keyword{"INCLUDE"sv, yy::parser::make_INCLUDE }, + Keyword{"ALIGN"sv, yy::parser::make_ALIGN }, + Keyword{"DS"sv, yy::parser::make_DS }, + Keyword{"OPTIONAL"sv, yy::parser::make_OPTIONAL}, +}; + +yy::parser::symbol_type yylex() { + LexerStackEntry &context = lexerStack.back(); + int c = context.file.sbumpc(); + + // First, skip leading whitespace. + while (isWhiteSpace(c)) { + c = context.file.sbumpc(); + } + // Then, skip a comment if applicable. + if (c == ';') { + while (c != EOF && !isNewline(c)) { + c = context.file.sbumpc(); + } + } + + // Alright, what token should we return? + if (c == EOF) { + return yywrap(); + } else if (c == ',') { + return yy::parser::make_COMMA(); + } else if (isNewline(c)) { + // Handle CRLF. + if (c == '\r' && context.file.sgetc() == '\n') { + context.file.sbumpc(); + } + return yy::parser::make_newline(); + } else if (c == '"') { + return parseString(); + } else if (c == '$') { + return parseHexNumber("$"); + } else if (c == '%') { + return parseBinNumber("%"); + } else if (c == '&') { + return parseOctNumber("&"); + } else if (isDecDigit(c)) { + return parseNumber(c); + } else if (isIdentChar(c)) { // Note that we match these *after* digit characters! + std::string ident = readIdent(c); + + auto strUpperCmp = [](char cmp, char ref) { return toupper(cmp) == ref; }; + + for (SectionType type : EnumSeq(SECTTYPE_INVALID)) { + if (std::equal(RANGE(ident), RANGE(sectionTypeInfo[type].name), strUpperCmp)) { + return yy::parser::make_sect_type(type); + } + } + + for (Keyword const &keyword : keywords) { + if (std::equal(RANGE(ident), RANGE(keyword.name), strUpperCmp)) { + return keyword.tokenGen(); + } + } + + scriptError(context, "Unknown keyword \"%s\"", ident.c_str()); + return yylex(); + } else { + scriptError(context, "Unexpected character %s", printChar(c)); + // Keep reading characters until the EOL, to avoid reporting too many errors. + for (c = context.file.sgetc(); !isNewline(c); c = context.file.sgetc()) { + if (c == EOF) { + break; + } + context.file.sbumpc(); + } + return yylex(); + } + // Not marking as unreachable; this will generate a warning if any codepath forgets to return. +} + +bool lexer_Init(char const *linkerScriptName) { + if (LexerStackEntry &newContext = lexerStack.emplace_back(std::string(linkerScriptName)); + !newContext.file.open(newContext.path, std::ios_base::in)) { + error("Failed to open linker script \"%s\"", linkerScriptName); + lexerStack.clear(); + return false; + } + return true; +} + +/******************** Semantic actions ********************/ + +static std::array, 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 (nbbanks(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) { + LexerStackEntry const &context = lexerStack.back(); + + if (nbbanks(type) != 1) { + scriptError( + context, "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) { + LexerStackEntry const &context = lexerStack.back(); + SectionTypeInfo const &typeInfo = sectionTypeInfo[type]; + + if (bank < typeInfo.firstBank) { + scriptError( + context, + "%s bank %" PRIu32 " doesn't exist (the minimum is %" PRIu32 ")", + typeInfo.name.c_str(), + bank, + typeInfo.firstBank + ); + bank = typeInfo.firstBank; + } else if (bank > typeInfo.lastBank) { + scriptError( + context, + "%s bank %" PRIu32 " doesn't exist (the maximum is %" PRIu32 ")", + typeInfo.name.c_str(), + bank, + typeInfo.lastBank + ); + } + + setActiveTypeAndIdx(type, bank - typeInfo.firstBank); +} + +void layout_SetAddr(uint32_t addr) { + LexerStackEntry const &context = lexerStack.back(); + if (activeType == SECTTYPE_INVALID) { + scriptError(context, "Cannot set the current address: no memory region is active"); + return; + } + if (activeBankIdx == UINT32_MAX) { + scriptError(context, "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(context, "Cannot decrease the current address (from $%04x to $%04x)", pc, addr); + } else if (addr > endaddr(activeType)) { // Allow "one past the end" sections. + scriptError( + context, + "Cannot set the current address to $%04" PRIx32 ": %s ends at $%04" PRIx16 "", + addr, + typeInfo.name.c_str(), + endaddr(activeType) + ); + pc = endaddr(activeType); + } else { + pc = addr; + } + isPcFloating = false; +} + +void layout_MakeAddrFloating() { + LexerStackEntry const &context = lexerStack.back(); + if (activeType == SECTTYPE_INVALID) { + scriptError( + context, "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) { + LexerStackEntry const &context = lexerStack.back(); + if (activeType == SECTTYPE_INVALID) { + scriptError(context, "Cannot align: no memory region is active"); + return; + } + + if (isPcFloating) { + if (alignment >= 16) { + layout_SetAddr(floatingAlignOffset); + } else { + uint32_t alignSize = 1u << alignment; + + if (alignOfs >= alignSize) { + scriptError( + context, + "Cannot align: The alignment offset (%" PRIu32 + ") must be less than alignment size (%" PRIu32 ")", + alignOfs, + alignSize + ); + return; + } + + floatingAlignMask = alignSize - 1; + floatingAlignOffset = alignOfs % alignSize; + } + return; + } + + SectionTypeInfo const &typeInfo = sectionTypeInfo[activeType]; + uint16_t &pc = curAddr[activeType][activeBankIdx]; + + if (alignment > 16) { + scriptError( + context, "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; + + if (alignOfs >= alignSize) { + scriptError( + context, + "Cannot align: The alignment offset (%" PRIu32 + ") must be less than alignment size (%" PRIu32 ")", + alignOfs, + alignSize + ); + return; + } + + assume(pc >= typeInfo.startAddr); + length %= alignSize; + } + + if (uint16_t offset = pc - typeInfo.startAddr; length > typeInfo.size - offset) { + scriptError( + context, + "Cannot align: the next suitable address after $%04" PRIx16 " is $%04" PRIx16 + ", past $%04" PRIx16, + pc, + static_cast(pc + length), + static_cast(endaddr(activeType) + 1) + ); + return; + } + + pc += length; +} + +void layout_Pad(uint32_t length) { + LexerStackEntry const &context = lexerStack.back(); + if (activeType == SECTTYPE_INVALID) { + scriptError(context, "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( + context, + "Cannot increase the current address by %u bytes: only %u bytes to $%04" PRIx16, + length, + typeInfo.size - offset, + static_cast(endaddr(activeType) + 1) + ); + } else { + pc += length; + } +} + +void layout_PlaceSection(std::string const &name, bool isOptional) { + LexerStackEntry const &context = lexerStack.back(); + if (activeType == SECTTYPE_INVALID) { + scriptError( + context, "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(context, "Unknown 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 (!sect_HasData(activeType) && !section->data.empty()) { + scriptError( + context, + "\"%s\" is specified to be a %s section, but it contains data", + name.c_str(), + typeInfo.name.c_str() + ); + } else if (sect_HasData(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( + context, + "\"%s\" is specified to be a %s section, but it doesn't contain data", + name.c_str(), + typeInfo.name.c_str() + ); + } else { + // SDCC areas don't have a type assigned yet, so the linker script is used to give them + // one. + for (Section *fragment = section; fragment; fragment = fragment->nextu.get()) { + fragment->type = activeType; + } + } + } else if (section->type != activeType) { + scriptError( + context, + "\"%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( + context, + "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( + context, + "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( + context, + "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(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( + context, + "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; + } +} diff --git a/src/link/main.cpp b/src/link/main.cpp index 6aefbbae..ba282d65 100644 --- a/src/link/main.cpp +++ b/src/link/main.cpp @@ -14,10 +14,11 @@ #include "helpers.hpp" // assume #include "itertools.hpp" #include "platform.hpp" -#include "script.hpp" +#include "script.hpp" // Generated from script.y #include "version.hpp" #include "link/assign.hpp" +#include "link/layout.hpp" #include "link/object.hpp" #include "link/output.hpp" #include "link/patch.hpp" @@ -257,6 +258,8 @@ next: // Can't `continue` a `for` loop with this nontrivial iteration logic } int main(int argc, char *argv[]) { + char const *linkerScriptName = nullptr; // -l + // Parse options for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) { switch (ch) { @@ -270,10 +273,10 @@ int main(int argc, char *argv[]) { exit(0); // LCOV_EXCL_STOP case 'l': - if (options.linkerScriptName) { - warnx("Overriding linker script %s", options.linkerScriptName); + if (linkerScriptName) { + warnx("Overriding linker script %s", linkerScriptName); } - options.linkerScriptName = musl_optarg; + linkerScriptName = musl_optarg; break; case 'M': options.noSymInMap = true; @@ -375,10 +378,15 @@ int main(int argc, char *argv[]) { } // apply the linker script's modifications, - if (options.linkerScriptName) { + if (linkerScriptName) { verbosePrint("Reading linker script...\n"); - script_ProcessScript(); + if (lexer_Init(linkerScriptName)) { + yy::parser parser; + // We don't care about the return value, as any error increments the global error count, + // which is what `main` checks. + (void)parser.parse(); + } // If the linker script produced any errors, some sections may be in an invalid state requireZeroErrors(); diff --git a/src/link/script.y b/src/link/script.y index 925ab445..2a504c89 100644 --- a/src/link/script.y +++ b/src/link/script.y @@ -9,49 +9,13 @@ #include #include "linkdefs.hpp" - - void script_ProcessScript(); } %code { - #include - #include - #include - #include - #include - #include - #include - #include - #include - - #include "helpers.hpp" - #include "itertools.hpp" - #include "util.hpp" - - #include "link/main.hpp" - #include "link/section.hpp" + #include "link/layout.hpp" #include "link/warning.hpp" - using namespace std::literals; - - static void includeFile(std::string &&path); - static void incLineNo(); - - static void setFloatingSectionType(SectionType type); - static void setSectionType(SectionType type); - static void setSectionType(SectionType type, uint32_t bank); - static void setAddr(uint32_t addr); - static void makeAddrFloating(); - static void alignTo(uint32_t alignment, uint32_t offset); - static void pad(uint32_t length); - static void placeSection(std::string const &name, bool isOptional); - - static yy::parser::symbol_type yylex(); - - struct Keyword { - std::string_view name; - yy::parser::symbol_type (*tokenGen)(); - }; + yy::parser::symbol_type yylex(); // Provided by layout.cpp } /******************** Tokens and data types ********************/ @@ -76,17 +40,6 @@ %type optional; -%code { - static std::array keywords{ - Keyword{"ORG"sv, yy::parser::make_ORG}, - Keyword{"FLOATING"sv, yy::parser::make_FLOATING}, - Keyword{"INCLUDE"sv, yy::parser::make_INCLUDE}, - Keyword{"ALIGN"sv, yy::parser::make_ALIGN}, - Keyword{"DS"sv, yy::parser::make_DS}, - Keyword{"OPTIONAL"sv, yy::parser::make_OPTIONAL}, - }; -} - %% /******************** Parser rules ********************/ @@ -98,48 +51,48 @@ lines: line: INCLUDE string newline { - includeFile(std::move($2)); // Note: this additionally increments the line number! + lexer_IncludeFile(std::move($2)); // Note: this additionally increments the line number! } | directive newline { - incLineNo(); + lexer_IncLineNo(); } | newline { - incLineNo(); + lexer_IncLineNo(); } // Error recovery. | error newline { yyerrok; - incLineNo(); + lexer_IncLineNo(); } ; directive: sect_type { - setSectionType($1); + layout_SetSectionType($1); } | sect_type number { - setSectionType($1, $2); + layout_SetSectionType($1, $2); } | sect_type FLOATING { - setFloatingSectionType($1); + layout_SetFloatingSectionType($1); } | FLOATING { - makeAddrFloating(); + layout_MakeAddrFloating(); } | ORG number { - setAddr($2); + layout_SetAddr($2); } | ALIGN number { - alignTo($2, 0); + layout_AlignTo($2, 0); } | ALIGN number COMMA number { - alignTo($2, $4); + layout_AlignTo($2, $4); } | DS number { - pad($2); + layout_Pad($2); } | string optional { - placeSection($1, $2); + layout_PlaceSection($1, $2); } ; @@ -154,699 +107,8 @@ optional: %% -#define scriptError(context, fmt, ...) \ - ::error( \ - "%s(%" PRIu32 "): " fmt, \ - context.path.c_str(), \ - context.lineNo __VA_OPT__(, ) __VA_ARGS__ \ - ) - -/******************** Lexer ********************/ - -struct LexerStackEntry { - std::filebuf file; - std::string path; - uint32_t lineNo; - - explicit LexerStackEntry(std::string &&path_) : file(), path(path_), lineNo(1) {} -}; -static std::vector lexerStack; -static bool atEof; +/******************** Error handler ********************/ void yy::parser::error(std::string const &msg) { - LexerStackEntry const &script = lexerStack.back(); - scriptError(script, "%s", msg.c_str()); -} - -static void includeFile(std::string &&path) { - // `emplace_back` can invalidate references to the stack's elements! - // This is why `newContext` must be gotten before `prevContext`. - LexerStackEntry &newContext = lexerStack.emplace_back(std::move(path)); - LexerStackEntry &prevContext = lexerStack[lexerStack.size() - 2]; - - if (!newContext.file.open(newContext.path, std::ios_base::in)) { - // The order is important: report the error, increment the line number, modify the stack! - scriptError( - prevContext, "Failed to open included linker script \"%s\"", newContext.path.c_str() - ); - ++prevContext.lineNo; - lexerStack.pop_back(); - } else { - // The lexer will use the new entry to lex the next token. - ++prevContext.lineNo; - } -} - -static void incLineNo() { - ++lexerStack.back().lineNo; -} - -static bool isWhiteSpace(int c) { - return c == ' ' || c == '\t'; -} - -static bool isNewline(int c) { - return c == '\r' || c == '\n'; -} - -static yy::parser::symbol_type yywrap() { - if (lexerStack.size() != 1) { - if (!atEof) { - // Inject a newline at EOF to simplify parsing. - atEof = true; - return yy::parser::make_newline(); - } - lexerStack.pop_back(); - return yylex(); - } - if (!atEof) { - // Inject a newline at EOF to simplify parsing. - atEof = true; - return yy::parser::make_newline(); - } - return yy::parser::make_YYEOF(); -} - -static bool isIdentChar(int c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); -} - -static std::string readIdent(int c) { - LexerStackEntry &context = lexerStack.back(); - std::string ident; - ident.push_back(c); - for (c = context.file.sgetc(); isIdentChar(c); c = context.file.snextc()) { - ident.push_back(c); - } - return ident; -} - -static bool isDecDigit(int c) { - return c >= '0' && c <= '9'; -} - -static yy::parser::symbol_type parseDecNumber(int c) { - LexerStackEntry &context = lexerStack.back(); - uint32_t number = c - '0'; - for (c = context.file.sgetc(); isDecDigit(c) || c == '_'; c = context.file.sgetc()) { - if (c != '_') { - number = number * 10 + (c - '0'); - } - context.file.sbumpc(); - } - return yy::parser::make_number(number); -} - -static bool isBinDigit(int c) { - return c >= '0' && c <= '1'; -} - -static yy::parser::symbol_type parseBinNumber(char const *prefix) { - LexerStackEntry &context = lexerStack.back(); - int c = context.file.sgetc(); - if (!isBinDigit(c)) { - scriptError(context, "No binary digits found after '%s'", prefix); - return yy::parser::make_number(0); - } - - uint32_t number = c - '0'; - context.file.sbumpc(); - for (c = context.file.sgetc(); isBinDigit(c) || c == '_'; c = context.file.sgetc()) { - if (c != '_') { - number = number * 2 + (c - '0'); - } - context.file.sbumpc(); - } - return yy::parser::make_number(number); -} - -static bool isOctDigit(int c) { - return c >= '0' && c <= '7'; -} - -static yy::parser::symbol_type parseOctNumber(char const *prefix) { - LexerStackEntry &context = lexerStack.back(); - int c = context.file.sgetc(); - if (!isOctDigit(c)) { - scriptError(context, "No octal digits found after '%s'", prefix); - return yy::parser::make_number(0); - } - - uint32_t number = c - '0'; - context.file.sbumpc(); - for (c = context.file.sgetc(); isOctDigit(c) || c == '_'; c = context.file.sgetc()) { - if (c != '_') { - number = number * 8 + (c - '0'); - } - context.file.sbumpc(); - } - return yy::parser::make_number(number); -} - -static bool isHexDigit(int c) { - return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); -} - -static uint8_t parseHexDigit(int c) { - if (c >= '0' && c <= '9') { - return c - '0'; - } else if (c >= 'A' && c <= 'F') { - return c - 'A' + 10; - } else if (c >= 'a' && c <= 'f') { - return c - 'a' + 10; - } else { - unreachable_(); // LCOV_EXCL_LINE - } -} - -static yy::parser::symbol_type parseHexNumber(char const *prefix) { - LexerStackEntry &context = lexerStack.back(); - int c = context.file.sgetc(); - if (!isHexDigit(c)) { - scriptError(context, "No hexadecimal digits found after '%s'", prefix); - return yy::parser::make_number(0); - } - - uint32_t number = parseHexDigit(c); - context.file.sbumpc(); - for (c = context.file.sgetc(); isHexDigit(c) || c == '_'; c = context.file.sgetc()) { - if (c != '_') { - number = number * 16 + parseHexDigit(c); - } - context.file.sbumpc(); - } - return yy::parser::make_number(number); -} - -static yy::parser::symbol_type parseNumber(int c) { - LexerStackEntry &context = lexerStack.back(); - if (c == '0') { - switch (context.file.sgetc()) { - case 'x': - context.file.sbumpc(); - return parseHexNumber("0x"); - case 'X': - context.file.sbumpc(); - return parseHexNumber("0X"); - case 'o': - context.file.sbumpc(); - return parseOctNumber("0o"); - case 'O': - context.file.sbumpc(); - return parseOctNumber("0O"); - case 'b': - context.file.sbumpc(); - return parseBinNumber("0b"); - case 'B': - context.file.sbumpc(); - return parseBinNumber("0B"); - } - } - return parseDecNumber(c); -} - -static yy::parser::symbol_type parseString() { - LexerStackEntry &context = lexerStack.back(); - int c = context.file.sgetc(); - std::string str; - for (; c != '"'; c = context.file.sgetc()) { - if (c == EOF || isNewline(c)) { - scriptError(context, "Unterminated string"); - break; - } - context.file.sbumpc(); - if (c == '\\') { - c = context.file.sgetc(); - if (c == EOF || isNewline(c)) { - scriptError(context, "Unterminated string"); - break; - } else if (c == 'n') { - c = '\n'; - } else if (c == 'r') { - c = '\r'; - } else if (c == 't') { - c = '\t'; - } else if (c == '0') { - c = '\0'; - } else if (c != '\\' && c != '"' && c != '\'') { - scriptError(context, "Cannot escape character %s", printChar(c)); - } - context.file.sbumpc(); - } - str.push_back(c); - } - if (c == '"') { - context.file.sbumpc(); - } - return yy::parser::make_string(std::move(str)); -} - -yy::parser::symbol_type yylex() { - LexerStackEntry &context = lexerStack.back(); - int c = context.file.sbumpc(); - - // First, skip leading whitespace. - while (isWhiteSpace(c)) { - c = context.file.sbumpc(); - } - // Then, skip a comment if applicable. - if (c == ';') { - while (c != EOF && !isNewline(c)) { - c = context.file.sbumpc(); - } - } - - // Alright, what token should we return? - if (c == EOF) { - return yywrap(); - } else if (c == ',') { - return yy::parser::make_COMMA(); - } else if (isNewline(c)) { - // Handle CRLF. - if (c == '\r' && context.file.sgetc() == '\n') { - context.file.sbumpc(); - } - return yy::parser::make_newline(); - } else if (c == '"') { - return parseString(); - } else if (c == '$') { - return parseHexNumber("$"); - } else if (c == '%') { - return parseBinNumber("%"); - } else if (c == '&') { - return parseOctNumber("&"); - } else if (isDecDigit(c)) { - return parseNumber(c); - } else if (isIdentChar(c)) { // Note that we match these *after* digit characters! - std::string ident = readIdent(c); - - auto strUpperCmp = [](char cmp, char ref) { - return toupper(cmp) == ref; - }; - - for (SectionType type : EnumSeq(SECTTYPE_INVALID)) { - if (std::equal(RANGE(ident), RANGE(sectionTypeInfo[type].name), strUpperCmp)) { - return yy::parser::make_sect_type(type); - } - } - - for (Keyword const &keyword : keywords) { - if (std::equal(RANGE(ident), RANGE(keyword.name), strUpperCmp)) { - return keyword.tokenGen(); - } - } - - scriptError(context, "Unknown keyword \"%s\"", ident.c_str()); - return yylex(); - } else { - scriptError(context, "Unexpected character %s", printChar(c)); - // Keep reading characters until the EOL, to avoid reporting too many errors. - for (c = context.file.sgetc(); !isNewline(c); c = context.file.sgetc()) { - if (c == EOF) { - break; - } - context.file.sbumpc(); - } - return yylex(); - } - // Not marking as unreachable; this will generate a warning if any codepath forgets to return. -} - -/******************** Semantic actions ********************/ - -static std::array, SECTTYPE_INVALID> curAddr; -static SectionType activeType; // 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); - } -} - -static void setFloatingSectionType(SectionType type) { - if (nbbanks(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; - } -} - -static void setSectionType(SectionType type) { - LexerStackEntry const &context = lexerStack.back(); - - if (nbbanks(type) != 1) { - scriptError( - context, "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. -} - -static void setSectionType(SectionType type, uint32_t bank) { - LexerStackEntry const &context = lexerStack.back(); - SectionTypeInfo const &typeInfo = sectionTypeInfo[type]; - - if (bank < typeInfo.firstBank) { - scriptError( - context, - "%s bank %" PRIu32 " doesn't exist (the minimum is %" PRIu32 ")", - typeInfo.name.c_str(), - bank, - typeInfo.firstBank - ); - bank = typeInfo.firstBank; - } else if (bank > typeInfo.lastBank) { - scriptError( - context, - "%s bank %" PRIu32 " doesn't exist (the maximum is %" PRIu32 ")", - typeInfo.name.c_str(), - bank, - typeInfo.lastBank - ); - } - - setActiveTypeAndIdx(type, bank - typeInfo.firstBank); -} - -static void setAddr(uint32_t addr) { - LexerStackEntry const &context = lexerStack.back(); - if (activeType == SECTTYPE_INVALID) { - scriptError(context, "Cannot set the current address: no memory region is active"); - return; - } - if (activeBankIdx == UINT32_MAX) { - scriptError(context, "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(context, "Cannot decrease the current address (from $%04x to $%04x)", pc, addr); - } else if (addr > endaddr(activeType)) { // Allow "one past the end" sections. - scriptError( - context, - "Cannot set the current address to $%04" PRIx32 ": %s ends at $%04" PRIx16 "", - addr, - typeInfo.name.c_str(), - endaddr(activeType) - ); - pc = endaddr(activeType); - } else { - pc = addr; - } - isPcFloating = false; -} - -static void makeAddrFloating() { - LexerStackEntry const &context = lexerStack.back(); - if (activeType == SECTTYPE_INVALID) { - scriptError( - context, "Cannot make the current address floating: no memory region is active" - ); - return; - } - - isPcFloating = true; - floatingAlignMask = 0; - floatingAlignOffset = 0; -} - -static void alignTo(uint32_t alignment, uint32_t alignOfs) { - LexerStackEntry const &context = lexerStack.back(); - if (activeType == SECTTYPE_INVALID) { - scriptError(context, "Cannot align: no memory region is active"); - return; - } - - if (isPcFloating) { - if (alignment >= 16) { - setAddr(floatingAlignOffset); - } else { - uint32_t alignSize = 1u << alignment; - - if (alignOfs >= alignSize) { - scriptError( - context, - "Cannot align: The alignment offset (%" PRIu32 - ") must be less than alignment size (%" PRIu32 ")", - alignOfs, - alignSize - ); - return; - } - - floatingAlignMask = alignSize - 1; - floatingAlignOffset = alignOfs % alignSize; - } - return; - } - - SectionTypeInfo const &typeInfo = sectionTypeInfo[activeType]; - uint16_t &pc = curAddr[activeType][activeBankIdx]; - - if (alignment > 16) { - scriptError( - context, "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; - - if (alignOfs >= alignSize) { - scriptError( - context, - "Cannot align: The alignment offset (%" PRIu32 - ") must be less than alignment size (%" PRIu32 ")", - alignOfs, - alignSize - ); - return; - } - - assume(pc >= typeInfo.startAddr); - length %= alignSize; - } - - if (uint16_t offset = pc - typeInfo.startAddr; length > typeInfo.size - offset) { - scriptError( - context, - "Cannot align: the next suitable address after $%04" PRIx16 " is $%04" PRIx16 - ", past $%04" PRIx16, - pc, - static_cast(pc + length), - static_cast(endaddr(activeType) + 1) - ); - return; - } - - pc += length; -} - -static void pad(uint32_t length) { - LexerStackEntry const &context = lexerStack.back(); - if (activeType == SECTTYPE_INVALID) { - scriptError(context, "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( - context, - "Cannot increase the current address by %u bytes: only %u bytes to $%04" PRIx16, - length, - typeInfo.size - offset, - static_cast(endaddr(activeType) + 1) - ); - } else { - pc += length; - } -} - -static void placeSection(std::string const &name, bool isOptional) { - LexerStackEntry const &context = lexerStack.back(); - if (activeType == SECTTYPE_INVALID) { - scriptError( - context, "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(context, "Unknown 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 (!sect_HasData(activeType) && !section->data.empty()) { - scriptError( - context, - "\"%s\" is specified to be a %s section, but it contains data", - name.c_str(), - typeInfo.name.c_str() - ); - } else if (sect_HasData(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( - context, - "\"%s\" is specified to be a %s section, but it doesn't contain data", - name.c_str(), - typeInfo.name.c_str() - ); - } else { - // SDCC areas don't have a type assigned yet, so the linker script is used to give them - // one. - for (Section *fragment = section; fragment; fragment = fragment->nextu.get()) { - fragment->type = activeType; - } - } - } else if (section->type != activeType) { - scriptError( - context, - "\"%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( - context, - "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( - context, - "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( - context, - "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(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( - context, - "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; - } -} - -/******************** External API ********************/ - -void script_ProcessScript() { - activeType = SECTTYPE_INVALID; - - lexerStack.clear(); - atEof = false; - LexerStackEntry &newContext = lexerStack.emplace_back(std::string(options.linkerScriptName)); - - if (!newContext.file.open(newContext.path, std::ios_base::in)) { - error("Failed to open linker script \"%s\"", newContext.path.c_str()); - lexerStack.clear(); - } else { - yy::parser linkerScriptParser; - // We don't care about the return value, as any error increments the global error count, - // which is what `main` checks. - (void)linkerScriptParser.parse(); - - // Free up working memory. - for (std::vector ®ion : curAddr) { - region.clear(); - } - } + scriptError(lexer_Context(), "%s", msg.c_str()); }