diff --git a/.gitignore b/.gitignore index 4ececbb7f..981c4036f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,3 @@ rgbshim.sh CMakeCache.txt CMakeFiles cmake_install.cmake - -test/pokecrystal -test/pokered -test/ucity diff --git a/include/hashmap.h b/include/hashmap.h index f81664f0f..25cdbe46d 100644 --- a/include/hashmap.h +++ b/include/hashmap.h @@ -47,9 +47,9 @@ bool hash_ReplaceElement(HashMap const map, char const *key, void *element); * Removes an element from a hashmap. * @param map The HashMap to remove the element from * @param key The key to search the element with - * @return True if the element was found and removed + * @return The element removed, or NULL if none was found */ -bool hash_RemoveElement(HashMap map, char const *key); +void *hash_RemoveElement(HashMap map, char const *key); /** * Finds an element in a hashmap. @@ -74,6 +74,6 @@ void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg); * This does not `free` the data structure itself! * @param map The map to empty */ -void hash_EmptyMap(HashMap map); +void hash_EmptyMap(HashMap map, void (*callback)(void *)); #endif /* RGBDS_LINK_HASHMAP_H */ diff --git a/include/link/object.h b/include/link/object.h index b43d728be..4818e0875 100644 --- a/include/link/object.h +++ b/include/link/object.h @@ -23,6 +23,11 @@ void obj_ReadFile(char const *fileName, unsigned int i); */ void obj_DoSanityChecks(void); +/** + * @return The first assertion in the linked list of all assertions + */ +struct Assertion const *obj_GetFirstAssertion(void); + /** * Evaluate all assertions */ diff --git a/include/link/patch.h b/include/link/patch.h index fe59df88b..34f9ddf52 100644 --- a/include/link/patch.h +++ b/include/link/patch.h @@ -17,6 +17,8 @@ #include "linkdefs.h" +struct Symbol; + struct Assertion { struct Patch patch; // enum AssertionType type; The `patch`'s field is instead re-used @@ -32,6 +34,7 @@ struct Assertion { /** * Checks all assertions + * @param assertion The first assertion to check (in a linked list) * @return true if assertion failed */ void patch_CheckAssertions(struct Assertion *assertion); @@ -41,4 +44,20 @@ void patch_CheckAssertions(struct Assertion *assertion); */ void patch_ApplyPatches(void); +/** + * Executes a callback on all sections referenced by a patch's expression + * @param patch The patch to scan the expression of + */ +void patch_FindRefdSections(struct Patch const *patch, void (*callback)(struct Section *), + struct Symbol const * const *fileSymbols); + +/** + * Properly deletes a patch object + * @param patch The patch to be deleted + */ +static inline void patch_DeletePatch(struct Patch *patch) +{ + free(patch->rpnExpression); +} + #endif /* RGBDS_LINK_PATCH_H */ diff --git a/include/link/section.h b/include/link/section.h index d9da1a5b0..b6771eae6 100644 --- a/include/link/section.h +++ b/include/link/section.h @@ -62,6 +62,7 @@ struct Section { uint32_t nbSymbols; struct Symbol const **symbols; struct Section *nextu; /* The next "component" of this unionized sect */ + bool smartLinked; // Set to true if kept by smart linking }; /* @@ -97,4 +98,11 @@ void sect_CleanupSections(void); */ void sect_DoSanityChecks(void); +/** + * Adds a new function as "root" of the smart link graph + */ +void sect_AddSmartSection(char const *name); + +void sect_PerformSmartLink(void); + #endif /* RGBDS_LINK_SECTION_H */ diff --git a/include/link/symbol.h b/include/link/symbol.h index d00857953..0e743c95e 100644 --- a/include/link/symbol.h +++ b/include/link/symbol.h @@ -53,6 +53,24 @@ void sym_AddSymbol(struct Symbol *symbol); */ struct Symbol *sym_GetSymbol(char const *name); +/** + * Cleanly deletes a symbol and associated data + * @param arg The symbol to delete + */ +static inline void sym_FreeSymbol(void *arg) +{ + struct Symbol *sym = arg; + + free(sym->name); + free(sym); +} + +/** + * Cleanly deletes a symbol and associated data + * @param name The name of the symbol to delete + */ +void sym_RemoveSymbol(char const *name); + /** * `free`s all symbol memory that was allocated. */ diff --git a/src/hashmap.c b/src/hashmap.c index a45cd4493..7f2127828 100644 --- a/src/hashmap.c +++ b/src/hashmap.c @@ -80,7 +80,7 @@ bool hash_ReplaceElement(HashMap const map, char const *key, void *element) return false; } -bool hash_RemoveElement(HashMap map, char const *key) +void *hash_RemoveElement(HashMap map, char const *key) { HashType hashedKey = hash(key); struct HashMapEntry **ptr = &map[(HalfHashType)hashedKey]; @@ -89,14 +89,15 @@ bool hash_RemoveElement(HashMap map, char const *key) if (hashedKey >> HALF_HASH_NB_BITS == (*ptr)->hash && !strcmp((*ptr)->key, key)) { struct HashMapEntry *next = (*ptr)->next; + void *elem = (*ptr)->content; free(*ptr); *ptr = next; - return true; + return elem; } ptr = &(*ptr)->next; } - return false; + return NULL; } void *hash_GetElement(HashMap const map, char const *key) @@ -126,7 +127,7 @@ void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg) } } -void hash_EmptyMap(HashMap map) +void hash_EmptyMap(HashMap map, void (*callback)(void *)) { for (size_t i = 0; i < HASHMAP_NB_BUCKETS; i++) { struct HashMapEntry *ptr = map[i]; @@ -134,6 +135,8 @@ void hash_EmptyMap(HashMap map) while (ptr) { struct HashMapEntry *next = ptr->next; + if (callback) + callback(ptr->content); free(ptr); ptr = next; } diff --git a/src/link/main.c b/src/link/main.c index 77f64f63d..d33db9230 100644 --- a/src/link/main.c +++ b/src/link/main.c @@ -200,6 +200,8 @@ static void printUsage(void) static void cleanup(void) { obj_Cleanup(); + sym_CleanupSymbols(); + sect_CleanupSections(); } int main(int argc, char *argv[]) @@ -244,9 +246,7 @@ int main(int argc, char *argv[]) padValue = value; break; case 's': - /* FIXME: nobody knows what this does, figure it out */ - (void)optarg; - warning(NULL, 0, "Nobody has any idea what `-s` does"); + sect_AddSmartSection(optarg); break; case 't': is32kMode = true; @@ -296,6 +296,7 @@ int main(int argc, char *argv[]) /* then process them, */ obj_DoSanityChecks(); + sect_PerformSmartLink(); assign_AssignSections(); obj_CheckAssertions(); assign_Cleanup(); diff --git a/src/link/object.c b/src/link/object.c index 3ce4a3bc9..478803dfe 100644 --- a/src/link/object.c +++ b/src/link/object.c @@ -316,17 +316,13 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam int32_t tmp; uint8_t byte; - tryReadstr(section->name, file, "%s: Cannot read section name: %s", - fileName); - tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", - fileName, section->name); + tryReadstr(section->name, file, "%s: Cannot read section name: %s", fileName); + tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section->name); if (tmp < 0 || tmp > UINT16_MAX) - errx(1, "\"%s\"'s section size (%" PRId32 ") is invalid", - section->name, tmp); + errx(1, "\"%s\"'s section size (%" PRId32 ") is invalid", section->name, tmp); section->size = tmp; section->offset = 0; - tryGetc(byte, file, "%s: Cannot read \"%s\"'s type: %s", - fileName, section->name); + tryGetc(byte, file, "%s: Cannot read \"%s\"'s type: %s", fileName, section->name); section->type = byte & 0x3F; if (byte >> 7) section->modifier = SECTION_UNION; @@ -334,21 +330,17 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam section->modifier = SECTION_FRAGMENT; else section->modifier = SECTION_NORMAL; - tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", - fileName, section->name); + tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section->name); section->isAddressFixed = tmp >= 0; if (tmp > UINT16_MAX) { - error(NULL, 0, "\"%s\"'s org is too large (%" PRId32 ")", - section->name, tmp); + error(NULL, 0, "\"%s\"'s org is too large (%" PRId32 ")", section->name, tmp); tmp = UINT16_MAX; } section->org = tmp; - tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s", - fileName, section->name); + tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s", fileName, section->name); section->isBankFixed = tmp >= 0; section->bank = tmp; - tryGetc(byte, file, "%s: Cannot read \"%s\"'s alignment: %s", - fileName, section->name); + tryGetc(byte, file, "%s: Cannot read \"%s\"'s alignment: %s", fileName, section->name); section->isAlignFixed = byte != 0; section->alignMask = (1 << byte) - 1; tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s alignment offset: %s", @@ -365,16 +357,13 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam uint8_t *data = malloc(sizeof(*data) * section->size + 1); if (!data) - err(1, "%s: Unable to read \"%s\"'s data", fileName, - section->name); + err(1, "%s: Unable to read \"%s\"'s data", fileName, section->name); if (section->size) { - size_t nbElementsRead = fread(data, sizeof(*data), - section->size, file); + size_t nbElementsRead = fread(data, sizeof(*data), section->size, file); if (nbElementsRead != section->size) errx(1, "%s: Cannot read \"%s\"'s data: %s", fileName, section->name, - feof(file) ? "Unexpected end of file" - : strerror(errno)); + feof(file) ? "Unexpected end of file" : strerror(errno)); } section->data = data; @@ -382,18 +371,18 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam "%s: Cannot read \"%s\"'s number of patches: %s", fileName, section->name); - struct Patch *patches = - malloc(sizeof(*patches) * section->nbPatches + 1); + struct Patch *patches = malloc(sizeof(*patches) * section->nbPatches + 1); if (!patches) - err(1, "%s: Unable to read \"%s\"'s patches", fileName, - section->name); + err(1, "%s: Unable to read \"%s\"'s patches", fileName, section->name); for (uint32_t i = 0; i < section->nbPatches; i++) { readPatch(file, &patches[i], fileName, section->name, i, fileSections, fileNodes); } section->patches = patches; } + + section->smartLinked = false; } /** @@ -612,6 +601,11 @@ void obj_DoSanityChecks(void) sect_DoSanityChecks(); } +struct Assertion const *obj_GetFirstAssertion(void) +{ + return assertions; +} + void obj_CheckAssertions(void) { patch_CheckAssertions(assertions); @@ -626,27 +620,6 @@ void obj_Setup(unsigned int nbFiles) nodes = malloc(sizeof(*nodes) * nbFiles); } -static void freeSection(struct Section *section, void *arg) -{ - (void)arg; - - free(section->name); - if (sect_HasData(section->type)) { - free(section->data); - for (int32_t i = 0; i < section->nbPatches; i++) - free(section->patches[i].rpnExpression); - free(section->patches); - } - free(section->symbols); - free(section); -} - -static void freeSymbol(struct Symbol *symbol) -{ - free(symbol->name); - free(symbol); -} - void obj_Cleanup(void) { for (unsigned int i = 0; i < nbObjFiles; i++) { @@ -658,16 +631,11 @@ void obj_Cleanup(void) } free(nodes); - sym_CleanupSymbols(); - - sect_ForEach(freeSection, NULL); - sect_CleanupSections(); - struct SymbolList *list = symbolLists; while (list) { for (size_t i = 0; i < list->nbSymbols; i++) - freeSymbol(list->symbolList[i]); + sym_FreeSymbol(list->symbolList[i]); free(list->symbolList); struct SymbolList *next = list->next; diff --git a/src/link/patch.c b/src/link/patch.c index 3a72ccbc4..be54383d9 100644 --- a/src/link/patch.c +++ b/src/link/patch.c @@ -428,7 +428,7 @@ static int32_t computeRPNExpr(struct Patch const *patch, void patch_CheckAssertions(struct Assertion *assert) { - verbosePrint("Checking assertions..."); + verbosePrint("Checking assertions...\n"); initRPNStack(); while (assert) { @@ -550,3 +550,101 @@ void patch_ApplyPatches(void) freeRPNStack(); } +void patch_FindRefdSections(struct Patch const *patch, void (*callback)(struct Section *), + struct Symbol const * const *fileSymbols) +{ + uint8_t const *expression = patch->rpnExpression; + int32_t size = patch->rpnSize; + + // Read through the RPN expression; to separate concerns more, do not attempt to validate + // + while (size > 0) { + enum RPNCommand command = getRPNByte(&expression, &size, + patch->src, patch->lineNo); + + switch (command) { + int32_t symbolID; + struct Symbol const *symbol; + char const *name; + struct Section *sect; + + // Ignore operators... + case RPN_ADD: + case RPN_SUB: + case RPN_MUL: + case RPN_DIV: + case RPN_MOD: + case RPN_UNSUB: + case RPN_OR: + case RPN_AND: + case RPN_XOR: + case RPN_UNNOT: + case RPN_LOGAND: + case RPN_LOGOR: + case RPN_LOGUNNOT: + case RPN_LOGEQ: + case RPN_LOGNE: + case RPN_LOGGT: + case RPN_LOGLT: + case RPN_LOGGE: + case RPN_LOGLE: + case RPN_SHL: + case RPN_SHR: + break; + + case RPN_BANK_SYM: + for (uint8_t shift = 0; shift < 32; shift += 8) + symbolID |= getRPNByte(&expression, &size, + patch->src, patch->lineNo) << shift; + symbol = getSymbol(fileSymbols, symbolID); + + if (!symbol) + error(patch->src, patch->lineNo, + "Requested BANK() of symbol \"%s\", which was not found", + fileSymbols[symbolID]->name); + else if (!symbol->section) + error(patch->src, patch->lineNo, + "Requested BANK() of non-label symbol \"%s\"", + fileSymbols[symbolID]->name); + else + callback(symbol->section); + break; + + case RPN_BANK_SECT: + name = (char const *)expression; + while (getRPNByte(&expression, &size, patch->src, patch->lineNo)) + ; + + sect = sect_GetSection(name); + + if (!sect) + error(patch->src, patch->lineNo, + "Requested BANK() of section \"%s\", which was not found", + name); + else + callback(sect); + break; + + // Ignore values that do not reference a symbol + case RPN_BANK_SELF: + case RPN_HRAM: + case RPN_RST: + case RPN_CONST: + break; + + case RPN_SYM: + symbolID = 0; + for (uint8_t shift = 0; shift < 32; shift += 8) + symbolID |= getRPNByte(&expression, &size, + patch->src, patch->lineNo) << shift; + + if (symbolID != -1) { // Ignore PC + symbol = getSymbol(fileSymbols, symbolID); + if (symbol->section) // Do not callback if symbol is a constant... + callback(symbol->section); + } + break; + } + } +} + diff --git a/src/link/rgblink.1 b/src/link/rgblink.1 index 360fa7dd3..a0568c902 100644 --- a/src/link/rgblink.1 +++ b/src/link/rgblink.1 @@ -89,10 +89,16 @@ Has no effect if .Fl O is specified. The default is 0. -.It Fl s Ar symbol , Fl Fl smart Ar symbol -This option is ignored. -It was supposed to perform smart linking but fell into disrepair, and so has been removed. -It will be reimplemented at some point. +.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 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. @@ -130,6 +136,107 @@ 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 +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/section.c b/src/link/section.c index 4dde6efff..86d54f1fa 100644 --- a/src/link/section.c +++ b/src/link/section.c @@ -6,13 +6,17 @@ * SPDX-License-Identifier: MIT */ +#include #include #include #include #include #include "link/main.h" +#include "link/object.h" +#include "link/patch.h" #include "link/section.h" +#include "link/symbol.h" #include "extern/err.h" @@ -141,6 +145,30 @@ void sect_AddSection(struct Section *section) } } +static void deleteSection(void *arg) +{ + struct Section *sect = arg; + + free(sect->name); + if (sect_HasData(sect->type)) { + free(sect->data); + for (size_t i = 0; i < sect->nbPatches; i++) + patch_DeletePatch(§->patches[i]); + free(sect->patches); + } + free(sect); +} + +static void sect_RemoveSection(char const *name) +{ + struct Section *sect = hash_RemoveElement(sections, name); + + for (size_t i = 0; i < sect->nbSymbols; i++) { + sym_RemoveSymbol(sect->symbols[i]->name); + } + deleteSection(sect); +} + struct Section *sect_GetSection(char const *name) { return (struct Section *)hash_GetElement(sections, name); @@ -148,7 +176,7 @@ struct Section *sect_GetSection(char const *name) void sect_CleanupSections(void) { - hash_EmptyMap(sections); + hash_EmptyMap(sections, deleteSection); } static bool sanityChecksFailed; @@ -259,3 +287,116 @@ void sect_DoSanityChecks(void) if (sanityChecksFailed) errx(1, "Sanity checks failed"); } + +// Base amount of sections allocated in array below +#define SMART_LINK_NB_SECTIONS 32 +// Names of +char const **smartLinkNames = NULL; +size_t nbSmartLinkNames; +size_t smartLinkNameCap; // Capacity + +void sect_AddSmartSection(char const *name) +{ + if (!smartLinkNames) { + nbSmartLinkNames = 0; + smartLinkNameCap = SMART_LINK_NB_SECTIONS; + smartLinkNames = malloc(sizeof(*smartLinkNames) * smartLinkNameCap); + } else if (nbSmartLinkNames == smartLinkNameCap) { + if (smartLinkNameCap == SIZE_MAX) + error(NULL, 0, + "Smart linking can only accept %zu section names, please stop", + SIZE_MAX); + if (smartLinkNameCap > SIZE_MAX / 2) + smartLinkNameCap = SIZE_MAX; + else + smartLinkNameCap *= 2; + smartLinkNames = realloc(smartLinkNames, sizeof(*smartLinkNames) * smartLinkNameCap); + } + + if (!smartLinkNames) + errx(1, "Failed to alloc smart link names: %s", strerror(errno)); + + smartLinkNames[nbSmartLinkNames] = name; + nbSmartLinkNames++; +} + +static void smartSectionPurge(void *arg, void *ign) +{ + (void)ign; + struct Section *sect = arg; + + if (!sect->smartLinked) { + verbosePrint("Dropping \"%s\" due to smart linking\n", sect->name); + sect_RemoveSection(sect->name); + } +} + +// Actually a stack, not a queue; however, the order in which sections are processed doesn't matter +struct Section **smartLinkQueue; +size_t queueSize = 0; +size_t queueCapacity; + +static void queueSmartSection(struct Section *sect) +{ + if (queueSize == queueCapacity) { + if (queueCapacity == SIZE_MAX) + error(NULL, 0, "Smart linking queue capacity exceeded!"); + else if (queueCapacity > SIZE_MAX / 2) + queueCapacity = SIZE_MAX; + else + queueCapacity *= 2; + smartLinkQueue = realloc(smartLinkQueue, sizeof(*smartLinkQueue) * queueCapacity); + } + + sect->smartLinked = true; +} + +void sect_LinkSection(struct Section const *sect) +{ + // Scan all linked sections + for (uint32_t i = 0; i < sect->nbPatches; i++) + patch_FindRefdSections(§->patches[i], queueSmartSection, (struct Symbol const * const *)sect->fileSymbols); +} + +void sect_PerformSmartLink(void) +{ + // If smart linking wasn't requested, do nothing + if (!smartLinkNames) + return; + + // Assume that each section is going to link a new one... + queueCapacity = nbSmartLinkNames; + smartLinkQueue = malloc(queueSize * sizeof(*smartLinkQueue)); + if (!smartLinkQueue) + error(NULL, 0, "Smart linking allocation failed: %s", strerror(errno)); + + // Add all sections requested on the CLI + for (size_t i = 0; i < nbSmartLinkNames; ++i) { + struct Section *sect = sect_GetSection(smartLinkNames[i]); + + if (!sect) { + error(NULL, 0, + "Section \"%s\" was specified for smart linking, but was not found", + smartLinkNames[i]); + } else { + sect->smartLinked = true; + sect_LinkSection(sect); + } + } + free(smartLinkNames); + + // Also add sections referenced by assertions + struct Assertion const *assertion = obj_GetFirstAssertion(); + + while (assertion) { + patch_FindRefdSections(&assertion->patch, queueSmartSection, (struct Symbol const * const *)assertion->fileSymbols); + assertion = assertion->next; + } + + // As long as the queue isn't empty, get the last one, and process it + while (queueSize) + sect_LinkSection(smartLinkQueue[--queueSize]); + free(smartLinkQueue); + + hash_ForEach(sections, smartSectionPurge, NULL); +} diff --git a/src/link/symbol.c b/src/link/symbol.c index 483c2c048..326cee746 100644 --- a/src/link/symbol.c +++ b/src/link/symbol.c @@ -65,7 +65,13 @@ struct Symbol *sym_GetSymbol(char const *name) return (struct Symbol *)hash_GetElement(symbols, name); } +void sym_RemoveSymbol(char const *name) +{ + sym_RemoveSymbol(hash_RemoveElement(symbols, name)); +} + void sym_CleanupSymbols(void) { - hash_EmptyMap(symbols); + // The hash map only contains exported symbols, so they're cleared elsewhere + hash_EmptyMap(symbols, NULL); } diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 000000000..462c33ed7 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,3 @@ +/pokecrystal +/pokered +/ucity diff --git a/test/link/label-diff.asm b/test/link/label-diff.asm new file mode 100644 index 000000000..15eef1032 --- /dev/null +++ b/test/link/label-diff.asm @@ -0,0 +1,15 @@ + +SECTION "A", ROM0 + +Label: + db 2, Label +.end + +SECTION "root", ROM0[0] + + db Label.end - Label + +SECTION "B", ROM0[3] + +Unrefd: + db Unrefd diff --git a/test/link/smart/assert.asm b/test/link/smart/assert.asm new file mode 100644 index 000000000..8b5ef554d --- /dev/null +++ b/test/link/smart/assert.asm @@ -0,0 +1,16 @@ + +SECTION "root", ROM0[0] + + db Label + +SECTION "A", ROM0[1] + +Label: + db 2, Label + +assert Unrefd == 3 ; The assertion will reference section "B" + +SECTION "B", ROM0[3] + +Unrefd: + db Unrefd ; References itself, but unless referenced externally, that doesn't matter diff --git a/test/link/smart/assert.bin b/test/link/smart/assert.bin new file mode 100644 index 000000000..0edfd8694 --- /dev/null +++ b/test/link/smart/assert.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/smart/assert.smart.bin b/test/link/smart/assert.smart.bin new file mode 100644 index 000000000..0edfd8694 --- /dev/null +++ b/test/link/smart/assert.smart.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/smart/const.asm b/test/link/smart/const.asm new file mode 100644 index 000000000..82f11aaf8 --- /dev/null +++ b/test/link/smart/const.asm @@ -0,0 +1,17 @@ + +BASE equ 1 + +SECTION "root", ROM0[0] + + db BASE + +; Although the base address is specified using a referenced symbol, no symbol within the section is +SECTION "A", ROM0[BASE] + +Label: + db 2, Label + +SECTION "B", ROM0[3] + +Unrefd: + db Unrefd diff --git a/test/link/smart/const.bin b/test/link/smart/const.bin new file mode 100644 index 000000000..0edfd8694 --- /dev/null +++ b/test/link/smart/const.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/smart/const.smart.bin b/test/link/smart/const.smart.bin new file mode 100644 index 000000000..6b2aaa764 --- /dev/null +++ b/test/link/smart/const.smart.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/smart/const_assert.asm b/test/link/smart/const_assert.asm new file mode 100644 index 000000000..15c37276d --- /dev/null +++ b/test/link/smart/const_assert.asm @@ -0,0 +1,16 @@ + +SECTION "root", ROM0[0] + + db Label + +SECTION "A", ROM0[1] + +Label: + db 2, Label + +SECTION "B", ROM0[3] + +Unrefd: + db Unrefd ; References itself, but unless referenced externally, that doesn't matter + +assert Unrefd == 3 ; The assertion should reference section "B"... but it'll be resolved by RGBASM diff --git a/test/link/smart/const_assert.bin b/test/link/smart/const_assert.bin new file mode 100644 index 000000000..0edfd8694 --- /dev/null +++ b/test/link/smart/const_assert.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/smart/const_assert.smart.bin b/test/link/smart/const_assert.smart.bin new file mode 100644 index 000000000..dd15415fe --- /dev/null +++ b/test/link/smart/const_assert.smart.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/smart/const_sect.asm b/test/link/smart/const_sect.asm new file mode 100644 index 000000000..efe4fb007 --- /dev/null +++ b/test/link/smart/const_sect.asm @@ -0,0 +1,17 @@ + +SECTION "root", ROM0[0] + +BASE equ 1 ; This may be defined while section "root" is in scope, it does not belong to the section + + db BASE + +; Although the base address is specified using a referenced symbol, no symbol within the section is +SECTION "A", ROM0[BASE] + +Label: + db 2, Label + +SECTION "B", ROM0[3] + +Unrefd: + db Unrefd diff --git a/test/link/smart/const_sect.bin b/test/link/smart/const_sect.bin new file mode 100644 index 000000000..0edfd8694 --- /dev/null +++ b/test/link/smart/const_sect.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/smart/const_sect.smart.bin b/test/link/smart/const_sect.smart.bin new file mode 100644 index 000000000..6b2aaa764 --- /dev/null +++ b/test/link/smart/const_sect.smart.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/smart/constexpr.asm b/test/link/smart/constexpr.asm new file mode 100644 index 000000000..d8cb9ff71 --- /dev/null +++ b/test/link/smart/constexpr.asm @@ -0,0 +1,14 @@ + +SECTION "A", ROM0[1] + +Label: + db 2, Label + +SECTION "root", ROM0[0] + + db Label + +SECTION "B", ROM0[3] + +Unrefd: + db Unrefd diff --git a/test/link/smart/constexpr.bin b/test/link/smart/constexpr.bin new file mode 100644 index 000000000..0edfd8694 --- /dev/null +++ b/test/link/smart/constexpr.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/smart/constexpr.smart.bin b/test/link/smart/constexpr.smart.bin new file mode 100644 index 000000000..6b2aaa764 --- /dev/null +++ b/test/link/smart/constexpr.smart.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/smart/label-diff.asm b/test/link/smart/label-diff.asm new file mode 100644 index 000000000..36d41ca35 --- /dev/null +++ b/test/link/smart/label-diff.asm @@ -0,0 +1,15 @@ + +SECTION "A", ROM0[1] + +Label: + db 2, Label +.end + +SECTION "root", ROM0[0] + + db Label.end - Label + +SECTION "B", ROM0[3] + +Unrefd: + db Unrefd diff --git a/test/link/smart/label-diff.bin b/test/link/smart/label-diff.bin new file mode 100644 index 000000000..3b3d9a179 --- /dev/null +++ b/test/link/smart/label-diff.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/smart/label-diff.smart.bin b/test/link/smart/label-diff.smart.bin new file mode 100644 index 000000000..25cb955ba --- /dev/null +++ b/test/link/smart/label-diff.smart.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/smart/simple.asm b/test/link/smart/simple.asm new file mode 100644 index 000000000..b79246ed3 --- /dev/null +++ b/test/link/smart/simple.asm @@ -0,0 +1,14 @@ + +SECTION "root", ROM0[0] + + db Label + +SECTION "A", ROM0[1] + +Label: + db 2, Label + +SECTION "B", ROM0[3] + +Unrefd: + db Unrefd ; References itself, but unless referenced externally, that doesn't matter diff --git a/test/link/smart/simple.bin b/test/link/smart/simple.bin new file mode 100644 index 000000000..0edfd8694 --- /dev/null +++ b/test/link/smart/simple.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/smart/simple.smart.bin b/test/link/smart/simple.smart.bin new file mode 100644 index 000000000..dd15415fe --- /dev/null +++ b/test/link/smart/simple.smart.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/link/test.sh b/test/link/test.sh index 2245718c6..85663f282 100755 --- a/test/link/test.sh +++ b/test/link/test.sh @@ -11,12 +11,13 @@ bold=$(tput bold) resbold=$(tput sgr0) red=$(tput setaf 1) rescolors=$(tput op) + +# CONVENTION: please always call these with the reference as the first arg tryDiff () { - diff -u --strip-trailing-cr $1 $2 || (echo "${bold}${red}${i%.asm}.out mismatch!${rescolors}${resbold}"; false) + diff -u --strip-trailing-cr $1 $2 || (echo "${bold}${red}$3 mismatch!${rescolors}${resbold}"; false) } - tryCmp () { - cmp $1 $2 || (../../contrib/gbdiff.bash $1 $2; echo "${bold}${red}${i%.asm}.out.bin mismatch!${rescolors}${resbold}"; false) + cmp $1 $2 || (../../contrib/gbdiff.bash $1 $2; echo "${bold}${red}$3 mismatch!${rescolors}${resbold}"; false) } RGBASM=../../rgbasm @@ -30,13 +31,13 @@ for i in *.asm; do for flag in '-d' '-t' '-w'; do if [ -f ${i%.asm}-no${flag}.out ]; then $RGBLINK -o $gbtemp $otemp > $outtemp 2>&1 - tryDiff ${i%.asm}-no${flag}.out $outtemp + tryDiff ${i%.asm}-no${flag}.out $outtemp ${i%.asm}-no${flag}.out rc=$(($? || $rc)) ran_flag=1 fi if [ -f ${i%.asm}${flag}.out ]; then $RGBLINK ${flag} -o $gbtemp $otemp > $outtemp 2>&1 - tryDiff ${i%.asm}${flag}.out $outtemp + tryDiff ${i%.asm}${flag}.out $outtemp ${i%.asm}${flag}.out rc=$(($? || $rc)) ran_flag=1 fi @@ -48,7 +49,7 @@ for i in *.asm; do # Other tests have several linker scripts find . -name "${i%.asm}*.link" | while read script; do $RGBLINK -l $script -o $gbtemp $otemp > $outtemp 2>&1 - tryDiff ${script%.link}.out $outtemp + tryDiff ${script%.link}.out $outtemp ${script%.link}.out rc=$(($? || $rc)) ran_flag=1 done @@ -59,16 +60,37 @@ for i in *.asm; do # The rest of the tests just links a file, and maybe checks the binary $RGBLINK -o $gbtemp $otemp > $outtemp 2>&1 if [ -f ${i%.asm}.out ]; then - tryDiff ${i%.asm}.out $outtemp + tryDiff ${i%.asm}.out $outtemp ${i%.asm}.out rc=$(($? || $rc)) fi bin=${i%.asm}.out.bin if [ -f $bin ]; then dd if=$gbtemp count=1 bs=$(printf %s $(wc -c < $bin)) > $otemp 2>/dev/null - tryCmp $bin $otemp + tryCmp $bin $otemp $bin rc=$(($? || $rc)) + + # TODO: check for output to stdout fi + + # TODO: check for RGBASM piping into RGBLINK +done + +ffpad() { # Pads $1 to size $3 into $2 with 0xFF bytes + dd if=/dev/zero count=1 ibs=$3 2>/dev/null | tr '\000' '\377' > $2 + dd if="$1" of=$2 conv=notrunc 2>/dev/null +} +for i in smart/*.asm; do + $RGBASM -o $otemp $i + $RGBLINK -p 0xff -o $gbtemp $otemp + SIZE=$(wc -c $gbtemp | cut -d ' ' -f 1) + ffpad "${i%.asm}.bin" $gbtemp2 $SIZE + tryCmp $gbtemp2 $gbtemp "${i%.asm}.bin" + rc=$(($? || $rc)) + $RGBLINK -p 0xff -vs "root" -o $gbtemp $otemp + ffpad "${i%.asm}.smart.bin" $gbtemp2 $SIZE + tryCmp $gbtemp2 $gbtemp "${i%.asm}.smart.bin" + rc=$(($? || $rc)) done # These tests do their own thing @@ -77,38 +99,38 @@ $RGBASM -o $otemp high-low/a.asm $RGBLINK -o $gbtemp $otemp $RGBASM -o $otemp high-low/b.asm $RGBLINK -o $gbtemp2 $otemp -i="high-low.asm" tryCmp $gbtemp $gbtemp2 +tryCmp $gbtemp $gbtemp2 "high-low/<...>.asm" rc=$(($? || $rc)) $RGBASM -o $otemp bank-const/a.asm $RGBASM -o $gbtemp2 bank-const/b.asm $RGBLINK -o $gbtemp $gbtemp2 $otemp > $outtemp 2>&1 -i="bank-const.asm" tryDiff bank-const/err.out $outtemp +tryDiff bank-const/err.out $outtemp "bank-const/<...>.asm" rc=$(($? || $rc)) $RGBASM -o $otemp section-union/good/a.asm $RGBASM -o $gbtemp2 section-union/good/b.asm $RGBLINK -o $gbtemp -l section-union/good/script.link $otemp $gbtemp2 dd if=$gbtemp count=1 bs=$(printf %s $(wc -c < section-union/good/ref.out.bin)) > $otemp 2>/dev/null -i="section-union/good.asm" tryCmp section-union/good/ref.out.bin $otemp +tryCmp section-union/good/ref.out.bin $otemp "section-union/good/<...>.asm" rc=$(($? || $rc)) $RGBASM -o $otemp section-union/fragments/a.asm $RGBASM -o $gbtemp2 section-union/fragments/b.asm $RGBLINK -o $gbtemp $otemp $gbtemp2 dd if=$gbtemp count=1 bs=$(printf %s $(wc -c < section-union/fragments/ref.out.bin)) > $otemp 2>/dev/null -i="section-union/fragments.asm" tryCmp section-union/fragments/ref.out.bin $otemp +tryCmp section-union/fragments/ref.out.bin $otemp "section-union/fragments/<...>.asm" rc=$(($? || $rc)) for i in section-union/*.asm; do $RGBASM -o $otemp $i $RGBASM -o $gbtemp2 $i -DSECOND if $RGBLINK $otemp $gbtemp2 > $outtemp 2>&1; then - echo -e "${bold}${red}$i didn't fail to link!${rescolors}${resbold}" + echo "${bold}${red}$i didn't fail to link!${rescolors}${resbold}" rc=1 fi echo --- >> $outtemp # Ensure RGBASM also errors out echo 'SECOND equs "1"' | cat $i - $i | $RGBASM - 2>> $outtemp - tryDiff ${i%.asm}.out $outtemp + tryDiff ${i%.asm}.out $outtemp ${i%.asm}.out rc=$(($? || $rc)) done