// SPDX-License-Identifier: MIT #include "link/output.hpp" #include #include #include #include #include #include #include #include #include "diagnostics.hpp" #include "extern/utf8decoder.hpp" #include "helpers.hpp" #include "linkdefs.hpp" #include "platform.hpp" #include "util.hpp" #include "link/main.hpp" #include "link/section.hpp" #include "link/symbol.hpp" #include "link/warning.hpp" static constexpr size_t BANK_SIZE = 0x4000; FILE *outputFile; FILE *overlayFile; FILE *symFile; FILE *mapFile; struct SortedSymbol { Symbol const *sym; uint16_t addr; uint16_t parentAddr; }; struct SortedSections { std::deque
sections; std::deque
zeroLenSections; }; static std::deque sections[SECTTYPE_INVALID]; // Defines the order in which types are output to the sym and map files static SectionType typeMap[SECTTYPE_INVALID] = { SECTTYPE_ROM0, SECTTYPE_ROMX, SECTTYPE_VRAM, SECTTYPE_SRAM, SECTTYPE_WRAM0, SECTTYPE_WRAMX, SECTTYPE_OAM, SECTTYPE_HRAM, }; void out_AddSection(Section const §ion) { static uint32_t const maxNbBanks[SECTTYPE_INVALID] = { 1, // SECTTYPE_WRAM0 2, // SECTTYPE_VRAM UINT32_MAX, // SECTTYPE_ROMX 1, // SECTTYPE_ROM0 1, // SECTTYPE_HRAM 7, // SECTTYPE_WRAMX UINT32_MAX, // SECTTYPE_SRAM 1, // SECTTYPE_OAM }; uint32_t targetBank = section.bank - sectionTypeInfo[section.type].firstBank; uint32_t minNbBanks = targetBank + 1; if (minNbBanks > maxNbBanks[section.type]) { fatal( "Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")", section.name.c_str(), section.bank, maxNbBanks[section.type] - 1 ); } for (uint32_t i = sections[section.type].size(); i < minNbBanks; i++) { sections[section.type].emplace_back(); } std::deque
&bankSections = section.size ? sections[section.type][targetBank].sections : sections[section.type][targetBank].zeroLenSections; auto pos = bankSections.begin(); while (pos != bankSections.end() && (*pos)->org < section.org) { pos++; } bankSections.insert(pos, §ion); } Section const *out_OverlappingSection(Section const §ion) { uint32_t bank = section.bank - sectionTypeInfo[section.type].firstBank; for (Section const *ptr : sections[section.type][bank].sections) { if (ptr->org < section.org + section.size && section.org < ptr->org + ptr->size) { return ptr; } } return nullptr; } // Performs sanity checks on the overlay file. // Returns the number of ROM banks in the overlay file. static uint32_t checkOverlaySize() { if (!overlayFile) { return 0; } if (fseek(overlayFile, 0, SEEK_END) != 0) { warnx("Overlay file is not seekable, cannot check if properly formed"); return 0; } long overlaySize = ftell(overlayFile); // Reset back to beginning fseek(overlayFile, 0, SEEK_SET); if (overlaySize % BANK_SIZE) { warnx("Overlay file does not have a size multiple of 0x4000"); } else if (is32kMode && overlaySize != 0x8000) { warnx("Overlay is not exactly 0x8000 bytes large"); } else if (overlaySize < 0x8000) { warnx("Overlay is less than 0x8000 bytes large"); } return (overlaySize + BANK_SIZE - 1) / BANK_SIZE; } // Expand `sections[SECTTYPE_ROMX]` to cover all the overlay banks. // This ensures that `writeROM` will output each bank, even if some are not // covered by any sections. static void coverOverlayBanks(uint32_t nbOverlayBanks) { // 2 if is32kMode, 1 otherwise uint32_t nbRom0Banks = sectionTypeInfo[SECTTYPE_ROM0].size / BANK_SIZE; // Discount ROM0 banks to avoid outputting too much uint32_t nbUncoveredBanks = nbOverlayBanks - nbRom0Banks > sections[SECTTYPE_ROMX].size() ? nbOverlayBanks - nbRom0Banks : 0; if (nbUncoveredBanks > sections[SECTTYPE_ROMX].size()) { for (uint32_t i = sections[SECTTYPE_ROMX].size(); i < nbUncoveredBanks; i++) { sections[SECTTYPE_ROMX].emplace_back(); } } } static uint8_t getNextFillByte() { if (overlayFile) { int c = getc(overlayFile); if (c != EOF) { return c; } if (static bool warned = false; !hasPadValue && !warned) { warnx("Output is larger than overlay file, but no padding value was specified"); warned = true; } } return padValue; } static void writeBank(std::deque
*bankSections, uint16_t baseOffset, uint16_t size) { uint16_t offset = 0; if (bankSections) { for (Section const *section : *bankSections) { assume(section->offset == 0); // Output padding up to the next SECTION while (offset + baseOffset < section->org) { putc(getNextFillByte(), outputFile); offset++; } // Output the section itself fwrite(section->data.data(), 1, section->size, outputFile); if (overlayFile) { // Skip bytes even with pipes for (uint16_t i = 0; i < section->size; i++) { getc(overlayFile); } } offset += section->size; } } if (!disablePadding) { while (offset < size) { putc(getNextFillByte(), outputFile); offset++; } } } static void writeROM() { if (outputFileName) { if (strcmp(outputFileName, "-")) { outputFile = fopen(outputFileName, "wb"); } else { outputFileName = ""; (void)setmode(STDOUT_FILENO, O_BINARY); outputFile = stdout; } if (!outputFile) { fatal("Failed to open output file \"%s\": %s", outputFileName, strerror(errno)); } } Defer closeOutputFile{[&] { if (outputFile) { fclose(outputFile); } }}; if (overlayFileName) { if (strcmp(overlayFileName, "-")) { overlayFile = fopen(overlayFileName, "rb"); } else { overlayFileName = ""; (void)setmode(STDIN_FILENO, O_BINARY); overlayFile = stdin; } if (!overlayFile) { fatal("Failed to open overlay file \"%s\": %s", overlayFileName, strerror(errno)); } } Defer closeOverlayFile{[&] { if (overlayFile) { fclose(overlayFile); } }}; uint32_t nbOverlayBanks = checkOverlaySize(); if (nbOverlayBanks > 0) { coverOverlayBanks(nbOverlayBanks); } if (outputFile) { writeBank( !sections[SECTTYPE_ROM0].empty() ? §ions[SECTTYPE_ROM0][0].sections : nullptr, sectionTypeInfo[SECTTYPE_ROM0].startAddr, sectionTypeInfo[SECTTYPE_ROM0].size ); for (uint32_t i = 0; i < sections[SECTTYPE_ROMX].size(); i++) { writeBank( §ions[SECTTYPE_ROMX][i].sections, sectionTypeInfo[SECTTYPE_ROMX].startAddr, sectionTypeInfo[SECTTYPE_ROMX].size ); } } } static void writeSymName(std::string const &name, FILE *file) { for (char const *ptr = name.c_str(); *ptr != '\0';) { char c = *ptr; if (continuesIdentifier(c)) { // Output legal ASCII characters as-is putc(c, file); ++ptr; } else { // Output illegal characters using Unicode escapes ('\u' or '\U') // Decode the UTF-8 codepoint; or at least attempt to uint32_t state = UTF8_ACCEPT, codepoint; do { decode(&state, &codepoint, *ptr); if (state == UTF8_REJECT) { // This sequence was invalid; emit a U+FFFD, and recover codepoint = 0xFFFD; // Skip continuation bytes // A NUL byte does not qualify, so we're good while ((*ptr & 0xC0) == 0x80) { ++ptr; } break; } ++ptr; } while (state != UTF8_ACCEPT); fprintf(file, codepoint <= 0xFFFF ? "\\u%04" PRIx32 : "\\U%08" PRIx32, codepoint); } } } // Comparator function for `std::stable_sort` to sort symbols static bool compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2) { // First, sort by address if (sym1.addr != sym2.addr) { return sym1.addr < sym2.addr; } // Second, sort by locality (global before local) std::string const &sym1_name = sym1.sym->name; std::string const &sym2_name = sym2.sym->name; bool sym1_local = sym1_name.find('.') != std::string::npos; bool sym2_local = sym2_name.find('.') != std::string::npos; if (sym1_local != sym2_local) { return sym1_local < sym2_local; } // Third, sort by parent address if (sym1.parentAddr != sym2.parentAddr) { return sym1.parentAddr < sym2.parentAddr; } // Fourth, sort by name return sym1_name < sym2_name; } template static void forEachSortedSection(SortedSections const &bankSections, F callback) { for (Section const *sect : bankSections.zeroLenSections) { for (; sect; sect = sect->nextu.get()) { callback(*sect); } } for (Section const *sect : bankSections.sections) { for (; sect; sect = sect->nextu.get()) { callback(*sect); } } } static void writeSymBank(SortedSections const &bankSections, SectionType type, uint32_t bank) { uint32_t nbSymbols = 0; forEachSortedSection(bankSections, [&](Section const §) { nbSymbols += sect.symbols.size(); }); if (!nbSymbols) { return; } std::vector symList; symList.reserve(nbSymbols); forEachSortedSection(bankSections, [&](Section const §) { for (Symbol const *sym : sect.symbols) { // Don't output symbols that begin with an illegal character if (sym->name.empty() || !startsIdentifier(sym->name[0])) { continue; } uint16_t addr = static_cast(sym->label().offset + sect.org); uint16_t parentAddr = addr; if (auto pos = sym->name.find('.'); pos != std::string::npos) { std::string parentName = sym->name.substr(0, pos); if (Symbol const *parentSym = sym_GetSymbol(parentName); parentSym && std::holds_alternative