diff --git a/Makefile b/Makefile index 1f49a5c5..0188e92b 100644 --- a/Makefile +++ b/Makefile @@ -32,10 +32,11 @@ VERSION_STRING := `git describe --tags --dirty --always 2>/dev/null` WARNFLAGS := -Wall # Overridable CFLAGS -CFLAGS := -g +CFLAGS := -g -O0 # Non-overridable CFLAGS -REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=c99 -D_POSIX_C_SOURCE=200809L \ - -Iinclude -DBUILD_VERSION_STRING=\"${VERSION_STRING}\" +REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=c11 -D_POSIX_C_SOURCE=200809L \ + -D_DEFAULT_SOURCE -Iinclude \ + -DBUILD_VERSION_STRING=\"${VERSION_STRING}\" # Overridable LDFLAGS LDFLAGS := # Non-overridable LDFLAGS @@ -73,21 +74,17 @@ src/asm/globlex.o src/asm/lexer.o src/asm/constexpr.o: src/asm/asmy.h rgblink_obj := \ src/link/assign.o \ - src/link/lexer.o \ - src/link/library.o \ src/link/main.o \ - src/link/mapfile.o \ src/link/object.o \ src/link/output.o \ src/link/patch.o \ - src/link/parser.o \ src/link/script.o \ + src/link/section.o \ src/link/symbol.o \ src/extern/err.o \ + src/hashmap.o \ src/version.o -src/link/lexer.o: src/link/parser.h - rgbfix_obj := \ src/fix/main.o \ src/extern/err.o \ diff --git a/include/common.h b/include/common.h index ae5955bc..a7133f01 100644 --- a/include/common.h +++ b/include/common.h @@ -9,7 +9,8 @@ #ifndef RGBDS_COMMON_H #define RGBDS_COMMON_H -#define RGBDS_OBJECT_VERSION_STRING "RGB6" +#define RGBDS_OBJECT_VERSION_STRING "RGB%1hhu" +#define RGBDS_OBJECT_VERSION_NUMBER (uint8_t)6 enum eBankCount { BANK_COUNT_ROM0 = 1, diff --git a/include/hashmap.h b/include/hashmap.h new file mode 100644 index 00000000..62ac4a32 --- /dev/null +++ b/include/hashmap.h @@ -0,0 +1,68 @@ +/* + * This file is part of RGBDS. + * + * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors. + * + * SPDX-License-Identifier: MIT + */ + +/* Generic hashmap implementation (C++ templates are calling...) */ +#ifndef RGBDS_LINK_HASHMAP_H +#define RGBDS_LINK_HASHMAP_H + +#include + +#define HASH_NB_BITS 32 +#define HALF_HASH_NB_BITS 16 +_Static_assert(HALF_HASH_NB_BITS * 2 == HASH_NB_BITS, ""); +#define HASHMAP_NB_BUCKETS (1 << HALF_HASH_NB_BITS) + +/* HashMapEntry is internal, please do not attempt to use it */ +typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS]; + +/** + * Adds an element to a hashmap. + * @warning Adding a new element with an already-present key will not cause an + * error, this must be handled externally. + * @warning Inserting a NULL will make `hash_GetElement`'s return ambiguous! + * @param map The HashMap to add the element to + * @param key The key with which the element will be stored and retrieved + * @param element The element to add + * @return True if a collision occurred (for statistics) + */ +bool hash_AddElement(HashMap 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 + */ +bool hash_RemoveElement(HashMap map, char const *key); + +/** + * Finds an element in a hashmap. + * @param map The map to consider the elements of + * @param key The key to search an element for + * @return A pointer to the element, or NULL if not found. (NULL can be returned + * if such an element was added, but that sounds pretty silly.) + */ +void *hash_GetElement(HashMap const map, char const *key); + +/** + * Executes a function on each element in a hashmap. + * @param map The map to consider the elements of + * @param func The function to run. The first argument will be the element, + * the second will be `arg`. + * @param arg An argument to be passed to all function calls + */ +void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg); + +/** + * Cleanly empties a hashmap from its contents. + * This does not `free` the data structure itself! + * @param map The map to empty + */ +void hash_EmptyMap(HashMap map); + +#endif /* RGBDS_LINK_HASHMAP_H */ diff --git a/include/helpers.h b/include/helpers.h index 3bf43aa0..567b67d3 100644 --- a/include/helpers.h +++ b/include/helpers.h @@ -13,10 +13,12 @@ /* GCC or compatible */ #define noreturn_ __attribute__ ((noreturn)) #define unused_ __attribute__ ((unused)) + #define trap_ __builtin_trap() #else /* Unsupported, but no need to throw a fit */ #define noreturn_ #define unused_ + #define trap_ #endif #endif /* HELPERS_H */ diff --git a/include/link/assign.h b/include/link/assign.h index 7448ed6d..6cd24a7a 100644 --- a/include/link/assign.h +++ b/include/link/assign.h @@ -1,54 +1,29 @@ /* * This file is part of RGBDS. * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. + * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors. * * SPDX-License-Identifier: MIT */ +/* Assigning all sections a place */ #ifndef RGBDS_LINK_ASSIGN_H #define RGBDS_LINK_ASSIGN_H #include #include "common.h" -#include "mylink.h" -/* Bank numbers as seen by the linker */ -enum eBankDefine { - BANK_INDEX_ROM0 = 0, - BANK_INDEX_ROMX = BANK_INDEX_ROM0 + BANK_COUNT_ROM0, - BANK_INDEX_WRAM0 = BANK_INDEX_ROMX + BANK_COUNT_ROMX, - BANK_INDEX_WRAMX = BANK_INDEX_WRAM0 + BANK_COUNT_WRAM0, - BANK_INDEX_VRAM = BANK_INDEX_WRAMX + BANK_COUNT_WRAMX, - BANK_INDEX_OAM = BANK_INDEX_VRAM + BANK_COUNT_VRAM, - BANK_INDEX_HRAM = BANK_INDEX_OAM + BANK_COUNT_OAM, - BANK_INDEX_SRAM = BANK_INDEX_HRAM + BANK_COUNT_HRAM, - BANK_INDEX_MAX = BANK_INDEX_SRAM + BANK_COUNT_SRAM -}; +extern uint64_t nbSectionsToAssign; -extern int32_t MaxBankUsed; -extern int32_t MaxAvail[BANK_INDEX_MAX]; +/** + * Assigns all sections a slice of the address space + */ +void assign_AssignSections(void); -int32_t area_Avail(int32_t bank); -void AssignSections(void); -void CreateSymbolTable(void); -struct sSection *GetSectionByName(const char *name); -int32_t IsSectionNameInUse(const char *name); -void SetLinkerscriptName(char *tzLinkerscriptFile); -int32_t IsSectionSameTypeBankAndAttrs(const char *name, - enum eSectionType type, int32_t bank, - int32_t org, int32_t align); -uint32_t AssignSectionAddressAndBankByName(const char *name, uint32_t address, - int32_t bank); - -int BankIndexIsROM0(int32_t bank); -int BankIndexIsROMX(int32_t bank); -int BankIndexIsWRAM0(int32_t bank); -int BankIndexIsWRAMX(int32_t bank); -int BankIndexIsVRAM(int32_t bank); -int BankIndexIsOAM(int32_t bank); -int BankIndexIsHRAM(int32_t bank); -int BankIndexIsSRAM(int32_t bank); +/** + * `free`s all assignment memory that was allocated. + */ +void assign_Cleanup(void); #endif /* RGBDS_LINK_ASSIGN_H */ diff --git a/include/link/library.h b/include/link/library.h deleted file mode 100644 index 90a91393..00000000 --- a/include/link/library.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * This file is part of RGBDS. - * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. - * - * SPDX-License-Identifier: MIT - */ - -#ifndef RGBDS_LINK_LIBRARY_H -#define RGBDS_LINK_LIBRARY_H - -void AddNeededModules(void); - -#endif /* RGBDS_LINK_LIBRARY_H */ diff --git a/include/link/main.h b/include/link/main.h index a5c4866d..e6ad30ab 100644 --- a/include/link/main.h +++ b/include/link/main.h @@ -1,17 +1,35 @@ /* * This file is part of RGBDS. * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. + * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors. * * SPDX-License-Identifier: MIT */ +/* Declarations that all modules use, as well as `main` and related */ #ifndef RGBDS_LINK_MAIN_H #define RGBDS_LINK_MAIN_H #include +#include +#include -extern int32_t fillchar; -extern char *smartlinkstartsymbol; +/* Variables related to CLI options */ +extern bool isDmgMode; +extern FILE *linkerScript; +extern FILE *mapFile; +extern FILE *symFile; +extern FILE *overlayFile; +extern FILE *outputFile; +extern uint8_t padValue; +extern bool is32kMode; +extern bool beVerbose; +extern bool isWRA0Mode; + +/* Helper macro for printing verbose-mode messages */ +#define verbosePrint(...) do { \ + if (beVerbose) \ + fprintf(stderr, __VA_ARGS__); \ + } while (0) #endif /* RGBDS_LINK_MAIN_H */ diff --git a/include/link/mapfile.h b/include/link/mapfile.h deleted file mode 100644 index 7f8a2ce9..00000000 --- a/include/link/mapfile.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * This file is part of RGBDS. - * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. - * - * SPDX-License-Identifier: MIT - */ - -#ifndef RGBDS_LINK_MAPFILE_H -#define RGBDS_LINK_MAPFILE_H - -#include - -void SetMapfileName(char *name); -void SetSymfileName(char *name); -void CloseMapfile(void); -void MapfileWriteSection(const struct sSection *pSect); -void MapfileInitBank(int32_t bank); -void MapfileCloseBank(int32_t slack); - -#endif /* RGBDS_LINK_MAPFILE_H */ diff --git a/include/link/mylink.h b/include/link/mylink.h deleted file mode 100644 index 8dda9da6..00000000 --- a/include/link/mylink.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of RGBDS. - * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. - * - * SPDX-License-Identifier: MIT - */ - -#ifndef RGBDS_LINK_LINK_H -#define RGBDS_LINK_LINK_H - -#include - -#include "linkdefs.h" - -extern int32_t options; - -#define OPT_TINY 0x01 -#define OPT_SMART_C_LINK 0x02 -#define OPT_OVERLAY 0x04 -#define OPT_CONTWRAM 0x08 -#define OPT_DMG_MODE 0x10 - -struct sSection { - int32_t nBank; - int32_t nOrg; - int32_t nAlign; - uint8_t oAssigned; - - char *pzName; - int32_t nByteSize; - enum eSectionType Type; - uint8_t *pData; - int32_t nNumberOfSymbols; - struct sSymbol **tSymbols; - struct sPatch *pPatches; - struct sSection *pNext; -}; - -struct sSymbol { - char *pzName; - enum eSymbolType Type; - - /* The following 3 items only valid when Type!=SYM_IMPORT */ - int32_t nSectionID; /* Internal to object.c */ - struct sSection *pSection; - int32_t nOffset; - - char *pzObjFileName; /* Object file where the symbol is located. */ - char *pzFileName; /* Source file where the symbol was defined. */ - uint32_t nFileLine; /* Line where the symbol was defined. */ -}; - -struct sPatch { - char *pzFilename; - int32_t nLineNo; - int32_t nOffset; - enum ePatchType Type; - int32_t nRPNSize; - uint8_t *pRPN; - struct sPatch *pNext; - uint8_t oRelocPatch; -}; - -extern struct sSection *pSections; -extern struct sSection *pLibSections; - -#endif /* RGBDS_LINK_LINK_H */ diff --git a/include/link/object.h b/include/link/object.h index 69a86099..b8231e02 100644 --- a/include/link/object.h +++ b/include/link/object.h @@ -1,14 +1,30 @@ /* * This file is part of RGBDS. * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. + * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors. * * SPDX-License-Identifier: MIT */ +/* Declarations related to processing of object (.o) files */ + #ifndef RGBDS_LINK_OBJECT_H #define RGBDS_LINK_OBJECT_H -void obj_Readfile(char *tzObjectfile); +/** + * Read an object (.o) file, and add its info to the data structures. + * @param fileName A path to the object file to be read + */ +void obj_ReadFile(char const *fileName); + +/** + * Perform validation on the object files' contents + */ +void obj_DoSanityChecks(void); + +/** + * `free`s all object memory that was allocated. + */ +void obj_Cleanup(void); #endif /* RGBDS_LINK_OBJECT_H */ diff --git a/include/link/output.h b/include/link/output.h index 9418f2c3..7c43d2c3 100644 --- a/include/link/output.h +++ b/include/link/output.h @@ -1,16 +1,21 @@ /* * This file is part of RGBDS. * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. + * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors. * * SPDX-License-Identifier: MIT */ +/* Outputting the result of linking */ #ifndef RGBDS_LINK_OUTPUT_H #define RGBDS_LINK_OUTPUT_H -void out_Setname(char *tzOutputfile); -void out_SetOverlayname(char *tzOverlayfile); -void Output(void); +#include + +#include "link/section.h" + +void out_AddSection(struct Section const *section); + +void out_WriteFiles(void); #endif /* RGBDS_LINK_OUTPUT_H */ diff --git a/include/link/patch.h b/include/link/patch.h index 9ce1e144..1caaff73 100644 --- a/include/link/patch.h +++ b/include/link/patch.h @@ -1,17 +1,18 @@ /* * This file is part of RGBDS. * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. + * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors. * * SPDX-License-Identifier: MIT */ +/* Applying patches to SECTIONs */ #ifndef RGBDS_LINK_PATCH_H #define RGBDS_LINK_PATCH_H -#include - -void Patch(void); -extern int32_t nPC; +/** + * Applies all SECTIONs' patches to them + */ +void patch_ApplyPatches(void); #endif /* RGBDS_LINK_PATCH_H */ diff --git a/include/link/script.h b/include/link/script.h index 7a8663e0..412ccee8 100644 --- a/include/link/script.h +++ b/include/link/script.h @@ -1,30 +1,34 @@ /* * This file is part of RGBDS. * - * Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors. + * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors. * * SPDX-License-Identifier: MIT */ +/* Parsing a linker script */ #ifndef RGBDS_LINK_SCRIPT_H #define RGBDS_LINK_SCRIPT_H #include -#include "helpers.h" +struct SectionPlacement { + struct Section *section; + uint16_t org; + uint32_t bank; +}; -noreturn_ void script_fatalerror(const char *fmt, ...); +extern uint64_t script_lineNo; -void script_Parse(const char *path); +/** + * Parses the linker script to return the next section constraint + * @return A pointer to a struct, or NULL on EOF. The pointer shouldn't be freed + */ +struct SectionPlacement *script_NextSection(void); -void script_IncludeFile(const char *path); -int32_t script_IncludeDepthGet(void); -void script_IncludePop(void); - -void script_InitSections(void); -void script_SetCurrentSectionType(const char *type, uint32_t bank); -void script_SetAddress(uint32_t addr); -void script_SetAlignment(uint32_t alignment); -void script_OutputSection(const char *section_name); +/** + * `free`s all assignment memory that was allocated. + */ +void script_Cleanup(void); #endif /* RGBDS_LINK_SCRIPT_H */ diff --git a/include/link/section.h b/include/link/section.h new file mode 100644 index 00000000..64b91278 --- /dev/null +++ b/include/link/section.h @@ -0,0 +1,112 @@ +/* + * This file is part of RGBDS. + * + * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors. + * + * SPDX-License-Identifier: MIT + */ + +/* Declarations manipulating symbols */ +#ifndef RGBDS_LINK_SECTION_H +#define RGBDS_LINK_SECTION_H + +/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */ + +#include +#include + +#include "link/main.h" + +#include "linkdefs.h" + +struct AttachedSymbol { + struct Symbol *symbol; + struct AttachedSymbol *next; +}; + +struct Patch { + char *fileName; + int32_t lineNo; + int32_t offset; + enum PatchType type; + int32_t rpnSize; + uint8_t *rpnExpression; +}; + +struct Section { + /* Info contained in the object files */ + char *name; + uint16_t size; + enum SectionType type; + bool isAddressFixed; + uint16_t org; + bool isBankFixed; + uint32_t bank; + bool isAlignFixed; + uint16_t alignMask; + uint8_t *data; /* Array of size `size`*/ + uint32_t nbPatches; + struct Patch *patches; + /* Extra info computed during linking */ + struct Symbol **fileSymbols; + uint32_t nbSymbols; + struct Symbol const **symbols; +}; + +extern uint16_t startaddr[]; +extern uint16_t maxsize[]; +extern uint32_t bankranges[][2]; +extern char const * const typeNames[SECTTYPE_INVALID]; + +/** + * Computes a memory region's end address (last byte), eg. 0x7FFF + * @return The address of the last byte in that memory region + */ +static inline uint16_t endaddr(enum SectionType type) +{ + return startaddr[type] + maxsize[type] - 1; +} + +/** + * Computes a memory region's number of banks + * @return The number of banks, 1 for regions without banking + */ +static inline uint32_t nbbanks(enum SectionType type) +{ + return bankranges[type][1] - bankranges[type][0] + 1; +} + +/* + * Execute a callback for each section currently registered. + * This is to avoid exposing the data structure in which sections are stored. + * @param callback The function to call for each structure; + * the first argument will be a pointer to the structure, + * the second argument will be the pointer `arg`. + * @param arg A pointer which will be passed to all calls to `callback`. + */ +void sect_ForEach(void (*callback)(struct Section *, void *), void *arg); + +/** + * Registers a section to be processed. + * @param section The section to register. + */ +void sect_AddSection(struct Section *section); + +/** + * Finds a section by its name. + * @param name The name of the section to look for + * @return A pointer to the section, or NULL if it wasn't found + */ +struct Section *sect_GetSection(char const *name); + +/** + * `free`s all section memory that was allocated. + */ +void sect_CleanupSections(void); + +/** + * Checks if all sections meet reasonable criteria, such as max size + */ +void sect_DoSanityChecks(void); + +#endif /* RGBDS_LINK_SECTION_H */ diff --git a/include/link/symbol.h b/include/link/symbol.h index 84cfa908..3733cdf6 100644 --- a/include/link/symbol.h +++ b/include/link/symbol.h @@ -1,21 +1,59 @@ /* * This file is part of RGBDS. * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. + * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors. * * SPDX-License-Identifier: MIT */ +/* Declarations manipulating symbols */ #ifndef RGBDS_LINK_SYMBOL_H #define RGBDS_LINK_SYMBOL_H +/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */ + #include -void sym_Init(void); -void sym_CreateSymbol(char *tzName, int32_t nValue, int32_t nBank, - char *tzObjFileName, char *tzFileName, - uint32_t nFileLine); -int32_t sym_GetValue(struct sPatch *pPatch, char *tzName); -int32_t sym_GetBank(struct sPatch *pPatch, char *tzName); +#include "linkdefs.h" + +struct Symbol { + /* Info contained in the object files */ + char *name; + enum SymbolType type; + char const *objFileName; + char *fileName; + int32_t lineNo; + int32_t sectionID; + union { + int32_t offset; + int32_t value; + }; + /* Extra info computed during linking */ + struct Section *section; +}; + +/* + * Execute a callback for each symbol currently registered. + * This is done to avoid exposing the data structure in which symbol are stored. + * @param callback The function to call for each symbol; + * the first argument will be a pointer to the symbol, + * the second argument will be the pointer `arg`. + * @param arg A pointer which will be passed to all calls to `callback`. + */ +void sym_ForEach(void (*callback)(struct Symbol *, void *), void *arg); + +void sym_AddSymbol(struct Symbol *symbol); + +/** + * Finds a symbol in all the defined symbols. + * @param name The name of the symbol to look for + * @return A pointer to the symbol, or NULL if not found. + */ +struct Symbol *sym_GetSymbol(char const *name); + +/** + * `free`s all symbol memory that was allocated. + */ +void sym_CleanupSymbols(void); #endif /* RGBDS_LINK_SYMBOL_H */ diff --git a/include/linkdefs.h b/include/linkdefs.h index b0ec4ee9..74665805 100644 --- a/include/linkdefs.h +++ b/include/linkdefs.h @@ -9,7 +9,7 @@ #ifndef RGBDS_LINKDEFS_H #define RGBDS_LINKDEFS_H -enum eRpnData { +enum RPNCommand { RPN_ADD = 0x00, RPN_SUB = 0x01, RPN_MUL = 0x02, @@ -46,28 +46,43 @@ enum eRpnData { RPN_SYM = 0x81 }; -enum eSectionType { - SECT_WRAM0 = 0x00, - SECT_VRAM = 0x01, - SECT_ROMX = 0x02, - SECT_ROM0 = 0x03, - SECT_HRAM = 0x04, - SECT_WRAMX = 0x05, - SECT_SRAM = 0x06, - SECT_OAM = 0x07 +enum SectionType { + SECTTYPE_WRAM0, + SECTTYPE_VRAM, + SECTTYPE_ROMX, + SECTTYPE_ROM0, + SECTTYPE_HRAM, + SECTTYPE_WRAMX, + SECTTYPE_SRAM, + SECTTYPE_OAM, + + SECTTYPE_INVALID }; -enum eSymbolType { - SYM_LOCAL = 0x00, - SYM_IMPORT = 0x01, - SYM_EXPORT = 0x02 +enum SymbolType { + SYMTYPE_LOCAL, + SYMTYPE_IMPORT, + SYMTYPE_EXPORT }; -enum ePatchType { - PATCH_BYTE = 0x00, - PATCH_WORD_L = 0x01, - PATCH_LONG_L = 0x02, - PATCH_BYTE_JR = 0x03 +enum PatchType { + PATCHTYPE_BYTE, + PATCHTYPE_WORD, + PATCHTYPE_LONG, + PATCHTYPE_JR, + + PATCHTYPE_INVALID }; +/** + * Tells whether a section has data in its object file definition, + * depending on type. + * @param type The section's type + * @return `true` if the section's definition includes data + */ +static inline bool sect_HasData(enum SectionType type) +{ + return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX; +} + #endif /* RGBDS_LINKDEFS_H */ diff --git a/src/asm/asmy.y b/src/asm/asmy.y index ff8d0fcf..44e449fb 100644 --- a/src/asm/asmy.y +++ b/src/asm/asmy.y @@ -45,22 +45,22 @@ static void bankrangecheck(char *name, uint32_t secttype, int32_t org, char *stype = NULL; switch (secttype) { - case SECT_ROMX: + case SECTTYPE_ROMX: stype = "ROMX"; minbank = BANK_MIN_ROMX; maxbank = BANK_MAX_ROMX; break; - case SECT_SRAM: + case SECTTYPE_SRAM: stype = "SRAM"; minbank = BANK_MIN_SRAM; maxbank = BANK_MAX_SRAM; break; - case SECT_WRAMX: + case SECTTYPE_WRAMX: stype = "WRAMX"; minbank = BANK_MIN_WRAMX; maxbank = BANK_MAX_WRAMX; break; - case SECT_VRAM: + case SECTTYPE_VRAM: stype = "VRAM"; minbank = BANK_MIN_VRAM; maxbank = BANK_MAX_VRAM; @@ -1523,33 +1523,33 @@ section : T_POP_SECTION string comma sectiontype } ; -sectiontype : T_SECT_WRAM0 { $$ = SECT_WRAM0; } - | T_SECT_VRAM { $$ = SECT_VRAM; } - | T_SECT_ROMX { $$ = SECT_ROMX; } - | T_SECT_ROM0 { $$ = SECT_ROM0; } - | T_SECT_HRAM { $$ = SECT_HRAM; } - | T_SECT_WRAMX { $$ = SECT_WRAMX; } - | T_SECT_SRAM { $$ = SECT_SRAM; } - | T_SECT_OAM { $$ = SECT_OAM; } +sectiontype : T_SECT_WRAM0 { $$ = SECTTYPE_WRAM0; } + | T_SECT_VRAM { $$ = SECTTYPE_VRAM; } + | T_SECT_ROMX { $$ = SECTTYPE_ROMX; } + | T_SECT_ROM0 { $$ = SECTTYPE_ROM0; } + | T_SECT_HRAM { $$ = SECTTYPE_HRAM; } + | T_SECT_WRAMX { $$ = SECTTYPE_WRAMX; } + | T_SECT_SRAM { $$ = SECTTYPE_SRAM; } + | T_SECT_OAM { $$ = SECTTYPE_OAM; } | T_SECT_HOME { warning("HOME section name is deprecated, use ROM0 instead."); - $$ = SECT_ROM0; + $$ = SECTTYPE_ROM0; } | T_SECT_DATA { warning("DATA section name is deprecated, use ROMX instead."); - $$ = SECT_ROMX; + $$ = SECTTYPE_ROMX; } | T_SECT_CODE { warning("CODE section name is deprecated, use ROMX instead."); - $$ = SECT_ROMX; + $$ = SECTTYPE_ROMX; } | T_SECT_BSS { warning("BSS section name is deprecated, use WRAM0 instead."); - $$ = SECT_WRAM0; + $$ = SECTTYPE_WRAM0; } ; diff --git a/src/asm/output.c b/src/asm/output.c index 60f8d57d..27ed3c51 100644 --- a/src/asm/output.c +++ b/src/asm/output.c @@ -96,21 +96,21 @@ void out_PopSection(void) static uint32_t getmaxsectionsize(uint32_t secttype, char *sectname) { switch (secttype) { - case SECT_ROM0: + case SECTTYPE_ROM0: return 0x8000; /* If ROMX sections not used */ - case SECT_ROMX: + case SECTTYPE_ROMX: return 0x4000; - case SECT_VRAM: + case SECTTYPE_VRAM: return 0x2000; - case SECT_SRAM: + case SECTTYPE_SRAM: return 0x2000; - case SECT_WRAM0: + case SECTTYPE_WRAM0: return 0x2000; /* If WRAMX sections not used */ - case SECT_WRAMX: + case SECTTYPE_WRAMX: return 0x1000; - case SECT_OAM: + case SECTTYPE_OAM: return 0xA0; - case SECT_HRAM: + case SECTTYPE_HRAM: return 0x7F; default: break; @@ -241,7 +241,7 @@ static void writesection(struct Section *pSect, FILE *f) fputlong(pSect->nBank, f); fputlong(pSect->nAlign, f); - if ((pSect->nType == SECT_ROM0) || (pSect->nType == SECT_ROMX)) { + if (sect_HasData(pSect->nType)) { struct Patch *pPatch; fwrite(pSect->tData, 1, pSect->nPC, f); @@ -265,23 +265,23 @@ static void writesymbol(struct sSymbol *pSym, FILE *f) int32_t sectid; if (!(pSym->nType & SYMF_DEFINED)) { - type = SYM_IMPORT; + type = SYMTYPE_IMPORT; } else if (pSym->nType & SYMF_EXPORT) { - type = SYM_EXPORT; + type = SYMTYPE_EXPORT; } else { - type = SYM_LOCAL; + type = SYMTYPE_LOCAL; } switch (type) { - case SYM_LOCAL: + case SYMTYPE_LOCAL: offset = pSym->nValue; sectid = getsectid(pSym->pSection); break; - case SYM_IMPORT: + case SYMTYPE_IMPORT: offset = 0; sectid = -1; break; - case SYM_EXPORT: + case SYMTYPE_EXPORT: offset = pSym->nValue; if (pSym->nType & SYMF_CONST) sectid = -1; @@ -293,7 +293,7 @@ static void writesymbol(struct sSymbol *pSym, FILE *f) fputstring(pSym->tzName, f); fputc(type, f); - if (type != SYM_IMPORT) { + if (type != SYMTYPE_IMPORT) { fputstring(pSym->tzFileName, f); fputlong(pSym->nFileLine, f); @@ -496,8 +496,7 @@ static void checksection(void) static void checkcodesection(void) { checksection(); - if (pCurrentSection->nType != SECT_ROM0 && - pCurrentSection->nType != SECT_ROMX) { + if (!sect_HasData(pCurrentSection->nType)) { fatalerror("Section '%s' cannot contain code or data (not ROM0 or ROMX)", pCurrentSection->pzName); } else if (nUnionDepth > 0) { @@ -570,8 +569,7 @@ void out_WriteObject(void) if (f == NULL) fatalerror("Couldn't write file '%s'\n", tzObjectname); - fwrite(RGBDS_OBJECT_VERSION_STRING, 1, - strlen(RGBDS_OBJECT_VERSION_STRING), f); + fprintf(f, RGBDS_OBJECT_VERSION_STRING, RGBDS_OBJECT_VERSION_NUMBER); fputlong(countsymbols(), f); fputlong(countsections(), f); @@ -647,7 +645,7 @@ struct Section *out_FindSection(char *pzName, uint32_t secttype, int32_t org, pSect->charmap = NULL; /* It is only needed to allocate memory for ROM sections. */ - if (secttype == SECT_ROM0 || secttype == SECT_ROMX) { + if (sect_HasData(secttype)) { uint32_t sectsize; sectsize = getmaxsectionsize(secttype, pzName); @@ -743,8 +741,7 @@ void out_Skip(int32_t skip) { checksection(); checksectionoverflow(skip); - if (!((pCurrentSection->nType == SECT_ROM0) - || (pCurrentSection->nType == SECT_ROMX))) { + if (!sect_HasData(pCurrentSection->nType)) { pCurrentSection->nPC += skip; nPC += skip; pPCSymbol->nValue += skip; @@ -779,7 +776,7 @@ void out_RelByte(struct Expression *expr) checksectionoverflow(1); if (rpn_isReloc(expr)) { pCurrentSection->tData[nPC] = 0; - createpatch(PATCH_BYTE, expr); + createpatch(PATCHTYPE_BYTE, expr); pCurrentSection->nPC += 1; nPC += 1; pPCSymbol->nValue += 1; @@ -815,7 +812,7 @@ void out_RelWord(struct Expression *expr) if (rpn_isReloc(expr)) { pCurrentSection->tData[nPC] = 0; pCurrentSection->tData[nPC + 1] = 0; - createpatch(PATCH_WORD_L, expr); + createpatch(PATCHTYPE_WORD, expr); pCurrentSection->nPC += 2; nPC += 2; pPCSymbol->nValue += 2; @@ -854,7 +851,7 @@ void out_RelLong(struct Expression *expr) pCurrentSection->tData[nPC + 1] = 0; pCurrentSection->tData[nPC + 2] = 0; pCurrentSection->tData[nPC + 3] = 0; - createpatch(PATCH_LONG_L, expr); + createpatch(PATCHTYPE_LONG, expr); pCurrentSection->nPC += 4; nPC += 4; pPCSymbol->nValue += 4; @@ -875,7 +872,7 @@ void out_PCRelByte(struct Expression *expr) /* Always let the linker calculate the offset. */ pCurrentSection->tData[nPC] = 0; - createpatch(PATCH_BYTE_JR, expr); + createpatch(PATCHTYPE_JR, expr); pCurrentSection->nPC += 1; nPC += 1; pPCSymbol->nValue += 1; diff --git a/src/hashmap.c b/src/hashmap.c new file mode 100644 index 00000000..4ad2c129 --- /dev/null +++ b/src/hashmap.c @@ -0,0 +1,118 @@ + +#include +#include +#include +#include +#include + +#include "hashmap.h" +#include "extern/err.h" + +/* + * The lower half of the hash is used to index the "master" table, + * the upper half is used to help resolve collisions more quickly + */ +#define UINT_BITS_(NB_BITS) uint##NB_BITS##_t +#define UINT_BITS(NB_BITS) UINT_BITS_(NB_BITS) +typedef UINT_BITS(HASH_NB_BITS) HashType; +typedef UINT_BITS(HALF_HASH_NB_BITS) HalfHashType; + +struct HashMapEntry { + HalfHashType hash; + char const *key; + void *content; + struct HashMapEntry *next; +}; + +#define FNV_OFFSET_BASIS 0x811c9dc5 +#define FNV_PRIME 16777619 + +/* FNV-1a hash */ +static HashType hash(char const *str) +{ + HashType hash = FNV_OFFSET_BASIS; + + while (*str) { + hash ^= (uint8_t)*str++; + hash *= FNV_PRIME; + } + return hash; +} + +bool hash_AddElement(HashMap map, char const *key, void *element) +{ + HashType hashedKey = hash(key); + HalfHashType index = hashedKey; + struct HashMapEntry *newEntry = malloc(sizeof(*newEntry)); + + if (!newEntry) + err(1, "%s: Failed to allocate new entry", __func__); + + newEntry->hash = hashedKey >> HALF_HASH_NB_BITS; + newEntry->key = key; + newEntry->content = element; + newEntry->next = map[index]; + map[index] = newEntry; + + return newEntry->next != NULL; +} + +bool hash_DeleteElement(HashMap map, char const *key) +{ + HashType hashedKey = hash(key); + struct HashMapEntry **ptr = &map[(HalfHashType)hashedKey]; + + while (*ptr) { + if (hashedKey >> HALF_HASH_NB_BITS == (*ptr)->hash + && !strcmp((*ptr)->key, key)) { + struct HashMapEntry *next = (*ptr)->next; + + free(*ptr); + *ptr = next; + return true; + } + } + return false; +} + +void *hash_GetElement(HashMap const map, char const *key) +{ + HashType hashedKey = hash(key); + struct HashMapEntry *ptr = map[(HalfHashType)hashedKey]; + + while (ptr) { + if (hashedKey >> HALF_HASH_NB_BITS == ptr->hash + && !strcmp(ptr->key, key)) { + return ptr->content; + } + ptr = ptr->next; + } + return NULL; +} + +void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg) +{ + for (size_t i = 0; i < HASHMAP_NB_BUCKETS; i++) { + struct HashMapEntry *ptr = map[i]; + + while (ptr) { + func(ptr->content, arg); + ptr = ptr->next; + } + } +} + +void hash_EmptyMap(HashMap map) +{ + for (size_t i = 0; i < HASHMAP_NB_BUCKETS; i++) { + struct HashMapEntry *ptr = map[i]; + + while (ptr) { + struct HashMapEntry *next = ptr->next; + + free(ptr); + ptr = next; + } + map[i] = NULL; + } +} diff --git a/src/link/assign.c b/src/link/assign.c index 5c31ebbf..5fa70b35 100644 --- a/src/link/assign.c +++ b/src/link/assign.c @@ -1,723 +1,401 @@ -/* - * This file is part of RGBDS. - * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. - * - * SPDX-License-Identifier: MIT - */ -#include -#include -#include #include -#include - -#include "extern/err.h" +#include #include "link/assign.h" -#include "link/mylink.h" +#include "link/section.h" +#include "link/symbol.h" +#include "link/object.h" #include "link/main.h" #include "link/script.h" -#include "link/symbol.h" +#include "link/output.h" -struct sFreeArea { - int32_t nOrg; - int32_t nSize; - struct sFreeArea *pPrev, *pNext; +#include "extern/err.h" +#include "helpers.h" + +struct MemoryLocation { + uint16_t address; + uint32_t bank; }; -struct sSectionAttributes { - const char *name; - /* bank + offset = bank originally stored in a section struct */ - int32_t bank; - int32_t offset; - int32_t minBank; - int32_t bankCount; +struct FreeSpace { + uint16_t address; + uint16_t size; + struct FreeSpace *next, *prev; }; -struct sFreeArea *BankFree[BANK_INDEX_MAX]; -int32_t MaxAvail[BANK_INDEX_MAX]; -int32_t MaxBankUsed; -int32_t MaxWBankUsed; -int32_t MaxSBankUsed; -int32_t MaxVBankUsed; +/* Table of free space for each bank */ +struct FreeSpace *memory[SECTTYPE_INVALID]; -const enum eSectionType SECT_MIN = SECT_WRAM0; -const enum eSectionType SECT_MAX = SECT_OAM; -const struct sSectionAttributes SECT_ATTRIBUTES[] = { - {"WRAM0", BANK_INDEX_WRAM0, 0, 0, BANK_COUNT_WRAM0}, - {"VRAM", BANK_INDEX_VRAM, 0, 0, BANK_COUNT_VRAM}, - {"ROMX", BANK_INDEX_ROMX, -BANK_MIN_ROMX, BANK_MIN_ROMX, BANK_COUNT_ROMX}, - {"ROM0", BANK_INDEX_ROM0, 0, 0, BANK_COUNT_ROM0}, - {"HRAM", BANK_INDEX_HRAM, 0, 0, BANK_COUNT_HRAM}, - {"WRAMX", BANK_INDEX_WRAMX, -BANK_MIN_WRAMX, BANK_MIN_WRAMX, BANK_COUNT_WRAMX}, - {"SRAM", BANK_INDEX_SRAM, 0, 0, BANK_COUNT_SRAM}, - {"OAM", BANK_INDEX_OAM, 0, 0, BANK_COUNT_OAM} -}; +uint64_t nbSectionsToAssign; -static void do_max_bank(enum eSectionType Type, int32_t nBank) +/** + * Init the free space-modelling structs + */ +static void initFreeSpace(void) { - switch (Type) { - case SECT_ROMX: - if (nBank > MaxBankUsed) - MaxBankUsed = nBank; - break; - case SECT_WRAMX: - if (nBank > MaxWBankUsed) - MaxWBankUsed = nBank; - break; - case SECT_SRAM: - if (nBank > MaxSBankUsed) - MaxSBankUsed = nBank; - break; - case SECT_VRAM: - if (nBank > MaxVBankUsed) - MaxVBankUsed = nBank; - break; - case SECT_ROM0: - case SECT_WRAM0: - case SECT_OAM: - case SECT_HRAM: - default: - break; - } -} + for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) { + memory[type] = malloc(sizeof(*memory[type]) * nbbanks(type)); + if (!memory[type]) + err(1, "Failed to init free space for region %d", type); -void ensureSectionTypeIsValid(enum eSectionType type) -{ - if (type < SECT_MIN || type > SECT_MAX) - errx(1, "%s: Invalid section type found: %d", __func__, type); -} - -int BankIndexIsROM0(int32_t bank) -{ - return (bank >= BANK_INDEX_ROM0) && - (bank < (BANK_INDEX_ROM0 + BANK_COUNT_ROM0)); -} - -int BankIndexIsROMX(int32_t bank) -{ - return (bank >= BANK_INDEX_ROMX) && - (bank < (BANK_INDEX_ROMX + BANK_COUNT_ROMX)); -} - -int BankIndexIsWRAM0(int32_t bank) -{ - return (bank >= BANK_INDEX_WRAM0) && - (bank < (BANK_INDEX_WRAM0 + BANK_COUNT_WRAM0)); -} - -int BankIndexIsWRAMX(int32_t bank) -{ - return (bank >= BANK_INDEX_WRAMX) && - (bank < (BANK_INDEX_WRAMX + BANK_COUNT_WRAMX)); -} - -int BankIndexIsVRAM(int32_t bank) -{ - return (bank >= BANK_INDEX_VRAM) && - (bank < (BANK_INDEX_VRAM + BANK_COUNT_VRAM)); -} - -int BankIndexIsOAM(int32_t bank) -{ - return (bank >= BANK_INDEX_OAM) && - (bank < (BANK_INDEX_OAM + BANK_COUNT_OAM)); -} - -int BankIndexIsHRAM(int32_t bank) -{ - return (bank >= BANK_INDEX_HRAM) && - (bank < (BANK_INDEX_HRAM + BANK_COUNT_HRAM)); -} - -int BankIndexIsSRAM(int32_t bank) -{ - return (bank >= BANK_INDEX_SRAM) && - (bank < (BANK_INDEX_SRAM + BANK_COUNT_SRAM)); -} - -int32_t area_Avail(int32_t bank) -{ - int32_t r; - struct sFreeArea *pArea; - - r = 0; - pArea = BankFree[bank]; - - while (pArea) { - r += pArea->nSize; - pArea = pArea->pNext; - } - - return r; -} - -int32_t area_doAlloc(struct sFreeArea *pArea, int32_t org, int32_t size) -{ - if (size == 0) { - /* 0-byte SECTIONs don't take any room, they can go anywhere */ - return org; - } - - if ((org >= pArea->nOrg) - && ((org + size) <= (pArea->nOrg + pArea->nSize))) { - - if (org == pArea->nOrg) { - pArea->nOrg += size; - pArea->nSize -= size; - return org; + for (uint32_t bank = 0; bank < nbbanks(type); bank++) { + memory[type][bank].next = + malloc(sizeof(*memory[type][0].next)); + if (!memory[type][bank].next) + err(1, "Failed to init free space for region %d bank %u", + type, bank); + memory[type][bank].next->address = startaddr[type]; + memory[type][bank].next->size = maxsize[type]; + memory[type][bank].next->next = NULL; + memory[type][bank].next->prev = &memory[type][bank]; } - - if ((org + size) == (pArea->nOrg + pArea->nSize)) { - pArea->nSize -= size; - return org; - } - - struct sFreeArea *pNewArea; - - pNewArea = malloc(sizeof(struct sFreeArea)); - - if (pNewArea == NULL) - err(1, "%s: Failed to allocate memory", __func__); - - *pNewArea = *pArea; - pNewArea->pPrev = pArea; - pArea->pNext = pNewArea; - pArea->nSize = org - pArea->nOrg; - pNewArea->nOrg = org + size; - pNewArea->nSize -= size + pArea->nSize; - - return org; } - - return -1; } -int32_t area_AllocAbs(struct sFreeArea **ppArea, int32_t org, int32_t size) +/** + * Alter sections' attributes based on the linker script + */ +static void processLinkerScript(void) { - struct sFreeArea *pArea; + if (!linkerScript) + return; + verbosePrint("Reading linker script...\n"); - pArea = *ppArea; - while (pArea) { - int32_t result = area_doAlloc(pArea, org, size); + /* Modify all sections according to the linker script */ + struct SectionPlacement *placement; - if (result != -1) - return result; + while ((placement = script_NextSection())) { + struct Section *section = placement->section; - ppArea = &(pArea->pNext); - pArea = *ppArea; + /* Check if this doesn't conflict with what the code says */ + if (section->isBankFixed && placement->bank != section->bank) + errx(1, "Linker script contradicts \"%s\"'s bank placement", + section->name); + if (section->isAddressFixed && placement->org != section->org) + errx(1, "Linker script contradicts \"%s\"'s address placement", + section->name); + if (section->isAlignFixed + && (placement->org & section->alignMask) != 0) + errx(1, "Linker script contradicts \"%s\"'s alignment", + section->name); + + section->isAddressFixed = true; + section->org = placement->org; + section->isBankFixed = true; + section->bank = placement->bank; + section->isAlignFixed = false; /* The alignment is satisfied */ } - - return -1; } -int32_t area_AllocAbsAnyBank(int32_t org, int32_t size, enum eSectionType type) +/** + * Assigns a section to a given memory location + * @param section The section to assign + * @param location The location to assign the section to + */ +static inline void assignSection(struct Section *section, + struct MemoryLocation const *location) { - ensureSectionTypeIsValid(type); + section->org = location->address; + section->bank = location->bank; - int32_t startBank = SECT_ATTRIBUTES[type].bank; - int32_t bankCount = SECT_ATTRIBUTES[type].bankCount; + nbSectionsToAssign--; - for (int32_t i = 0; i < bankCount; i++) { - if (area_AllocAbs(&BankFree[startBank + i], org, size) != -1) - return startBank + i; - } - - return -1; + out_AddSection(section); } -int32_t area_Alloc(struct sFreeArea **ppArea, int32_t size, int32_t alignment) +static bool isLocationSuitable(struct Section const *section, + struct FreeSpace const *freeSpace, + struct MemoryLocation const *location) { - struct sFreeArea *pArea; + if (section->isAddressFixed && section->org != location->address) + return false; - if (alignment < 1) - alignment = 1; + if (section->isAlignFixed && location->address & section->alignMask) + return false; - pArea = *ppArea; - while (pArea) { - int32_t org = pArea->nOrg; - - if (org % alignment) - org += alignment; - - org -= org % alignment; - - int32_t result = area_doAlloc(pArea, org, size); - - if (result != -1) - return result; - - ppArea = &(pArea->pNext); - pArea = *ppArea; - } - - return -1; + return location->address + section->size + <= freeSpace->address + freeSpace->size; } -int32_t area_AllocAnyBank(int32_t size, int32_t alignment, - enum eSectionType type) +static struct FreeSpace *getPlacement(struct Section const *section, + struct MemoryLocation *location) { - ensureSectionTypeIsValid(type); + location->bank = section->isBankFixed + ? section->bank + : bankranges[section->type][0]; + struct FreeSpace *space; - int32_t i, org; - int32_t startBank = SECT_ATTRIBUTES[type].bank; - int32_t bankCount = SECT_ATTRIBUTES[type].bankCount; + for (;;) { + /* Switch to the beginning of the next bank */ +#define BANK_INDEX (location->bank - bankranges[section->type][0]) + space = memory[section->type][BANK_INDEX].next; + if (space) + location->address = space->address; - for (i = 0; i < bankCount; i++) { - org = area_Alloc(&BankFree[startBank + i], size, alignment); - if (org != -1) - return ((startBank + i) << 16) | org; - } + /* Process locations in that bank */ + while (space) { + /* If that location is OK, return it */ + if (isLocationSuitable(section, space, location)) + return space; - return -1; -} - -struct sSection *FindLargestSection(enum eSectionType type, bool bankFixed) -{ - struct sSection *pSection, *r = NULL; - int32_t nLargest = 0; - int32_t nLargestAlignment = 0; - - pSection = pSections; - while (pSection) { - if (pSection->oAssigned == 0 && pSection->Type == type - && (bankFixed ^ (pSection->nBank == -1))) { - if (pSection->nAlign > nLargestAlignment - || (pSection->nAlign == nLargestAlignment - && pSection->nByteSize > nLargest)) { - - nLargest = pSection->nByteSize; - nLargestAlignment = pSection->nAlign; - r = pSection; + /* Go to the next *possible* location */ + if (section->isAddressFixed) { + /* + * If the address is fixed, there can be only + * one candidate block per bank; if we already + * reached it, give up. + */ + if (location->address == section->org) + /* Try again in next bank */ + space = NULL; + else + location->address = section->org; + } else if (section->isAlignFixed) { + /* Move to next aligned location */ + location->address &= ~section->alignMask; + location->address += section->alignMask + 1; + } else { + /* Any location is fine, so, next free block */ + space = space->next; + if (space) + location->address = space->address; } + + /* + * If that location is past the current block's end, + * go forwards until that is no longer the case. + */ + while (space && location->address >= + space->address + space->size) + space = space->next; + + /* Try again with the new location/free space combo */ } - pSection = pSection->pNext; - } - return r; + /* Try again in the next bank */ + location->bank++; + if (location->bank > bankranges[section->type][1]) + return NULL; + } } -int32_t IsSectionNameInUse(const char *name) +static void placeSection(struct Section *section) { - const struct sSection *pSection = pSections; - - while (pSection) { - if (strcmp(pSection->pzName, name) == 0) - return 1; - - pSection = pSection->pNext; - } - - return 0; -} - -struct sSection *GetSectionByName(const char *name) -{ - struct sSection *pSection = pSections; - - while (pSection) { - if (strcmp(pSection->pzName, name) == 0) - return pSection; - - pSection = pSection->pNext; - } - - return NULL; -} - -int32_t IsSectionSameTypeBankAndAttrs(const char *name, - enum eSectionType type, int32_t bank, - int32_t org, int32_t align) -{ - const struct sSection *pSection; - - for (pSection = pSections; pSection; pSection = pSection->pNext) { - /* Skip if it has already been assigned */ - if (pSection->oAssigned == 1) - continue; - - /* Check if it has the same name */ - if (strcmp(pSection->pzName, name) != 0) - continue; + struct MemoryLocation location; + /* Specially handle 0-byte SECTIONs, as they can't overlap anything */ + if (section->size == 0) { /* - * The section has the same name, now check if there is a - * mismatch or not. + * Unless the SECTION's address was fixed, the starting address + * is fine for any alignment, as checked in sect_DoSanityChecks. */ - - /* Section must have the same attributes or float */ - if ((pSection->nOrg != -1 && pSection->nOrg != org) || - (pSection->nAlign != 1 && pSection->nAlign != align)) - return 0; - - /* It must have the same type in source and linkerscript */ - if (pSection->Type != type) - return 0; - - /* Bank number must be unassigned in source or equal */ - if (pSection->nBank != -1 && pSection->nBank != bank) - return 0; - - return 1; + location.address = section->isAddressFixed + ? section->org + : startaddr[section->type]; + location.bank = section->isBankFixed + ? section->bank + : bankranges[section->type][0]; + assignSection(section, &location); + return; } - errx(1, "Section \"%s\" not found (or already used).\n", name); -} + /* + * Place section using first-fit decreasing algorithm + * https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm + */ + struct FreeSpace *freeSpace = getPlacement(section, &location); -uint32_t AssignSectionAddressAndBankByName(const char *name, uint32_t address, - int32_t bank) -{ - struct sSection *pSection; + if (freeSpace) { + assignSection(section, &location); - for (pSection = pSections; pSection; pSection = pSection->pNext) { - /* Skip if it has already been assigned */ - if (pSection->oAssigned == 1) - continue; + /* Split the free space */ + bool noLeftSpace = freeSpace->address == section->org; + bool noRightSpace = freeSpace->address + freeSpace->size + == section->org + section->size; + if (noLeftSpace && noRightSpace) { + /* The free space is entirely deleted */ + freeSpace->prev->next = freeSpace->next; + if (freeSpace->next) + freeSpace->next->prev = freeSpace->prev; + /* + * If the space is the last one on the list, set its + * size to 0 so it doesn't get picked, but don't free() + * it as it will be freed when cleaning up + */ + free(freeSpace); + } else if (!noLeftSpace && !noRightSpace) { + /* The free space is split in two */ + struct FreeSpace *newSpace = malloc(sizeof(*newSpace)); - /* Check if it has the same name */ - if (strcmp(pSection->pzName, name) != 0) - continue; - - /* Section has been found. */ - - /* The bank can be left as unassigned or be the same */ - if (pSection->nBank != -1 && pSection->nBank != bank) { - errx(1, "Section \"%s\" from linkerscript has different bank number than in the source.\n", - name); - } - - pSection->nOrg = address; - pSection->nBank = bank; - pSection->nAlign = -1; - - return pSection->nByteSize; - } - - errx(1, "Section \"%s\" not found (or already used).\n", name); -} - -bool VerifyAndSetBank(struct sSection *pSection) -{ - enum eSectionType Type = pSection->Type; - - ensureSectionTypeIsValid(Type); - - if (pSection->nBank >= SECT_ATTRIBUTES[Type].minBank) { - if (pSection->nBank < SECT_ATTRIBUTES[Type].minBank - + SECT_ATTRIBUTES[Type].bankCount) { - pSection->nBank += SECT_ATTRIBUTES[Type].bank - + SECT_ATTRIBUTES[Type].offset; - return true; - } - } - - return false; -} - -void AssignFixedBankSections(enum eSectionType type) -{ - ensureSectionTypeIsValid(type); - - struct sSection *pSection; - - while ((pSection = FindLargestSection(type, true))) { - if (VerifyAndSetBank(pSection)) { - pSection->nOrg = area_Alloc(&BankFree[pSection->nBank], - pSection->nByteSize, - pSection->nAlign); - if (pSection->nOrg != -1) { - pSection->oAssigned = 1; - do_max_bank(pSection->Type, pSection->nBank); - continue; - } - } - - if (pSection->nAlign <= 1) { - errx(1, "Unable to place '%s' (%s section) in bank $%02lX", - pSection->pzName, - SECT_ATTRIBUTES[pSection->Type].name, - pSection->nBank); + if (!newSpace) + err(1, "Failed to split new free space"); + /* Append the new space after the chosen one */ + newSpace->prev = freeSpace; + newSpace->next = freeSpace->next; + if (freeSpace->next) + freeSpace->next->prev = newSpace; + freeSpace->next = newSpace; + /* Set its parameters */ + newSpace->address = section->org + section->size; + newSpace->size = freeSpace->address + freeSpace->size - + newSpace->address; + /* Set the original space's new parameters */ + freeSpace->size = section->org - freeSpace->address; + /* address is unmodified */ + } else if (noLeftSpace) { + /* The free space is only moved and resized */ + freeSpace->address += section->size; + freeSpace->size -= section->size; } else { - errx(1, "Unable to place '%s' (%s section) in bank $%02lX (with $%lX-byte alignment)", - pSection->pzName, - SECT_ATTRIBUTES[pSection->Type].name, - pSection->nBank, pSection->nAlign); + /* The free space is only resized */ + freeSpace->size -= section->size; } + return; + } + + if (section->isBankFixed && nbbanks(section->type) != 1) { + if (section->isAddressFixed) + errx(1, "Unable to place \"%s\" (%s section) at $%02x:%04x", + section->name, typeNames[section->type], + section->bank, section->org); + else if (section->isAlignFixed) + errx(1, "Unable to place \"%s\" (%s section) in bank %u with align mask %x", + section->name, typeNames[section->type], + section->bank, ~section->alignMask); + else + errx(1, "Unable to place \"%s\" (%s section) in bank %u", + section->name, typeNames[section->type], + section->bank); + } else { + if (section->isAddressFixed) + errx(1, "Unable to place \"%s\" (%s section) at address $%x", + section->name, typeNames[section->type], + section->org); + else if (section->isAlignFixed) + errx(1, "Unable to place \"%s\" (%s section) with align mask %x", + section->name, typeNames[section->type], + ~section->alignMask); + else + errx(1, "Unable to place \"%s\" (%s section) anywhere", + section->name, typeNames[section->type]); } } -void AssignFloatingBankSections(enum eSectionType type) +struct UnassignedSection { + struct Section *section; + struct UnassignedSection *next; +}; + +#define BANK_CONSTRAINED (1 << 2) +#define ORG_CONSTRAINED (1 << 1) +#define ALIGN_CONSTRAINED (1 << 0) +static struct UnassignedSection *unassignedSections[1 << 3] = {0}; +static struct UnassignedSection *sections; + +static void categorizeSection(struct Section *section, void *arg) { - ensureSectionTypeIsValid(type); + (void)arg; + uint8_t constraints = 0; - struct sSection *pSection; + if (section->isBankFixed) + constraints |= BANK_CONSTRAINED; + if (section->isAddressFixed) + constraints |= ORG_CONSTRAINED; + /* Can't have both! */ + else if (section->isAlignFixed) + constraints |= ALIGN_CONSTRAINED; - while ((pSection = FindLargestSection(type, false))) { - int32_t org; + struct UnassignedSection **ptr = &unassignedSections[constraints]; - org = area_AllocAnyBank(pSection->nByteSize, pSection->nAlign, - type); + /* Insert section while keeping the list sorted by decreasing size */ + while (*ptr && (*ptr)->section->size > section->size) + ptr = &(*ptr)->next; - if (org != -1) { - if (options & OPT_OVERLAY) - errx(1, "All sections must be fixed when using an overlay file."); + sections[nbSectionsToAssign].section = section; + sections[nbSectionsToAssign].next = *ptr; + *ptr = §ions[nbSectionsToAssign]; - pSection->nOrg = org & 0xFFFF; - pSection->nBank = org >> 16; - pSection->oAssigned = 1; - do_max_bank(pSection->Type, pSection->nBank); - } else { - const char *locality = "anywhere"; - - if (SECT_ATTRIBUTES[pSection->Type].bankCount > 1) - locality = "in any bank"; - - if (pSection->nAlign <= 1) { - errx(1, "Unable to place '%s' (%s section) %s", - pSection->pzName, - SECT_ATTRIBUTES[type].name, locality); - } else { - errx(1, "Unable to place '%s' (%s section) %s (with $%lX-byte alignment)", - pSection->pzName, - SECT_ATTRIBUTES[type].name, locality, - pSection->nAlign); - } - } - } + nbSectionsToAssign++; } -char *tzLinkerscriptName; - -void SetLinkerscriptName(char *tzLinkerscriptFile) +void assign_AssignSections(void) { - tzLinkerscriptName = tzLinkerscriptFile; + verbosePrint("Beginning assignment...\n"); + + /** Initialize assignment **/ + + /* Generate linked lists of sections to assign */ + sections = malloc(sizeof(*sections) * nbSectionsToAssign + 1); + if (!sections) + err(1, "Failed to allocate memory for section assignment"); + + nbSectionsToAssign = 0; + sect_ForEach(categorizeSection, NULL); + + initFreeSpace(); + + /* Process linker script, if any */ + processLinkerScript(); + + /** Place sections, starting with the most constrained **/ + + /* Specially process fully-constrained sections because of overlaying */ + struct UnassignedSection *sectionPtr = + unassignedSections[BANK_CONSTRAINED | ORG_CONSTRAINED]; + + verbosePrint("Assigning bank+org-constrained...\n"); + while (sectionPtr) { + placeSection(sectionPtr->section); + sectionPtr = sectionPtr->next; + } + + /* If all sections were fully constrained, we have nothing left to do */ + if (!nbSectionsToAssign) + return; + + /* Overlaying requires only fully-constrained sections */ + verbosePrint("Assigning other sections...\n"); + if (overlayFile) + errx(1, "All sections must be fixed when using an overlay file; %u, %sn't", + nbSectionsToAssign, nbSectionsToAssign == 1 ? "is" : "are"); + + /* Assign all remaining sections by decreasing constraint order */ + for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; + constraints >= 0; constraints--) { + sectionPtr = unassignedSections[constraints]; + + while (sectionPtr) { + placeSection(sectionPtr->section); + sectionPtr = sectionPtr->next; + } + + if (!nbSectionsToAssign) + return; + } + + trap_; } -void AssignSections(void) +void assign_Cleanup(void) { - struct sSection *pSection; + for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) { + for (uint32_t bank = 0; bank < nbbanks(type); bank++) { + struct FreeSpace *ptr = + memory[type][bank].next; - MaxBankUsed = 0; + while (ptr) { + struct FreeSpace *next = ptr->next; - /* - * Initialize the memory areas - */ - - for (int32_t i = 0; i < BANK_INDEX_MAX; i += 1) { - BankFree[i] = malloc(sizeof(*BankFree[i])); - - if (!BankFree[i]) { - errx(1, "%s: Couldn't allocate mem for bank %d", - __func__, i); - } - - if (BankIndexIsROM0(i)) { - /* ROM0 bank */ - BankFree[i]->nOrg = 0x0000; - if (options & OPT_TINY) - BankFree[i]->nSize = 0x8000; - else - BankFree[i]->nSize = 0x4000; - } else if (BankIndexIsROMX(i)) { - /* Swappable ROM bank */ - BankFree[i]->nOrg = 0x4000; - BankFree[i]->nSize = 0x4000; - } else if (BankIndexIsWRAM0(i)) { - /* WRAM */ - BankFree[i]->nOrg = 0xC000; - if (options & OPT_CONTWRAM) - BankFree[i]->nSize = 0x2000; - else - BankFree[i]->nSize = 0x1000; - } else if (BankIndexIsSRAM(i)) { - /* Swappable SRAM bank */ - BankFree[i]->nOrg = 0xA000; - BankFree[i]->nSize = 0x2000; - } else if (BankIndexIsWRAMX(i)) { - /* Swappable WRAM bank */ - BankFree[i]->nOrg = 0xD000; - BankFree[i]->nSize = 0x1000; - } else if (BankIndexIsVRAM(i)) { - /* Swappable VRAM bank */ - BankFree[i]->nOrg = 0x8000; - if (options & OPT_DMG_MODE && i != BANK_INDEX_VRAM) - BankFree[i]->nSize = 0; - else - BankFree[i]->nSize = 0x2000; - } else if (BankIndexIsOAM(i)) { - BankFree[i]->nOrg = 0xFE00; - BankFree[i]->nSize = 0x00A0; - } else if (BankIndexIsHRAM(i)) { - /* HRAM */ - BankFree[i]->nOrg = 0xFF80; - BankFree[i]->nSize = 0x007F; - } else { - errx(1, "%s: Unknown bank type %d", __func__, i); - } - - MaxAvail[i] = BankFree[i]->nSize; - BankFree[i]->pPrev = NULL; - BankFree[i]->pNext = NULL; - } - - /* - * First, let's parse the linkerscript. - */ - - if (tzLinkerscriptName) { - script_InitSections(); - script_Parse(tzLinkerscriptName); - } - - /* - * Second, let's assign all the fixed sections... - */ - - for (pSection = pSections ; pSection; pSection = pSection->pNext) { - if (!((pSection->nOrg != -1 || pSection->nBank != -1) - && pSection->oAssigned == 0)) - continue; - - /* User wants to have a say... */ - - switch (pSection->Type) { - case SECT_WRAM0: - case SECT_HRAM: - case SECT_ROM0: - case SECT_OAM: - pSection->nBank = SECT_ATTRIBUTES[pSection->Type].bank; - if (area_AllocAbs(&BankFree[pSection->nBank], - pSection->nOrg, - pSection->nByteSize) == -1) { - errx(1, "Unable to place '%s' (%s section) at $%X", - pSection->pzName, - SECT_ATTRIBUTES[pSection->Type].name, - pSection->nOrg); + free(ptr); + ptr = next; } - pSection->oAssigned = 1; - break; - - case SECT_SRAM: - case SECT_WRAMX: - case SECT_VRAM: - case SECT_ROMX: - if (!(pSection->nBank != -1 && pSection->nOrg != -1)) - break; - - if (VerifyAndSetBank(pSection) && - area_AllocAbs(&BankFree[pSection->nBank], - pSection->nOrg, - pSection->nByteSize) != -1) { - do_max_bank(pSection->Type, pSection->nBank); - pSection->oAssigned = 1; - } else { - errx(1, "Unable to place '%s' (%s section) at $%X in bank $%02X", - pSection->pzName, - SECT_ATTRIBUTES[pSection->Type].name, - pSection->nOrg, pSection->nBank); - } - break; - default: - errx(1, "%s: Internal error: Type %d", __func__, - pSection->Type); } + + free(memory[type]); } - /* - * Next, let's assign all the bankfixed ONLY sections... - */ - for (enum eSectionType i = SECT_MIN; i <= SECT_MAX; i++) - AssignFixedBankSections(i); + free(sections); - /* - * Now, let's assign all the floating bank but fixed ROMX sections... - */ - - for (pSection = pSections ; pSection; pSection = pSection->pNext) { - if (!(pSection->oAssigned == 0 - && pSection->nOrg != -1 && pSection->nBank == -1)) - continue; - - if (options & OPT_OVERLAY) { - errx(1, "All sections must be fixed when using an overlay file: '%s'", - pSection->pzName); - } - - switch (pSection->Type) { - case SECT_ROMX: - case SECT_VRAM: - case SECT_SRAM: - case SECT_WRAMX: - pSection->nBank = - area_AllocAbsAnyBank(pSection->nOrg, - pSection->nByteSize, - pSection->Type); - - if (pSection->nBank == -1) { - errx(1, "Unable to place '%s' (%s section) at $%X in any bank", - pSection->pzName, - SECT_ATTRIBUTES[pSection->Type].name, - pSection->nOrg); - } - pSection->oAssigned = 1; - do_max_bank(pSection->Type, pSection->nBank); - break; - - case SECT_ROM0: - case SECT_WRAM0: - case SECT_OAM: - case SECT_HRAM: - default: /* Handle other sections later */ - break; - } - } - - /* - * OK, all that nasty stuff is done so let's assign all the other - * sections - */ - for (enum eSectionType i = SECT_MIN; i <= SECT_MAX; i++) - AssignFloatingBankSections(i); -} - -void CreateSymbolTable(void) -{ - const struct sSection *pSect; - - sym_Init(); - - pSect = pSections; - - while (pSect) { - int32_t i; - - i = pSect->nNumberOfSymbols; - - while (i--) { - const struct sSymbol *tSymbol = pSect->tSymbols[i]; - - if ((tSymbol->Type == SYM_EXPORT) && - ((tSymbol->pSection == pSect) || - (tSymbol->pSection == NULL))) { - if (tSymbol->pSection == NULL) { - sym_CreateSymbol(tSymbol->pzName, - tSymbol->nOffset, - -1, - tSymbol->pzObjFileName, - tSymbol->pzFileName, - tSymbol->nFileLine); - } else { - sym_CreateSymbol(tSymbol->pzName, - pSect->nOrg + - tSymbol->nOffset, - pSect->nBank, - tSymbol->pzObjFileName, - tSymbol->pzFileName, - tSymbol->nFileLine); - } - } - } - pSect = pSect->pNext; - } + script_Cleanup(); } diff --git a/src/link/lexer.l b/src/link/lexer.l deleted file mode 100644 index ef59a378..00000000 --- a/src/link/lexer.l +++ /dev/null @@ -1,194 +0,0 @@ -/* - * This file is part of RGBDS. - * - * Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors. - * - * SPDX-License-Identifier: MIT - */ - -%option noinput -%option yylineno - -%{ -#include -#include -#include - -#include "extern/err.h" - -#include "link/mylink.h" -#include "link/script.h" - -#include "parser.h" - -#include "types.h" - -extern int yyparse(void); - -/* File include stack. */ - -#define MAX_INCLUDE_DEPTH 8 - -static int32_t include_stack_ptr; - -static YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH]; -static char include_path[MAX_INCLUDE_DEPTH][_MAX_PATH + 1]; -static int32_t include_line[MAX_INCLUDE_DEPTH]; - -static char linkerscript_path[_MAX_PATH + 1]; /* Base file */ -%} - -%% - -\"([^\\\"]|\\.)*\" { - if (strlen(yytext) > sizeof(yylval.s) - 1) { - script_fatalerror("String is too long: %s\n.", - yytext); - } - - if (strlen(yytext) < 3) { /* 2 quotes + 1 character */ - script_fatalerror("String %s is invalid\n.", - yytext); - } - - /* Ignore first quote */ - yytext++; - strcpy(yylval.s, yytext); - /* Remove end quote */ - yylval.s[strlen(yylval.s)-1] = '\0'; - - return STRING; - } - -\$[a-fA-F0-9]+ { - yytext++; /* Skip prefix */ - yylval.i = strtol(yytext, NULL, 16); - return INTEGER; - } -[0-9]+ { - yylval.i = strtol(yytext, NULL, 10); - return INTEGER; - } - -(?i:ROM0) { strcpy(yylval.s, "ROM0"); return SECTION_NONBANKED; } -(?i:ROMX) { strcpy(yylval.s, "ROMX"); return SECTION_BANKED; } -(?i:VRAM) { strcpy(yylval.s, "VRAM"); return SECTION_BANKED; } -(?i:WRAM0) { strcpy(yylval.s, "WRAM0"); return SECTION_NONBANKED; } -(?i:WRAMX) { strcpy(yylval.s, "WRAMX"); return SECTION_BANKED; } -(?i:SRAM) { strcpy(yylval.s, "SRAM"); return SECTION_BANKED; } -(?i:OAM) { strcpy(yylval.s, "OAM"); return SECTION_NONBANKED; } -(?i:HRAM) { strcpy(yylval.s, "HRAM"); return SECTION_NONBANKED; } - -(?i:ALIGN) { return COMMAND_ALIGN; } -(?i:ORG) { return COMMAND_ORG; } - -(?i:INCLUDE) { return COMMAND_INCLUDE; } - -"\n" { return NEWLINE; } - -;.* { /* Ignore comments. A dot doesn't match newline. */ } - -[[:space:]] { /* Ignore whitespace. */ } - -. { script_fatalerror("Invalid character [%s]\n.", yytext); } - -%% - -extern FILE *yyin; - -void script_Parse(const char * path) -{ - yyin = fopen(path, "r"); - - if (!yyin) - errx(1, "Error opening file! \"%s\"\n", path); - - strncpy(linkerscript_path, path, sizeof(linkerscript_path)); - linkerscript_path[sizeof(linkerscript_path) - 1] = '\0'; - - do { - yyparse(); - } while (!feof(yyin)); - - fclose(yyin); -} - -void script_IncludeFile(const char * path) -{ - if (include_stack_ptr == (MAX_INCLUDE_DEPTH - 1)) - script_fatalerror("Includes nested too deeply."); - - include_line[include_stack_ptr] = yylineno; - include_stack[include_stack_ptr] = YY_CURRENT_BUFFER; - - include_stack_ptr++; - - yyin = fopen(path, "r"); - - if (!yyin) - script_fatalerror("Couldn't open file \"%s\"", path); - - strncpy(include_path[include_stack_ptr], path, sizeof(include_path[0])); - include_path[include_stack_ptr][sizeof(include_path[0]) - 1] = '\0'; - - yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE)); - yylineno = 1; - - /* - * The INCLUDE keyword is handled before reaching a newline token, it's - * handled right after parsing the string with the file name that has to - * be included. It isn't actually needed to include a newline after the - * path, the last line of the linkerscript doesn't need to have a - * newline character but it can have a command. - * - * This means that, when opening a new file, we must tell the parser - * that what it is going to start at a new line or it will think that - * the first line of the included script is the continuation of the - * INCLUDE line of the parent script. If this is not done, the first - * line of an included linkerscript can only be a comment (or empty). - */ - unput('\n'); -} - -int32_t script_IncludeDepthGet(void) -{ - return include_stack_ptr; -} - -void script_IncludePop(void) -{ - fclose(yyin); - - include_stack_ptr--; - - yy_delete_buffer(YY_CURRENT_BUFFER); - yy_switch_to_buffer(include_stack[include_stack_ptr]); - yylineno = include_line[include_stack_ptr]; -} - -void script_PrintFileStack(void) -{ - int32_t i = include_stack_ptr; - - include_line[i] = yylineno; - - while (i > 0) { - fprintf(stderr, "%s(%d) -> ", include_path[i], include_line[i]); - i--; - } - fprintf(stderr, "%s(%d)", linkerscript_path, include_line[i]); -} - -noreturn_ void script_fatalerror(const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - fprintf(stderr, "error: "); - script_PrintFileStack(); - fprintf(stderr, ":\n "); - vfprintf(stderr, fmt, args); - fprintf(stderr, "\n"); - va_end(args); - exit(1); -} - diff --git a/src/link/library.c b/src/link/library.c deleted file mode 100644 index 971f0279..00000000 --- a/src/link/library.c +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of RGBDS. - * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. - * - * SPDX-License-Identifier: MIT - */ - -#include -#include -#include -#include - -#include "extern/err.h" - -#include "link/mylink.h" -#include "link/main.h" - -static uint8_t symboldefined(char *name) -{ - const struct sSection *pSect; - - pSect = pSections; - - while (pSect) { - int32_t i; - - for (i = 0; i < pSect->nNumberOfSymbols; i += 1) { - const struct sSymbol *tSymbol = pSect->tSymbols[i]; - - if ((tSymbol->Type == SYM_EXPORT) - || ((tSymbol->Type == SYM_LOCAL) - && (pSect == tSymbol->pSection))) { - - if (strcmp(tSymbol->pzName, name) == 0) - return 1; - } - } - pSect = pSect->pNext; - } - return 0; -} - -static uint8_t addmodulecontaining(char *name) -{ - struct sSection **ppLSect = &pLibSections; - - while (*ppLSect) { - int32_t i; - - for (i = 0; i < (*ppLSect)->nNumberOfSymbols; i += 1) { - const struct sSymbol *tSymbol = (*ppLSect)->tSymbols[i]; - - if ((tSymbol->Type == SYM_EXPORT) - || ((tSymbol->Type == SYM_LOCAL) - && ((*ppLSect) == tSymbol->pSection))) { - - if (strcmp(tSymbol->pzName, name) == 0) { - struct sSection **ppSect = &pSections; - - while (*ppSect) - ppSect = &((*ppSect)->pNext); - - *ppSect = *ppLSect; - *ppLSect = (*ppLSect)->pNext; - (*ppSect)->pNext = NULL; - return 1; - } - } - } - ppLSect = &((*ppLSect)->pNext); - } - return 0; -} - -void AddNeededModules(void) -{ - struct sSection *pSect; - - if ((options & OPT_SMART_C_LINK) == 0) { - struct sSection **ppLSect; - - ppLSect = &pLibSections; - - while (*ppLSect) { - struct sSection **ppSect = &pSections; - - while (*ppSect) - ppSect = &((*ppSect)->pNext); - - *ppSect = *ppLSect; - *ppLSect = (*ppLSect)->pNext; - (*ppSect)->pNext = NULL; - - /* ppLSect=&((*ppLSect)->pNext); */ - } - return; - } - if (options & OPT_SMART_C_LINK) { - if (!addmodulecontaining(smartlinkstartsymbol)) { - errx(1, "Can't find start symbol '%s'", - smartlinkstartsymbol); - } else { - printf("Smart linking with symbol '%s'\n", - smartlinkstartsymbol); - } - } - pSect = pSections; - - while (pSect) { - int32_t i; - - for (i = 0; i < pSect->nNumberOfSymbols; i += 1) { - if ((pSect->tSymbols[i]->Type == SYM_IMPORT) - || (pSect->tSymbols[i]->Type == SYM_LOCAL)) { - if (!symboldefined(pSect->tSymbols[i]->pzName)) - addmodulecontaining(pSect->tSymbols[i]->pzName); - } - } - pSect = pSect->pNext; - } -} diff --git a/src/link/main.c b/src/link/main.c index 37668850..c2c8b2f5 100644 --- a/src/link/main.c +++ b/src/link/main.c @@ -1,141 +1,177 @@ /* * This file is part of RGBDS. * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. + * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors. * * SPDX-License-Identifier: MIT */ -#include -#include -#include -#include #include - -#include "extern/err.h" +#include +#include +#include +#include #include "link/object.h" -#include "link/output.h" +#include "link/symbol.h" +#include "link/section.h" #include "link/assign.h" #include "link/patch.h" -#include "link/mylink.h" -#include "link/mapfile.h" -#include "link/main.h" -#include "link/library.h" +#include "link/output.h" +#include "extern/err.h" #include "version.h" -enum eBlockType { - BLOCK_COMMENT, - BLOCK_OBJECTS, - BLOCK_LIBRARIES, - BLOCK_OUTPUT -}; +bool isDmgMode; /* -d */ +FILE *linkerScript; /* -l */ +FILE *mapFile; /* -m */ +FILE *symFile; /* -n */ +FILE *overlayFile; /* -O */ +FILE *outputFile; /* -o */ +uint8_t padValue; /* -p */ +bool is32kMode; /* -t */ +bool beVerbose; /* -v */ +bool isWRA0Mode; /* -w */ -int32_t options; -int32_t fillchar; -char *smartlinkstartsymbol; - -/* - * Print the usagescreen +/** + * Prints the program's usage to stdout. */ - -static void print_usage(void) +static void printUsage(void) { - printf( -"usage: rgblink [-dtVw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n" -" [-o outfile] [-p pad_value] [-s symbol] file [...]\n"); - exit(1); + puts("usage: rgblink [-dtVvw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n" + " [-o outfile] [-p pad_value] [-s symbol] file [...]"); } -/* - * The main routine +/** + * Helper function for `main`'s argument parsing. + * For use with options which take a file name to operate on + * If the file fails to be opened, an error message will be printed, + * and the function `exit`s. + * @param mode The mode to open the file in + * @param failureMessage A format string that will be printed on failure. + * A single (string) argument is given, the file name. + * @return What `fopen` returned; this cannot be NULL. */ +static FILE *openArgFile(char const *mode, char const *failureMessage) +{ + FILE *file = fopen(optarg, mode); + + if (!file) + err(1, failureMessage, optarg); + return file; +} + +/** + * Cleans up what has been done + * Mostly here to please tools such as `valgrind` so actual errors can be seen + */ +static void cleanup(void) +{ + if (linkerScript) + fclose(linkerScript); + if (mapFile) + fclose(mapFile); + if (symFile) + fclose(symFile); + if (overlayFile) + fclose(overlayFile); + if (outputFile) + fclose(outputFile); + + obj_Cleanup(); +} int main(int argc, char *argv[]) { - int ch; - char *ep; + char optionChar; + char *endptr; /* For error checking with `strtol` */ + unsigned long value; /* For storing `strtoul`'s return value */ - if (argc == 1) - print_usage(); - - while ((ch = getopt(argc, argv, "dl:m:n:O:o:p:s:tVw")) != -1) { - switch (ch) { + /* Parse options */ + while ((optionChar = getopt(argc, argv, "dl:m:n:O:o:p:s:tVvw")) != -1) { + switch (optionChar) { + case 'd': + isDmgMode = true; + isWRA0Mode = true; + break; case 'l': - SetLinkerscriptName(optarg); + linkerScript = openArgFile("r", "Could not open linker script file \"%s\""); break; case 'm': - SetMapfileName(optarg); + mapFile = openArgFile("w", "Could not open map file \"%s\""); break; case 'n': - SetSymfileName(optarg); - break; - case 'o': - out_Setname(optarg); + symFile = openArgFile("w", "Could not open sym file \"%s\""); break; case 'O': - out_SetOverlayname(optarg); - options |= OPT_OVERLAY; + overlayFile = openArgFile("r+b", "Could not open overlay file \"%s\""); + break; + case 'o': + outputFile = openArgFile("wb", "Could not open output file \"%s\""); break; case 'p': - fillchar = strtoul(optarg, &ep, 0); - if (optarg[0] == '\0' || *ep != '\0') + value = strtoul(optarg, &endptr, 0); + if (optarg[0] == '\0' || *endptr != '\0') errx(1, "Invalid argument for option 'p'"); - if (fillchar < 0 || fillchar > 0xFF) - errx(1, "Argument for option 'p' must be between 0 and 0xFF"); + if (value > 0xFF) + errx(1, "Argument for 'p' must be a byte (between 0 and 0xFF)"); + padValue = value; break; case 's': - options |= OPT_SMART_C_LINK; - smartlinkstartsymbol = optarg; + /* FIXME: nobody knows what this does, figure it out */ + (void)optarg; + warnx("Nobody has any idea what `-s` does"); break; case 't': - options |= OPT_TINY; - break; - case 'd': - /* - * Set to set WRAM as a single continuous block as on - * DMG. All WRAM sections must be WRAM0 as bankable WRAM - * sections do not exist in this mode. A WRAMX section - * will raise an error. VRAM bank 1 can't be used if - * this option is enabled either. - * - * This option implies OPT_CONTWRAM. - */ - options |= OPT_DMG_MODE; - /* FALLTHROUGH */ - case 'w': - /* - * Set to set WRAM as a single continuous block as on - * DMG. All WRAM sections must be WRAM0 as bankable WRAM - * sections do not exist in this mode. A WRAMX section - * will raise an error. - */ - options |= OPT_CONTWRAM; + is32kMode = true; break; case 'V': printf("rgblink %s\n", get_package_version_string()); exit(0); + case 'v': + beVerbose = true; + break; + case 'w': + isWRA0Mode = true; + break; default: - print_usage(); - /* NOTREACHED */ + printUsage(); + exit(1); } } - argc -= optind; - argv += optind; - if (argc == 0) - print_usage(); + int curArgIndex = optind; - for (int32_t i = 0; i < argc; ++i) - obj_Readfile(argv[i]); + /* If no input files were specified, the user must have screwed up */ + if (curArgIndex == argc) { + fprintf(stderr, "No input files"); + printUsage(); + exit(1); + } - AddNeededModules(); - AssignSections(); - CreateSymbolTable(); - Patch(); - Output(); - CloseMapfile(); + /* Patch the size array depending on command-line options */ + if (is32kMode) + maxsize[SECTTYPE_ROM0] = 0x8000; + if (isWRA0Mode) + maxsize[SECTTYPE_WRAM0] = 0x2000; - return 0; + /* Patch the bank ranges array depending on command-line options */ + if (isDmgMode) + bankranges[SECTTYPE_VRAM][1] = BANK_MIN_VRAM; + + /* Read all object files first, */ + while (curArgIndex < argc) + obj_ReadFile(argv[curArgIndex++]); + + /* then process them, */ + obj_DoSanityChecks(); + assign_AssignSections(); + assign_Cleanup(); + + /* and finally output the result. */ + patch_ApplyPatches(); + out_WriteFiles(); + + /* Do cleanup before quitting, though. */ + cleanup(); } diff --git a/src/link/mapfile.c b/src/link/mapfile.c deleted file mode 100644 index 46dd69ed..00000000 --- a/src/link/mapfile.c +++ /dev/null @@ -1,151 +0,0 @@ -/* - * This file is part of RGBDS. - * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. - * - * SPDX-License-Identifier: MIT - */ - -#include -#include -#include -#include -#include - -#include "extern/err.h" - -#include "link/assign.h" -#include "link/main.h" -#include "link/mylink.h" - -static int32_t currentbank; -static int32_t sfbank; -static FILE *mf; -static FILE *sf; - -void SetMapfileName(char *name) -{ - mf = fopen(name, "w"); - - if (mf == NULL) - err(1, "Cannot open mapfile '%s'", name); -} - -void SetSymfileName(char *name) -{ - sf = fopen(name, "w"); - - if (sf == NULL) - err(1, "Cannot open symfile '%s'", name); - - fprintf(sf, "; File generated by rgblink\n\n"); -} - -void CloseMapfile(void) -{ - if (mf) { - fclose(mf); - mf = NULL; - } - if (sf) { - fclose(sf); - sf = NULL; - } -} - -void MapfileInitBank(int32_t bank) -{ - if ((bank < 0) || (bank >= BANK_INDEX_MAX)) - errx(1, "%s: Unknown bank %d\n", __func__, bank); - - if (mf) { - currentbank = bank; - if (BankIndexIsROM0(bank)) { - fprintf(mf, "ROM Bank #0 (HOME):\n"); - } else if (BankIndexIsROMX(bank)) { - fprintf(mf, "ROM Bank #%d:\n", - bank - BANK_INDEX_ROMX + 1); - } else if (BankIndexIsWRAM0(bank)) { - fprintf(mf, "WRAM Bank #0:\n"); - } else if (BankIndexIsWRAMX(bank)) { - fprintf(mf, "WRAM Bank #%d:\n", - bank - BANK_INDEX_WRAMX + 1); - } else if (BankIndexIsVRAM(bank)) { - fprintf(mf, "VRAM Bank #%d:\n", bank - BANK_INDEX_VRAM); - } else if (BankIndexIsOAM(bank)) { - fprintf(mf, "OAM:\n"); - } else if (BankIndexIsHRAM(bank)) { - fprintf(mf, "HRAM:\n"); - } else if (BankIndexIsSRAM(bank)) { - fprintf(mf, "SRAM Bank #%d:\n", bank - BANK_INDEX_SRAM); - } - } - if (sf) { - if (BankIndexIsROM0(bank)) - sfbank = 0; - else if (BankIndexIsROMX(bank)) - sfbank = bank - BANK_INDEX_ROMX + 1; - else if (BankIndexIsWRAM0(bank)) - sfbank = 0; - else if (BankIndexIsWRAMX(bank)) - sfbank = bank - BANK_INDEX_WRAMX + 1; - else if (BankIndexIsVRAM(bank)) - sfbank = bank - BANK_INDEX_VRAM; - else if (BankIndexIsOAM(bank)) - sfbank = 0; - else if (BankIndexIsHRAM(bank)) - sfbank = 0; - else if (BankIndexIsSRAM(bank)) - sfbank = bank - BANK_INDEX_SRAM; - else - sfbank = 0; - } -} - -void MapfileWriteSection(const struct sSection *pSect) -{ - int32_t i; - - if (mf) { - if (pSect->nByteSize > 0) { - fprintf(mf, " SECTION: $%04X-$%04X ($%04X bytes) [\"%s\"]\n", - pSect->nOrg, pSect->nOrg + pSect->nByteSize - 1, - pSect->nByteSize, pSect->pzName); - } else { - fprintf(mf, " SECTION: $%04X ($0 bytes) [\"%s\"]\n", - pSect->nOrg, pSect->pzName); - } - } - - for (i = 0; i < pSect->nNumberOfSymbols; i += 1) { - const struct sSymbol *pSym = pSect->tSymbols[i]; - - /* Don't print '@' */ - if (strcmp(pSym->pzName, "@") == 0) - continue; - - if ((pSym->pSection == pSect) && (pSym->Type != SYM_IMPORT)) { - if (mf) { - fprintf(mf, " $%04X = %s\n", - pSym->nOffset + pSect->nOrg, - pSym->pzName); - } - if (sf) { - fprintf(sf, "%02X:%04X %s\n", sfbank, - pSym->nOffset + pSect->nOrg, - pSym->pzName); - } - } - } -} - -void MapfileCloseBank(int32_t slack) -{ - if (!mf) - return; - - if (slack == MaxAvail[currentbank]) - fprintf(mf, " EMPTY\n\n"); - else - fprintf(mf, " SLACK: $%04X bytes\n\n", slack); -} diff --git a/src/link/object.c b/src/link/object.c index 818adbd7..28c7633e 100644 --- a/src/link/object.c +++ b/src/link/object.c @@ -1,381 +1,461 @@ -/* - * This file is part of RGBDS. - * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. - * - * SPDX-License-Identifier: MIT - */ -/* - * Here we have the routines that read an objectfile - */ - -#include -#include -#include #include -#include +#include #include +#include +#include +#include -#include "common.h" +#include "link/object.h" +#include "link/main.h" +#include "link/symbol.h" +#include "link/section.h" +#include "link/assign.h" #include "extern/err.h" +#include "linkdefs.h" +#include "common.h" -#include "link/assign.h" -#include "link/mylink.h" -#include "link/main.h" +static struct SymbolList { + size_t nbSymbols; + struct Symbol **symbolList; + struct SymbolList *next; +} *symbolLists; -struct sSymbol **tSymbols; -struct sSection *pSections; -struct sSection *pLibSections; -uint8_t oReadLib; +/***** Helper functions for reading object files *****/ /* - * Read 32-bit values with the correct endianness + * Internal, DO NOT USE. + * For helper wrapper macros defined below, such as `tryReadlong` */ -static int32_t readlong(FILE *f) +#define tryRead(func, type, errval, var, file, ...) \ + do { \ + FILE *tmpFile = file; \ + type tmpVal = func(tmpFile); \ + if (tmpVal == (errval)) { \ + errx(1, __VA_ARGS__, feof(tmpFile) \ + ? "Unexpected end of file" \ + : strerror(errno)); \ + } \ + var = tmpVal; \ + } while (0) + +/** + * Reads an unsigned long (32-bit) value from a file. + * @param file The file to read from. This will read 4 bytes from the file. + * @return The value read, cast to a int64_t, or -1 on failure. + */ +static int64_t readlong(FILE *file) { - uint32_t r; + uint32_t value = 0; - r = ((uint32_t)(uint8_t)fgetc(f)); - r |= ((uint32_t)(uint8_t)fgetc(f)) << 8; - r |= ((uint32_t)(uint8_t)fgetc(f)) << 16; - r |= ((uint32_t)(uint8_t)fgetc(f)) << 24; + /* Read the little-endian value byte by byte */ + for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) { + int byte = getc(file); - return (int32_t)r; + if (byte == EOF) + return INT64_MAX; + value |= (uint8_t)byte << shift; + } + return value; } -/* - * Read a NULL terminated string from a file +/** + * Helper macro for reading longs from a file, and errors out if it fails to. + * Not as a function to avoid overhead in the general case. + * TODO: maybe mark the condition as `unlikely`; how to do that portably? + * @param var The variable to stash the number into + * @param file The file to read from. Its position will be advanced + * @param ... A format string and related arguments; note that an extra string + * argument is provided, the reason for failure */ -int32_t readasciiz(char **dest, FILE *f) +#define tryReadlong(var, file, ...) \ + tryRead(readlong, int64_t, INT64_MAX, var, file, __VA_ARGS__) + +/* There is no `readbyte`, just use `fgetc` or `getc`. */ + +/** + * Helper macro for reading bytes from a file, and errors out if it fails to. + * Differs from `tryGetc` in that the backing function is fgetc(1). + * Not as a function to avoid overhead in the general case. + * TODO: maybe mark the condition as `unlikely`; how to do that portably? + * @param var The variable to stash the number into + * @param file The file to read from. Its position will be advanced + * @param ... A format string and related arguments; note that an extra string + * argument is provided, the reason for failure + */ +#define tryFgetc(var, file, ...) \ + tryRead(fgetc, int, EOF, var, file, __VA_ARGS__) + +/** + * Helper macro for reading bytes from a file, and errors out if it fails to. + * Differs from `tryGetc` in that the backing function is fgetc(1). + * Not as a function to avoid overhead in the general case. + * TODO: maybe mark the condition as `unlikely`; how to do that portably? + * @param var The variable to stash the number into + * @param file The file to read from. Its position will be advanced + * @param ... A format string and related arguments; note that an extra string + * argument is provided, the reason for failure + */ +#define tryGetc(var, file, ...) \ + tryRead(getc, int, EOF, var, file, __VA_ARGS__) + +/** + * Reads a '\0'-terminated string from a file. + * @param file The file to read from. The file position will be advanced. + * @return The string read, or NULL on failure. + * If a non-NULL pointer is returned, make sure to `free` it when done! + */ +static char *readstr(FILE *file) { - size_t r = 0; + /* Default buffer size, have it close to the average string length */ + size_t capacity = 32 / 2; + size_t index = -1; + /* Force the first iteration to allocate */ + char *str = NULL; - size_t bufferLength = 16; - char *start = malloc(bufferLength); - char *s = start; - - if (!s) - err(1, "%s: Couldn't allocate memory", __func__); - - while (((*s++) = fgetc(f)) != 0) { - r += 1; - - if (r >= bufferLength) { - bufferLength *= 2; - start = realloc(start, bufferLength); - if (!start) { - err(1, "%s: Couldn't allocate memory", - __func__); - } - s = start + r; + do { + /* Prepare going to next char */ + index++; + /* If the buffer isn't suitable to write the next char... */ + if (index >= capacity || !str) { + capacity *= 2; + str = realloc(str, capacity); + /* End now in case of error */ + if (!str) + return NULL; } - } - - *dest = start; - return (r + 1); + /* Read char */ + str[index] = getc(file); + } while (str[index]); + return str; } -/* - * Allocate a new section and link it into the list +/** + * Helper macro for reading bytes from a file, and errors out if it fails to. + * Not as a function to avoid overhead in the general case. + * TODO: maybe mark the condition as `unlikely`; how to do that portably? + * @param var The variable to stash the string into + * @param file The file to read from. Its position will be advanced + * @param ... A format string and related arguments; note that an extra string + * argument is provided, the reason for failure */ -struct sSection *AllocSection(void) +#define tryReadstr(var, file, ...) \ + tryRead(readstr, char*, NULL, var, file, __VA_ARGS__) + +/***** Functions to parse object files *****/ + +static void readSymbol(FILE *file, struct Symbol *symbol, char const *fileName) { - struct sSection **ppSections; - - if (oReadLib == 1) - ppSections = &pLibSections; - else - ppSections = &pSections; - - while (*ppSections) - ppSections = &((*ppSections)->pNext); - - *ppSections = malloc(sizeof **ppSections); - if (!*ppSections) - err(1, "%s: Couldn't allocate memory", __func__); - - (*ppSections)->tSymbols = tSymbols; - (*ppSections)->pNext = NULL; - (*ppSections)->pPatches = NULL; - (*ppSections)->oAssigned = 0; - return *ppSections; -} - -/* - * Read a symbol from a file - */ -struct sSymbol *obj_ReadSymbol(FILE *f, char *tzObjectfile) -{ - struct sSymbol *pSym; - - pSym = malloc(sizeof(*pSym)); - if (!pSym) - err(1, "%s: Couldn't allocate memory", __func__); - - readasciiz(&pSym->pzName, f); - pSym->Type = (enum eSymbolType)fgetc(f); - - pSym->pzObjFileName = tzObjectfile; - - if (pSym->Type != SYM_IMPORT) { - readasciiz(&pSym->pzFileName, f); - pSym->nFileLine = readlong(f); - - pSym->nSectionID = readlong(f); - pSym->nOffset = readlong(f); - } - - return pSym; -} - -/* - * RGB object reader routines - */ -struct sSection *obj_ReadRGBSection(FILE *f) -{ - struct sSection *pSection; - char *pzName; - - readasciiz(&pzName, f); - if (IsSectionNameInUse(pzName)) - errx(1, "Section name \"%s\" is already in use.", pzName); - - pSection = AllocSection(); - pSection->pzName = pzName; - - pSection->nByteSize = readlong(f); - pSection->Type = (enum eSectionType)fgetc(f); - pSection->nOrg = readlong(f); - pSection->nBank = readlong(f); - pSection->nAlign = readlong(f); - - if ((options & OPT_TINY) && (pSection->Type == SECT_ROMX)) - errx(1, "ROMX sections can't be used with option -t."); - - if ((options & OPT_CONTWRAM) && (pSection->Type == SECT_WRAMX)) - errx(1, "WRAMX sections can't be used with options -w or -d."); - - if (options & OPT_DMG_MODE) { - /* WRAMX sections are checked for OPT_CONTWRAM */ - if (pSection->Type == SECT_VRAM && pSection->nBank == 1) - errx(1, "VRAM bank 1 can't be used with option -d."); - } - - uint32_t maxsize = 0; - - /* Verify that the section isn't too big */ - switch (pSection->Type) { - case SECT_ROM0: - maxsize = (options & OPT_TINY) ? 0x8000 : 0x4000; - break; - case SECT_ROMX: - maxsize = 0x4000; - break; - case SECT_VRAM: - case SECT_SRAM: - maxsize = 0x2000; - break; - case SECT_WRAM0: - maxsize = (options & OPT_CONTWRAM) ? 0x2000 : 0x1000; - break; - case SECT_WRAMX: - maxsize = 0x1000; - break; - case SECT_OAM: - maxsize = 0xA0; - break; - case SECT_HRAM: - maxsize = 0x7F; - break; - default: - errx(1, "Section \"%s\" has an invalid section type.", pzName); - break; - } - if (pSection->nByteSize > maxsize) { - errx(1, "Section \"%s\" is bigger than the max size for that type: 0x%X > 0x%X", - pzName, pSection->nByteSize, maxsize); - } - - /* - * If the section doesn't contain data, it is ready - */ - if ((pSection->Type != SECT_ROMX) && (pSection->Type != SECT_ROM0)) - return pSection; - - /* If there is no data to read, exit */ - if (pSection->nByteSize == 0) { - /* Skip number of patches */ - readlong(f); - pSection->pData = NULL; - return pSection; - } - - pSection->pData = malloc(pSection->nByteSize); - if (!pSection->pData) - err(1, "%s: Couldn't allocate memory", __func__); - - int32_t nNumberOfPatches; - struct sPatch **ppPatch, *pPatch; - - if (fread(pSection->pData, sizeof(uint8_t), pSection->nByteSize, f) - != pSection->nByteSize) { - err(1, "%s: Read error", __func__); - } - - nNumberOfPatches = readlong(f); - ppPatch = &pSection->pPatches; - - /* - * And patches... - */ - while (nNumberOfPatches--) { - pPatch = malloc(sizeof(*pPatch)); - if (!pPatch) - err(1, "%s: Couldn't allocate memory", __func__); - - *ppPatch = pPatch; - readasciiz(&pPatch->pzFilename, f); - pPatch->nLineNo = readlong(f); - pPatch->nOffset = readlong(f); - pPatch->Type = (enum ePatchType)fgetc(f); - pPatch->nRPNSize = readlong(f); - - if (pPatch->nRPNSize > 0) { - pPatch->pRPN = malloc(pPatch->nRPNSize); - if (!pPatch->pRPN) { - err(1, "%s: Couldn't allocate memory", - __func__); - } - - if (fread(pPatch->pRPN, sizeof(uint8_t), - pPatch->nRPNSize, f) != pPatch->nRPNSize) { - errx(1, "%s: Read error", __func__); - } - } else { - pPatch->pRPN = NULL; - } - - pPatch->pNext = NULL; - ppPatch = &(pPatch->pNext); - } - - return pSection; -} - -void obj_ReadRGB(FILE *pObjfile, char *tzObjectfile) -{ - struct sSection *pFirstSection; - int32_t nNumberOfSymbols, nNumberOfSections, i; - - nNumberOfSymbols = readlong(pObjfile); - nNumberOfSections = readlong(pObjfile); - - /* First comes the symbols */ - - if (nNumberOfSymbols) { - tSymbols = malloc(nNumberOfSymbols * sizeof(*tSymbols)); - if (!tSymbols) - err(1, "%s: Couldn't allocate memory", __func__); - - for (i = 0; i < nNumberOfSymbols; i += 1) - tSymbols[i] = obj_ReadSymbol(pObjfile, tzObjectfile); + tryReadstr(symbol->name, file, "%s: Cannot read symbol name: %s", + fileName); + tryGetc(symbol->type, file, "%s: Cannot read \"%s\"'s type: %s", + fileName, symbol->name); + /* If the symbol is defined in this file, read its definition */ + if (symbol->type != SYMTYPE_IMPORT) { + symbol->objFileName = fileName; + tryReadstr(symbol->fileName, file, + "%s: Cannot read \"%s\"'s file name: %s", + fileName, symbol->name); + tryReadlong(symbol->lineNo, file, + "%s: Cannot read \"%s\"'s line number: %s", + fileName, symbol->name); + tryReadlong(symbol->sectionID, file, + "%s: Cannot read \"%s\"'s section ID: %s", + fileName, symbol->name); + tryReadlong(symbol->offset, file, + "%s: Cannot read \"%s\"'s value: %s", + fileName, symbol->name); } else { - tSymbols = NULL; + symbol->sectionID = -1; + } +} + +static void readPatch(FILE *file, struct Patch *patch, + char const *fileName, char const *sectName, uint32_t i) +{ + tryReadstr(patch->fileName, file, + "%s: Unable to read \"%s\"'s patch #%u's name: %s", + fileName, sectName, i); + tryReadlong(patch->lineNo, file, + "%s: Unable to read \"%s\"'s patch #%u's line number: %s", + fileName, sectName, i); + tryReadlong(patch->offset, file, + "%s: Unable to read \"%s\"'s patch #%u's offset: %s", + fileName, sectName, i); + tryGetc(patch->type, file, + "%s: Unable to read \"%s\"'s patch #%u's type: %s", + fileName, sectName, i); + tryReadlong(patch->rpnSize, file, + "%s: Unable to read \"%s\"'s patch #%u's RPN size: %s", + fileName, sectName, i); + + uint8_t *rpnExpression = + malloc(sizeof(*rpnExpression) * patch->rpnSize); + size_t nbElementsRead = fread(rpnExpression, sizeof(*rpnExpression), + patch->rpnSize, file); + + if (nbElementsRead != patch->rpnSize) + errx(1, "%s: Cannot read \"%s\"'s patch #%u's RPN expression: %s", + fileName, sectName, i); + patch->rpnExpression = rpnExpression; +} + +static void readSection(FILE *file, struct Section *section, + char const *fileName) +{ + int32_t tmp; + + 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 (%d) is invalid", section->name, + tmp); + section->size = tmp; + tryGetc(section->type, file, "%s: Cannot read \"%s\"'s type: %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) + errx(1, "\"%s\" is too large (%d)", tmp); + section->org = tmp; + tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s", + fileName, section->name); + section->isBankFixed = tmp >= 0; + section->bank = tmp; + tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s alignment: %s", + fileName, section->name); + section->isAlignFixed = tmp != 1; + section->alignMask = tmp - 1; + + if (sect_HasData(section->type)) { + /* Ensure we never allocate 0 bytes */ + uint8_t *data = malloc(sizeof(*data) * section->size + 1); + + if (!data) + 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); + if (nbElementsRead != section->size) + errx(1, "%s: Cannot read \"%s\"'s data: %s", + fileName, section->name, + feof(file) ? "Unexpected end of file" + : strerror(errno)); + } + section->data = data; + + tryReadlong(section->nbPatches, file, + "%s: Cannot read \"%s\"'s number of patches: %s", + fileName, section->name); + + struct Patch *patches = + malloc(sizeof(*patches) * section->nbPatches + 1); + + if (!patches) + 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); + section->patches = patches; + } +} + +static void linkSymToSect(struct Symbol const *symbol, struct Section *section) +{ + uint32_t a = 0, b = section->nbSymbols; + + while (a != b) { + uint32_t c = (a + b) / 2; + + if (section->symbols[c]->offset > symbol->offset) + b = c; + else + a = c + 1; } - /* Next we have the sections */ + struct Symbol const *tmp = symbol; - pFirstSection = NULL; - while (nNumberOfSections--) { - struct sSection *pNewSection; - - pNewSection = obj_ReadRGBSection(pObjfile); - pNewSection->nNumberOfSymbols = nNumberOfSymbols; - if (pFirstSection == NULL) - pFirstSection = pNewSection; + for (uint32_t i = a; i <= section->nbSymbols; i++) { + symbol = tmp; + tmp = section->symbols[i]; + section->symbols[i] = symbol; } - /* - * Fill in the pSection entry in the symbolstructure. - * This REALLY needs some cleaning up... but, hey, it works - */ + section->nbSymbols++; +} - for (i = 0; i < nNumberOfSymbols; i += 1) { - struct sSection *pConvSect = pFirstSection; +static void readRGB6File(FILE *file, char const *fileName) +{ + uint32_t nbSymbols; + uint32_t nbSections; - if ((tSymbols[i]->Type != SYM_IMPORT) && - (tSymbols[i]->nSectionID != -1)) { - int32_t j = 0; + tryReadlong(nbSymbols, file, "%s: Cannot read number of symbols: %s", + fileName); + tryReadlong(nbSections, file, "%s: Cannot read number of sections: %s", + fileName); - while (j != tSymbols[i]->nSectionID) { - j += 1; - pConvSect = pConvSect->pNext; - } - tSymbols[i]->pSection = pConvSect; + nbSectionsToAssign += nbSections; + + /* This file's symbols, kept to link sections to them */ + struct Symbol **fileSymbols = + malloc(sizeof(*fileSymbols) * nbSymbols + 1); + + if (!fileSymbols) + err(1, "Failed to get memory for %s's symbols", fileName); + + struct SymbolList *symbolList = malloc(sizeof(*symbolList)); + + if (!symbolList) + err(1, "Failed to register %s's symbol list", fileName); + symbolList->symbolList = fileSymbols; + symbolList->nbSymbols = nbSymbols; + symbolList->next = symbolLists; + symbolLists = symbolList; + + uint32_t nbSymPerSect[nbSections]; + + memset(nbSymPerSect, 0, sizeof(nbSymPerSect)); + + verbosePrint("Reading %u symbols...\n", nbSymbols); + for (uint32_t i = 0; i < nbSymbols; i++) { + /* Read symbol */ + struct Symbol *symbol = malloc(sizeof(*symbol)); + + if (!symbol) + err(1, "%s: Couldn't create new symbol", fileName); + readSymbol(file, symbol, fileName); + + fileSymbols[i] = symbol; + if (symbol->type == SYMTYPE_EXPORT) + sym_AddSymbol(symbol); + if (symbol->sectionID != -1) + nbSymPerSect[symbol->sectionID]++; + } + + /* This file's sections, stored in a table to link symbols to them */ + struct Section *fileSections[nbSections ? nbSections : 1]; + + verbosePrint("Reading %u sections...\n", nbSections); + for (uint32_t i = 0; i < nbSections; i++) { + /* Read section */ + struct Section *section = malloc(sizeof(*section)); + + if (!section) + err(1, "%s: Couldn't create new section", fileName); + readSection(file, section, fileName); + section->fileSymbols = fileSymbols; + + sect_AddSection(section); + fileSections[i] = section; + if (nbSymPerSect[i]) { + section->symbols = malloc(sizeof(*section->symbols) + * nbSymPerSect[i]); + if (!section->symbols) + err(1, "%s: Couldn't link to symbols"); } else { - tSymbols[i]->pSection = NULL; + section->symbols = NULL; + } + section->nbSymbols = 0; + } + + /* Give symbols pointers to their sections */ + for (uint32_t i = 0; i < nbSymbols; i++) { + int32_t sectionID = fileSymbols[i]->sectionID; + + if (sectionID == -1) { + fileSymbols[i]->section = NULL; + } else { + fileSymbols[i]->section = fileSections[sectionID]; + /* Give the section a pointer to the symbol as well */ + linkSymToSect(fileSymbols[i], fileSections[sectionID]); } } } -/* - * The main objectfileloadroutine (phew) - */ -void obj_ReadOpenFile(FILE *pObjfile, char *tzObjectfile) +void obj_ReadFile(char const *fileName) { - char tzHeader[strlen(RGBDS_OBJECT_VERSION_STRING) + 1]; + FILE *file = strcmp("-", fileName) ? fopen(fileName, "rb") : stdin; - if (fread(tzHeader, sizeof(char), strlen(RGBDS_OBJECT_VERSION_STRING), - pObjfile) != strlen(RGBDS_OBJECT_VERSION_STRING)) { - errx(1, "%s: Read error", tzObjectfile); + if (!file) { + err(1, "Could not open file %s", fileName); + return; } - tzHeader[strlen(RGBDS_OBJECT_VERSION_STRING)] = 0; + /* Begin by reading the magic bytes and version number */ + uint8_t versionNumber; + int matchedElems = fscanf(file, RGBDS_OBJECT_VERSION_STRING, + &versionNumber); - if (strncmp(tzHeader, RGBDS_OBJECT_VERSION_STRING, - strlen(RGBDS_OBJECT_VERSION_STRING)) == 0) { - obj_ReadRGB(pObjfile, tzObjectfile); - } else { - int32_t i; + if (matchedElems != 1) + errx(1, "\"%s\" is not a RGBDS object file", fileName); + /* TODO: support other versions? */ + if (versionNumber != 6) + errx(1, "\"%s\" is an incompatible version %hhu object file", + fileName, versionNumber); - for (i = 0; i < strlen(RGBDS_OBJECT_VERSION_STRING); i++) - if (!isprint(tzHeader[i])) - tzHeader[i] = '?'; + verbosePrint("Reading object file %s, version %hhu\n", + fileName, versionNumber); - errx(1, "%s: Invalid file or object file version [%s]", - tzObjectfile, tzHeader); + readRGB6File(file, fileName); + + fclose(file); +} + +void obj_DoSanityChecks(void) +{ + sect_DoSanityChecks(); +} + +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++) { + struct Patch *patch = §ion->patches[i]; + + free(patch->fileName); + free(patch->rpnExpression); + } + free(section->patches); + } + free(section->symbols); + free(section); +} + +static void freeSymbol(struct Symbol *symbol) +{ + free(symbol->name); + if (symbol->type != SYMTYPE_IMPORT) + free(symbol->fileName); + free(symbol); +} + +void obj_Cleanup(void) +{ + 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]); + free(list->symbolList); + + struct SymbolList *next = list->next; + + free(list); + list = next; } } - -void obj_Readfile(char *tzObjectfile) -{ - FILE *pObjfile; - - if (options & OPT_SMART_C_LINK) - oReadLib = 1; - else - oReadLib = 0; - - pObjfile = fopen(tzObjectfile, "rb"); - if (pObjfile == NULL) - err(1, "Unable to open object '%s'", tzObjectfile); - - obj_ReadOpenFile(pObjfile, tzObjectfile); - fclose(pObjfile); - - oReadLib = 0; -} - -int32_t file_Length(FILE *f) -{ - uint32_t r, p; - - p = ftell(f); - fseek(f, 0, SEEK_END); - r = ftell(f); - fseek(f, p, SEEK_SET); - - return r; -} diff --git a/src/link/output.c b/src/link/output.c index 9b52d68d..81605c2b 100644 --- a/src/link/output.c +++ b/src/link/output.c @@ -1,174 +1,344 @@ -/* - * This file is part of RGBDS. - * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. - * - * SPDX-License-Identifier: MIT - */ -#include -#include #include -#include +#include + +#include "link/output.h" +#include "link/main.h" +#include "link/section.h" +#include "link/symbol.h" #include "extern/err.h" -#include "link/mylink.h" -#include "link/mapfile.h" -#include "link/main.h" -#include "link/assign.h" +struct SortedSection { + struct Section const *section; + struct SortedSection *next; +}; -char *tzOutname; -char *tzOverlayname; +static struct { + uint32_t nbBanks; + struct SortedSections { + struct SortedSection *sections; + struct SortedSection *zeroLenSections; + } *banks; +} sections[SECTTYPE_INVALID]; -int32_t MaxOverlayBank; - -void writehome(FILE *f, FILE *f_overlay) +void out_AddSection(struct Section const *section) { - const struct sSection *pSect; - uint8_t *mem; + static uint32_t maxNbBanks[] = { + [SECTTYPE_ROM0] = 1, + [SECTTYPE_ROMX] = UINT32_MAX, + [SECTTYPE_VRAM] = 2, + [SECTTYPE_SRAM] = UINT32_MAX, + [SECTTYPE_WRAM0] = 1, + [SECTTYPE_WRAMX] = 7, + [SECTTYPE_OAM] = 1, + [SECTTYPE_HRAM] = 1 + }; - mem = malloc(MaxAvail[BANK_INDEX_ROM0]); - if (!mem) + uint32_t targetBank = section->bank - bankranges[section->type][0]; + uint32_t minNbBanks = targetBank + 1; + + if (minNbBanks > maxNbBanks[section->type]) + errx(1, "Section \"%s\" has invalid bank range (%u > %u)", + section->name, section->bank, + maxNbBanks[section->type] - 1); + + if (minNbBanks > sections[section->type].nbBanks) { + sections[section->type].banks = + realloc(sections[section->type].banks, + sizeof(*sections[0].banks) * minNbBanks); + for (uint32_t i = sections[section->type].nbBanks; + i < minNbBanks; i++) { + sections[section->type].banks[i].sections = NULL; + sections[section->type].banks[i].zeroLenSections = NULL; + } + sections[section->type].nbBanks = minNbBanks; + } + if (!sections[section->type].banks) + err(1, "Failed to realloc banks"); + + struct SortedSection *newSection = malloc(sizeof(*newSection)); + struct SortedSection **ptr = section->size + ? §ions[section->type].banks[targetBank].sections + : §ions[section->type].banks[targetBank].zeroLenSections; + + if (!newSection) + err(1, "Failed to add new section \"%s\"", section->name); + newSection->section = section; + + while (*ptr && (*ptr)->section->org < section->org) + ptr = &(*ptr)->next; + + newSection->next = *ptr; + *ptr = newSection; +} + +static void checkOverlay(void) +{ + if (!overlayFile) return; - if (f_overlay != NULL) { - fseek(f_overlay, 0L, SEEK_SET); - if (fread(mem, 1, MaxAvail[BANK_INDEX_ROM0], f_overlay) != - MaxAvail[BANK_INDEX_ROM0]) { - warnx("Failed to read data from overlay file."); - } - } else { - memset(mem, fillchar, MaxAvail[BANK_INDEX_ROM0]); - } - MapfileInitBank(0); - - pSect = pSections; - while (pSect) { - if (pSect->Type == SECT_ROM0) { - memcpy(mem + pSect->nOrg, pSect->pData, - pSect->nByteSize); - MapfileWriteSection(pSect); - } - pSect = pSect->pNext; + if (fseek(overlayFile, 0, SEEK_END) != 0) { + warnx("Overlay file is not seekable, cannot check if properly formed"); + return; } - MapfileCloseBank(area_Avail(0)); + long overlaySize = ftell(overlayFile); - fwrite(mem, 1, MaxAvail[BANK_INDEX_ROM0], f); - free(mem); + if (overlaySize % 0x4000) + errx(1, "Overlay file must have a size multiple of 0x4000"); + + uint32_t nbOverlayBanks = overlaySize / 0x4000 - 1; + + if (nbOverlayBanks < 1) + errx(1, "Overlay must be at least 0x8000 bytes large"); + + if (nbOverlayBanks > sections[SECTTYPE_ROMX].nbBanks) { + sections[SECTTYPE_ROMX].banks = + realloc(sections[SECTTYPE_ROMX].banks, + sizeof(*sections[SECTTYPE_ROMX].banks) * + nbOverlayBanks); + if (!sections[SECTTYPE_ROMX].banks) + err(1, "Failed to realloc banks for overlay"); + sections[SECTTYPE_ROMX].nbBanks = nbOverlayBanks; + } } -void writebank(FILE *f, FILE *f_overlay, int32_t bank) +static void writeBank(struct SortedSection *bankSections, uint16_t baseOffset, + uint16_t size) { - const struct sSection *pSect; - uint8_t *mem; + uint16_t offset = 0; - mem = malloc(MaxAvail[bank]); - if (!mem) + while (bankSections) { + struct Section const *section = bankSections->section; + + /* Output padding up to the next SECTION */ + while (offset + baseOffset < section->org) { + putc_unlocked(overlayFile ? getc_unlocked(overlayFile) + : padValue, + outputFile); + offset++; + } + + /* Output the section itself */ + fwrite(section->data, sizeof(*section->data), section->size, + outputFile); + if (overlayFile) { + /* Skip bytes even with pipes */ + for (uint16_t i = 0; i < section->size; i++) + getc_unlocked(overlayFile); + } + offset += section->size; + + bankSections = bankSections->next; + } + + while (offset < size) { + putc_unlocked(overlayFile ? getc_unlocked(overlayFile) + : padValue, + outputFile); + offset++; + } +} + +static void writeROM(void) +{ + checkOverlay(); + + if (outputFile) { + flockfile(outputFile); + if (overlayFile) + flockfile(overlayFile); + + if (sections[SECTTYPE_ROM0].nbBanks > 0) + writeBank(sections[SECTTYPE_ROM0].banks[0].sections, + 0x0000, 0x4000); + + for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++) + writeBank(sections[SECTTYPE_ROMX].banks[i].sections, + 0x4000, 0x4000); + + if (overlayFile) + funlockfile(overlayFile); + funlockfile(outputFile); + } +} + +static struct SortedSection const **nextSection(struct SortedSection const **s1, + struct SortedSection const **s2) +{ + if (!*s1) + return s2; + if (!*s2) + return s1; + + return (*s1)->section->org < (*s2)->section->org ? s1 : s2; +} + +static void writeSymBank(struct SortedSections const *bankSections) +{ + if (!symFile) return; - if (f_overlay != NULL && bank <= MaxOverlayBank) { - fseek(f_overlay, bank * 0x4000, SEEK_SET); - if (fread(mem, 1, MaxAvail[bank], f_overlay) != MaxAvail[bank]) - warnx("Failed to read data from overlay file."); - } else { - memset(mem, fillchar, MaxAvail[bank]); - } - MapfileInitBank(bank); + struct { + struct SortedSection const *sections; +#define sect sections->section /* Fake member as a shortcut */ + uint32_t i; + struct Symbol const *sym; + uint16_t addr; + } sectList = { .sections = bankSections->sections, .i = 0 }, + zlSectList = { .sections = bankSections->zeroLenSections, .i = 0 }, + *minSectList; - pSect = pSections; - while (pSect) { - if (pSect->Type == SECT_ROMX && pSect->nBank == bank) { - memcpy(mem + pSect->nOrg - 0x4000, pSect->pData, - pSect->nByteSize); - MapfileWriteSection(pSect); + for (;;) { + while (sectList.sections + && sectList.i == sectList.sect->nbSymbols) { + sectList.sections = sectList.sections->next; + sectList.i = 0; } - pSect = pSect->pNext; - } - - MapfileCloseBank(area_Avail(bank)); - - fwrite(mem, 1, MaxAvail[bank], f); - free(mem); -} - -void out_Setname(char *tzOutputfile) -{ - tzOutname = tzOutputfile; -} - -void out_SetOverlayname(char *tzOverlayfile) -{ - tzOverlayname = tzOverlayfile; -} - -void Output(void) -{ - int32_t i; - FILE *f; - FILE *f_overlay = NULL; - - /* - * Load overlay - */ - - if (tzOverlayname) { - f_overlay = fopen(tzOverlayname, "rb"); - - if (!f_overlay) { - errx(1, "Failed to open overlay file %s\n", - tzOverlayname); + while (zlSectList.sections + && zlSectList.i == zlSectList.sect->nbSymbols) { + zlSectList.sections = zlSectList.sections->next; + zlSectList.i = 0; } - fseek(f_overlay, 0, SEEK_END); + if (!sectList.sections && !zlSectList.sections) { + break; + } else if (sectList.sections && zlSectList.sections) { + sectList.sym = sectList.sect->symbols[sectList.i]; + zlSectList.sym = zlSectList.sect->symbols[zlSectList.i]; + sectList.addr = + sectList.sym->offset + sectList.sect->org; + zlSectList.addr = + zlSectList.sym->offset + zlSectList.sect->org; - if (ftell(f_overlay) % 0x4000 != 0) - errx(1, "Overlay file must be aligned to 0x4000 bytes."); + minSectList = sectList.addr < zlSectList.addr + ? §List + : &zlSectList; + } else if (sectList.sect) { + sectList.sym = sectList.sect->symbols[sectList.i]; + sectList.addr = + sectList.sym->offset + sectList.sect->org; - MaxOverlayBank = (ftell(f_overlay) / 0x4000) - 1; + minSectList = §List; + } else { + zlSectList.sym = zlSectList.sect->symbols[zlSectList.i]; + zlSectList.addr = + zlSectList.sym->offset + zlSectList.sect->org; - if (MaxOverlayBank < 1) - errx(1, "Overlay file must be at least 0x8000 bytes."); - - if (MaxOverlayBank > MaxBankUsed) - MaxBankUsed = MaxOverlayBank; - } - - /* - * Write ROM. - */ - - f = fopen(tzOutname, "wb"); - if (f != NULL) { - writehome(f, f_overlay); - for (i = 1; i <= MaxBankUsed; i += 1) - writebank(f, f_overlay, i); - - fclose(f); - } - - /* - * Close overlay - */ - - if (tzOverlayname) - fclose(f_overlay); - - /* - * Add regular sections to map and sym files. - */ - - for (i = BANK_INDEX_WRAM0; i < BANK_INDEX_MAX; i++) { - const struct sSection *pSect; - - MapfileInitBank(i); - pSect = pSections; - while (pSect) { - if (pSect->nBank == i) - MapfileWriteSection(pSect); - pSect = pSect->pNext; + minSectList = &zlSectList; + } + fprintf(symFile, "%02x:%04x %s\n", + minSectList->sect->bank, minSectList->addr, + minSectList->sym->name); + minSectList->i++; + } +#undef sect +} + +static void writeMapBank(struct SortedSections const *sectList, + enum SectionType type, uint32_t bank) +{ + if (!mapFile) + return; + + struct SortedSection const *section = sectList->sections; + struct SortedSection const *zeroLenSection = sectList->zeroLenSections; + + fprintf(mapFile, "%s bank #%u:\n", typeNames[type], + bank + bankranges[type][0]); + + uint16_t slack = maxsize[type]; + + while (section || zeroLenSection) { + struct SortedSection const **pickedSection = + nextSection(§ion, &zeroLenSection); + struct Section const *sect = (*pickedSection)->section; + + slack -= sect->size; + + fprintf(mapFile, " SECTION: $%04x-$%04x ($%04x byte%s) [\"%s\"]\n", + sect->org, sect->org + sect->size - 1, sect->size, + sect->size == 1 ? "" : "s", sect->name); + + for (size_t i = 0; i < sect->nbSymbols; i++) + fprintf(mapFile, " $%04x = %s\n", + sect->symbols[i]->offset + sect->org, + sect->symbols[i]->name); + + *pickedSection = (*pickedSection)->next; + } + + if (slack == maxsize[type]) + fputs(" EMPTY\n\n", mapFile); + else + fprintf(mapFile, " SLACK: $%04x byte%s\n\n", slack, + slack == 1 ? "" : "s"); +} + +static void writeSymAndMap(void) +{ + if (!symFile && !mapFile) + return; + + enum SectionType typeMap[SECTTYPE_INVALID] = { + SECTTYPE_ROM0, + SECTTYPE_ROMX, + SECTTYPE_VRAM, + SECTTYPE_SRAM, + SECTTYPE_WRAM0, + SECTTYPE_WRAMX, + SECTTYPE_OAM, + SECTTYPE_HRAM + }; + + if (symFile) + fputs("; File generated by rgblink\n", symFile); + + for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) { + enum SectionType type = typeMap[i]; + + if (sections[type].nbBanks > 0) { + for (uint32_t bank = 0; bank < sections[type].nbBanks; + bank++) { + writeSymBank(§ions[type].banks[bank]); + writeMapBank(§ions[type].banks[bank], + type, bank); + } } - MapfileCloseBank(area_Avail(i)); } } + +static void cleanupSections(struct SortedSection *section) +{ + while (section) { + struct SortedSection *next = section->next; + + free(section); + section = next; + } +} + +static void cleanup(void) +{ + for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) { + if (sections[type].nbBanks > 0) { + for (uint32_t i = 0; i < sections[type].nbBanks; i++) { + struct SortedSections *bank = + §ions[type].banks[i]; + + cleanupSections(bank->sections); + cleanupSections(bank->zeroLenSections); + } + free(sections[type].banks); + } + } +} + +void out_WriteFiles(void) +{ + writeROM(); + writeSymAndMap(); + + cleanup(); +} diff --git a/src/link/parser.y b/src/link/parser.y deleted file mode 100644 index 8e6a2094..00000000 --- a/src/link/parser.y +++ /dev/null @@ -1,127 +0,0 @@ -/* - * This file is part of RGBDS. - * - * Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors. - * - * SPDX-License-Identifier: MIT - */ - -%{ -#include -#include - -#include "extern/err.h" - -#include "link/script.h" - -int yylex(void); -void yyerror(char *); - -extern int yylineno; -%} - -%union { - int32_t i; - char s[512]; -} - -%token INTEGER -%token STRING - -%token SECTION_NONBANKED -%token SECTION_BANKED - -%token COMMAND_ALIGN -%token COMMAND_ORG - -%token COMMAND_INCLUDE - -%token NEWLINE - -%start lines - -%% - -lines: - /* empty */ - | lines line NEWLINE -; - -line: - /* empty */ - | statement -; - -statement: - /* Statements to set the current section */ - SECTION_NONBANKED - { - script_SetCurrentSectionType($1, 0); - } - | SECTION_NONBANKED INTEGER - { - script_fatalerror("Trying to assign a bank to a non-banked section.\n"); - } - - | SECTION_BANKED - { - script_fatalerror("Banked section without assigned bank.\n"); - } - | SECTION_BANKED INTEGER - { - script_SetCurrentSectionType($1, $2); - } - - /* Commands to adjust the address inside the current section */ - | COMMAND_ALIGN INTEGER - { - script_SetAlignment($2); - } - | COMMAND_ALIGN - { - script_fatalerror("ALIGN keyword needs an argument.\n"); - } - | COMMAND_ORG INTEGER - { - script_SetAddress($2); - } - | COMMAND_ORG - { - script_fatalerror("ORG keyword needs an argument.\n"); - } - - /* Section name */ - | STRING - { - script_OutputSection($1); - } - - /* Include file */ - | COMMAND_INCLUDE STRING - { - script_IncludeFile($2); - } - - /* End */ -; - -%% - -extern int yylex(void); -extern int yyparse(void); - -int yywrap(void) -{ - if (script_IncludeDepthGet() == 0) - return 1; - - script_IncludePop(); - - return 0; -} - -void yyerror(char *s) -{ - script_fatalerror("Linkerscript parse error: \"%s\"\n", s); -} - diff --git a/src/link/patch.c b/src/link/patch.c index 44dfaa93..bc86fb06 100644 --- a/src/link/patch.c +++ b/src/link/patch.c @@ -1,344 +1,328 @@ -/* - * This file is part of RGBDS. - * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. - * - * SPDX-License-Identifier: MIT - */ -#include -#include #include +#include #include +#include "link/patch.h" +#include "link/section.h" +#include "link/symbol.h" + +#include "linkdefs.h" + #include "extern/err.h" -#include "link/assign.h" -#include "link/main.h" -#include "link/mylink.h" -#include "link/symbol.h" +/* This is an "empty"-type stack */ +struct RPNStack { + int32_t *buf; + size_t size; + size_t capacity; +} stack; -#define RPN_STACK_SIZE 256 - -static struct sSection *pCurrentSection; -static int32_t rpnstack[RPN_STACK_SIZE]; -static int32_t rpnp; -int32_t nPC; - -static void rpnpush(int32_t i) +static inline void initRPNStack(void) { - if (rpnp >= RPN_STACK_SIZE) - errx(1, "RPN stack overflow"); - - rpnstack[rpnp] = i; - rpnp++; + stack.capacity = 64; + stack.buf = malloc(sizeof(*stack.buf) * stack.capacity); + if (!stack.buf) + err(1, "Failed to init RPN stack"); } -static int32_t rpnpop(void) +static inline void clearRPNStack(void) { - rpnp--; - return rpnstack[rpnp]; + stack.size = 0; } -static int32_t getsymvalue(struct sPatch *pPatch, int32_t symid) +static void pushRPN(int32_t value) { - const struct sSymbol *tSymbol = pCurrentSection->tSymbols[symid]; - - switch (tSymbol->Type) { - case SYM_IMPORT: - return sym_GetValue(pPatch, tSymbol->pzName); - - case SYM_EXPORT: - case SYM_LOCAL: - if (strcmp(tSymbol->pzName, "@") == 0) - return nPC; - - return tSymbol->nOffset + tSymbol->pSection->nOrg; - - default: - break; + if (stack.size >= stack.capacity) { + stack.capacity *= 2; + stack.buf = + realloc(stack.buf, sizeof(*stack.buf) * stack.capacity); + if (!stack.buf) + err(1, "Failed to resize RPN stack"); } - errx(1, "%s: Unknown symbol type", __func__); + stack.buf[stack.size] = value; + stack.size++; } -static int32_t getrealbankfrominternalbank(int32_t n) +static int32_t popRPN(void) { - if (BankIndexIsWRAM0(n) || BankIndexIsROM0(n) || - BankIndexIsOAM(n) || BankIndexIsHRAM(n)) { - return 0; - } else if (BankIndexIsROMX(n)) { - return n - BANK_INDEX_ROMX + 1; - } else if (BankIndexIsWRAMX(n)) { - return n - BANK_INDEX_WRAMX + 1; - } else if (BankIndexIsVRAM(n)) { - return n - BANK_INDEX_VRAM; - } else if (BankIndexIsSRAM(n)) { - return n - BANK_INDEX_SRAM; - } + if (stack.size == 0) + errx(1, "Internal error, RPN stack empty"); - return n; + stack.size--; + return stack.buf[stack.size]; } -static int32_t getsymbank(struct sPatch *pPatch, int32_t symid) +static inline void freeRPNStack(void) { - int32_t nBank; - const struct sSymbol *tSymbol = pCurrentSection->tSymbols[symid]; - - switch (tSymbol->Type) { - case SYM_IMPORT: - nBank = sym_GetBank(pPatch, tSymbol->pzName); - break; - case SYM_EXPORT: - case SYM_LOCAL: - nBank = tSymbol->pSection->nBank; - break; - default: - errx(1, "%s: Unknown symbol type", __func__); - } - - return getrealbankfrominternalbank(nBank); + free(stack.buf); } -int32_t calcrpn(struct sPatch *pPatch) +/* RPN operators */ + +static uint8_t getRPNByte(uint8_t const **expression, int32_t *size, + char const *fileName, int32_t lineNo) { - int32_t t, size; - uint8_t *rpn; - uint8_t rpn_cmd; - int32_t nBank; + if (!(*size)--) + errx(1, "%s(%d): RPN expression overread", fileName, lineNo); + return *(*expression)++; +} - rpnp = 0; +static int32_t computeRPNExpr(struct Patch const *patch, + struct Section const *section) +{ + uint8_t const *expression = patch->rpnExpression; + int32_t size = patch->rpnSize; - size = pPatch->nRPNSize; - rpn = pPatch->pRPN; - pPatch->oRelocPatch = 0; + clearRPNStack(); while (size > 0) { - size -= 1; - rpn_cmd = *rpn++; + enum RPNCommand command = getRPNByte(&expression, &size, + patch->fileName, + patch->lineNo); + int32_t value; + + /* + * Friendly reminder: + * Be VERY careful with two `popRPN` in the same expression. + * C does not guarantee order of evaluation of operands!! + * So, if there are two `popRPN` in the same expression, make + * sure the operation is commutative. + */ + switch (command) { + struct Symbol const *symbol; + char const *name; + struct Section const *sect; - switch (rpn_cmd) { case RPN_ADD: - rpnpush(rpnpop() + rpnpop()); + value = popRPN() + popRPN(); break; case RPN_SUB: - t = rpnpop(); - rpnpush(rpnpop() - t); + value = popRPN(); + value = popRPN() - value; break; case RPN_MUL: - rpnpush(rpnpop() * rpnpop()); + value = popRPN() * popRPN(); break; case RPN_DIV: - t = rpnpop(); - rpnpush(rpnpop() / t); + value = popRPN(); + value = popRPN() / value; break; case RPN_MOD: - t = rpnpop(); - rpnpush(rpnpop() % t); + value = popRPN(); + value = popRPN() % value; break; case RPN_UNSUB: - rpnpush(-rpnpop()); + value = -popRPN(); break; + case RPN_OR: - rpnpush(rpnpop() | rpnpop()); + value = popRPN() | popRPN(); break; case RPN_AND: - rpnpush(rpnpop() & rpnpop()); + value = popRPN() & popRPN(); break; case RPN_XOR: - rpnpush(rpnpop() ^ rpnpop()); + value = popRPN() ^ popRPN(); break; case RPN_UNNOT: - rpnpush(~rpnpop()); + value = ~popRPN(); break; + case RPN_LOGAND: - rpnpush(rpnpop() && rpnpop()); + value = popRPN(); + value = popRPN() && value; break; case RPN_LOGOR: - rpnpush(rpnpop() || rpnpop()); + value = popRPN(); + value = popRPN() || value; break; case RPN_LOGUNNOT: - rpnpush(!rpnpop()); + value = !popRPN(); break; + case RPN_LOGEQ: - rpnpush(rpnpop() == rpnpop()); + value = popRPN() == popRPN(); break; case RPN_LOGNE: - rpnpush(rpnpop() != rpnpop()); + value = popRPN() != popRPN(); break; case RPN_LOGGT: - t = rpnpop(); - rpnpush(rpnpop() > t); + value = popRPN(); + value = popRPN() > value; break; case RPN_LOGLT: - t = rpnpop(); - rpnpush(rpnpop() < t); + value = popRPN(); + value = popRPN() < value; break; case RPN_LOGGE: - t = rpnpop(); - rpnpush(rpnpop() >= t); + value = popRPN(); + value = popRPN() >= value; break; case RPN_LOGLE: - t = rpnpop(); - rpnpush(rpnpop() <= t); + value = popRPN(); + value = popRPN() <= value; break; + + /* FIXME: sanitize shifts */ case RPN_SHL: - t = rpnpop(); - rpnpush(rpnpop() << t); + value = popRPN(); + value = popRPN() << value; break; case RPN_SHR: - t = rpnpop(); - rpnpush(rpnpop() >> t); - break; - case RPN_HRAM: - t = rpnpop(); - rpnpush(t & 0xFF); - if (t < 0 || (t > 0xFF && t < 0xFF00) || t > 0xFFFF) { - errx(1, - "%s(%ld) : Value must be in the HRAM area", - pPatch->pzFilename, pPatch->nLineNo); - } - break; - case RPN_CONST: - /* constant */ - t = (*rpn++); - t |= (*rpn++) << 8; - t |= (*rpn++) << 16; - t |= (*rpn++) << 24; - rpnpush(t); - size -= 4; - break; - case RPN_SYM: - /* symbol */ - t = (*rpn++); - t |= (*rpn++) << 8; - t |= (*rpn++) << 16; - t |= (*rpn++) << 24; - rpnpush(getsymvalue(pPatch, t)); - pPatch->oRelocPatch |= (getsymbank(pPatch, t) != -1); - size -= 4; + value = popRPN(); + value = popRPN() >> value; break; + case RPN_BANK_SYM: - /* symbol */ - t = (*rpn++); - t |= (*rpn++) << 8; - t |= (*rpn++) << 16; - t |= (*rpn++) << 24; - rpnpush(getsymbank(pPatch, t)); - size -= 4; + value = 0; + for (uint8_t shift = 0; shift < 32; shift += 8) + value |= getRPNByte(&expression, &size, + patch->fileName, + patch->lineNo) << shift; + + symbol = section->fileSymbols[value]; + + /* If the symbol is defined elsewhere... */ + if (symbol->type == SYMTYPE_IMPORT) { + symbol = sym_GetSymbol(symbol->name); + if (!symbol) + errx(1, "%s(%d): Unknown symbol \"%s\"", + patch->fileName, patch->lineNo, + symbol->name); + } + + value = symbol->section->bank; break; + case RPN_BANK_SECT: - { - char *name = (char *)rpn; + name = (char const *)expression; + while (getRPNByte(&expression, &size, patch->fileName, + patch->lineNo)) + ; - struct sSection *pSection = GetSectionByName(name); + sect = sect_GetSection(name); - if (pSection == NULL) { - errx(1, - "%s(%ld) : Requested BANK() of section \"%s\", which was not found.\n", - pPatch->pzFilename, pPatch->nLineNo, - name); - } + if (!sect) + errx(1, "%s(%d): Requested BANK() of section \"%s\", which was not found", + patch->fileName, patch->lineNo, name); - nBank = pSection->nBank; - rpnpush(getrealbankfrominternalbank(nBank)); - - int len = strlen(name); - - size -= len + 1; - rpn += len + 1; + value = sect->bank; break; - } + case RPN_BANK_SELF: - nBank = pCurrentSection->nBank; - rpnpush(getrealbankfrominternalbank(nBank)); + value = section->bank; break; - default: - errx(1, "%s: Invalid command %d\n", __func__, - rpn_cmd); + + case RPN_HRAM: + value = popRPN(); + if (value < 0 + || (value > 0xFF && value < 0xFF00) + || value > 0xFFFF) + errx(1, "%s(%d): Value %d is not in HRAM range", + patch->fileName, patch->lineNo, value); + value &= 0xFF; break; - } - } - return rpnpop(); -} -void Patch(void) -{ - struct sSection *pSect; + case RPN_CONST: + value = 0; + for (uint8_t shift = 0; shift < 32; shift += 8) + value |= getRPNByte(&expression, &size, + patch->fileName, + patch->lineNo) << shift; + break; - pSect = pSections; - while (pSect) { - struct sPatch *pPatch; + case RPN_SYM: + value = 0; + for (uint8_t shift = 0; shift < 32; shift += 8) + value |= getRPNByte(&expression, &size, + patch->fileName, + patch->lineNo) << shift; - pCurrentSection = pSect; - pPatch = pSect->pPatches; - while (pPatch) { - int32_t t; - int32_t nPatchOrg; + symbol = section->fileSymbols[value]; - nPC = pSect->nOrg + pPatch->nOffset; - t = calcrpn(pPatch); - switch (pPatch->Type) { - case PATCH_BYTE: - if (t >= -128 && t <= 255) { - t &= 0xFF; - pSect->pData[pPatch->nOffset] = - (uint8_t)t; - } else { - errx(1, - "%s(%ld) : Value must be 8-bit", - pPatch->pzFilename, - pPatch->nLineNo); - } - break; - case PATCH_WORD_L: - if (t >= -32768 && t <= 65535) { - t &= 0xFFFF; - pSect->pData[pPatch->nOffset] = - t & 0xFF; - pSect->pData[pPatch->nOffset + 1] = - (t >> 8) & 0xFF; - } else { - errx(1, - "%s(%ld) : Value must be 16-bit", - pPatch->pzFilename, - pPatch->nLineNo); - } - break; - case PATCH_LONG_L: - pSect->pData[pPatch->nOffset + 0] = t & 0xFF; - pSect->pData[pPatch->nOffset + 1] = - (t >> 8) & 0xFF; - pSect->pData[pPatch->nOffset + 2] = - (t >> 16) & 0xFF; - pSect->pData[pPatch->nOffset + 3] = - (t >> 24) & 0xFF; - break; - case PATCH_BYTE_JR: - /* Calculate absolute address of the patch */ - nPatchOrg = pSect->nOrg + pPatch->nOffset; - - /* t contains the destination of the jump */ - t = (int16_t)((t & 0xFFFF) - (nPatchOrg + 1)); - - if (t >= -128 && t <= 127) { - t &= 0xFF; - pSect->pData[pPatch->nOffset] = - (uint8_t)t; - } else { - errx(1, - "%s(%ld) : Value must be 8-bit", - pPatch->pzFilename, - pPatch->nLineNo); - } - break; - default: - errx(1, "%s: Internal error.", __func__); + /* If the symbol is defined elsewhere... */ + if (symbol->type == SYMTYPE_IMPORT) { + symbol = sym_GetSymbol(symbol->name); + if (!symbol) + errx(1, "%s(%d): Unknown symbol \"%s\"", + patch->fileName, patch->lineNo, + symbol->name); } - pPatch = pPatch->pNext; + if (!strcmp(symbol->name, "@")) { + value = section->org + patch->offset; + } else { + value = symbol->value; + /* Symbols attached to sections have offsets */ + if (symbol->section) + value += symbol->section->org; + } + break; } - pSect = pSect->pNext; + pushRPN(value); + } + + if (stack.size > 1) + warnx("%s(%d): RPN stack has %lu entries on exit, not 1", + patch->fileName, patch->lineNo, stack.size); + + return popRPN(); +} + +static void applyPatches(struct Section *section, void *arg) +{ + (void)arg; + + if (!sect_HasData(section->type)) + return; + + verbosePrint("Patching section \"%s\"...\n", section->name); + for (uint32_t patchID = 0; patchID < section->nbPatches; patchID++) { + struct Patch *patch = §ion->patches[patchID]; + int32_t value = computeRPNExpr(patch, section); + + if (patch->type == PATCHTYPE_JR) { + /* `jr` is quite unlike the others... */ + uint16_t address = section->org + patch->offset; + /* Target is relative to the byte *after* the operand */ + int32_t offset = value - (address + 1); + + if (offset < -128 || offset > 127) + errx(1, "%s(%d): jr target out of reach (%d)", + patch->fileName, patch->lineNo, offset); + section->data[patch->offset] = offset & 0xFF; + } else { + /* Patch a certain number of bytes */ + struct { + uint8_t size; + int32_t min; + int32_t max; + } const types[] = { + [PATCHTYPE_BYTE] = {1, -128, 255}, + [PATCHTYPE_WORD] = {2, -32768, 65536}, + [PATCHTYPE_LONG] = {4, INT32_MIN, INT32_MAX} + }; + + if (value < types[patch->type].min + || value > types[patch->type].max) + errx(1, "%s(%d): Value %#x%s is not %u-bit", + patch->fileName, patch->lineNo, value, + value < 0 ? " (maybe negative?)" : "", + types[patch->type].size * 8); + for (uint8_t i = 0; i < types[patch->type].size; i++) { + section->data[patch->offset + i] = value & 0xFF; + value >>= 8; + } + } } } + +void patch_ApplyPatches(void) +{ + initRPNStack(); + sect_ForEach(applyPatches, NULL); + freeRPNStack(); +} diff --git a/src/link/script.c b/src/link/script.c index bf8a436f..0f499370 100644 --- a/src/link/script.c +++ b/src/link/script.c @@ -1,237 +1,410 @@ -/* - * This file is part of RGBDS. - * - * Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors. - * - * SPDX-License-Identifier: MIT - */ +#include +#include #include #include +#include + +#include "link/main.h" +#include "link/script.h" +#include "link/section.h" #include "extern/err.h" -#include "link/assign.h" -#include "link/mylink.h" - -static struct { - uint32_t address; /* current address to write sections to */ - uint32_t top_address; /* not inclusive */ - enum eSectionType type; -} bank[BANK_INDEX_MAX]; - -static int32_t current_bank = -1; /* Bank as seen by the bank array */ -static int32_t current_real_bank = -1; /* bank as seen by the GB */ - -/* Current section attributes */ -static int32_t fix_org = -1; -static int32_t fix_align = 1; - -void script_InitSections(void) +static inline bool isWhiteSpace(int c) { - int32_t i; + return c == ' ' || c == '\t'; +} - for (i = 0; i < BANK_INDEX_MAX; i++) { - if (BankIndexIsROM0(i)) { - /* ROM0 bank */ - bank[i].address = 0x0000; - if (options & OPT_TINY) - bank[i].top_address = 0x8000; - else - bank[i].top_address = 0x4000; - bank[i].type = SECT_ROM0; - } else if (BankIndexIsROMX(i)) { - /* Swappable ROM bank */ - bank[i].address = 0x4000; - bank[i].top_address = 0x8000; - bank[i].type = SECT_ROMX; - } else if (BankIndexIsWRAM0(i)) { - /* WRAM */ - bank[i].address = 0xC000; - if (options & OPT_CONTWRAM) - bank[i].top_address = 0xE000; - else - bank[i].top_address = 0xD000; - bank[i].type = SECT_WRAM0; - } else if (BankIndexIsSRAM(i)) { - /* Swappable SRAM bank */ - bank[i].address = 0xA000; - bank[i].top_address = 0xC000; - bank[i].type = SECT_SRAM; - } else if (BankIndexIsWRAMX(i)) { - /* Swappable WRAM bank */ - bank[i].address = 0xD000; - bank[i].top_address = 0xE000; - bank[i].type = SECT_WRAMX; - } else if (BankIndexIsVRAM(i)) { - /* Swappable VRAM bank */ - bank[i].address = 0x8000; - bank[i].type = SECT_VRAM; - if (options & OPT_DMG_MODE && i != BANK_INDEX_VRAM) { - /* In DMG the only available bank is bank 0. */ - bank[i].top_address = 0x8000; - } else { - bank[i].top_address = 0xA000; +static inline bool isNewline(int c) +{ + return c == '\r' || c == '\n'; +} + +static bool tryParseNumber(char const *str, uint32_t *number) +{ + static char const digits[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F' + }; + uint8_t base = 10; + + if (*str == '$') { + str++; + base = 16; + } + + /* An empty string is not a number */ + if (!*str) + return false; + + *number = 0; + do { + char chr = toupper(*str++); + uint8_t digit = 0; + + while (digit < base) { + if (chr == digits[digit]) + break; + digit++; + } + if (digit == base) + return false; + *number = *number * base + digit; + } while (*str); + + return true; +} + +enum LinkerScriptTokenType { + TOKEN_NEWLINE, + TOKEN_COMMAND, + TOKEN_BANK, + TOKEN_NUMBER, + TOKEN_SECTION, + TOKEN_EOF, + + TOKEN_INVALID +}; + +enum LinkerScriptCommand { + COMMAND_ORG, + COMMAND_ALIGN, + + COMMAND_INVALID +}; + +struct LinkerScriptToken { + enum LinkerScriptTokenType type; + union LinkerScriptTokenAttr { + enum LinkerScriptCommand command; + enum SectionType secttype; + uint32_t number; + char *string; + } attr; +}; + +static char const * const commands[] = { + [COMMAND_ORG] = "ORG", + [COMMAND_ALIGN] = "ALIGN" +}; + +static uint32_t lineNo; + +static int readChar(FILE *file) +{ + int curchar = getc_unlocked(file); + + if (curchar == EOF && ferror(file)) + err(1, "%s: Unexpected error reading linker script", __func__); + return curchar; +} + +static struct LinkerScriptToken const *nextToken(void) +{ + static struct LinkerScriptToken token; + int curchar; + + /* If the token has a string, make sure to avoid leaking it */ + if (token.type == TOKEN_SECTION) + free(token.attr.string); + + /* Skip initial whitespace... */ + do + curchar = readChar(linkerScript); + while (isWhiteSpace(curchar)); + + /* If this is a comment, skip to the end of the line */ + if (curchar == ';') { + do + curchar = readChar(linkerScript); + while (!isNewline(curchar) && curchar != EOF); + } + + if (curchar == EOF) { + token.type = TOKEN_EOF; + } else if (isNewline(curchar)) { + /* If we have a newline char, this is a newline token */ + token.type = TOKEN_NEWLINE; + + /* FIXME: This works with CRLF newlines, but not CR-only */ + if (curchar == '\r') + readChar(linkerScript); /* Read and discard LF */ + } else if (curchar == '"') { + /* If we have a string start, this is a section name */ + token.type = TOKEN_SECTION; + token.attr.string = NULL; /* Force initial alloc */ + + size_t size = 0; + size_t capacity = 16; /* Half of the default capacity */ + + do { + curchar = readChar(linkerScript); + if (curchar == EOF || isNewline(curchar)) + errx(1, "Line %u: Unterminated string", lineNo); + else if (curchar == '"') + /* Quotes force a string termination */ + curchar = '\0'; + + if (size >= capacity || token.attr.string == NULL) { + capacity *= 2; + token.attr.string = realloc(token.attr.string, + capacity); + if (!token.attr.string) + err(1, "%s: Failed to allocate memory for section name", + __func__); + } + token.attr.string[size++] = curchar; + } while (curchar); + } else { + /* This is either a number, command or bank, that is: a word */ + char *str = NULL; + size_t size = 0; + size_t capacity = 8; /* Half of the default capacity */ + + for (;;) { + if (size >= capacity || str == NULL) { + capacity *= 2; + str = realloc(str, capacity); + if (!str) + err(1, "%s: Failed to allocate memory for token", + __func__); + } + str[size] = toupper(curchar); + size++; + + if (!curchar) + break; + + curchar = readChar(linkerScript); + /* Whitespace, a newline or a comment end the token */ + if (isWhiteSpace(curchar) || isNewline(curchar) + || curchar == ';') { + ungetc(curchar, linkerScript); + curchar = '\0'; } - } else if (BankIndexIsOAM(i)) { - /* OAM */ - bank[i].address = 0xFE00; - bank[i].top_address = 0xFEA0; - bank[i].type = SECT_OAM; - } else if (BankIndexIsHRAM(i)) { - /* HRAM */ - bank[i].address = 0xFF80; - bank[i].top_address = 0xFFFF; - bank[i].type = SECT_HRAM; - } else { - errx(1, "%s: Unknown bank type %d", __func__, i); } + + token.type = TOKEN_INVALID; + for (enum LinkerScriptCommand i = 0; i < COMMAND_INVALID; i++) { + if (!strcmp(commands[i], str)) { + token.type = TOKEN_COMMAND; + token.attr.command = i; + break; + } + } + + if (token.type == TOKEN_INVALID) { + for (enum SectionType type = 0; type < SECTTYPE_INVALID; + type++) { + if (!strcmp(typeNames[type], str)) { + token.type = TOKEN_BANK; + token.attr.secttype = type; + break; + } + } + } + + if (token.type == TOKEN_INVALID) { + /* None of the strings matched, do we have a number? */ + if (tryParseNumber(str, &token.attr.number)) + token.type = TOKEN_NUMBER; + else + errx(1, "Unknown token \"%s\" on linker script line %u", + str, lineNo); + } + + free(str); } + + return &token; } -void script_SetCurrentSectionType(const char *type, uint32_t bank_num) +static void processCommand(enum LinkerScriptCommand command, uint16_t arg, + uint16_t *pc) { - if (strcmp(type, "ROM0") == 0) { - if (bank_num != 0) - errx(1, "Trying to assign a bank number to ROM0.\n"); - current_bank = BANK_INDEX_ROM0; - current_real_bank = 0; - return; - } else if (strcmp(type, "ROMX") == 0) { - if (bank_num == 0) - errx(1, "ROMX index can't be 0.\n"); - if (bank_num > BANK_COUNT_ROMX) { - errx(1, "ROMX index too big (%d > %d).\n", bank_num, - BANK_COUNT_ROMX); - } - current_bank = BANK_INDEX_ROMX + bank_num - 1; - current_real_bank = bank_num; - return; - } else if (strcmp(type, "VRAM") == 0) { - if (bank_num >= BANK_COUNT_VRAM) { - errx(1, "VRAM index too big (%d >= %d).\n", bank_num, - BANK_COUNT_VRAM); - } - current_bank = BANK_INDEX_VRAM + bank_num; - current_real_bank = bank_num; - return; - } else if (strcmp(type, "WRAM0") == 0) { - if (bank_num != 0) - errx(1, "Trying to assign a bank number to WRAM0.\n"); + switch (command) { + case COMMAND_INVALID: + trap_; - current_bank = BANK_INDEX_WRAM0; - current_real_bank = 0; - return; - } else if (strcmp(type, "WRAMX") == 0) { - if (bank_num == 0) - errx(1, "WRAMX index can't be 0.\n"); - if (bank_num > BANK_COUNT_WRAMX) { - errx(1, "WRAMX index too big (%d > %d).\n", bank_num, - BANK_COUNT_WRAMX); - } - current_bank = BANK_INDEX_WRAMX + bank_num - 1; - current_real_bank = bank_num; - return; - } else if (strcmp(type, "SRAM") == 0) { - if (bank_num >= BANK_COUNT_SRAM) { - errx(1, "SRAM index too big (%d >= %d).\n", bank_num, - BANK_COUNT_SRAM); - } - current_bank = BANK_INDEX_SRAM + bank_num; - current_real_bank = bank_num; - return; - } else if (strcmp(type, "OAM") == 0) { - if (bank_num != 0) { - errx(1, "%s: Trying to assign a bank number to OAM.\n", - __func__); - } - current_bank = BANK_INDEX_OAM; - current_real_bank = 0; - return; - } else if (strcmp(type, "HRAM") == 0) { - if (bank_num != 0) { - errx(1, "%s: Trying to assign a bank number to HRAM.\n", - __func__); - } - current_bank = BANK_INDEX_HRAM; - current_real_bank = 0; - return; + case COMMAND_ORG: + *pc = arg; + break; + + case COMMAND_ALIGN: + if (arg >= 16) + arg = 0; + else + arg = (*pc + (1 << arg) - 1) & ~((1 << arg) - 1); } - errx(1, "%s: Unknown section type \"%s\".\n", __func__, type); + if (arg < *pc) + errx(1, "Linkerscript line %u: `%s` cannot be used to go backwards", + lineNo, commands[command]); + *pc = arg; } -void script_SetAddress(uint32_t addr) +enum LinkerScriptParserState { + PARSER_FIRSTTIME, + PARSER_LINESTART, + PARSER_LINEEND +}; + +/* Part of internal state, but has data that needs to be freed */ +static uint16_t *curaddr[SECTTYPE_INVALID]; + +/* Put as global to ensure it's initialized only once */ +static enum LinkerScriptParserState parserState = PARSER_FIRSTTIME; + +struct SectionPlacement *script_NextSection(void) { - if (current_bank == -1) - errx(1, "Trying to set an address without assigned bank\n"); + static struct SectionPlacement section; + static enum SectionType type; + static uint32_t bank; + static uint32_t bankID; - /* Make sure that we don't go back. */ - if (bank[current_bank].address > addr) { - errx(1, "Trying to go to a previous address (0x%04X to 0x%04X)\n", - bank[current_bank].address, addr); + if (parserState == PARSER_FIRSTTIME) { + lineNo = 1; + + /* Init PC for all banks */ + for (enum SectionType i = 0; i < SECTTYPE_INVALID; i++) { + curaddr[i] = malloc(sizeof(*curaddr[i]) * nbbanks(i)); + for (uint32_t b = 0; b < nbbanks(i); b++) + curaddr[i][b] = startaddr[i]; + } + + type = SECTTYPE_INVALID; + + parserState = PARSER_LINESTART; } - bank[current_bank].address = addr; + for (;;) { + struct LinkerScriptToken const *token = nextToken(); + enum LinkerScriptTokenType tokType; + union LinkerScriptTokenAttr attr; + bool hasArg; + uint32_t arg; - /* Make sure we don't overflow */ - if (bank[current_bank].address >= bank[current_bank].top_address) { - errx(1, "Bank overflowed (0x%04X >= 0x%04X)\n", - bank[current_bank].address, - bank[current_bank].top_address); + if (type != SECTTYPE_INVALID) { + if (curaddr[type][bankID] > endaddr(type) + 1) + errx(1, "Linkerscript line %u: PC overflowed (%u > %u)", + lineNo, curaddr[type][bankID], + endaddr(type)); + if (curaddr[type][bankID] < startaddr[type]) + errx(1, "Linkerscript line %u: PC underflowed (%u < %u)", + lineNo, curaddr[type][bankID], + startaddr[type]); + } + + switch (parserState) { + case PARSER_FIRSTTIME: + trap_; + + case PARSER_LINESTART: + switch (token->type) { + case TOKEN_INVALID: + trap_; + + case TOKEN_EOF: + return NULL; + + case TOKEN_NUMBER: + errx(1, "Linkerscript line %u: stray number", + lineNo); + + case TOKEN_NEWLINE: + lineNo++; + break; + + case TOKEN_SECTION: + parserState = PARSER_LINEEND; + + if (type == SECTTYPE_INVALID) + errx(1, "Linkerscript line %u: Didn't specify a location before the section", + lineNo); + + section.section = + sect_GetSection(token->attr.string); + section.org = curaddr[type][bankID]; + section.bank = bank; + + curaddr[type][bankID] += section.section->size; + return §ion; + + case TOKEN_COMMAND: + case TOKEN_BANK: + tokType = token->type; + attr = token->attr; + + token = nextToken(); + hasArg = token->type == TOKEN_NUMBER; + /* + * Leaving `arg` uninitialized when `!hasArg` + * causes GCC to warn about its use as an + * argument to `processCommand`. This cannot + * happen because `hasArg` has to be true, but + * silence the warning anyways. + * I dislike doing this because it could swallow + * actual errors, but I don't have a choice. + */ + arg = hasArg ? token->attr.number : 0; + + if (tokType == TOKEN_COMMAND) { + if (type == SECTTYPE_INVALID) + errx(1, "Linkerscript line %u: Didn't specify a location before the command", + lineNo); + if (!hasArg) + errx(1, "Linkerscript line %u: Command specified without an argument", + lineNo); + + processCommand(attr.command, arg, + &curaddr[type][bankID]); + } else { /* TOKEN_BANK */ + type = attr.secttype; + /* + * If there's only one bank, + * specifying the number is optional. + */ + if (!hasArg && nbbanks(type) != 1) + errx(1, "Linkerscript line %u: Didn't specify a bank number", + lineNo); + else if (!hasArg) + arg = bankranges[type][0]; + else if (arg < bankranges[type][0]) + errx(1, "Linkerscript line %u: specified bank number is too low (%u < %u)", + lineNo, arg, + bankranges[type][0]); + else if (arg > bankranges[type][1]) + errx(1, "Linkerscript line %u: specified bank number is too high (%u > %u)", + lineNo, arg, + bankranges[type][1]); + bank = arg; + bankID = arg - bankranges[type][0]; + } + + /* If we read a token we shouldn't have... */ + if (token->type != TOKEN_NUMBER) + goto lineend; + break; + } + break; + + case PARSER_LINEEND: +lineend: + if (token->type == TOKEN_EOF) + return NULL; + else if (token->type != TOKEN_NEWLINE) + errx(1, "Linkerscript line %u: Unexpected token at the end", + lineNo); + lineNo++; + parserState = PARSER_LINESTART; + break; + } } - - fix_org = addr; } -void script_SetAlignment(uint32_t alignment) +void script_Cleanup(void) { - if (current_bank == -1) - errx(1, "Trying to set an alignment without assigned bank\n"); - - if (alignment > 15) - errx(1, "Trying to set an alignment too big: %d\n", alignment); - - uint32_t size = 1 << alignment; - uint32_t mask = size - 1; - - if (bank[current_bank].address & mask) { - bank[current_bank].address &= ~mask; - bank[current_bank].address += size; - } - - /* Make sure we don't overflow */ - if (bank[current_bank].address >= bank[current_bank].top_address) { - errx(1, "Bank overflowed (0x%04X >= 0x%04X)\n", - bank[current_bank].address, - bank[current_bank].top_address); - } - - fix_align = size; -} - -void script_OutputSection(const char *section_name) -{ - if (current_bank == -1) { - errx(1, "Trying to place section \"%s\" without assigned bank\n", - section_name); - } - - if (!IsSectionSameTypeBankAndAttrs(section_name, - bank[current_bank].type, - current_real_bank, - fix_org, - fix_align)) { - errx(1, "Different attributes for \"%s\" in source and linkerscript\n", - section_name); - } - - /* Move section to its place. */ - bank[current_bank].address += - AssignSectionAddressAndBankByName(section_name, - bank[current_bank].address, - current_real_bank); - - fix_org = -1; - fix_align = 1; + for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) + free(curaddr[type]); } diff --git a/src/link/section.c b/src/link/section.c new file mode 100644 index 00000000..3a2c296b --- /dev/null +++ b/src/link/section.c @@ -0,0 +1,168 @@ + +#include + +#include "link/main.h" +#include "link/section.h" + +#include "extern/err.h" + +#include "hashmap.h" +#include "common.h" + +uint16_t startaddr[] = { + [SECTTYPE_ROM0] = 0x0000, + [SECTTYPE_ROMX] = 0x4000, + [SECTTYPE_VRAM] = 0x8000, + [SECTTYPE_SRAM] = 0xA000, + [SECTTYPE_WRAM0] = 0xC000, + [SECTTYPE_WRAMX] = 0xD000, + [SECTTYPE_OAM] = 0xFE00, + [SECTTYPE_HRAM] = 0xFF80 +}; + +uint16_t maxsize[] = { + [SECTTYPE_ROM0] = 0x4000, + [SECTTYPE_ROMX] = 0x4000, + [SECTTYPE_VRAM] = 0x2000, + [SECTTYPE_SRAM] = 0x2000, + [SECTTYPE_WRAM0] = 0x1000, + [SECTTYPE_WRAMX] = 0x1000, + [SECTTYPE_OAM] = 0x00A0, + [SECTTYPE_HRAM] = 0x007F +}; + +uint32_t bankranges[][2] = { + [SECTTYPE_ROM0] = {BANK_MIN_ROM0, BANK_MAX_ROM0}, + [SECTTYPE_ROMX] = {BANK_MIN_ROMX, BANK_MAX_ROMX}, + [SECTTYPE_VRAM] = {BANK_MIN_VRAM, BANK_MAX_VRAM}, + [SECTTYPE_SRAM] = {BANK_MIN_SRAM, BANK_MAX_SRAM}, + [SECTTYPE_WRAM0] = {BANK_MIN_WRAM0, BANK_MAX_WRAM0}, + [SECTTYPE_WRAMX] = {BANK_MIN_WRAMX, BANK_MAX_WRAMX}, + [SECTTYPE_OAM] = {BANK_MIN_OAM, BANK_MAX_OAM}, + [SECTTYPE_HRAM] = {BANK_MIN_HRAM, BANK_MAX_HRAM} +}; + +char const * const typeNames[] = { + [SECTTYPE_ROM0] = "ROM0", + [SECTTYPE_ROMX] = "ROMX", + [SECTTYPE_VRAM] = "VRAM", + [SECTTYPE_SRAM] = "SRAM", + [SECTTYPE_WRAM0] = "WRAM0", + [SECTTYPE_WRAMX] = "WRAMX", + [SECTTYPE_OAM] = "OAM", + [SECTTYPE_HRAM] = "HRAM" +}; + +HashMap sections; + +struct ForEachArg { + void (*callback)(struct Section *section, void *arg); + void *arg; +}; + +static void forEach(void *section, void *arg) +{ + struct ForEachArg *callbackArg = (struct ForEachArg *)arg; + + callbackArg->callback((struct Section *)section, callbackArg->arg); +} + +void sect_ForEach(void (*callback)(struct Section *, void *), void *arg) +{ + struct ForEachArg callbackArg = { .callback = callback, .arg = arg}; + + hash_ForEach(sections, forEach, &callbackArg); +} + +void sect_AddSection(struct Section *section) +{ + /* Check if the section already exists */ + if (hash_GetElement(sections, section->name)) + errx(1, "Section name \"%s\" is already in use", section->name); + + /* If not, add it */ + bool collided = hash_AddElement(sections, section->name, section); + + if (beVerbose && collided) + warnx("Section hashmap collision occurred!"); +} + +struct Section *sect_GetSection(char const *name) +{ + return (struct Section *)hash_GetElement(sections, name); +} + +void sect_CleanupSections(void) +{ + hash_EmptyMap(sections); +} + +static void doSanityChecks(struct Section *section, void *ptr) +{ + (void)ptr; + + /* Sanity check the section's type */ + + if (section->type < 0 || section->type >= SECTTYPE_INVALID) + errx(1, "Section \"%s\" has an invalid type.", section->name); + if (is32kMode && section->type == SECTTYPE_ROMX) + errx(1, "%s: ROMX sections cannot be used with option -t.", + section->name); + if (isWRA0Mode && section->type == SECTTYPE_WRAMX) + errx(1, "%s: WRAMX sections cannot be used with options -w or -d.", + section->name); + if (isDmgMode && section->type == SECTTYPE_VRAM && section->bank == 1) + errx(1, "%s: VRAM bank 1 can't be used with option -d.", + section->name); + + /* + * Check if alignment is reasonable, this is important to avoid UB + * An alignment of zero is equivalent to no alignment, basically + */ + if (section->isAlignFixed && section->alignMask == 1) + section->isAlignFixed = false; + + uint32_t minbank = bankranges[section->type][0], + maxbank = bankranges[section->type][1]; + + if (section->isBankFixed && section->bank < minbank + && section->bank > maxbank) + errx(1, minbank == maxbank + ? "Cannot place section \"%s\" in bank %d, it must be %d" + : "Cannot place section \"%s\" in bank %d, it must be between %d and %d", + section->name, section->bank, minbank, maxbank); + + /* Check if section has a chance to be placed */ + if (section->size > maxsize[section->type]) + errx(1, "Section \"%s\" is bigger than the max size for that type: %#X > %#X", + section->size, maxsize[section->type]); + + /* Translate loose constraints to strong ones when they're equivalent */ + + if (minbank == maxbank) { + section->bank = minbank; + section->isBankFixed = true; + } + + if (section->isAlignFixed) { + enum SectionType type = section->type; + + /* It doesn't make sense to have both org and alignment set */ + if (section->isAddressFixed) { + if (section->org & section->alignMask) + errx(1, "Section \"%s\"'s fixed address doesn't match its alignment", + section->name); + section->isAlignFixed = false; + } else if ((endaddr(type) & section->alignMask) + == startaddr[type]) { + section->org = startaddr[type]; + section->isAlignFixed = false; + section->isAddressFixed = true; + } + } +} + +void sect_DoSanityChecks(void) +{ + sect_ForEach(doSanityChecks, NULL); +} diff --git a/src/link/symbol.c b/src/link/symbol.c index c8db903a..3d3232fb 100644 --- a/src/link/symbol.c +++ b/src/link/symbol.c @@ -1,137 +1,56 @@ -/* - * This file is part of RGBDS. - * - * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. - * - * SPDX-License-Identifier: MIT - */ -#include -#include -#include -#include - -#include "extern/err.h" +#include +#include "link/symbol.h" #include "link/main.h" -#include "link/patch.h" -#include "link/mylink.h" +#include "extern/err.h" +#include "hashmap.h" -#include "types.h" +HashMap symbols; -#define HASHSIZE 73 - -struct ISymbol { - char *pzName; - int32_t nValue; - int32_t nBank; /* -1 = constant */ - /* Object file where the symbol was defined. */ - char tzObjFileName[_MAX_PATH + 1]; - /* Source file where the symbol was defined. */ - char tzFileName[_MAX_PATH + 1]; - /* Line where the symbol was defined. */ - uint32_t nFileLine; - struct ISymbol *pNext; +struct ForEachArg { + void (*callback)(struct Symbol *symbol, void *arg); + void *arg; }; -struct ISymbol *tHash[HASHSIZE]; - -int32_t calchash(char *s) +static void forEach(void *symbol, void *arg) { - int32_t r = 0; + struct ForEachArg *callbackArg = (struct ForEachArg *)arg; - while (*s) - r += *s++; - - return r % HASHSIZE; + callbackArg->callback((struct Symbol *)symbol, callbackArg->arg); } -void sym_Init(void) +void sym_ForEach(void (*callback)(struct Symbol *, void *), void *arg) { - int32_t i; + struct ForEachArg callbackArg = { .callback = callback, .arg = arg}; - for (i = 0; i < HASHSIZE; i += 1) - tHash[i] = NULL; + hash_ForEach(symbols, forEach, &callbackArg); } -int32_t sym_GetValue(struct sPatch *pPatch, char *tzName) +void sym_AddSymbol(struct Symbol *symbol) { - if (strcmp(tzName, "@") == 0) - return nPC; + /* Check if the symbol already exists */ + struct Symbol *other = hash_GetElement(symbols, symbol->name); - struct ISymbol **ppSym; + if (other) + errx(1, "\"%s\" both in %s from %s(%d) and in %s from %s(%d)", + symbol->name, + symbol->objFileName, symbol->fileName, symbol->lineNo, + other->objFileName, other->fileName, other->lineNo); - ppSym = &(tHash[calchash(tzName)]); - while (*ppSym) { - if (strcmp(tzName, (*ppSym)->pzName)) - ppSym = &((*ppSym)->pNext); - else - return ((*ppSym)->nValue); - } + /* If not, add it */ + bool collided = hash_AddElement(symbols, symbol->name, symbol); - errx(1, - "%s(%ld) : Unknown symbol '%s'", - pPatch->pzFilename, pPatch->nLineNo, - tzName); + if (beVerbose && collided) + warnx("Symbol hashmap collision occurred!"); } -int32_t sym_GetBank(struct sPatch *pPatch, char *tzName) +struct Symbol *sym_GetSymbol(char const *name) { - struct ISymbol **ppSym; - - ppSym = &(tHash[calchash(tzName)]); - while (*ppSym) { - if (strcmp(tzName, (*ppSym)->pzName)) - ppSym = &((*ppSym)->pNext); - else - return ((*ppSym)->nBank); - } - - errx(1, - "%s(%ld) : Unknown symbol '%s'", - pPatch->pzFilename, pPatch->nLineNo, - tzName); + return (struct Symbol *)hash_GetElement(symbols, name); } -void sym_CreateSymbol(char *tzName, int32_t nValue, int32_t nBank, - char *tzObjFileName, char *tzFileName, uint32_t nFileLine) +void sym_CleanupSymbols(void) { - if (strcmp(tzName, "@") == 0) - return; - - struct ISymbol **ppSym; - - ppSym = &(tHash[calchash(tzName)]); - - while (*ppSym) { - if (strcmp(tzName, (*ppSym)->pzName)) { - ppSym = &((*ppSym)->pNext); - } else { - if (nBank == -1) - return; - - errx(1, "'%s' in both %s : %s(%d) and %s : %s(%d)", - tzName, tzObjFileName, tzFileName, nFileLine, - (*ppSym)->tzObjFileName, - (*ppSym)->tzFileName, (*ppSym)->nFileLine); - } - } - - *ppSym = malloc(sizeof **ppSym); - - if (*ppSym != NULL) { - (*ppSym)->pzName = malloc(strlen(tzName) + 1); - - if ((*ppSym)->pzName != NULL) { - strcpy((*ppSym)->pzName, tzName); - (*ppSym)->nValue = nValue; - (*ppSym)->nBank = nBank; - (*ppSym)->pNext = NULL; - strncpy((*ppSym)->tzObjFileName, tzObjFileName, - sizeof((*ppSym)->tzObjFileName)); - strncpy((*ppSym)->tzFileName, tzFileName, - sizeof((*ppSym)->tzFileName)); - (*ppSym)->nFileLine = nFileLine; - } - } + hash_EmptyMap(symbols); } diff --git a/test/link/romx-tiny-no-t.out b/test/link/romx-tiny-no-t.out index dabf2904..6c007d90 100644 --- a/test/link/romx-tiny-no-t.out +++ b/test/link/romx-tiny-no-t.out @@ -1 +1 @@ -error: Unable to place 'r0b' (ROM0 section) anywhere +error: Unable to place "r0a" (ROM0 section) anywhere diff --git a/test/link/romx-tiny-t.out b/test/link/romx-tiny-t.out index ef714e24..fbe09bff 100644 --- a/test/link/romx-tiny-t.out +++ b/test/link/romx-tiny-t.out @@ -1 +1 @@ -error: ROMX sections can't be used with option -t. +error: rx: ROMX sections cannot be used with option -t. diff --git a/test/link/section-attributes-mismatch.link b/test/link/section-attributes-mismatch.link index e9cf75af..25ec6137 100644 --- a/test/link/section-attributes-mismatch.link +++ b/test/link/section-attributes-mismatch.link @@ -1,5 +1,5 @@ ROM0 - org $10 + org $18 "sec" org $20 "secfix" diff --git a/test/link/section-attributes-mismatch.out b/test/link/section-attributes-mismatch.out index e7ac2a38..4512445d 100644 --- a/test/link/section-attributes-mismatch.out +++ b/test/link/section-attributes-mismatch.out @@ -1,2 +1 @@ -error: Different attributes for "sec" in source and linkerscript - +error: Linker script contradicts "sec"'s alignment diff --git a/test/link/vram-fixed-dmg-mode-d.out b/test/link/vram-fixed-dmg-mode-d.out index dc1e50a1..7565050e 100644 --- a/test/link/vram-fixed-dmg-mode-d.out +++ b/test/link/vram-fixed-dmg-mode-d.out @@ -1 +1 @@ -error: VRAM bank 1 can't be used with option -d. +error: v1: VRAM bank 1 can't be used with option -d. diff --git a/test/link/vram-floating-dmg-mode-d.out b/test/link/vram-floating-dmg-mode-d.out index 4367a910..cbbbeb0a 100644 --- a/test/link/vram-floating-dmg-mode-d.out +++ b/test/link/vram-floating-dmg-mode-d.out @@ -1 +1 @@ -error: Unable to place 'v1' (VRAM section) in any bank +error: Unable to place "v1" (VRAM section) anywhere diff --git a/test/link/wramx-dmg-mode-d.out b/test/link/wramx-dmg-mode-d.out index 0c732ca8..cbef56f7 100644 --- a/test/link/wramx-dmg-mode-d.out +++ b/test/link/wramx-dmg-mode-d.out @@ -1 +1 @@ -error: WRAMX sections can't be used with options -w or -d. +error: wx: WRAMX sections cannot be used with options -w or -d. diff --git a/test/link/wramx-dmg-mode-no-d.out b/test/link/wramx-dmg-mode-no-d.out index dab00306..7ca08ed9 100644 --- a/test/link/wramx-dmg-mode-no-d.out +++ b/test/link/wramx-dmg-mode-no-d.out @@ -1 +1 @@ -error: Unable to place 'w0b' (WRAM0 section) anywhere +error: Unable to place "w0b" (WRAM0 section) anywhere