// SPDX-License-Identifier: MIT #include "link/output.hpp" #include #include #include #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; static FILE *outputFile; static FILE *overlayFile; static FILE *symFile; static 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; // Insert section while keeping the list sorted by increasing org 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 (options.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 options.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; !options.hasPadValue && !warned) { warnx("Output is larger than overlay file, but no padding value was specified"); warned = true; } } return options.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); offset += section->size; if (!overlayFile) { continue; } // Skip bytes even with pipes for (uint16_t i = 0; i < section->size; ++i) { getc(overlayFile); } } } if (!options.disablePadding) { while (offset < size) { putc(getNextFillByte(), outputFile); ++offset; } } } static void writeROM() { if (options.outputFileName) { if (strcmp(options.outputFileName, "-")) { outputFile = fopen(options.outputFileName, "wb"); } else { options.outputFileName = ""; (void)setmode(STDOUT_FILENO, O_BINARY); outputFile = stdout; } if (!outputFile) { fatal("Failed to open output file \"%s\": %s", options.outputFileName, strerror(errno)); } } Defer closeOutputFile{[&] { if (outputFile) { fclose(outputFile); } }}; if (options.overlayFileName) { if (strcmp(options.overlayFileName, "-")) { overlayFile = fopen(options.overlayFileName, "rb"); } else { options.overlayFileName = ""; (void)setmode(STDIN_FILENO, O_BINARY); overlayFile = stdin; } if (!overlayFile) { fatal( "Failed to open overlay file \"%s\": %s", options.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';) { // Output legal ASCII characters as-is if (char c = *ptr; continuesIdentifier(c)) { putc(c, file); ++ptr; continue; } // 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) { ++ptr; continue; } // 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; } 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) { 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; // First, sort by address // Second, sort by locality (global before local) // Third, sort by parent address // Fourth, sort by name return std::tie(sym1.addr, sym1_local, sym1.parentAddr, sym1_name) < std::tie(sym2.addr, sym2_local, sym2.parentAddr, sym2_name); } template static void forEachSortedSection(SortedSections const &bankSections, F callback) { for (Section const *sect : bankSections.zeroLenSections) { for (; sect != nullptr; sect = sect->nextPiece.get()) { callback(*sect); } } for (Section const *sect : bankSections.sections) { for (; sect != nullptr; sect = sect->nextPiece.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, [&symList](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; } assume(std::holds_alternative