// SPDX-License-Identifier: MIT #include "link/output.hpp" #include #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; if (targetBank >= maxNbBanks[section.type]) { fatal( "Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")", section.name.c_str(), section.bank, maxNbBanks[section.type] - 1 ); } if (sections[section.type].size() <= targetBank) { sections[section.type].resize(targetBank + 1); } // Insert section while keeping the list sorted by increasing org 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; } std::optional overlaySize = seekSize(overlayFile); if (!overlaySize.has_value()) { warnx("Overlay file is not seekable, cannot check if properly formed"); return 0; } 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"); } 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 && 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 assume(section->size == section->data.size()); 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) { char const *outputFileName = options.outputFileName->c_str(); if (*options.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) { xfclose(outputFile); } }}; if (options.overlayFileName) { char const *overlayFileName = options.overlayFileName->c_str(); if (*options.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) { xfclose(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 (size_t i = 0; i < name.length();) { // Output legal ASCII characters as-is if (char c = name[i]; continuesIdentifier(c)) { putc(c, file); ++i; continue; } // Output illegal characters using Unicode escapes ('\u' or '\U') // Decode the UTF-8 codepoint; or at least attempt to Utf8Decoder decoder; while (i < name.length()) { decoder.update(static_cast(name[i++])); if (decoder.state == UTF8_ACCEPT || decoder.state == UTF8_REJECT) { break; } } if (decoder.state != UTF8_ACCEPT) { // This sequence was invalid or incomplete; emit a U+FFFD instead decoder.codepoint = 0xFFFD; } fprintf( file, decoder.codepoint <= 0xFFFF ? "\\u%04" PRIx32 : "\\U%08" PRIx32, decoder.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); } static void forEachSortedSection( SortedSections const &bankSections, Procedure
auto callback ) { for (Section const *sect : bankSections.zeroLenSections) { for (Section const &piece : sect->pieces()) { callback(piece); } } for (Section const *sect : bankSections.sections) { for (Section const &piece : sect->pieces()) { callback(piece); } } } 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