diff --git a/assume.tsv b/assume.tsv new file mode 100644 index 000000000..bdf50a108 --- /dev/null +++ b/assume.tsv @@ -0,0 +1,5 @@ +commit rgbasm rgblink rgbfix t1 t2 t3 time rom message +6b5248f1 314784 175320 43272 5.21 5.32 5.54 5.36 f4cd194b Add a build target for `include-what-you-use` +9ab3446d 314768 175320 43272 5.26 5.39 5.46 5.37 f4cd194b Fix two bugs with RGBASM fixed-point math (#1388) +1d39e5ed 322680 175320 43272 5.33 5.47 5.50 5.44 f4cd194b Use `std::variant` for RPN expression value (#1389) +8f9a0660 321976 174872 43272 5.32 5.43 5.54 5.43 f4cd194b Replace `assert` with `assume` for release build optimization 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 b33a9abec..63685d24a 100644 --- a/include/link/section.hpp +++ b/include/link/section.hpp @@ -52,6 +52,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 { @@ -88,4 +89,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 cbb436da3..c9864d08b 100644 --- a/include/link/symbol.hpp +++ b/include/link/symbol.hpp @@ -47,4 +47,6 @@ void sym_AddSymbol(Symbol &symbol); */ Symbol *sym_GetSymbol(std::string const &name); +void sym_RemoveSymbol(std::string const &name); + #endif // RGBDS_LINK_SYMBOL_HPP diff --git a/man/rgblink.1 b/man/rgblink.1 index afd3e64dd..f20e46a37 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 @@ -102,6 +103,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. @@ -189,6 +200,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 a8b706d71..d8aa21654 100644 --- a/src/link/assign.cpp +++ b/src/link/assign.cpp @@ -366,6 +366,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 adff69b80..b8543f73d 100644 --- a/src/link/main.cpp +++ b/src/link/main.cpp @@ -150,7 +150,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 @@ -172,6 +172,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'}, @@ -184,7 +185,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" @@ -394,6 +395,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 2558cac02..d652c5a00 100644 --- a/src/link/patch.cpp +++ b/src/link/patch.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "helpers.hpp" // assume #include "linkdefs.hpp" @@ -510,3 +509,161 @@ 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: + 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 (auto *label = std::get_if