diff --git a/contrib/bash_compl/_rgblink.bash b/contrib/bash_compl/_rgblink.bash index 41af5d9c2..ec9446d54 100755 --- a/contrib/bash_compl/_rgblink.bash +++ b/contrib/bash_compl/_rgblink.bash @@ -20,6 +20,7 @@ _rgblink_completions() { [O]="overlay:glob-*.gb *.gbc *.sgb" [o]="output:glob-*.gb *.gbc *.sgb" [p]="pad:unk" + [s]="smart:unk" ) # Parse command-line up to current word local opt_ena=true diff --git a/contrib/zsh_compl/_rgblink b/contrib/zsh_compl/_rgblink index 3e9ca6be5..9bc7a1a9e 100644 --- a/contrib/zsh_compl/_rgblink +++ b/contrib/zsh_compl/_rgblink @@ -18,6 +18,7 @@ local args=( '(-o --output)'{-o,--output}"+[Write ROM image to this file]:rom file:_files -g '*.{gb,sgb,gbc}'" '(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:' '(-S --scramble)'{-s,--scramble}'+[Activate scrambling]:scramble spec' + '(-s --smart)'{-s,--smart}'+[Perform smart linking from this section]:section name:' '*'":object files:_files -g '*.o'" ) diff --git a/include/link/patch.hpp b/include/link/patch.hpp index 532ba50c8..d486356ee 100644 --- a/include/link/patch.hpp +++ b/include/link/patch.hpp @@ -3,6 +3,12 @@ #ifndef RGBDS_LINK_PATCH_HPP #define RGBDS_LINK_PATCH_HPP +#include + +struct Patch; +struct Section; +struct Symbol; + /* * Checks all assertions * @return true if assertion failed @@ -14,4 +20,12 @@ void patch_CheckAssertions(); */ void patch_ApplyPatches(); +/** + * Executes a callback on all sections referenced by a patch's expression + * @param patch The patch to scan the expression of + */ +void patch_FindReferencedSections( + Patch const &patch, void (*callback)(Section &), std::vector const &fileSymbols +); + #endif // RGBDS_LINK_PATCH_HPP diff --git a/include/link/section.hpp b/include/link/section.hpp index 09f7a4e34..51e6f8cea 100644 --- a/include/link/section.hpp +++ b/include/link/section.hpp @@ -54,6 +54,7 @@ struct Section { std::vector *fileSymbols; std::vector symbols; std::unique_ptr
nextu; // The next "component" of this unionized sect + bool smartLinked = false; // Set to true if kept by smart linking }; struct Assertion { @@ -90,4 +91,11 @@ Section *sect_GetSection(std::string const &name); */ void sect_DoSanityChecks(); +/** + * Adds a section as a new "root" of the smart link graph + */ +void sect_AddSmartSection(std::string const &name); + +void sect_PerformSmartLink(); + #endif // RGBDS_LINK_SECTION_HPP diff --git a/include/link/symbol.hpp b/include/link/symbol.hpp index f84d69744..d9ad58dd7 100644 --- a/include/link/symbol.hpp +++ b/include/link/symbol.hpp @@ -48,6 +48,7 @@ void sym_AddSymbol(Symbol &symbol); */ Symbol *sym_GetSymbol(std::string const &name); +void sym_RemoveSymbol(std::string const &name); void sym_DumpLocalAliasedSymbols(std::string const &name); #endif // RGBDS_LINK_SYMBOL_HPP diff --git a/man/rgblink.1 b/man/rgblink.1 index 7d1b87d94..ef50ff1a1 100644 --- a/man/rgblink.1 +++ b/man/rgblink.1 @@ -16,6 +16,7 @@ .Op Fl o Ar out_file .Op Fl p Ar pad_value .Op Fl S Ar spec +.Op Fl s Ar symbol .Ar .Sh DESCRIPTION The @@ -104,6 +105,16 @@ See .Sx Scrambling algorithm below for an explanation and a description of .Ar spec . +.It Fl s Ar sect_name , Fl Fl smart Ar sect_name +This option specifies the name of a section that will be used as a starting point for smart linking; it may appear several times per +.Nm +invocation. +See +.Sx SMART LINKING +below. +If no section with that name is found, +.Nm +will error out. .It Fl t , Fl \-tiny Expand the ROM0 section size from 16 KiB to the full 32 KiB assigned to ROM. ROMX sections that are fixed to a bank other than 1 become errors, other ROMX sections are treated as ROM0. @@ -191,6 +202,103 @@ to fix these so that the program will actually run in a Game Boy: Here is a more complete example: .Pp .Dl $ rgblink -o bin/game.gb -n bin/game.sym -p 0xFF obj/title.o obj/engine.o +.Sh SMART LINKING +Smart linking is a feature of +.Nm +that allows "trimming the fat" off of a ROM. +It is enabled only if at least one +.Fl s +option is given on the command line. +.Pp +The smart linking process begins by finding all the +.Ql referenced +sections. +A section is referenced if its name is specified using a +.Fl s +option, or if it is referred to by a referenced section. +This definition applies recursively, so if section +.Ql A +is specified using +.Fl s Va A , +section +.Ql A +references section +.Ql B , +and section +.Ql B +references section +.Ql C , +then all three sections +.Ql A , +.Ql B , +and +.Ql C +are referenced. +.Pp +Sections refer to each other through expressions. For example: +.Bd -literal -offset indent +SECTION "A", ROM0 + db Someplace + db BANK("C") +SECTION "B", ROM0 +Someplace: +SECTION "C", ROMX + db 42 +.Ed +Here, section +.Ql A +references section +.Ql B +via the label +.Ql Someplace , +and references section +.Ql C +via its name in +.Ql BANK("C") . +.Pp +After all the referenced sections are found, all sections that were not referenced are deleted, and the linking process continues as normal. +.Sy This should not cause any symbols not to be found, please report a bug (see Sx BUGS Ns Sy ) if this occurs. +.Pp +This is useful to detect +.Dq unused +sections, i.e. sections that contain data not used by anything. +Typically, the section containing the header, including the entry point at +.Ad $00:0100 +will be one of the starting sections; more exotic use cases may require more starting sections. +It may be a good idea to start with the header as the only root, and if needed, add more root sections. +.Pp +Be careful, as numeric expressions do +.Sy not +cause references: +.Bd -literal -offset indent +DEF BASE_ADDR EQU $4000 +SECTION "A", ROM0 + dw BASE_ADDR +SECTION "B", ROMX[BASE_ADDR] + db 42 +.Ed +Section +.Ql A +does +.Sy not +reference section +.Ql B , +since +.Va BASE_ADDR +is a constant, and thus does not belong to section +.Ql B . +.Pp +Finally, be careful that +.Xr rgbasm 1 +tries to fill in data by itself to speed up +.Nm Ap s +work, which may cause +.Nm +not to see references to sections whose bank and/or address are fixed. +It may be advisable to avoid fixing those (notably, to enable +.Nm +to improve section placement), but they can still be manually referenced using +.Fl s . .Sh BUGS Please report bugs on .Lk https://github.com/gbdev/rgbds/issues GitHub . diff --git a/src/link/assign.cpp b/src/link/assign.cpp index 9a2e4edc9..de0b05646 100644 --- a/src/link/assign.cpp +++ b/src/link/assign.cpp @@ -367,6 +367,9 @@ void assign_AssignSections() { initFreeSpace(); + // Check if we need to do smart linking and discard any sections + sect_PerformSmartLink(); + // Generate linked lists of sections to assign nbSectionsToAssign = 0; sect_ForEach(categorizeSection); diff --git a/src/link/main.cpp b/src/link/main.cpp index 2f542a70e..61ed525fe 100644 --- a/src/link/main.cpp +++ b/src/link/main.cpp @@ -130,7 +130,7 @@ void argErr(char flag, char const *fmt, ...) { } // Short options -static char const *optstring = "dl:m:Mn:O:o:p:S:tVvWwx"; +static char const *optstring = "dl:m:Mn:O:o:p:S:s:tVvWwx"; /* * Equivalent long options @@ -152,6 +152,7 @@ static option const longopts[] = { {"output", required_argument, nullptr, 'o'}, {"pad", required_argument, nullptr, 'p'}, {"scramble", required_argument, nullptr, 'S'}, + {"smart", required_argument, nullptr, 's'}, {"tiny", no_argument, nullptr, 't'}, {"version", no_argument, nullptr, 'V'}, {"verbose", no_argument, nullptr, 'v'}, @@ -164,7 +165,7 @@ static void printUsage() { fputs( "Usage: rgblink [-dMtVvwx] [-l script] [-m map_file] [-n sym_file]\n" " [-O overlay_file] [-o out_file] [-p pad_value]\n" - " [-S spec] ...\n" + " [-S spec] [-s symbol] ...\n" "Useful options:\n" " -l, --linkerscript set the input linker script\n" " -m, --map set the output map file\n" @@ -374,6 +375,9 @@ int main(int argc, char *argv[]) { case 'S': parseScrambleSpec(musl_optarg); break; + case 's': + sect_AddSmartSection(musl_optarg); + break; case 't': is32kMode = true; break; diff --git a/src/link/patch.cpp b/src/link/patch.cpp index a3724519d..71fd0af06 100644 --- a/src/link/patch.cpp +++ b/src/link/patch.cpp @@ -536,3 +536,165 @@ static void applyPatches(Section §ion) { void patch_ApplyPatches() { sect_ForEach(applyPatches); } + +void patch_FindReferencedSections( + Patch const &patch, void (*callback)(Section &), std::vector const &fileSymbols +) { + uint8_t const *expression = patch.rpnExpression.data(); + int32_t size = (int32_t)patch.rpnExpression.size(); + + while (size > 0) { + RPNCommand command = (RPNCommand)getRPNByte(expression, size, patch); + + switch (command) { + // Ignore operators + case RPN_ADD: + case RPN_SUB: + case RPN_MUL: + case RPN_DIV: + case RPN_MOD: + case RPN_NEG: + case RPN_EXP: + case RPN_OR: + case RPN_AND: + case RPN_XOR: + case RPN_NOT: + case RPN_LOGAND: + case RPN_LOGOR: + case RPN_LOGNOT: + case RPN_LOGEQ: + case RPN_LOGNE: + case RPN_LOGGT: + case RPN_LOGLT: + case RPN_LOGGE: + case RPN_LOGLE: + case RPN_SHL: + case RPN_SHR: + case RPN_USHR: + case RPN_HIGH: + case RPN_LOW: + case RPN_BITWIDTH: + case RPN_TZCOUNT: + break; + + case RPN_BANK_SYM: { + int32_t symbolID = 0; + for (uint8_t shift = 0; shift < 32; shift += 8) + symbolID |= getRPNByte(expression, size, patch) << shift; + + if (Symbol const *symbol = getSymbol(fileSymbols, symbolID); !symbol) { + error( + patch.src, + patch.lineNo, + "Requested BANK() of symbol \"%s\", which was not found", + fileSymbols[symbolID].name.c_str() + ); + } else if (symbol->data.holds