ref: 5aea30f40d2f3354c76988384bcf70347a26d4a3
parent: 323738e7b8426e59aa753ca1f1d623d5c1f24368
parent: 09c9395ff8b23b5c7beb431d0228d5e2129d56d6
author: Eldred Habert <[email protected]>
date: Sun Nov 3 11:15:37 EST 2019
Merge pull request #434 from ISSOtm/rgblink_rewrite Rewrite RGBLINK entirely
--- a/Makefile
+++ b/Makefile
@@ -32,10 +32,11 @@
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,20 +74,16 @@
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 \
--- 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,
--- /dev/null
+++ b/include/hashmap.h
@@ -1,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 <assert.h>
+
+#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 */
--- 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 */
--- a/include/link/assign.h
+++ b/include/link/assign.h
@@ -1,11 +1,12 @@
/*
* 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
@@ -12,43 +13,17 @@
#include <stdint.h>
#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 */
--- a/include/link/library.h
+++ /dev/null
@@ -1,14 +1,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 */
--- a/include/link/main.h
+++ b/include/link/main.h
@@ -1,17 +1,49 @@
/*
* 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 <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
-extern int32_t fillchar;
-extern char *smartlinkstartsymbol;
+/* Variables related to CLI options */
+extern bool isDmgMode;
+extern char const *linkerScriptName;
+extern char const *mapFileName;
+extern char const *symFileName;
+extern char const *overlayFileName;
+extern char const *outputFileName;
+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)
+
+/**
+ * Opens a file if specified, and aborts on error.
+ * @param fileName The name of the file to open; if NULL, no file will be opened
+ * @param mode The mode to open the file with
+ * @return A pointer to a valid FILE structure, or NULL if fileName was NULL
+ */
+FILE *openFile(char const *fileName, char const *mode);
+
+#define closeFile(file) do { \
+ FILE *tmp = file; \
+ if (tmp) \
+ fclose(tmp); \
+ } while (0)
#endif /* RGBDS_LINK_MAIN_H */
--- a/include/link/mapfile.h
+++ /dev/null
@@ -1,21 +1,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 <stdint.h>
-
-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 */
--- a/include/link/mylink.h
+++ /dev/null
@@ -1,68 +1,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 <stdint.h>
-
-#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 */
--- 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 */
--- a/include/link/output.h
+++ b/include/link/output.h
@@ -1,16 +1,28 @@
/*
* 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 <stdint.h>
+
+#include "link/section.h"
+
+/**
+ * Registers a section for output.
+ * @param section The section to add
+ */
+void out_AddSection(struct Section const *section);
+
+/**
+ * Writes all output (bin, sym, map) files.
+ */
+void out_WriteFiles(void);
#endif /* RGBDS_LINK_OUTPUT_H */
--- 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 <stdint.h>
-
-void Patch(void);
-extern int32_t nPC;
+/**
+ * Applies all SECTIONs' patches to them
+ */
+void patch_ApplyPatches(void);
#endif /* RGBDS_LINK_PATCH_H */
--- a/include/link/script.h
+++ b/include/link/script.h
@@ -1,30 +1,36 @@
/*
* 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 <stdint.h>
-#include "helpers.h"
+extern FILE *linkerScript;
-noreturn_ void script_fatalerror(const char *fmt, ...);
+struct SectionPlacement {
+ struct Section *section;
+ uint16_t org;
+ uint32_t bank;
+};
-void script_Parse(const char *path);
+extern uint64_t script_lineNo;
-void script_IncludeFile(const char *path);
-int32_t script_IncludeDepthGet(void);
-void script_IncludePop(void);
+/**
+ * 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_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 */
--- /dev/null
+++ b/include/link/section.h
@@ -1,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 <stdint.h>
+#include <stdbool.h>
+
+#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 */
--- 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 <stdint.h>
-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 */
--- 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 @@
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 */
--- a/src/asm/asmy.y
+++ b/src/asm/asmy.y
@@ -45,22 +45,22 @@
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 @@
}
;
-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;
}
;
--- a/src/asm/output.c
+++ b/src/asm/output.c
@@ -96,21 +96,21 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
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 @@
{
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 @@
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 @@
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 @@
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 @@
/* 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;
--- a/src/extern/err.c
+++ b/src/extern/err.c
@@ -14,13 +14,12 @@
void rgbds_vwarn(const char *fmt, va_list ap)
{
- fprintf(stderr, "warning");
+ fprintf(stderr, "warning: ");
if (fmt) {
- fputs(": ", stderr);
vfprintf(stderr, fmt, ap);
+ fputs(": ", stderr);
}
- putc('\n', stderr);
- perror(0);
+ perror(NULL);
}
void rgbds_vwarnx(const char *fmt, va_list ap)
@@ -35,12 +34,12 @@
noreturn_ void rgbds_verr(int status, const char *fmt, va_list ap)
{
- fprintf(stderr, "error");
+ fprintf(stderr, "error: ");
if (fmt) {
- fputs(": ", stderr);
vfprintf(stderr, fmt, ap);
+ fputs(": ", stderr);
}
- putc('\n', stderr);
+ perror(NULL);
exit(status);
}
--- /dev/null
+++ b/src/hashmap.c
@@ -1,0 +1,118 @@
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#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;
+ }
+}
--- a/src/link/assign.c
+++ b/src/link/assign.c
@@ -1,723 +1,437 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
#include <stdbool.h>
-#include <string.h>
+#include <stdlib.h>
-#include "extern/err.h"
-
#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 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 MemoryLocation {
+ uint16_t address;
+ uint32_t bank;
};
-struct sFreeArea *BankFree[BANK_INDEX_MAX];
-int32_t MaxAvail[BANK_INDEX_MAX];
-int32_t MaxBankUsed;
-int32_t MaxWBankUsed;
-int32_t MaxSBankUsed;
-int32_t MaxVBankUsed;
-
-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}
+struct FreeSpace {
+ uint16_t address;
+ uint16_t size;
+ struct FreeSpace *next, *prev;
};
-static void do_max_bank(enum eSectionType Type, int32_t nBank)
-{
- 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;
- }
-}
+/* Table of free space for each bank */
+struct FreeSpace *memory[SECTTYPE_INVALID];
-void ensureSectionTypeIsValid(enum eSectionType type)
-{
- if (type < SECT_MIN || type > SECT_MAX)
- errx(1, "%s: Invalid section type found: %d", __func__, type);
-}
+uint64_t nbSectionsToAssign;
-int BankIndexIsROM0(int32_t bank)
+/**
+ * Init the free space-modelling structs
+ */
+static void initFreeSpace(void)
{
- return (bank >= BANK_INDEX_ROM0) &&
- (bank < (BANK_INDEX_ROM0 + BANK_COUNT_ROM0));
-}
+ 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);
-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;
+ 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];
+ }
}
-
- return r;
}
-int32_t area_doAlloc(struct sFreeArea *pArea, int32_t org, int32_t size)
+/**
+ * Alter sections' attributes based on the linker script
+ */
+static void processLinkerScript(void)
{
- if (size == 0) {
- /* 0-byte SECTIONs don't take any room, they can go anywhere */
- return org;
- }
+ if (!linkerScriptName)
+ return;
+ verbosePrint("Reading linker script...\n");
- if ((org >= pArea->nOrg)
- && ((org + size) <= (pArea->nOrg + pArea->nSize))) {
+ linkerScript = openFile(linkerScriptName, "r");
- if (org == pArea->nOrg) {
- pArea->nOrg += size;
- pArea->nSize -= size;
- return org;
- }
+ /* Modify all sections according to the linker script */
+ struct SectionPlacement *placement;
- if ((org + size) == (pArea->nOrg + pArea->nSize)) {
- pArea->nSize -= size;
- return org;
- }
+ while ((placement = script_NextSection())) {
+ struct Section *section = placement->section;
- struct sFreeArea *pNewArea;
+ /* 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);
- 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;
+ section->isAddressFixed = true;
+ section->org = placement->org;
+ section->isBankFixed = true;
+ section->bank = placement->bank;
+ section->isAlignFixed = false; /* The alignment is satisfied */
}
- return -1;
+ fclose(linkerScript);
}
-int32_t area_AllocAbs(struct sFreeArea **ppArea, int32_t org, int32_t size)
+/**
+ * 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)
{
- struct sFreeArea *pArea;
+ section->org = location->address;
+ section->bank = location->bank;
- pArea = *ppArea;
- while (pArea) {
- int32_t result = area_doAlloc(pArea, org, size);
+ nbSectionsToAssign--;
- if (result != -1)
- return result;
-
- ppArea = &(pArea->pNext);
- pArea = *ppArea;
- }
-
- return -1;
+ out_AddSection(section);
}
-int32_t area_AllocAbsAnyBank(int32_t org, int32_t size, enum eSectionType type)
+/**
+ * Checks whether a given location is suitable for placing a given section
+ * This checks not only that the location has enough room for the section, but
+ * also that the constraints (alignment...) are respected.
+ * @param section The section to be placed
+ * @param freeSpace The candidate free space to place the section into
+ * @param location The location to attempt placing the section at
+ * @return True if the location is suitable, false otherwise.
+ */
+static bool isLocationSuitable(struct Section const *section,
+ struct FreeSpace const *freeSpace,
+ struct MemoryLocation const *location)
{
- ensureSectionTypeIsValid(type);
+ if (section->isAddressFixed && section->org != location->address)
+ return false;
- int32_t startBank = SECT_ATTRIBUTES[type].bank;
- int32_t bankCount = SECT_ATTRIBUTES[type].bankCount;
+ if (section->isAlignFixed && location->address & section->alignMask)
+ return false;
- for (int32_t i = 0; i < bankCount; i++) {
- if (area_AllocAbs(&BankFree[startBank + i], org, size) != -1)
- return startBank + i;
- }
-
- return -1;
+ return location->address + section->size
+ <= freeSpace->address + freeSpace->size;
}
-int32_t area_Alloc(struct sFreeArea **ppArea, int32_t size, int32_t alignment)
+/**
+ * Finds a suitable location to place a section at.
+ * @param section The section to be placed
+ * @param location A pointer to a location struct that will be filled
+ * @return A pointer to the free space encompassing the location, or NULL if
+ * none was found
+ */
+static struct FreeSpace *getPlacement(struct Section const *section,
+ struct MemoryLocation *location)
{
- struct sFreeArea *pArea;
+ location->bank = section->isBankFixed
+ ? section->bank
+ : bankranges[section->type][0];
+ struct FreeSpace *space;
- if (alignment < 1)
- alignment = 1;
+ 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;
- pArea = *ppArea;
- while (pArea) {
- int32_t org = pArea->nOrg;
+ /* Process locations in that bank */
+ while (space) {
+ /* If that location is OK, return it */
+ if (isLocationSuitable(section, space, location))
+ return space;
- if (org % alignment)
- org += alignment;
+ /* 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)
+ location->address = section->org;
+ else
+ /* Try again in next bank */
+ space = NULL;
+ } 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;
+ }
- org -= org % alignment;
+ /*
+ * 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;
- int32_t result = area_doAlloc(pArea, org, size);
-
- if (result != -1)
- return result;
-
- ppArea = &(pArea->pNext);
- pArea = *ppArea;
- }
-
- return -1;
-}
-
-int32_t area_AllocAnyBank(int32_t size, int32_t alignment,
- enum eSectionType type)
-{
- ensureSectionTypeIsValid(type);
-
- int32_t i, org;
- int32_t startBank = SECT_ATTRIBUTES[type].bank;
- int32_t bankCount = SECT_ATTRIBUTES[type].bankCount;
-
- for (i = 0; i < bankCount; i++) {
- org = area_Alloc(&BankFree[startBank + i], size, alignment);
- if (org != -1)
- return ((startBank + i) << 16) | org;
- }
-
- 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;
- }
+ /* Try again with the new location/free space combo */
}
- pSection = pSection->pNext;
- }
- return r;
-}
+ if (section->isBankFixed)
+ return NULL;
-int32_t IsSectionNameInUse(const char *name)
-{
- const struct sSection *pSection = pSections;
-
- while (pSection) {
- if (strcmp(pSection->pzName, name) == 0)
- return 1;
-
- pSection = pSection->pNext;
+ /* Try again in the next bank */
+ location->bank++;
+ if (location->bank > bankranges[section->type][1])
+ return NULL;
+#undef BANK_INDEX
}
-
- return 0;
}
-struct sSection *GetSectionByName(const char *name)
+/**
+ * Places a section in a suitable location, or error out if it fails to.
+ * @warning Due to the implemented algorithm, this should be called with
+ * sections of decreasing size.
+ * @param section The section to place
+ */
+static void placeSection(struct Section *section)
{
- struct sSection *pSection = pSections;
+ struct MemoryLocation location;
- 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;
-
+ /* 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);
+ 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 {
+ /* The free space is only resized */
+ freeSpace->size -= section->size;
}
-
- pSection->nOrg = address;
- pSection->nBank = bank;
- pSection->nAlign = -1;
-
- return pSection->nByteSize;
+ return;
}
- 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;
- }
+ 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]);
}
-
- return false;
}
-void AssignFixedBankSections(enum eSectionType type)
-{
- ensureSectionTypeIsValid(type);
+struct UnassignedSection {
+ struct Section *section;
+ struct UnassignedSection *next;
+};
- struct sSection *pSection;
+#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;
- 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);
- } 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);
- }
- }
-}
-
-void AssignFloatingBankSections(enum eSectionType type)
+/**
+ * Categorize a section depending on how constrained it is
+ * This is so the most-constrained sections are placed first
+ * @param section The section to categorize
+ * @param arg Callback arg, unused
+ */
+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");
-void AssignSections(void)
-{
- struct sSection *pSection;
+ /** Initialize assignment **/
- MaxBankUsed = 0;
+ /* Generate linked lists of sections to assign */
+ sections = malloc(sizeof(*sections) * nbSectionsToAssign + 1);
+ if (!sections)
+ err(1, "Failed to allocate memory for section assignment");
- /*
- * Initialize the memory areas
- */
+ initFreeSpace();
- for (int32_t i = 0; i < BANK_INDEX_MAX; i += 1) {
- BankFree[i] = malloc(sizeof(*BankFree[i]));
+ /* Process linker script, if any */
+ processLinkerScript();
- if (!BankFree[i]) {
- errx(1, "%s: Couldn't allocate mem for bank %d",
- __func__, i);
- }
+ nbSectionsToAssign = 0;
+ sect_ForEach(categorizeSection, NULL);
- 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);
- }
+ /** Place sections, starting with the most constrained **/
- MaxAvail[i] = BankFree[i]->nSize;
- BankFree[i]->pPrev = NULL;
- BankFree[i]->pNext = NULL;
- }
+ /* Specially process fully-constrained sections because of overlaying */
+ struct UnassignedSection *sectionPtr =
+ unassignedSections[BANK_CONSTRAINED | ORG_CONSTRAINED];
- /*
- * First, let's parse the linkerscript.
- */
-
- if (tzLinkerscriptName) {
- script_InitSections();
- script_Parse(tzLinkerscriptName);
+ verbosePrint("Assigning bank+org-constrained...\n");
+ while (sectionPtr) {
+ placeSection(sectionPtr->section);
+ sectionPtr = sectionPtr->next;
}
- /*
- * Second, let's assign all the fixed sections...
- */
+ /* If all sections were fully constrained, we have nothing left to do */
+ if (!nbSectionsToAssign)
+ return;
- for (pSection = pSections ; pSection; pSection = pSection->pNext) {
- if (!((pSection->nOrg != -1 || pSection->nBank != -1)
- && pSection->oAssigned == 0))
- continue;
+ /* Overlaying requires only fully-constrained sections */
+ verbosePrint("Assigning other sections...\n");
+ if (overlayFileName)
+ errx(1, "All sections must be fixed when using an overlay file; %u, %sn't",
+ nbSectionsToAssign, nbSectionsToAssign == 1 ? "is" : "are");
- /* User wants to have a say... */
+ /* Assign all remaining sections by decreasing constraint order */
+ for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED;
+ constraints >= 0; constraints--) {
+ sectionPtr = unassignedSections[constraints];
- 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);
- }
- 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);
+ while (sectionPtr) {
+ placeSection(sectionPtr->section);
+ sectionPtr = sectionPtr->next;
}
- }
- /*
- * Next, let's assign all the bankfixed ONLY sections...
- */
- for (enum eSectionType i = SECT_MIN; i <= SECT_MAX; i++)
- AssignFixedBankSections(i);
-
- /*
- * 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;
- }
+ if (!nbSectionsToAssign)
+ return;
}
- /*
- * 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);
+ trap_;
}
-void CreateSymbolTable(void)
+void assign_Cleanup(void)
{
- const struct sSection *pSect;
+ 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;
- sym_Init();
+ while (ptr) {
+ struct FreeSpace *next = ptr->next;
- 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);
- }
+ free(ptr);
+ ptr = next;
}
}
- pSect = pSect->pNext;
+
+ free(memory[type]);
}
+
+ free(sections);
+
+ script_Cleanup();
}
--- a/src/link/lexer.l
+++ /dev/null
@@ -1,194 +1,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 <stdarg.h>
-#include <stdint.h>
-#include <unistd.h>
-
-#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);
-}
-
--- a/src/link/library.c
+++ /dev/null
@@ -1,122 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#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;
- }
-}
--- a/src/link/main.c
+++ b/src/link/main.c
@@ -1,141 +1,162 @@
/*
* 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 <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include "extern/err.h"
-
#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 */
+char const *linkerScriptName; /* -l */
+char const *mapFileName; /* -m */
+char const *symFileName; /* -n */
+char const *overlayFileName; /* -O */
+char const *outputFileName; /* -o */
+uint8_t padValue; /* -p */
+bool is32kMode; /* -t */
+bool beVerbose; /* -v */
+bool isWRA0Mode; /* -w */
-int32_t options;
-int32_t fillchar;
-char *smartlinkstartsymbol;
+FILE *openFile(char const *fileName, char const *mode)
+{
+ if (!fileName)
+ return NULL;
-/*
- * Print the usagescreen
- */
+ FILE *file = fopen(fileName, mode);
-static void print_usage(void)
+ if (!file)
+ err(1, "Could not open file \"%s\"", fileName);
+
+ return file;
+}
+
+/**
+ * Prints the program's usage to stdout.
+ */
+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
+/**
+ * Cleans up what has been done
+ * Mostly here to please tools such as `valgrind` so actual errors can be seen
*/
+static void cleanup(void)
+{
+ 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);
+ linkerScriptName = optarg;
break;
case 'm':
- SetMapfileName(optarg);
+ mapFileName = optarg;
break;
case 'n':
- SetSymfileName(optarg);
+ symFileName = optarg;
break;
- case 'o':
- out_Setname(optarg);
- break;
case 'O':
- out_SetOverlayname(optarg);
- options |= OPT_OVERLAY;
+ overlayFileName = optarg;
break;
+ case 'o':
+ outputFileName = optarg;
+ 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;
+ is32kMode = true;
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;
- 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();
}
--- a/src/link/mapfile.c
+++ /dev/null
@@ -1,151 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-#include <errno.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#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);
-}
--- a/src/link/object.c
+++ b/src/link/object.c
@@ -1,381 +1,500 @@
-/*
- * 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 <ctype.h>
-#include <errno.h>
-#include <stdint.h>
#include <stdio.h>
-#include <stdlib.h>
+#include <stdint.h>
#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
-#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)
-{
- size_t r = 0;
+#define tryReadlong(var, file, ...) \
+ tryRead(readlong, int64_t, INT64_MAX, var, file, __VA_ARGS__)
- size_t bufferLength = 16;
- char *start = malloc(bufferLength);
- char *s = start;
+/* There is no `readbyte`, just use `fgetc` or `getc`. */
- if (!s)
- err(1, "%s: Couldn't allocate memory", __func__);
+/**
+ * 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__)
- while (((*s++) = fgetc(f)) != 0) {
- r += 1;
+/**
+ * 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__)
- if (r >= bufferLength) {
- bufferLength *= 2;
- start = realloc(start, bufferLength);
- if (!start) {
- err(1, "%s: Couldn't allocate memory",
- __func__);
- }
- s = start + r;
- }
- }
-
- *dest = start;
- return (r + 1);
-}
-
-/*
- * Allocate a new section and link it into the list
+/**
+ * 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!
*/
-struct sSection *AllocSection(void)
+static char *readstr(FILE *file)
{
- struct sSection **ppSections;
+ /* 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;
- if (oReadLib == 1)
- ppSections = &pLibSections;
- else
- ppSections = &pSections;
+ do {
+ /* Prepare going to next char */
+ index++;
- while (*ppSections)
- ppSections = &((*ppSections)->pNext);
+ /* 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;
+ }
- *ppSections = malloc(sizeof **ppSections);
- if (!*ppSections)
- err(1, "%s: Couldn't allocate memory", __func__);
+ /* Read char */
+ int byte = getc(file);
- (*ppSections)->tSymbols = tSymbols;
- (*ppSections)->pNext = NULL;
- (*ppSections)->pPatches = NULL;
- (*ppSections)->oAssigned = 0;
- return *ppSections;
+ if (byte == EOF)
+ return NULL;
+ str[index] = byte;
+ } while (str[index]);
+ return str;
}
-/*
- * Read a symbol from a file
+/**
+ * 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 sSymbol *obj_ReadSymbol(FILE *f, char *tzObjectfile)
-{
- struct sSymbol *pSym;
+#define tryReadstr(var, file, ...) \
+ tryRead(readstr, char*, NULL, var, file, __VA_ARGS__)
- pSym = malloc(sizeof(*pSym));
- if (!pSym)
- err(1, "%s: Couldn't allocate memory", __func__);
+/***** Functions to parse object files *****/
- readasciiz(&pSym->pzName, f);
- pSym->Type = (enum eSymbolType)fgetc(f);
+/**
+ * Reads a RGB6 symbol from a file.
+ * @param file The file to read from
+ * @param symbol The struct to fill
+ * @param fileName The filename to report in errors
+ */
+static void readSymbol(FILE *file, struct Symbol *symbol, char const *fileName)
+{
+ 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 {
+ symbol->sectionID = -1;
+ }
+}
- pSym->pzObjFileName = tzObjectfile;
+/**
+ * Reads a RGB6 patch from a file.
+ * @param file The file to read from
+ * @param patch The struct to fill
+ * @param fileName The filename to report in errors
+ * @param i The number of the patch to report in errors
+ */
+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);
- if (pSym->Type != SYM_IMPORT) {
- readasciiz(&pSym->pzFileName, f);
- pSym->nFileLine = readlong(f);
+ uint8_t *rpnExpression =
+ malloc(sizeof(*rpnExpression) * patch->rpnSize);
+ size_t nbElementsRead = fread(rpnExpression, sizeof(*rpnExpression),
+ patch->rpnSize, file);
- pSym->nSectionID = readlong(f);
- pSym->nOffset = readlong(f);
- }
-
- return pSym;
+ if (nbElementsRead != patch->rpnSize)
+ errx(1, "%s: Cannot read \"%s\"'s patch #%u's RPN expression: %s",
+ fileName, sectName, i);
+ patch->rpnExpression = rpnExpression;
}
-/*
- * RGB object reader routines
+/**
+ * Reads a RGB6 section from a file.
+ * @param file The file to read from
+ * @param section The struct to fill
+ * @param fileName The filename to report in errors
*/
-struct sSection *obj_ReadRGBSection(FILE *f)
+static void readSection(FILE *file, struct Section *section,
+ char const *fileName)
{
- struct sSection *pSection;
- char *pzName;
+ int32_t tmp;
- readasciiz(&pzName, f);
- if (IsSectionNameInUse(pzName))
- errx(1, "Section name \"%s\" is already in use.", pzName);
+ 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;
- pSection = AllocSection();
- pSection->pzName = pzName;
+ if (sect_HasData(section->type)) {
+ /* Ensure we never allocate 0 bytes */
+ uint8_t *data = malloc(sizeof(*data) * section->size + 1);
- pSection->nByteSize = readlong(f);
- pSection->Type = (enum eSectionType)fgetc(f);
- pSection->nOrg = readlong(f);
- pSection->nBank = readlong(f);
- pSection->nAlign = readlong(f);
+ 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;
- if ((options & OPT_TINY) && (pSection->Type == SECT_ROMX))
- errx(1, "ROMX sections can't be used with option -t.");
+ tryReadlong(section->nbPatches, file,
+ "%s: Cannot read \"%s\"'s number of patches: %s",
+ fileName, section->name);
- if ((options & OPT_CONTWRAM) && (pSection->Type == SECT_WRAMX))
- errx(1, "WRAMX sections can't be used with options -w or -d.");
+ struct Patch *patches =
+ malloc(sizeof(*patches) * section->nbPatches + 1);
- 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.");
+ 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;
}
+}
- uint32_t maxsize = 0;
+/**
+ * Links a symbol to a section, keeping the section's symbol list sorted.
+ * @param symbol The symbol to link
+ * @param section The section to link
+ */
+static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
+{
+ uint32_t a = 0, b = section->nbSymbols;
- /* 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);
- }
+ while (a != b) {
+ uint32_t c = (a + b) / 2;
- /*
- * 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;
+ if (section->symbols[c]->offset > symbol->offset)
+ b = c;
+ else
+ a = c + 1;
}
- pSection->pData = malloc(pSection->nByteSize);
- if (!pSection->pData)
- err(1, "%s: Couldn't allocate memory", __func__);
+ struct Symbol const *tmp = symbol;
- 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__);
+ for (uint32_t i = a; i <= section->nbSymbols; i++) {
+ symbol = tmp;
+ tmp = section->symbols[i];
+ section->symbols[i] = symbol;
}
- nNumberOfPatches = readlong(f);
- ppPatch = &pSection->pPatches;
+ section->nbSymbols++;
+}
- /*
- * And patches...
- */
- while (nNumberOfPatches--) {
- pPatch = malloc(sizeof(*pPatch));
- if (!pPatch)
- err(1, "%s: Couldn't allocate memory", __func__);
+/**
+ * Reads a RGB6 object file.
+ * @param file The file to read from
+ * @param fileName The filename to report in errors
+ */
+static void readRGB6File(FILE *file, char const *fileName)
+{
+ uint32_t nbSymbols;
+ uint32_t nbSections;
- *ppPatch = pPatch;
- readasciiz(&pPatch->pzFilename, f);
- pPatch->nLineNo = readlong(f);
- pPatch->nOffset = readlong(f);
- pPatch->Type = (enum ePatchType)fgetc(f);
- pPatch->nRPNSize = readlong(f);
+ tryReadlong(nbSymbols, file, "%s: Cannot read number of symbols: %s",
+ fileName);
+ tryReadlong(nbSections, file, "%s: Cannot read number of sections: %s",
+ fileName);
- if (pPatch->nRPNSize > 0) {
- pPatch->pRPN = malloc(pPatch->nRPNSize);
- if (!pPatch->pRPN) {
- err(1, "%s: Couldn't allocate memory",
- __func__);
- }
+ nbSectionsToAssign += nbSections;
- if (fread(pPatch->pRPN, sizeof(uint8_t),
- pPatch->nRPNSize, f) != pPatch->nRPNSize) {
- errx(1, "%s: Read error", __func__);
- }
- } else {
- pPatch->pRPN = NULL;
- }
+ /* This file's symbols, kept to link sections to them */
+ struct Symbol **fileSymbols =
+ malloc(sizeof(*fileSymbols) * nbSymbols + 1);
- pPatch->pNext = NULL;
- ppPatch = &(pPatch->pNext);
- }
+ if (!fileSymbols)
+ err(1, "Failed to get memory for %s's symbols", fileName);
- return pSection;
-}
+ struct SymbolList *symbolList = malloc(sizeof(*symbolList));
-void obj_ReadRGB(FILE *pObjfile, char *tzObjectfile)
-{
- struct sSection *pFirstSection;
- int32_t nNumberOfSymbols, nNumberOfSections, i;
+ if (!symbolList)
+ err(1, "Failed to register %s's symbol list", fileName);
+ symbolList->symbolList = fileSymbols;
+ symbolList->nbSymbols = nbSymbols;
+ symbolList->next = symbolLists;
+ symbolLists = symbolList;
- nNumberOfSymbols = readlong(pObjfile);
- nNumberOfSections = readlong(pObjfile);
+ uint32_t nbSymPerSect[nbSections];
- /* First comes the symbols */
+ memset(nbSymPerSect, 0, sizeof(nbSymPerSect));
- if (nNumberOfSymbols) {
- tSymbols = malloc(nNumberOfSymbols * sizeof(*tSymbols));
- if (!tSymbols)
- err(1, "%s: Couldn't allocate memory", __func__);
+ verbosePrint("Reading %u symbols...\n", nbSymbols);
+ for (uint32_t i = 0; i < nbSymbols; i++) {
+ /* Read symbol */
+ struct Symbol *symbol = malloc(sizeof(*symbol));
- for (i = 0; i < nNumberOfSymbols; i += 1)
- tSymbols[i] = obj_ReadSymbol(pObjfile, tzObjectfile);
- } else {
- tSymbols = NULL;
+ 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]++;
}
- /* Next we have the sections */
+ /* This file's sections, stored in a table to link symbols to them */
+ struct Section *fileSections[nbSections ? nbSections : 1];
- pFirstSection = NULL;
- while (nNumberOfSections--) {
- struct sSection *pNewSection;
+ verbosePrint("Reading %u sections...\n", nbSections);
+ for (uint32_t i = 0; i < nbSections; i++) {
+ /* Read section */
+ struct Section *section = malloc(sizeof(*section));
- pNewSection = obj_ReadRGBSection(pObjfile);
- pNewSection->nNumberOfSymbols = nNumberOfSymbols;
- if (pFirstSection == NULL)
- pFirstSection = pNewSection;
+ 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 {
+ section->symbols = NULL;
+ }
+ section->nbSymbols = 0;
}
- /*
- * Fill in the pSection entry in the symbolstructure.
- * This REALLY needs some cleaning up... but, hey, it works
- */
+ /* Give symbols pointers to their sections */
+ for (uint32_t i = 0; i < nbSymbols; i++) {
+ int32_t sectionID = fileSymbols[i]->sectionID;
- for (i = 0; i < nNumberOfSymbols; i += 1) {
- struct sSection *pConvSect = pFirstSection;
-
- if ((tSymbols[i]->Type != SYM_IMPORT) &&
- (tSymbols[i]->nSectionID != -1)) {
- int32_t j = 0;
-
- while (j != tSymbols[i]->nSectionID) {
- j += 1;
- pConvSect = pConvSect->pNext;
- }
- tSymbols[i]->pSection = pConvSect;
+ if (sectionID == -1) {
+ fileSymbols[i]->section = NULL;
} else {
- tSymbols[i]->pSection = NULL;
+ fileSymbols[i]->section = fileSections[sectionID];
+ /* Give the section a pointer to the symbol as well */
+ linkSymToSect(fileSymbols[i], fileSections[sectionID]);
}
}
}
-/*
- * The main objectfileloadroutine (phew)
+/**
+ * Reads an object file of any supported format
+ * @param fileName The filename to report for errors
*/
-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_Readfile(char *tzObjectfile)
+void obj_DoSanityChecks(void)
{
- FILE *pObjfile;
+ sect_DoSanityChecks();
+}
- if (options & OPT_SMART_C_LINK)
- oReadLib = 1;
- else
- oReadLib = 0;
+static void freeSection(struct Section *section, void *arg)
+{
+ (void)arg;
- pObjfile = fopen(tzObjectfile, "rb");
- if (pObjfile == NULL)
- err(1, "Unable to open object '%s'", tzObjectfile);
+ 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];
- obj_ReadOpenFile(pObjfile, tzObjectfile);
- fclose(pObjfile);
+ free(patch->fileName);
+ free(patch->rpnExpression);
+ }
+ free(section->patches);
+ }
+ free(section->symbols);
+ free(section);
+}
- oReadLib = 0;
+static void freeSymbol(struct Symbol *symbol)
+{
+ free(symbol->name);
+ if (symbol->type != SYMTYPE_IMPORT)
+ free(symbol->fileName);
+ free(symbol);
}
-int32_t file_Length(FILE *f)
+void obj_Cleanup(void)
{
- uint32_t r, p;
+ sym_CleanupSymbols();
- p = ftell(f);
- fseek(f, 0, SEEK_END);
- r = ftell(f);
- fseek(f, p, SEEK_SET);
+ sect_ForEach(freeSection, NULL);
+ sect_CleanupSections();
- return r;
+ 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;
+ }
}
--- a/src/link/output.c
+++ b/src/link/output.c
@@ -1,174 +1,394 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-#include <stdint.h>
-#include <stdio.h>
#include <stdlib.h>
-#include <string.h>
+#include <stdint.h>
+#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"
+FILE *outputFile;
+FILE *overlayFile;
+FILE *symFile;
+FILE *mapFile;
-char *tzOutname;
-char *tzOverlayname;
+struct SortedSection {
+ struct Section const *section;
+ struct SortedSection *next;
+};
-int32_t MaxOverlayBank;
+static struct {
+ uint32_t nbBanks;
+ struct SortedSections {
+ struct SortedSection *sections;
+ struct SortedSection *zeroLenSections;
+ } *banks;
+} sections[SECTTYPE_INVALID];
-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)
- return;
+ uint32_t targetBank = section->bank - bankranges[section->type][0];
+ uint32_t minNbBanks = targetBank + 1;
- 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);
+ if (minNbBanks > maxNbBanks[section->type])
+ errx(1, "Section \"%s\" has invalid bank range (%u > %u)",
+ section->name, section->bank,
+ maxNbBanks[section->type] - 1);
- pSect = pSections;
- while (pSect) {
- if (pSect->Type == SECT_ROM0) {
- memcpy(mem + pSect->nOrg, pSect->pData,
- pSect->nByteSize);
- MapfileWriteSection(pSect);
+ 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;
}
- pSect = pSect->pNext;
+ sections[section->type].nbBanks = minNbBanks;
}
+ if (!sections[section->type].banks)
+ err(1, "Failed to realloc banks");
- MapfileCloseBank(area_Avail(0));
+ struct SortedSection *newSection = malloc(sizeof(*newSection));
+ struct SortedSection **ptr = section->size
+ ? §ions[section->type].banks[targetBank].sections
+ : §ions[section->type].banks[targetBank].zeroLenSections;
- fwrite(mem, 1, MaxAvail[BANK_INDEX_ROM0], f);
- free(mem);
+ 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;
}
-void writebank(FILE *f, FILE *f_overlay, int32_t bank)
+/**
+ * Performs sanity checks on the overlay file.
+ */
+static void checkOverlay(void)
{
- const struct sSection *pSect;
- uint8_t *mem;
+ if (!overlayFile)
+ return;
- mem = malloc(MaxAvail[bank]);
- if (!mem)
+ if (fseek(overlayFile, 0, SEEK_END) != 0) {
+ warnx("Overlay file is not seekable, cannot check if properly formed");
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]);
+ long overlaySize = ftell(overlayFile);
+
+ if (overlaySize % 0x4000)
+ errx(1, "Overlay file must have a size multiple of 0x4000");
+
+ /* Reset back to beginning */
+ fseek(overlayFile, 0, SEEK_SET);
+
+ 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;
}
- MapfileInitBank(bank);
+}
- pSect = pSections;
- while (pSect) {
- if (pSect->Type == SECT_ROMX && pSect->nBank == bank) {
- memcpy(mem + pSect->nOrg - 0x4000, pSect->pData,
- pSect->nByteSize);
- MapfileWriteSection(pSect);
+/**
+ * Write a ROM bank's sections to the output file.
+ * @param bankSections The bank's sections, ordered by increasing address
+ * @param baseOffset The address of the bank's first byte in GB address space
+ * @param size The size of the bank
+ */
+static void writeBank(struct SortedSection *bankSections, uint16_t baseOffset,
+ uint16_t size)
+{
+ uint16_t offset = 0;
+
+ 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++;
}
- pSect = pSect->pNext;
- }
- MapfileCloseBank(area_Avail(bank));
+ /* 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;
- fwrite(mem, 1, MaxAvail[bank], f);
- free(mem);
+ bankSections = bankSections->next;
+ }
+
+ while (offset < size) {
+ putc_unlocked(overlayFile ? getc_unlocked(overlayFile)
+ : padValue,
+ outputFile);
+ offset++;
+ }
}
-void out_Setname(char *tzOutputfile)
+/**
+ * Writes a ROM file to the output.
+ */
+static void writeROM(void)
{
- tzOutname = tzOutputfile;
+ outputFile = openFile(outputFileName, "wb");
+ overlayFile = openFile(overlayFileName, "rb");
+
+ 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);
+ }
+
+ closeFile(outputFile);
+ closeFile(overlayFile);
}
-void out_SetOverlayname(char *tzOverlayfile)
+/**
+ * Get the lowest section by address out of the two
+ * @param s1 One choice
+ * @param s2 The other
+ * @return The lowest section of the two, or the non-NULL one if applicable
+ */
+static struct SortedSection const **nextSection(struct SortedSection const **s1,
+ struct SortedSection const **s2)
{
- tzOverlayname = tzOverlayfile;
+ if (!*s1)
+ return s2;
+ if (!*s2)
+ return s1;
+
+ return (*s1)->section->org < (*s2)->section->org ? s1 : s2;
}
-void Output(void)
+/**
+ * Write a bank's contents to the sym file
+ * @param bankSections The bank's sections
+ */
+static void writeSymBank(struct SortedSections const *bankSections)
{
- int32_t i;
- FILE *f;
- FILE *f_overlay = NULL;
+ if (!symFile)
+ return;
- /*
- * Load overlay
- */
+ 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;
- if (tzOverlayname) {
- f_overlay = fopen(tzOverlayname, "rb");
+ for (;;) {
+ while (sectList.sections
+ && sectList.i == sectList.sect->nbSymbols) {
+ sectList.sections = sectList.sections->next;
+ sectList.i = 0;
+ }
+ while (zlSectList.sections
+ && zlSectList.i == zlSectList.sect->nbSymbols) {
+ zlSectList.sections = zlSectList.sections->next;
+ zlSectList.i = 0;
+ }
- if (!f_overlay) {
- errx(1, "Failed to open overlay file %s\n",
- tzOverlayname);
+ 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;
+
+ minSectList = sectList.addr < zlSectList.addr
+ ? §List
+ : &zlSectList;
+ } else if (sectList.sections) {
+ sectList.sym = sectList.sect->symbols[sectList.i];
+ sectList.addr =
+ sectList.sym->offset + sectList.sect->org;
+
+ minSectList = §List;
+ } else {
+ zlSectList.sym = zlSectList.sect->symbols[zlSectList.i];
+ zlSectList.addr =
+ zlSectList.sym->offset + zlSectList.sect->org;
+
+ minSectList = &zlSectList;
}
+ fprintf(symFile, "%02x:%04x %s\n",
+ minSectList->sect->bank, minSectList->addr,
+ minSectList->sym->name);
+ minSectList->i++;
+ }
+#undef sect
+}
- fseek(f_overlay, 0, SEEK_END);
+/**
+ * Write a bank's contents to the map file
+ * @param bankSections The bank's sections
+ */
+static void writeMapBank(struct SortedSections const *sectList,
+ enum SectionType type, uint32_t bank)
+{
+ if (!mapFile)
+ return;
- if (ftell(f_overlay) % 0x4000 != 0)
- errx(1, "Overlay file must be aligned to 0x4000 bytes.");
+ struct SortedSection const *section = sectList->sections;
+ struct SortedSection const *zeroLenSection = sectList->zeroLenSections;
- MaxOverlayBank = (ftell(f_overlay) / 0x4000) - 1;
+ fprintf(mapFile, "%s bank #%u:\n", typeNames[type],
+ bank + bankranges[type][0]);
- if (MaxOverlayBank < 1)
- errx(1, "Overlay file must be at least 0x8000 bytes.");
+ uint16_t slack = maxsize[type];
- if (MaxOverlayBank > MaxBankUsed)
- MaxBankUsed = MaxOverlayBank;
+ 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;
}
- /*
- * Write ROM.
- */
+ if (slack == maxsize[type])
+ fputs(" EMPTY\n\n", mapFile);
+ else
+ fprintf(mapFile, " SLACK: $%04x byte%s\n\n", slack,
+ slack == 1 ? "" : "s");
+}
- f = fopen(tzOutname, "wb");
- if (f != NULL) {
- writehome(f, f_overlay);
- for (i = 1; i <= MaxBankUsed; i += 1)
- writebank(f, f_overlay, i);
+/**
+ * Writes the sym and/or map files, if applicable.
+ */
+static void writeSymAndMap(void)
+{
+ if (!symFileName && !mapFileName)
+ return;
- fclose(f);
+ enum SectionType typeMap[SECTTYPE_INVALID] = {
+ SECTTYPE_ROM0,
+ SECTTYPE_ROMX,
+ SECTTYPE_VRAM,
+ SECTTYPE_SRAM,
+ SECTTYPE_WRAM0,
+ SECTTYPE_WRAMX,
+ SECTTYPE_OAM,
+ SECTTYPE_HRAM
+ };
+
+ symFile = openFile(symFileName, "w");
+ mapFile = openFile(mapFileName, "w");
+
+ if (symFileName) {
+ fputs("; File generated by rgblink\n", symFile);
}
- /*
- * Close overlay
- */
+ for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
+ enum SectionType type = typeMap[i];
- if (tzOverlayname)
- fclose(f_overlay);
+ 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);
+ }
+ }
+ }
- /*
- * Add regular sections to map and sym files.
- */
+ closeFile(symFile);
+ closeFile(mapFile);
+}
- for (i = BANK_INDEX_WRAM0; i < BANK_INDEX_MAX; i++) {
- const struct sSection *pSect;
+static void cleanupSections(struct SortedSection *section)
+{
+ while (section) {
+ struct SortedSection *next = section->next;
- MapfileInitBank(i);
- pSect = pSections;
- while (pSect) {
- if (pSect->nBank == i)
- MapfileWriteSection(pSect);
- pSect = pSect->pNext;
+ 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);
}
- MapfileCloseBank(area_Avail(i));
}
+}
+
+void out_WriteFiles(void)
+{
+ writeROM();
+ writeSymAndMap();
+
+ cleanup();
}
--- a/src/link/parser.y
+++ /dev/null
@@ -1,127 +1,0 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-
-%{
-#include <stdint.h>
-#include <stdio.h>
-
-#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<i> INTEGER
-%token<s> STRING
-
-%token<s> SECTION_NONBANKED
-%token<s> 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);
-}
-
--- a/src/link/patch.c
+++ b/src/link/patch.c
@@ -1,344 +1,343 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
-#include <stdint.h>
-#include <stdio.h>
#include <stdlib.h>
+#include <limits.h>
#include <string.h>
-#include "extern/err.h"
-
-#include "link/assign.h"
-#include "link/main.h"
-#include "link/mylink.h"
+#include "link/patch.h"
+#include "link/section.h"
#include "link/symbol.h"
-#define RPN_STACK_SIZE 256
+#include "linkdefs.h"
-static struct sSection *pCurrentSection;
-static int32_t rpnstack[RPN_STACK_SIZE];
-static int32_t rpnp;
-int32_t nPC;
+#include "extern/err.h"
-static void rpnpush(int32_t i)
-{
- if (rpnp >= RPN_STACK_SIZE)
- errx(1, "RPN stack overflow");
+/* This is an "empty"-type stack */
+struct RPNStack {
+ int32_t *buf;
+ size_t size;
+ size_t capacity;
+} stack;
- rpnstack[rpnp] = i;
- rpnp++;
+static inline void initRPNStack(void)
+{
+ 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];
+ free(stack.buf);
+}
- 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__);
- }
+/* RPN operators */
- return getrealbankfrominternalbank(nBank);
+static uint8_t getRPNByte(uint8_t const **expression, int32_t *size,
+ char const *fileName, int32_t lineNo)
+{
+ if (!(*size)--)
+ errx(1, "%s(%d): RPN expression overread", fileName, lineNo);
+ return *(*expression)++;
}
-int32_t calcrpn(struct sPatch *pPatch)
+/**
+ * Compute a patch's value from its RPN string.
+ * @param patch The patch to compute the value of
+ * @param section The section the patch is contained in
+ * @return The patch's value
+ */
+static int32_t computeRPNExpr(struct Patch const *patch,
+ struct Section const *section)
{
- int32_t t, size;
- uint8_t *rpn;
- uint8_t rpn_cmd;
- int32_t nBank;
+ uint8_t const *expression = patch->rpnExpression;
+ int32_t size = patch->rpnSize;
- rpnp = 0;
+ clearRPNStack();
- size = pPatch->nRPNSize;
- rpn = pPatch->pRPN;
- pPatch->oRelocPatch = 0;
-
while (size > 0) {
- size -= 1;
- rpn_cmd = *rpn++;
+ enum RPNCommand command = getRPNByte(&expression, &size,
+ patch->fileName,
+ patch->lineNo);
+ int32_t value;
- switch (rpn_cmd) {
+ /*
+ * 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;
+
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);
+ value = popRPN();
+ value = popRPN() >> value;
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);
+
+ case RPN_BANK_SYM:
+ 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) {
+ struct Symbol const *symbolDefinition =
+ sym_GetSymbol(symbol->name);
+ if (!symbolDefinition)
+ errx(1, "%s(%d): Unknown symbol \"%s\"",
+ patch->fileName, patch->lineNo,
+ symbol->name);
+ symbol = symbolDefinition;
}
+
+ value = symbol->section->bank;
break;
- case RPN_CONST:
- /* constant */
- t = (*rpn++);
- t |= (*rpn++) << 8;
- t |= (*rpn++) << 16;
- t |= (*rpn++) << 24;
- rpnpush(t);
- size -= 4;
+
+ case RPN_BANK_SECT:
+ name = (char const *)expression;
+ while (getRPNByte(&expression, &size, patch->fileName,
+ patch->lineNo))
+ ;
+
+ sect = sect_GetSection(name);
+
+ if (!sect)
+ errx(1, "%s(%d): Requested BANK() of section \"%s\", which was not found",
+ patch->fileName, patch->lineNo, name);
+
+ value = sect->bank;
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;
+
+ case RPN_BANK_SELF:
+ value = section->bank;
break;
- case RPN_BANK_SYM:
- /* symbol */
- t = (*rpn++);
- t |= (*rpn++) << 8;
- t |= (*rpn++) << 16;
- t |= (*rpn++) << 24;
- rpnpush(getsymbank(pPatch, t));
- size -= 4;
+
+ 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;
- case RPN_BANK_SECT:
- {
- char *name = (char *)rpn;
- struct sSection *pSection = GetSectionByName(name);
+ case RPN_CONST:
+ value = 0;
+ for (uint8_t shift = 0; shift < 32; shift += 8)
+ value |= getRPNByte(&expression, &size,
+ patch->fileName,
+ patch->lineNo) << shift;
+ break;
- if (pSection == NULL) {
- errx(1,
- "%s(%ld) : Requested BANK() of section \"%s\", which was not found.\n",
- pPatch->pzFilename, pPatch->nLineNo,
- name);
- }
+ case RPN_SYM:
+ value = 0;
+ for (uint8_t shift = 0; shift < 32; shift += 8)
+ value |= getRPNByte(&expression, &size,
+ patch->fileName,
+ patch->lineNo) << shift;
- nBank = pSection->nBank;
- rpnpush(getrealbankfrominternalbank(nBank));
+ symbol = section->fileSymbols[value];
- int len = strlen(name);
+ /* If the symbol is defined elsewhere... */
+ if (symbol->type == SYMTYPE_IMPORT) {
+ struct Symbol const *symbolDefinition =
+ sym_GetSymbol(symbol->name);
+ if (!symbolDefinition)
+ errx(1, "%s(%d): Unknown symbol \"%s\"",
+ patch->fileName, patch->lineNo,
+ symbol->name);
+ symbol = symbolDefinition;
+ }
- size -= len + 1;
- rpn += len + 1;
+ 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;
}
- case RPN_BANK_SELF:
- nBank = pCurrentSection->nBank;
- rpnpush(getrealbankfrominternalbank(nBank));
- break;
- default:
- errx(1, "%s: Invalid command %d\n", __func__,
- rpn_cmd);
- break;
- }
+
+ pushRPN(value);
}
- return rpnpop();
+
+ if (stack.size > 1)
+ warnx("%s(%d): RPN stack has %lu entries on exit, not 1",
+ patch->fileName, patch->lineNo, stack.size);
+
+ return popRPN();
}
-void Patch(void)
+/**
+ * Applies all of a section's patches
+ * @param section The section to patch
+ * @param arg Ignored callback arg
+ */
+static void applyPatches(struct Section *section, void *arg)
{
- struct sSection *pSect;
+ (void)arg;
- pSect = pSections;
- while (pSect) {
- struct sPatch *pPatch;
+ if (!sect_HasData(section->type))
+ return;
- pCurrentSection = pSect;
- pPatch = pSect->pPatches;
- while (pPatch) {
- int32_t t;
- int32_t nPatchOrg;
+ 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);
- 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;
+ 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);
- /* t contains the destination of the jump */
- t = (int16_t)((t & 0xFFFF) - (nPatchOrg + 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 (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 (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;
}
-
- pPatch = pPatch->pNext;
}
-
- pSect = pSect->pNext;
}
+}
+
+void patch_ApplyPatches(void)
+{
+ initRPNStack();
+ sect_ForEach(applyPatches, NULL);
+ freeRPNStack();
}
--- a/src/link/script.c
+++ b/src/link/script.c
@@ -1,237 +1,516 @@
-/*
- * This file is part of RGBDS.
- *
- * Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
- *
- * SPDX-License-Identifier: MIT
- */
+#include <stdlib.h>
+#include <stdbool.h>
#include <stdint.h>
#include <string.h>
+#include <ctype.h>
+#include "link/main.h"
+#include "link/script.h"
+#include "link/section.h"
+
#include "extern/err.h"
-#include "link/assign.h"
-#include "link/mylink.h"
+FILE *linkerScript;
+static uint32_t lineNo;
+
static struct {
- uint32_t address; /* current address to write sections to */
- uint32_t top_address; /* not inclusive */
- enum eSectionType type;
-} bank[BANK_INDEX_MAX];
+ FILE *file;
+ uint32_t lineNo;
+ char const *name;
+} *fileStack;
-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 */
+static uint32_t fileStackSize;
+static uint32_t fileStackIndex;
-/* Current section attributes */
-static int32_t fix_org = -1;
-static int32_t fix_align = 1;
-
-void script_InitSections(void)
+static void pushFile(char const *newFileName)
{
- int32_t i;
+ if (fileStackIndex == UINT32_MAX)
+ errx(1, "%s(%u): INCLUDE recursion limit reached",
+ linkerScriptName, lineNo);
- 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;
- }
- } 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);
- }
+ if (fileStackIndex == fileStackSize) {
+ if (!fileStackSize) /* Init file stack */
+ fileStackSize = 4;
+ fileStackSize *= 2;
+ fileStack = realloc(fileStack,
+ sizeof(*fileStack) * fileStackSize);
+ if (!fileStack)
+ err(1, "%s(%u): Internal INCLUDE error",
+ linkerScriptName, lineNo);
}
+
+ fileStack[fileStackIndex].file = linkerScript;
+ fileStack[fileStackIndex].lineNo = lineNo;
+ fileStack[fileStackIndex].name = linkerScriptName;
+ fileStackIndex++;
+
+ linkerScript = fopen(newFileName, "r");
+ if (!linkerScript)
+ err(1, "%s(%u): Could not open \"%s\"",
+ linkerScriptName, lineNo, newFileName);
+ lineNo = 1;
+ linkerScriptName = newFileName;
}
-void script_SetCurrentSectionType(const char *type, uint32_t bank_num)
+static bool popFile(void)
{
- 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");
+ if (!fileStackIndex)
+ return false;
+
+ fileStackIndex--;
+ linkerScript = fileStack[fileStackIndex].file;
+ lineNo = fileStack[fileStackIndex].lineNo;
+ linkerScriptName = fileStack[fileStackIndex].name;
+
+ return true;
+}
+
+static inline bool isWhiteSpace(int c)
+{
+ return c == ' ' || c == '\t';
+}
+
+static inline bool isNewline(int c)
+{
+ return c == '\r' || c == '\n';
+}
+
+/**
+ * Try parsing a number, in base 16 if it begins with a dollar,
+ * in base 10 otherwise
+ * @param str The number to parse
+ * @param number A pointer where the number will be written to
+ * @return True if parsing was successful, false otherwise
+ */
+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;
- 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);
+ 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_INCLUDE,
+ TOKEN_NUMBER,
+ TOKEN_STRING,
+ TOKEN_EOF,
+
+ TOKEN_INVALID
+};
+
+char const *tokenTypes[] = {
+ [TOKEN_NEWLINE] = "newline",
+ [TOKEN_COMMAND] = "command",
+ [TOKEN_BANK] = "bank command",
+ [TOKEN_NUMBER] = "number",
+ [TOKEN_STRING] = "string",
+ [TOKEN_EOF] = "end of file"
+};
+
+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 int readChar(FILE *file)
+{
+ int curchar = getc_unlocked(file);
+
+ if (curchar == EOF && ferror(file))
+ err(1, "%s(%u): Unexpected error in %s", linkerScriptName,
+ lineNo, __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_STRING)
+ 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 string */
+ token.type = TOKEN_STRING;
+ 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, "%s(%u): Unterminated string",
+ linkerScriptName, 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 string",
+ __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';
+ }
}
- 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);
+
+ token.type = TOKEN_INVALID;
+
+ /* Try to match a command */
+ for (enum LinkerScriptCommand i = 0; i < COMMAND_INVALID; i++) {
+ if (!strcmp(commands[i], str)) {
+ token.type = TOKEN_COMMAND;
+ token.attr.command = i;
+ break;
+ }
}
- 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__);
+
+ if (token.type == TOKEN_INVALID) {
+ /* Try to match a bank specifier */
+ for (enum SectionType type = 0; type < SECTTYPE_INVALID;
+ type++) {
+ if (!strcmp(typeNames[type], str)) {
+ token.type = TOKEN_BANK;
+ token.attr.secttype = type;
+ break;
+ }
+ }
}
- 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__);
+
+ if (token.type == TOKEN_INVALID) {
+ /* Try to match an include token */
+ if (!strcmp("INCLUDE", str))
+ token.type = TOKEN_INCLUDE;
}
- current_bank = BANK_INDEX_HRAM;
- current_real_bank = 0;
- return;
+
+ 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, "%s(%u): Unknown token \"%s\"",
+ linkerScriptName, lineNo, str);
+ }
+
+ free(str);
}
- errx(1, "%s: Unknown section type \"%s\".\n", __func__, type);
+ return &token;
}
-void script_SetAddress(uint32_t addr)
+static void processCommand(enum LinkerScriptCommand command, uint16_t arg,
+ uint16_t *pc)
{
- if (current_bank == -1)
- errx(1, "Trying to set an address without assigned bank\n");
+ switch (command) {
+ case COMMAND_INVALID:
+ trap_;
- /* 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);
- }
+ case COMMAND_ORG:
+ *pc = arg;
+ break;
- bank[current_bank].address = addr;
-
- /* 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);
+ case COMMAND_ALIGN:
+ if (arg >= 16)
+ arg = 0;
+ else
+ arg = (*pc + (1 << arg) - 1) & ~((1 << arg) - 1);
}
- fix_org = addr;
+ if (arg < *pc)
+ errx(1, "%s(%u): `%s` cannot be used to go backwards",
+ linkerScriptName, lineNo, commands[command]);
+ *pc = arg;
}
-void script_SetAlignment(uint32_t alignment)
+enum LinkerScriptParserState {
+ PARSER_FIRSTTIME,
+ PARSER_LINESTART,
+ PARSER_INCLUDE, /* After an INCLUDE token */
+ 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 alignment without assigned bank\n");
+ static struct SectionPlacement section;
+ static enum SectionType type;
+ static uint32_t bank;
+ static uint32_t bankID;
- if (alignment > 15)
- errx(1, "Trying to set an alignment too big: %d\n", alignment);
+ if (parserState == PARSER_FIRSTTIME) {
+ lineNo = 1;
- uint32_t size = 1 << alignment;
- uint32_t mask = size - 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];
+ }
- if (bank[current_bank].address & mask) {
- bank[current_bank].address &= ~mask;
- bank[current_bank].address += size;
- }
+ type = SECTTYPE_INVALID;
- /* 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);
+ parserState = PARSER_LINESTART;
}
- fix_align = size;
-}
+ for (;;) {
+ struct LinkerScriptToken const *token = nextToken();
+ enum LinkerScriptTokenType tokType;
+ union LinkerScriptTokenAttr attr;
+ bool hasArg;
+ uint32_t arg;
-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 (type != SECTTYPE_INVALID) {
+ if (curaddr[type][bankID] > endaddr(type) + 1)
+ errx(1, "%s(%u): Sections would extend past the end of %s ($%04hx > $%04hx)",
+ linkerScriptName, lineNo, typeNames[type],
+ curaddr[type][bankID], endaddr(type));
+ if (curaddr[type][bankID] < startaddr[type])
+ errx(1, "%s(%u): PC underflowed ($%04hx < $%04hx)",
+ linkerScriptName, lineNo,
+ curaddr[type][bankID], startaddr[type]);
+ }
- 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);
- }
+ switch (parserState) {
+ case PARSER_FIRSTTIME:
+ trap_;
- /* Move section to its place. */
- bank[current_bank].address +=
- AssignSectionAddressAndBankByName(section_name,
- bank[current_bank].address,
- current_real_bank);
+ case PARSER_LINESTART:
+ switch (token->type) {
+ case TOKEN_INVALID:
+ trap_;
- fix_org = -1;
- fix_align = 1;
+ case TOKEN_EOF:
+ if (!popFile())
+ return NULL;
+ parserState = PARSER_LINEEND;
+ break;
+
+ case TOKEN_NUMBER:
+ errx(1, "%s(%u): stray number \"%u\"",
+ linkerScriptName, lineNo,
+ token->attr.number);
+
+ case TOKEN_NEWLINE:
+ lineNo++;
+ break;
+
+ /* A stray string is a section name */
+ case TOKEN_STRING:
+ parserState = PARSER_LINEEND;
+
+ if (type == SECTTYPE_INVALID)
+ errx(1, "%s(%u): Didn't specify a location before the section",
+ linkerScriptName, 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, "%s(%u): Didn't specify a location before the command",
+ linkerScriptName, lineNo);
+ if (!hasArg)
+ errx(1, "%s(%u): Command specified without an argument",
+ linkerScriptName, 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, "%s(%u): Didn't specify a bank number",
+ linkerScriptName, lineNo);
+ else if (!hasArg)
+ arg = bankranges[type][0];
+ else if (arg < bankranges[type][0])
+ errx(1, "%s(%u): specified bank number is too low (%u < %u)",
+ linkerScriptName, lineNo,
+ arg, bankranges[type][0]);
+ else if (arg > bankranges[type][1])
+ errx(1, "%s(%u): specified bank number is too high (%u > %u)",
+ linkerScriptName, 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;
+
+ case TOKEN_INCLUDE:
+ parserState = PARSER_INCLUDE;
+ break;
+ }
+ break;
+
+ case PARSER_INCLUDE:
+ if (token->type != TOKEN_STRING)
+ errx(1, "%s(%u): Expected a file name after INCLUDE",
+ linkerScriptName, lineNo);
+
+ /* Switch to that file */
+ pushFile(token->attr.string);
+
+ parserState = PARSER_LINESTART;
+ break;
+
+ case PARSER_LINEEND:
+lineend:
+ lineNo++;
+ parserState = PARSER_LINESTART;
+ if (token->type == TOKEN_EOF) {
+ if (!popFile())
+ return NULL;
+ parserState = PARSER_LINEEND;
+ } else if (token->type != TOKEN_NEWLINE)
+ errx(1, "%s(%u): Unexpected %s at the end of the line",
+ linkerScriptName, lineNo,
+ tokenTypes[token->type]);
+ break;
+ }
+ }
+}
+
+void script_Cleanup(void)
+{
+ for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++)
+ free(curaddr[type]);
}
--- /dev/null
+++ b/src/link/section.c
@@ -1,0 +1,168 @@
+
+#include <stdbool.h>
+
+#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);
+}
--- 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 <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include <stdbool.h>
+#include "link/symbol.h"
+#include "link/main.h"
#include "extern/err.h"
+#include "hashmap.h"
-#include "link/main.h"
-#include "link/patch.h"
-#include "link/mylink.h"
+HashMap symbols;
-#include "types.h"
-
-#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);
}
--- a/src/rgbds.5
+++ b/src/rgbds.5
@@ -91,8 +91,8 @@
; decide (floating bank). This field is only valid for ROMX,
; VRAM, WRAMX and SRAM sections.
- LONG Align ; Alignment of this section (expressed as number of low bits
- ; to leave as 0). -1 if not defined.
+ LONG Align ; Alignment of this section, expressed as 1 << align. 1 if
+ ; not specified.
IF (Type == ROMX) || (Type == ROM0) ; Sections that can contain data.
--- a/test/link/romx-tiny-no-t.out
+++ b/test/link/romx-tiny-no-t.out
@@ -1,1 +1,1 @@
-error: Unable to place 'r0b' (ROM0 section) anywhere
+error: Unable to place "r0a" (ROM0 section) anywhere
--- a/test/link/romx-tiny-t.out
+++ b/test/link/romx-tiny-t.out
@@ -1,1 +1,1 @@
-error: ROMX sections can't be used with option -t.
+error: rx: ROMX sections cannot be used with option -t.
--- 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"
--- a/test/link/section-attributes-mismatch.out
+++ b/test/link/section-attributes-mismatch.out
@@ -1,2 +1,1 @@
-error: Different attributes for "sec" in source and linkerscript
-
+error: Linker script contradicts "sec"'s alignment
--- a/test/link/vram-fixed-dmg-mode-d.out
+++ b/test/link/vram-fixed-dmg-mode-d.out
@@ -1,1 +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.
--- a/test/link/vram-floating-dmg-mode-d.out
+++ b/test/link/vram-floating-dmg-mode-d.out
@@ -1,1 +1,1 @@
-error: Unable to place 'v1' (VRAM section) in any bank
+error: Unable to place "v1" (VRAM section) anywhere
--- a/test/link/wramx-dmg-mode-d.out
+++ b/test/link/wramx-dmg-mode-d.out
@@ -1,1 +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.
--- a/test/link/wramx-dmg-mode-no-d.out
+++ b/test/link/wramx-dmg-mode-no-d.out
@@ -1,1 +1,1 @@
-error: Unable to place 'w0b' (WRAM0 section) anywhere
+error: Unable to place "w0b" (WRAM0 section) anywhere