mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Port linkerscript parser to Bison (#1266)
Notable side effects: * Use the standard-conformant MSVC preproc * Add test for linker script INCLUDE * Improve wording of placement conflict errors * Fix errors from not newline-terminated files * Teach checkdiff about the linker script doc * Call linker script "commands" "directives" instead --------- Co-authored-by: Rangi42 <remy.oukaour+rangi42@gmail.com>
This commit is contained in:
@@ -23,7 +23,8 @@ option(MORE_WARNINGS "Turn on more warnings" OFF) # Ignored on MSVC
|
|||||||
if(MSVC)
|
if(MSVC)
|
||||||
# MSVC's standard library triggers warning C5105,
|
# MSVC's standard library triggers warning C5105,
|
||||||
# "macro expansion producing 'defined' has undefined behavior"
|
# "macro expansion producing 'defined' has undefined behavior"
|
||||||
add_compile_options(/MP /wd5105)
|
# We also opt into the C++20-conformant preprocessor.
|
||||||
|
add_compile_options(/MP /wd5105 /Zc:preprocessor)
|
||||||
add_definitions(/D_CRT_SECURE_NO_WARNINGS)
|
add_definitions(/D_CRT_SECURE_NO_WARNINGS)
|
||||||
|
|
||||||
if(SANITIZERS)
|
if(SANITIZERS)
|
||||||
|
|||||||
14
Makefile
14
Makefile
@@ -65,14 +65,14 @@ rgbasm_obj := \
|
|||||||
src/asm/rpn.o \
|
src/asm/rpn.o \
|
||||||
src/asm/section.o \
|
src/asm/section.o \
|
||||||
src/asm/symbol.o \
|
src/asm/symbol.o \
|
||||||
src/asm/util.o \
|
|
||||||
src/asm/warning.o \
|
src/asm/warning.o \
|
||||||
src/extern/getopt.o \
|
src/extern/getopt.o \
|
||||||
src/extern/utf8decoder.o \
|
src/extern/utf8decoder.o \
|
||||||
src/error.o \
|
src/error.o \
|
||||||
src/hashmap.o \
|
src/hashmap.o \
|
||||||
src/linkdefs.o \
|
src/linkdefs.o \
|
||||||
src/opmath.o
|
src/opmath.o \
|
||||||
|
src/util.o
|
||||||
|
|
||||||
src/asm/lexer.o src/asm/main.o: src/asm/parser.hpp
|
src/asm/lexer.o src/asm/main.o: src/asm/parser.hpp
|
||||||
|
|
||||||
@@ -91,7 +91,8 @@ rgblink_obj := \
|
|||||||
src/error.o \
|
src/error.o \
|
||||||
src/hashmap.o \
|
src/hashmap.o \
|
||||||
src/linkdefs.o \
|
src/linkdefs.o \
|
||||||
src/opmath.o
|
src/opmath.o \
|
||||||
|
src/util.o
|
||||||
|
|
||||||
rgbfix_obj := \
|
rgbfix_obj := \
|
||||||
src/fix/main.o \
|
src/fix/main.o \
|
||||||
@@ -133,13 +134,13 @@ test/gfx/rgbgfx_test: test/gfx/rgbgfx_test.cpp
|
|||||||
# We want the Bison invocation to pass through our rules, not default ones
|
# We want the Bison invocation to pass through our rules, not default ones
|
||||||
.y.o:
|
.y.o:
|
||||||
|
|
||||||
|
.y.cpp:
|
||||||
|
$Q${BISON} $@ $<
|
||||||
|
|
||||||
# Bison-generated C++ files have an accompanying header
|
# Bison-generated C++ files have an accompanying header
|
||||||
src/asm/parser.hpp: src/asm/parser.cpp
|
src/asm/parser.hpp: src/asm/parser.cpp
|
||||||
$Qtouch $@
|
$Qtouch $@
|
||||||
|
|
||||||
src/asm/parser.cpp: src/asm/parser.y
|
|
||||||
$Q${BISON} $@ $<
|
|
||||||
|
|
||||||
# Only RGBGFX uses libpng (POSIX make doesn't support pattern rules to cover all these)
|
# Only RGBGFX uses libpng (POSIX make doesn't support pattern rules to cover all these)
|
||||||
src/gfx/main.o: src/gfx/main.cpp
|
src/gfx/main.o: src/gfx/main.cpp
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
@@ -172,6 +173,7 @@ clean:
|
|||||||
$Qfind . -type f \( -name "*.gcno" -o -name "*.gcda" -o -name "*.gcov" \) -exec rm {} \;
|
$Qfind . -type f \( -name "*.gcno" -o -name "*.gcda" -o -name "*.gcov" \) -exec rm {} \;
|
||||||
$Q${RM} rgbshim.sh
|
$Q${RM} rgbshim.sh
|
||||||
$Q${RM} src/asm/parser.cpp src/asm/parser.hpp
|
$Q${RM} src/asm/parser.cpp src/asm/parser.hpp
|
||||||
|
$Q${RM} src/link/script.cpp src/link/script.hpp src/link/stack.hh
|
||||||
$Q${RM} test/gfx/randtilegen test/gfx/rgbgfx_test
|
$Q${RM} test/gfx/randtilegen test/gfx/rgbgfx_test
|
||||||
|
|
||||||
# Target used to install the binaries and man pages.
|
# Target used to install the binaries and man pages.
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ dependency include/linkdefs.hpp man/rgbds.5 \
|
|||||||
dependency src/asm/parser.y man/rgbasm.5 \
|
dependency src/asm/parser.y man/rgbasm.5 \
|
||||||
"Was the rgbasm grammar changed?"
|
"Was the rgbasm grammar changed?"
|
||||||
|
|
||||||
|
dependency src/link/script.y man/rgblink.5 \
|
||||||
|
"Was the linker script grammar changed?"
|
||||||
|
|
||||||
dependency include/asm/warning.hpp man/rgbasm.1 \
|
dependency include/asm/warning.hpp man/rgbasm.1 \
|
||||||
"Were the rgbasm warnings changed?"
|
"Were the rgbasm warnings changed?"
|
||||||
|
|
||||||
|
|||||||
@@ -8,26 +8,9 @@
|
|||||||
|
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
|
|
||||||
extern FILE * linkerScript;
|
|
||||||
|
|
||||||
struct SectionPlacement {
|
|
||||||
struct Section *section;
|
|
||||||
enum SectionType type;
|
|
||||||
uint16_t org;
|
|
||||||
uint32_t bank;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern uint64_t script_lineNo;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parses the linker script to return the next section constraint
|
* Parses the linker script, and modifies the sections mentioned within appropriately.
|
||||||
* @return A pointer to a struct, or NULL on EOF. The pointer shouldn't be freed
|
|
||||||
*/
|
*/
|
||||||
struct SectionPlacement *script_NextSection(void);
|
void script_ProcessScript(char const *path);
|
||||||
|
|
||||||
/*
|
|
||||||
* `free`s all assignment memory that was allocated.
|
|
||||||
*/
|
|
||||||
void script_Cleanup(void);
|
|
||||||
|
|
||||||
#endif // RGBDS_LINK_SCRIPT_H
|
#endif // RGBDS_LINK_SCRIPT_H
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
||||||
#define RGBDS_OBJECT_REV 9U
|
#define RGBDS_OBJECT_REV 9U
|
||||||
@@ -83,7 +84,7 @@ enum FileStackNodeType {
|
|||||||
|
|
||||||
// Nont-`const` members may be patched in RGBLINK depending on CLI flags
|
// Nont-`const` members may be patched in RGBLINK depending on CLI flags
|
||||||
extern struct SectionTypeInfo {
|
extern struct SectionTypeInfo {
|
||||||
char const *const name;
|
std::string const name;
|
||||||
uint16_t const startAddr;
|
uint16_t const startAddr;
|
||||||
uint16_t size;
|
uint16_t size;
|
||||||
uint32_t const firstBank;
|
uint32_t const firstBank;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#ifndef RGBDS_UTIL_H
|
#ifndef RGBDS_UTIL_H
|
||||||
#define RGBDS_UTIL_H
|
#define RGBDS_UTIL_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
char const *printChar(int c);
|
char const *printChar(int c);
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
The linker script is an external file that allows the user to specify the order of sections at link time and in a centralized manner.
|
The linker script is an external file that allows the user to specify the order of sections at link time and in a centralized manner.
|
||||||
.Pp
|
.Pp
|
||||||
A linker script consists of a series of bank declarations, each optionally followed by a list of section names (in double quotes) or commands.
|
A linker script consists of a series of bank declarations, each optionally followed by a list of section names (in double quotes) or directives.
|
||||||
All reserved keywords (bank types and command names) are case-insensitive; all section names are case-sensitive.
|
All reserved keywords (bank types and directive names) are case-insensitive; all section names are case-sensitive.
|
||||||
.Pp
|
.Pp
|
||||||
Any line can contain a comment starting with
|
Any line can contain a comment starting with
|
||||||
.Ql \&;
|
.Ql \&;
|
||||||
@@ -19,17 +19,17 @@ that ends at the end of the line.
|
|||||||
; This line is a comment
|
; This line is a comment
|
||||||
ROMX $F ; start a bank
|
ROMX $F ; start a bank
|
||||||
"Some functions" ; a section name
|
"Some functions" ; a section name
|
||||||
ALIGN 8 ; a command
|
ALIGN 8 ; a directive
|
||||||
"Some array"
|
"Some array"
|
||||||
|
|
||||||
WRAMX 2 ; start another bank
|
WRAMX 2 ; start another bank
|
||||||
org $d123 ; another command
|
org $d123 ; another directive
|
||||||
"Some variables"
|
"Some variables"
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
Numbers can be in decimal or hexadecimal format
|
Numbers can be in decimal or hexadecimal format
|
||||||
.Pq the prefix is Ql $ .
|
.Pq the prefix is Ql $ .
|
||||||
It is an error if any section name or command is found before setting a bank.
|
It is an error if any section name or directive is found before setting a bank.
|
||||||
.Pp
|
.Pp
|
||||||
Files can be included by using the
|
Files can be included by using the
|
||||||
.Ic INCLUDE
|
.Ic INCLUDE
|
||||||
@@ -43,7 +43,7 @@ Unless there is a single bank, which can occur with types
|
|||||||
.Cm ROMX , VRAM , SRAM
|
.Cm ROMX , VRAM , SRAM
|
||||||
and
|
and
|
||||||
.Cm WRAMX ,
|
.Cm WRAMX ,
|
||||||
it is needed to specify a bank number after the type.
|
it is mandatory to specify a bank number after the type.
|
||||||
.Pp
|
.Pp
|
||||||
Section names in double quotes support the same character escape sequences as strings in
|
Section names in double quotes support the same character escape sequences as strings in
|
||||||
.Xr rgbasm 5 ,
|
.Xr rgbasm 5 ,
|
||||||
@@ -61,7 +61,7 @@ are only relevant to assembly code and do not apply in section names.
|
|||||||
When a new bank statement is found, sections found after it will be placed right from the beginning of that bank.
|
When a new bank statement is found, sections found after it will be placed right from the beginning of that bank.
|
||||||
If the linker script switches to a different bank and then comes back to a previous one, it will continue from the last address that was used.
|
If the linker script switches to a different bank and then comes back to a previous one, it will continue from the last address that was used.
|
||||||
.Pp
|
.Pp
|
||||||
The only three commands are
|
The only three directives are
|
||||||
.Ic ORG ,
|
.Ic ORG ,
|
||||||
.Ic ALIGN ,
|
.Ic ALIGN ,
|
||||||
and
|
and
|
||||||
@@ -74,7 +74,7 @@ It can not be lower than the current address.
|
|||||||
.It
|
.It
|
||||||
.Ic ALIGN
|
.Ic ALIGN
|
||||||
will increase the address until it is aligned to the specified boundary
|
will increase the address until it is aligned to the specified boundary
|
||||||
.Po it tries to set to 0 the number of bits specified after the command:
|
.Po it tries to set to 0 the number of bits specified after the directive:
|
||||||
.Ql ALIGN 8
|
.Ql ALIGN 8
|
||||||
will align to $100
|
will align to $100
|
||||||
.Pc .
|
.Pc .
|
||||||
|
|||||||
@@ -22,14 +22,20 @@ endif()
|
|||||||
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.lac=full")
|
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.lac=full")
|
||||||
set(BISON_FLAGS "${BISON_FLAGS} -Dlr.type=ielr")
|
set(BISON_FLAGS "${BISON_FLAGS} -Dlr.type=ielr")
|
||||||
|
|
||||||
BISON_TARGET(PARSER "asm/parser.y"
|
BISON_TARGET(ASM_PARSER "asm/parser.y"
|
||||||
"${PROJECT_SOURCE_DIR}/src/asm/parser.cpp"
|
"${PROJECT_SOURCE_DIR}/src/asm/parser.cpp"
|
||||||
COMPILE_FLAGS "${BISON_FLAGS}"
|
COMPILE_FLAGS "${BISON_FLAGS}"
|
||||||
DEFINES_FILE "${PROJECT_SOURCE_DIR}/src/asm/parser.hpp"
|
DEFINES_FILE "${PROJECT_SOURCE_DIR}/src/asm/parser.hpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
BISON_TARGET(LINKER_SCRIPT_PARSER "link/script.y"
|
||||||
|
"${PROJECT_SOURCE_DIR}/src/link/script.cpp"
|
||||||
|
COMPILE_FLAGS "${BISON_FLAGS}"
|
||||||
|
DEFINES_FILE "${PROJECT_SOURCE_DIR}/src/link/script.hpp"
|
||||||
|
)
|
||||||
|
|
||||||
set(rgbasm_src
|
set(rgbasm_src
|
||||||
"${BISON_PARSER_OUTPUT_SOURCE}"
|
"${BISON_ASM_PARSER_OUTPUT_SOURCE}"
|
||||||
"asm/charmap.cpp"
|
"asm/charmap.cpp"
|
||||||
"asm/fixpoint.cpp"
|
"asm/fixpoint.cpp"
|
||||||
"asm/format.cpp"
|
"asm/format.cpp"
|
||||||
@@ -42,12 +48,12 @@ set(rgbasm_src
|
|||||||
"asm/rpn.cpp"
|
"asm/rpn.cpp"
|
||||||
"asm/section.cpp"
|
"asm/section.cpp"
|
||||||
"asm/symbol.cpp"
|
"asm/symbol.cpp"
|
||||||
"asm/util.cpp"
|
|
||||||
"asm/warning.cpp"
|
"asm/warning.cpp"
|
||||||
"extern/utf8decoder.cpp"
|
"extern/utf8decoder.cpp"
|
||||||
"hashmap.cpp"
|
"hashmap.cpp"
|
||||||
"linkdefs.cpp"
|
"linkdefs.cpp"
|
||||||
"opmath.cpp"
|
"opmath.cpp"
|
||||||
|
"util.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(rgbfix_src
|
set(rgbfix_src
|
||||||
@@ -68,12 +74,12 @@ set(rgbgfx_src
|
|||||||
)
|
)
|
||||||
|
|
||||||
set(rgblink_src
|
set(rgblink_src
|
||||||
|
"${BISON_LINKER_SCRIPT_PARSER_OUTPUT_SOURCE}"
|
||||||
"link/assign.cpp"
|
"link/assign.cpp"
|
||||||
"link/main.cpp"
|
"link/main.cpp"
|
||||||
"link/object.cpp"
|
"link/object.cpp"
|
||||||
"link/output.cpp"
|
"link/output.cpp"
|
||||||
"link/patch.cpp"
|
"link/patch.cpp"
|
||||||
"link/script.cpp"
|
|
||||||
"link/sdas_obj.cpp"
|
"link/sdas_obj.cpp"
|
||||||
"link/section.cpp"
|
"link/section.cpp"
|
||||||
"link/symbol.cpp"
|
"link/symbol.cpp"
|
||||||
@@ -81,6 +87,7 @@ set(rgblink_src
|
|||||||
"hashmap.cpp"
|
"hashmap.cpp"
|
||||||
"linkdefs.cpp"
|
"linkdefs.cpp"
|
||||||
"opmath.cpp"
|
"opmath.cpp"
|
||||||
|
"util.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
foreach(PROG "asm" "fix" "gfx" "link")
|
foreach(PROG "asm" "fix" "gfx" "link")
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
#include "asm/charmap.hpp"
|
#include "asm/charmap.hpp"
|
||||||
#include "asm/main.hpp"
|
#include "asm/main.hpp"
|
||||||
#include "asm/output.hpp"
|
#include "asm/output.hpp"
|
||||||
#include "asm/util.hpp"
|
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
#include "hashmap.hpp"
|
#include "hashmap.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
// Charmaps are stored using a structure known as "trie".
|
// Charmaps are stored using a structure known as "trie".
|
||||||
// Essentially a tree, where each nodes stores a single character's worth of info:
|
// Essentially a tree, where each nodes stores a single character's worth of info:
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "platform.hpp" // For `ssize_t` and `AT`
|
#include "platform.hpp" // For `ssize_t` and `AT`
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
#include "asm/lexer.hpp"
|
#include "asm/lexer.hpp"
|
||||||
#include "asm/fixpoint.hpp"
|
#include "asm/fixpoint.hpp"
|
||||||
@@ -28,7 +29,6 @@
|
|||||||
#include "asm/main.hpp"
|
#include "asm/main.hpp"
|
||||||
#include "asm/rpn.hpp"
|
#include "asm/rpn.hpp"
|
||||||
#include "asm/symbol.hpp"
|
#include "asm/symbol.hpp"
|
||||||
#include "asm/util.hpp"
|
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
// Include this last so it gets all type & constant definitions
|
// Include this last so it gets all type & constant definitions
|
||||||
#include "parser.hpp" // For token definitions, generated from parser.y
|
#include "parser.hpp" // For token definitions, generated from parser.y
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
#include "asm/rpn.hpp"
|
#include "asm/rpn.hpp"
|
||||||
#include "asm/section.hpp"
|
#include "asm/section.hpp"
|
||||||
#include "asm/symbol.hpp"
|
#include "asm/symbol.hpp"
|
||||||
#include "asm/util.hpp"
|
#include "util.hpp"
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
#include "extern/utf8decoder.hpp"
|
#include "extern/utf8decoder.hpp"
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
|
|||||||
unsigned int nbSectErrors = 0;
|
unsigned int nbSectErrors = 0;
|
||||||
|
|
||||||
if (type != sect->type)
|
if (type != sect->type)
|
||||||
fail("Section already exists but with type %s\n", sectionTypeInfo[sect->type].name);
|
fail("Section already exists but with type %s\n", sectionTypeInfo[sect->type].name.c_str());
|
||||||
|
|
||||||
if (sect->modifier != mod) {
|
if (sect->modifier != mod) {
|
||||||
fail("Section already declared as %s section\n", sectionModNames[sect->modifier]);
|
fail("Section already declared as %s section\n", sectionModNames[sect->modifier]);
|
||||||
@@ -314,7 +314,7 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
|
|||||||
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
||||||
else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank)
|
else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank)
|
||||||
error("%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04"
|
error("%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04"
|
||||||
PRIx32 ")\n", sectionTypeInfo[type].name, bank,
|
PRIx32 ")\n", sectionTypeInfo[type].name.c_str(), bank,
|
||||||
sectionTypeInfo[type].firstBank, sectionTypeInfo[type].lastBank);
|
sectionTypeInfo[type].firstBank, sectionTypeInfo[type].lastBank);
|
||||||
} else if (nbbanks(type) == 1) {
|
} else if (nbbanks(type) == 1) {
|
||||||
// If the section type only has a single bank, implicitly force it
|
// If the section type only has a single bank, implicitly force it
|
||||||
@@ -349,7 +349,7 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
|
|||||||
alignment = 0; // Ignore it if it's satisfied
|
alignment = 0; // Ignore it if it's satisfied
|
||||||
} else if (sectionTypeInfo[type].startAddr & mask) {
|
} else if (sectionTypeInfo[type].startAddr & mask) {
|
||||||
error("Section \"%s\"'s alignment cannot be attained in %s\n",
|
error("Section \"%s\"'s alignment cannot be attained in %s\n",
|
||||||
name, sectionTypeInfo[type].name);
|
name, sectionTypeInfo[type].name.c_str());
|
||||||
alignment = 0; // Ignore it if it's unattainable
|
alignment = 0; // Ignore it if it's unattainable
|
||||||
org = 0;
|
org = 0;
|
||||||
} else if (alignment == 16) {
|
} else if (alignment == 16) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
#include "asm/output.hpp"
|
#include "asm/output.hpp"
|
||||||
#include "asm/section.hpp"
|
#include "asm/section.hpp"
|
||||||
#include "asm/symbol.hpp"
|
#include "asm/symbol.hpp"
|
||||||
#include "asm/util.hpp"
|
#include "util.hpp"
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
#include "error.hpp"
|
#include "error.hpp"
|
||||||
|
|||||||
3
src/link/.gitignore
vendored
Normal file
3
src/link/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/script.cpp
|
||||||
|
/script.hpp
|
||||||
|
/stack.hh
|
||||||
@@ -298,16 +298,16 @@ static void placeSection(struct Section *section)
|
|||||||
// If a section failed to go to several places, nothing we can report
|
// If a section failed to go to several places, nothing we can report
|
||||||
if (!section->isBankFixed || !section->isAddressFixed)
|
if (!section->isBankFixed || !section->isAddressFixed)
|
||||||
errx("Unable to place \"%s\" (%s section) %s",
|
errx("Unable to place \"%s\" (%s section) %s",
|
||||||
section->name, sectionTypeInfo[section->type].name, where);
|
section->name, sectionTypeInfo[section->type].name.c_str(), where);
|
||||||
// If the section just can't fit the bank, report that
|
// If the section just can't fit the bank, report that
|
||||||
else if (section->org + section->size > endaddr(section->type) + 1)
|
else if (section->org + section->size > endaddr(section->type) + 1)
|
||||||
errx("Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > $%04x)",
|
errx("Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > $%04x)",
|
||||||
section->name, sectionTypeInfo[section->type].name, where,
|
section->name, sectionTypeInfo[section->type].name.c_str(), where,
|
||||||
section->org + section->size, endaddr(section->type) + 1);
|
section->org + section->size, endaddr(section->type) + 1);
|
||||||
// Otherwise there is overlap with another section
|
// Otherwise there is overlap with another section
|
||||||
else
|
else
|
||||||
errx("Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
|
errx("Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
|
||||||
section->name, sectionTypeInfo[section->type].name, where,
|
section->name, sectionTypeInfo[section->type].name.c_str(), where,
|
||||||
out_OverlappingSection(section)->name);
|
out_OverlappingSection(section)->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ bool beVerbose; // -v
|
|||||||
bool isWRA0Mode; // -w
|
bool isWRA0Mode; // -w
|
||||||
bool disablePadding; // -x
|
bool disablePadding; // -x
|
||||||
|
|
||||||
|
FILE *linkerScript;
|
||||||
|
|
||||||
static uint32_t nbErrors = 0;
|
static uint32_t nbErrors = 0;
|
||||||
|
|
||||||
// Helper function to dump a file stack to stderr
|
// Helper function to dump a file stack to stderr
|
||||||
@@ -460,44 +462,7 @@ int main(int argc, char *argv[])
|
|||||||
if (linkerScriptName) {
|
if (linkerScriptName) {
|
||||||
verbosePrint("Reading linker script...\n");
|
verbosePrint("Reading linker script...\n");
|
||||||
|
|
||||||
linkerScript = openFile(linkerScriptName, "r");
|
script_ProcessScript(linkerScriptName);
|
||||||
|
|
||||||
// Modify all sections according to the linker script
|
|
||||||
struct SectionPlacement *placement;
|
|
||||||
|
|
||||||
while ((placement = script_NextSection())) {
|
|
||||||
struct Section *section = placement->section;
|
|
||||||
|
|
||||||
assert(section->offset == 0);
|
|
||||||
// Check if this doesn't conflict with what the code says
|
|
||||||
if (section->type == SECTTYPE_INVALID) {
|
|
||||||
for (struct Section *sect = section; sect; sect = sect->nextu)
|
|
||||||
sect->type = placement->type; // SDCC "unknown" sections
|
|
||||||
} else if (section->type != placement->type) {
|
|
||||||
error(NULL, 0, "Linker script contradicts \"%s\"'s type",
|
|
||||||
section->name);
|
|
||||||
}
|
|
||||||
if (section->isBankFixed && placement->bank != section->bank)
|
|
||||||
error(NULL, 0, "Linker script contradicts \"%s\"'s bank placement",
|
|
||||||
section->name);
|
|
||||||
if (section->isAddressFixed && placement->org != section->org)
|
|
||||||
error(NULL, 0, "Linker script contradicts \"%s\"'s address placement",
|
|
||||||
section->name);
|
|
||||||
if (section->isAlignFixed
|
|
||||||
&& (placement->org & section->alignMask) != 0)
|
|
||||||
error(NULL, 0, "Linker script contradicts \"%s\"'s alignment",
|
|
||||||
section->name);
|
|
||||||
|
|
||||||
section->isAddressFixed = true;
|
|
||||||
section->org = placement->org;
|
|
||||||
section->isBankFixed = true;
|
|
||||||
section->bank = placement->bank;
|
|
||||||
section->isAlignFixed = false; // The alignment is satisfied
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(linkerScript);
|
|
||||||
|
|
||||||
script_Cleanup();
|
|
||||||
|
|
||||||
// If the linker script produced any errors, some sections may be in an invalid state
|
// If the linker script produced any errors, some sections may be in an invalid state
|
||||||
if (nbErrors != 0)
|
if (nbErrors != 0)
|
||||||
|
|||||||
@@ -430,7 +430,7 @@ static void writeEmptySpace(uint16_t begin, uint16_t end)
|
|||||||
static void writeMapBank(struct SortedSections const *sectList, enum SectionType type,
|
static void writeMapBank(struct SortedSections const *sectList, enum SectionType type,
|
||||||
uint32_t bank)
|
uint32_t bank)
|
||||||
{
|
{
|
||||||
fprintf(mapFile, "\n%s bank #%" PRIu32 ":\n", sectionTypeInfo[type].name,
|
fprintf(mapFile, "\n%s bank #%" PRIu32 ":\n", sectionTypeInfo[type].name.c_str(),
|
||||||
bank + sectionTypeInfo[type].firstBank);
|
bank + sectionTypeInfo[type].firstBank);
|
||||||
|
|
||||||
uint16_t used = 0;
|
uint16_t used = 0;
|
||||||
@@ -540,7 +540,7 @@ static void writeMapSummary(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fprintf(mapFile, "\t%s: %" PRId32 " byte%s used / %" PRId32 " free",
|
fprintf(mapFile, "\t%s: %" PRId32 " byte%s used / %" PRId32 " free",
|
||||||
sectionTypeInfo[type].name, usedTotal, usedTotal == 1 ? "" : "s",
|
sectionTypeInfo[type].name.c_str(), usedTotal, usedTotal == 1 ? "" : "s",
|
||||||
nbBanks * sectionTypeInfo[type].size - usedTotal);
|
nbBanks * sectionTypeInfo[type].size - usedTotal);
|
||||||
if (sectionTypeInfo[type].firstBank != sectionTypeInfo[type].lastBank
|
if (sectionTypeInfo[type].firstBank != sectionTypeInfo[type].lastBank
|
||||||
|| nbBanks > 1)
|
|| nbBanks > 1)
|
||||||
|
|||||||
@@ -1,550 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
|
||||||
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "link/main.hpp"
|
|
||||||
#include "link/script.hpp"
|
|
||||||
#include "link/section.hpp"
|
|
||||||
|
|
||||||
#include "error.hpp"
|
|
||||||
#include "itertools.hpp"
|
|
||||||
#include "linkdefs.hpp"
|
|
||||||
#include "platform.hpp"
|
|
||||||
|
|
||||||
FILE *linkerScript;
|
|
||||||
char *includeFileName;
|
|
||||||
|
|
||||||
static uint32_t lineNo;
|
|
||||||
|
|
||||||
struct FileNode {
|
|
||||||
FILE *file;
|
|
||||||
uint32_t lineNo;
|
|
||||||
char *name;
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct FileNode *fileStack;
|
|
||||||
|
|
||||||
static uint32_t fileStackSize;
|
|
||||||
static uint32_t fileStackIndex;
|
|
||||||
|
|
||||||
static void pushFile(char *newFileName)
|
|
||||||
{
|
|
||||||
if (fileStackIndex == UINT32_MAX)
|
|
||||||
errx("%s(%" PRIu32 "): INCLUDE recursion limit reached",
|
|
||||||
linkerScriptName, lineNo);
|
|
||||||
|
|
||||||
if (fileStackIndex == fileStackSize) {
|
|
||||||
if (!fileStackSize) // Init file stack
|
|
||||||
fileStackSize = 4;
|
|
||||||
fileStackSize *= 2;
|
|
||||||
fileStack = (struct FileNode *)realloc(fileStack, sizeof(*fileStack) * fileStackSize);
|
|
||||||
if (!fileStack)
|
|
||||||
err("%s(%" PRIu32 "): 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("%s(%" PRIu32 "): Could not open \"%s\"",
|
|
||||||
linkerScriptName, lineNo, newFileName);
|
|
||||||
lineNo = 1;
|
|
||||||
linkerScriptName = newFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool popFile(void)
|
|
||||||
{
|
|
||||||
if (!fileStackIndex)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
free(linkerScriptName);
|
|
||||||
|
|
||||||
fileStackIndex--;
|
|
||||||
linkerScript = fileStack[fileStackIndex].file;
|
|
||||||
lineNo = fileStack[fileStackIndex].lineNo;
|
|
||||||
linkerScriptName = fileStack[fileStackIndex].name;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isWhiteSpace(int c)
|
|
||||||
{
|
|
||||||
return c == ' ' || c == '\t';
|
|
||||||
}
|
|
||||||
|
|
||||||
static 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;
|
|
||||||
|
|
||||||
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_INVALID] = {
|
|
||||||
AT(TOKEN_NEWLINE) "newline",
|
|
||||||
AT(TOKEN_COMMAND) "command",
|
|
||||||
AT(TOKEN_BANK) "bank command",
|
|
||||||
AT(TOKEN_INCLUDE) NULL,
|
|
||||||
AT(TOKEN_NUMBER) "number",
|
|
||||||
AT(TOKEN_STRING) "string",
|
|
||||||
AT(TOKEN_EOF) "end of file",
|
|
||||||
};
|
|
||||||
|
|
||||||
enum LinkerScriptCommand {
|
|
||||||
COMMAND_ORG,
|
|
||||||
COMMAND_ALIGN,
|
|
||||||
COMMAND_DS,
|
|
||||||
|
|
||||||
COMMAND_INVALID
|
|
||||||
};
|
|
||||||
|
|
||||||
union LinkerScriptTokenAttr {
|
|
||||||
enum LinkerScriptCommand command;
|
|
||||||
enum SectionType secttype;
|
|
||||||
uint32_t number;
|
|
||||||
char *string;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct LinkerScriptToken {
|
|
||||||
enum LinkerScriptTokenType type;
|
|
||||||
union LinkerScriptTokenAttr attr;
|
|
||||||
};
|
|
||||||
|
|
||||||
static char const * const commands[COMMAND_INVALID] = {
|
|
||||||
AT(COMMAND_ORG) "ORG",
|
|
||||||
AT(COMMAND_ALIGN) "ALIGN",
|
|
||||||
AT(COMMAND_DS) "DS"
|
|
||||||
};
|
|
||||||
|
|
||||||
static int nextChar(void)
|
|
||||||
{
|
|
||||||
int curchar = getc(linkerScript);
|
|
||||||
|
|
||||||
if (curchar == EOF && ferror(linkerScript))
|
|
||||||
err("%s(%" PRIu32 "): Unexpected error in %s",
|
|
||||||
linkerScriptName, lineNo, __func__);
|
|
||||||
return curchar;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct LinkerScriptToken *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 = nextChar();
|
|
||||||
while (isWhiteSpace(curchar));
|
|
||||||
|
|
||||||
// If this is a comment, skip to the end of the line
|
|
||||||
if (curchar == ';') {
|
|
||||||
do {
|
|
||||||
curchar = nextChar();
|
|
||||||
} 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;
|
|
||||||
|
|
||||||
if (curchar == '\r') {
|
|
||||||
// Handle CRLF
|
|
||||||
curchar = nextChar();
|
|
||||||
if (curchar != '\n')
|
|
||||||
ungetc(curchar, linkerScript);
|
|
||||||
}
|
|
||||||
} 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 = nextChar();
|
|
||||||
if (curchar == EOF || isNewline(curchar)) {
|
|
||||||
errx("%s(%" PRIu32 "): Unterminated string",
|
|
||||||
linkerScriptName, lineNo);
|
|
||||||
} else if (curchar == '"') {
|
|
||||||
// Quotes force a string termination
|
|
||||||
curchar = '\0';
|
|
||||||
} else if (curchar == '\\') {
|
|
||||||
// Backslashes are escape sequences
|
|
||||||
curchar = nextChar();
|
|
||||||
if (curchar == EOF || isNewline(curchar))
|
|
||||||
errx("%s(%" PRIu32 "): Unterminated string",
|
|
||||||
linkerScriptName, lineNo);
|
|
||||||
else if (curchar == 'n')
|
|
||||||
curchar = '\n';
|
|
||||||
else if (curchar == 'r')
|
|
||||||
curchar = '\r';
|
|
||||||
else if (curchar == 't')
|
|
||||||
curchar = '\t';
|
|
||||||
else if (curchar != '\\' && curchar != '"')
|
|
||||||
errx("%s(%" PRIu32 "): Illegal character escape",
|
|
||||||
linkerScriptName, lineNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size >= capacity || token.attr.string == NULL) {
|
|
||||||
capacity *= 2;
|
|
||||||
token.attr.string = (char *)realloc(token.attr.string, capacity);
|
|
||||||
if (!token.attr.string)
|
|
||||||
err("%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 = (char *)realloc(str, capacity);
|
|
||||||
if (!str)
|
|
||||||
err("%s: Failed to allocate memory for token",
|
|
||||||
__func__);
|
|
||||||
}
|
|
||||||
str[size] = toupper(curchar);
|
|
||||||
size++;
|
|
||||||
|
|
||||||
if (!curchar)
|
|
||||||
break;
|
|
||||||
|
|
||||||
curchar = nextChar();
|
|
||||||
// Whitespace, a newline or a comment end the token
|
|
||||||
if (isWhiteSpace(curchar) || isNewline(curchar) || curchar == ';') {
|
|
||||||
ungetc(curchar, linkerScript);
|
|
||||||
curchar = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
token.type = TOKEN_INVALID;
|
|
||||||
|
|
||||||
// Try to match a command
|
|
||||||
for (enum LinkerScriptCommand i : EnumSeq(COMMAND_INVALID)) {
|
|
||||||
if (!strcmp(commands[i], str)) {
|
|
||||||
token.type = TOKEN_COMMAND;
|
|
||||||
token.attr.command = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.type == TOKEN_INVALID) {
|
|
||||||
// Try to match a bank specifier
|
|
||||||
for (enum SectionType type : EnumSeq(SECTTYPE_INVALID)) {
|
|
||||||
if (!strcmp(sectionTypeInfo[type].name, str)) {
|
|
||||||
token.type = TOKEN_BANK;
|
|
||||||
token.attr.secttype = type;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.type == TOKEN_INVALID) {
|
|
||||||
// Try to match an include token
|
|
||||||
if (!strcmp("INCLUDE", str))
|
|
||||||
token.type = TOKEN_INCLUDE;
|
|
||||||
}
|
|
||||||
|
|
||||||
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("%s(%" PRIu32 "): Unknown token \"%s\"",
|
|
||||||
linkerScriptName, lineNo, str);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
return &token;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void processCommand(enum LinkerScriptCommand command, uint16_t arg, uint16_t *pc)
|
|
||||||
{
|
|
||||||
switch (command) {
|
|
||||||
case COMMAND_INVALID:
|
|
||||||
unreachable_();
|
|
||||||
|
|
||||||
case COMMAND_ORG:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COMMAND_ALIGN:
|
|
||||||
if (arg >= 16) {
|
|
||||||
arg = 0;
|
|
||||||
} else {
|
|
||||||
uint16_t mask = (1 << arg) - 1;
|
|
||||||
|
|
||||||
arg = (*pc + mask) & ~mask;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COMMAND_DS:
|
|
||||||
arg += *pc;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg < *pc)
|
|
||||||
errx("%s(%" PRIu32 "): `%s` cannot be used to go backwards (currently at $%x)",
|
|
||||||
linkerScriptName, lineNo, commands[command], *pc);
|
|
||||||
*pc = arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
static struct SectionPlacement placement;
|
|
||||||
static uint32_t bank;
|
|
||||||
static uint32_t bankID;
|
|
||||||
|
|
||||||
if (parserState == PARSER_FIRSTTIME) {
|
|
||||||
lineNo = 1;
|
|
||||||
|
|
||||||
// Init PC for all banks
|
|
||||||
for (enum SectionType i : EnumSeq(SECTTYPE_INVALID)) {
|
|
||||||
curaddr[i] = (uint16_t *)malloc(sizeof(*curaddr[i]) * nbbanks(i));
|
|
||||||
for (uint32_t b = 0; b < nbbanks(i); b++)
|
|
||||||
curaddr[i][b] = sectionTypeInfo[i].startAddr;
|
|
||||||
}
|
|
||||||
|
|
||||||
placement.type = SECTTYPE_INVALID;
|
|
||||||
|
|
||||||
parserState = PARSER_LINESTART;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
struct LinkerScriptToken *token = nextToken();
|
|
||||||
enum LinkerScriptTokenType tokType;
|
|
||||||
union LinkerScriptTokenAttr attr;
|
|
||||||
bool hasArg;
|
|
||||||
uint32_t arg;
|
|
||||||
|
|
||||||
if (placement.type != SECTTYPE_INVALID) {
|
|
||||||
if (curaddr[placement.type][bankID] > endaddr(placement.type) + 1)
|
|
||||||
errx("%s(%" PRIu32 "): Sections would extend past the end of %s ($%04" PRIx16 " > $%04" PRIx16 ")",
|
|
||||||
linkerScriptName, lineNo, sectionTypeInfo[placement.type].name,
|
|
||||||
curaddr[placement.type][bankID], endaddr(placement.type));
|
|
||||||
if (curaddr[placement.type][bankID] < sectionTypeInfo[placement.type].startAddr)
|
|
||||||
errx("%s(%" PRIu32 "): PC underflowed ($%04" PRIx16 " < $%04" PRIx16 ")",
|
|
||||||
linkerScriptName, lineNo,
|
|
||||||
curaddr[placement.type][bankID], sectionTypeInfo[placement.type].startAddr);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (parserState) {
|
|
||||||
case PARSER_FIRSTTIME:
|
|
||||||
unreachable_();
|
|
||||||
|
|
||||||
case PARSER_LINESTART:
|
|
||||||
switch (token->type) {
|
|
||||||
case TOKEN_INVALID:
|
|
||||||
unreachable_();
|
|
||||||
|
|
||||||
case TOKEN_EOF:
|
|
||||||
if (!popFile())
|
|
||||||
return NULL;
|
|
||||||
parserState = PARSER_LINEEND;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TOKEN_NUMBER:
|
|
||||||
errx("%s(%" PRIu32 "): stray number \"%" PRIu32 "\"",
|
|
||||||
linkerScriptName, lineNo,
|
|
||||||
token->attr.number);
|
|
||||||
|
|
||||||
case TOKEN_NEWLINE:
|
|
||||||
lineNo++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
// A stray string is a section name
|
|
||||||
case TOKEN_STRING:
|
|
||||||
parserState = PARSER_LINEEND;
|
|
||||||
|
|
||||||
if (placement.type == SECTTYPE_INVALID)
|
|
||||||
errx("%s(%" PRIu32 "): Didn't specify a location before the section",
|
|
||||||
linkerScriptName, lineNo);
|
|
||||||
|
|
||||||
placement.section =
|
|
||||||
sect_GetSection(token->attr.string);
|
|
||||||
if (!placement.section)
|
|
||||||
errx("%s(%" PRIu32 "): Unknown section \"%s\"",
|
|
||||||
linkerScriptName, lineNo,
|
|
||||||
token->attr.string);
|
|
||||||
placement.org = curaddr[placement.type][bankID];
|
|
||||||
placement.bank = bank;
|
|
||||||
|
|
||||||
curaddr[placement.type][bankID] += placement.section->size;
|
|
||||||
return &placement;
|
|
||||||
|
|
||||||
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 (placement.type == SECTTYPE_INVALID)
|
|
||||||
errx("%s(%" PRIu32 "): Didn't specify a location before the command",
|
|
||||||
linkerScriptName, lineNo);
|
|
||||||
if (!hasArg)
|
|
||||||
errx("%s(%" PRIu32 "): Command specified without an argument",
|
|
||||||
linkerScriptName, lineNo);
|
|
||||||
|
|
||||||
processCommand(attr.command, arg, &curaddr[placement.type][bankID]);
|
|
||||||
} else { // TOKEN_BANK
|
|
||||||
placement.type = attr.secttype;
|
|
||||||
// If there's only one bank,
|
|
||||||
// specifying the number is optional.
|
|
||||||
if (!hasArg && nbbanks(placement.type) != 1)
|
|
||||||
errx("%s(%" PRIu32 "): Didn't specify a bank number",
|
|
||||||
linkerScriptName, lineNo);
|
|
||||||
else if (!hasArg)
|
|
||||||
arg = sectionTypeInfo[placement.type].firstBank;
|
|
||||||
else if (arg < sectionTypeInfo[placement.type].firstBank)
|
|
||||||
errx("%s(%" PRIu32 "): specified bank number is too low (%" PRIu32 " < %" PRIu32 ")",
|
|
||||||
linkerScriptName, lineNo,
|
|
||||||
arg, sectionTypeInfo[placement.type].firstBank);
|
|
||||||
else if (arg > sectionTypeInfo[placement.type].lastBank)
|
|
||||||
errx("%s(%" PRIu32 "): specified bank number is too high (%" PRIu32 " > %" PRIu32 ")",
|
|
||||||
linkerScriptName, lineNo,
|
|
||||||
arg, sectionTypeInfo[placement.type].lastBank);
|
|
||||||
bank = arg;
|
|
||||||
bankID = arg - sectionTypeInfo[placement.type].firstBank;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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("%s(%" PRIu32 "): Expected a file name after INCLUDE",
|
|
||||||
linkerScriptName, lineNo);
|
|
||||||
|
|
||||||
// Switch to that file
|
|
||||||
pushFile(token->attr.string);
|
|
||||||
// The file stack took ownership of the string
|
|
||||||
token->attr.string = NULL;
|
|
||||||
|
|
||||||
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("%s(%" PRIu32 "): Unexpected %s at the end of the line",
|
|
||||||
linkerScriptName, lineNo,
|
|
||||||
tokenTypes[token->type]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void script_Cleanup(void)
|
|
||||||
{
|
|
||||||
for (enum SectionType type : EnumSeq(SECTTYPE_INVALID))
|
|
||||||
free(curaddr[type]);
|
|
||||||
}
|
|
||||||
490
src/link/script.y
Normal file
490
src/link/script.y
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
%language "c++"
|
||||||
|
%define api.value.type variant
|
||||||
|
%define api.token.constructor
|
||||||
|
|
||||||
|
%code requires {
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "linkdefs.hpp"
|
||||||
|
}
|
||||||
|
%code {
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <bit>
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <fstream>
|
||||||
|
#include <locale>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "itertools.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include "link/main.hpp"
|
||||||
|
#include "link/section.hpp"
|
||||||
|
|
||||||
|
using namespace std::literals;
|
||||||
|
|
||||||
|
static void includeFile(std::string &&path);
|
||||||
|
static void incLineNo(void);
|
||||||
|
|
||||||
|
static void setSectionType(SectionType type);
|
||||||
|
static void setSectionType(SectionType type, uint32_t bank);
|
||||||
|
static void setAddr(uint32_t addr);
|
||||||
|
static void alignTo(uint32_t alignment, uint32_t offset);
|
||||||
|
static void pad(uint32_t length);
|
||||||
|
static void placeSection(std::string const &name);
|
||||||
|
|
||||||
|
static yy::parser::symbol_type yylex(void);
|
||||||
|
|
||||||
|
struct Keyword {
|
||||||
|
std::string_view name;
|
||||||
|
yy::parser::symbol_type (* tokenGen)(void);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
%token YYEOF 0 "end of file"
|
||||||
|
%token newline
|
||||||
|
%token ORG "ORG"
|
||||||
|
INCLUDE "INCLUDE"
|
||||||
|
ALIGN "ALIGN"
|
||||||
|
DS "DS"
|
||||||
|
%code {
|
||||||
|
static std::array keywords{
|
||||||
|
Keyword{"ORG"sv, yy::parser::make_ORG},
|
||||||
|
Keyword{"INCLUDE"sv, yy::parser::make_INCLUDE},
|
||||||
|
Keyword{"ALIGN"sv, yy::parser::make_ALIGN},
|
||||||
|
Keyword{"DS"sv, yy::parser::make_DS},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
%token <std::string> string;
|
||||||
|
%token <uint32_t> number;
|
||||||
|
%token <SectionType> section_type;
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
lines: %empty
|
||||||
|
| line lines
|
||||||
|
;
|
||||||
|
|
||||||
|
line: INCLUDE string newline { includeFile(std::move($2)); } // Note: this additionally increments the line number!
|
||||||
|
| directive newline { incLineNo(); }
|
||||||
|
| newline { incLineNo(); }
|
||||||
|
| error newline { yyerrok; incLineNo(); } // Error recovery.
|
||||||
|
;
|
||||||
|
|
||||||
|
directive: section_type { setSectionType($1); }
|
||||||
|
| section_type number { setSectionType($1, $2); }
|
||||||
|
| ORG number { setAddr($2); }
|
||||||
|
| ALIGN number { alignTo($2, 0); }
|
||||||
|
| DS number { pad($2); }
|
||||||
|
| string { placeSection($1); }
|
||||||
|
;
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
#define scriptError(context, fmt, ...) \
|
||||||
|
::error(NULL, 0, "%s(%" PRIu32 "): " fmt, \
|
||||||
|
context.path.c_str(), context.lineNo __VA_OPT__(,) __VA_ARGS__)
|
||||||
|
|
||||||
|
// Lexer.
|
||||||
|
|
||||||
|
struct LexerStackEntry {
|
||||||
|
std::filebuf file;
|
||||||
|
std::string path;
|
||||||
|
uint32_t lineNo;
|
||||||
|
|
||||||
|
explicit LexerStackEntry(std::string &&path_) : file(), path(path_), lineNo(1) {}
|
||||||
|
};
|
||||||
|
static std::vector<LexerStackEntry> lexerStack;
|
||||||
|
static bool atEof;
|
||||||
|
|
||||||
|
void yy::parser::error(std::string const &msg) {
|
||||||
|
auto const &script = lexerStack.back();
|
||||||
|
scriptError(script, "%s", msg.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void includeFile(std::string &&path) {
|
||||||
|
// `emplace_back` can invalidate references to the stack's elements!
|
||||||
|
// This is why `newContext` must be gotten before `prevContext`.
|
||||||
|
auto &newContext = lexerStack.emplace_back(std::move(path));
|
||||||
|
auto &prevContext = lexerStack[lexerStack.size() - 2];
|
||||||
|
|
||||||
|
if (!newContext.file.open(newContext.path, std::ios_base::in)) {
|
||||||
|
// The order is important: report the error, increment the line number, modify the stack!
|
||||||
|
scriptError(prevContext, "Could not open included linker script \"%s\"",
|
||||||
|
newContext.path.c_str());
|
||||||
|
++prevContext.lineNo;
|
||||||
|
lexerStack.pop_back();
|
||||||
|
} else {
|
||||||
|
// The lexer will use the new entry to lex the next token.
|
||||||
|
++prevContext.lineNo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void incLineNo(void) {
|
||||||
|
++lexerStack.back().lineNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isWhiteSpace(int c) {
|
||||||
|
return c == ' ' || c == '\t';
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isNewline(int c) {
|
||||||
|
return c == '\r' || c == '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isIdentChar(int c) {
|
||||||
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isDecDigit(int c) {
|
||||||
|
return c >= '0' && c <= '9';
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isBinDigit(int c) {
|
||||||
|
return c >= '0' && c <= '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isHexDigit(int c) {
|
||||||
|
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t parseHexDigit(int c) {
|
||||||
|
if (c >= '0' && c <= '9') {
|
||||||
|
return c - '0';
|
||||||
|
} else if (c >= 'A' && c <= 'F') {
|
||||||
|
return c - 'A' + 10;
|
||||||
|
} else if (c >= 'a' && c <= 'f') {
|
||||||
|
return c - 'a' + 10;
|
||||||
|
} else {
|
||||||
|
unreachable_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yy::parser::symbol_type yylex(void) {
|
||||||
|
try_again: // Can't use a `do {} while(0)` loop, otherwise compilers (wrongly) think it can end.
|
||||||
|
auto &context = lexerStack.back();
|
||||||
|
auto c = context.file.sbumpc();
|
||||||
|
|
||||||
|
// First, skip leading whitespace.
|
||||||
|
while (isWhiteSpace(c)) {
|
||||||
|
c = context.file.sbumpc();
|
||||||
|
}
|
||||||
|
// Then, skip a comment if applicable.
|
||||||
|
if (c == ';') {
|
||||||
|
while (!isNewline(c)) {
|
||||||
|
c = context.file.sbumpc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alright, what token should we return?
|
||||||
|
if (c == EOF) {
|
||||||
|
// Basically yywrap().
|
||||||
|
if (lexerStack.size() != 1) {
|
||||||
|
lexerStack.pop_back();
|
||||||
|
goto try_again;
|
||||||
|
} else if (!atEof) {
|
||||||
|
// Inject a newline at EOF, to avoid errors for files that don't end with one.
|
||||||
|
atEof = true;
|
||||||
|
return yy::parser::make_newline();
|
||||||
|
} else {
|
||||||
|
return yy::parser::make_YYEOF();
|
||||||
|
}
|
||||||
|
} else if (isNewline(c)) {
|
||||||
|
// Handle CRLF.
|
||||||
|
if (c == '\r' && context.file.sgetc() == '\n') {
|
||||||
|
context.file.sbumpc();
|
||||||
|
}
|
||||||
|
return yy::parser::make_newline();
|
||||||
|
} else if (c == '"') {
|
||||||
|
std::string str;
|
||||||
|
|
||||||
|
for (c = context.file.sgetc(); c != '"'; c = context.file.sgetc()) {
|
||||||
|
if (c == EOF || isNewline(c)) {
|
||||||
|
scriptError(context, "Unterminated string");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
context.file.sbumpc();
|
||||||
|
if (c == '\\') {
|
||||||
|
c = context.file.sgetc();
|
||||||
|
if (c == EOF || isNewline(c)) {
|
||||||
|
scriptError(context, "Unterminated string");
|
||||||
|
break;
|
||||||
|
} else if (c == 'n') {
|
||||||
|
c = '\n';
|
||||||
|
} else if (c == 'r') {
|
||||||
|
c = '\r';
|
||||||
|
} else if (c == 't') {
|
||||||
|
c = '\t';
|
||||||
|
} else if (c != '\\' && c != '"') {
|
||||||
|
scriptError(context, "Cannot escape character %s", printChar(c));
|
||||||
|
}
|
||||||
|
context.file.sbumpc();
|
||||||
|
}
|
||||||
|
str.push_back(c);
|
||||||
|
}
|
||||||
|
context.file.sbumpc(); // Consume the closing quote.
|
||||||
|
|
||||||
|
return yy::parser::make_string(std::move(str));
|
||||||
|
} else if (c == '$') {
|
||||||
|
c = context.file.sgetc();
|
||||||
|
if (!isHexDigit(c)) {
|
||||||
|
scriptError(context, "No hexadecimal digits found after '$'");
|
||||||
|
return yy::parser::make_number(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t number = parseHexDigit(c);
|
||||||
|
context.file.sbumpc();
|
||||||
|
for (c = context.file.sgetc(); isHexDigit(c); c = context.file.sgetc()) {
|
||||||
|
number = number * 16 + parseHexDigit(c);
|
||||||
|
context.file.sbumpc();
|
||||||
|
}
|
||||||
|
return yy::parser::make_number(number);
|
||||||
|
} else if (c == '%') {
|
||||||
|
c = context.file.sgetc();
|
||||||
|
if (!isBinDigit(c)) {
|
||||||
|
scriptError(context, "No binary digits found after '%%'");
|
||||||
|
return yy::parser::make_number(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t number = c - '0';
|
||||||
|
context.file.sbumpc();
|
||||||
|
for (c = context.file.sgetc(); isBinDigit(c); c = context.file.sgetc()) {
|
||||||
|
number = number * 2 + (c - '0');
|
||||||
|
context.file.sbumpc();
|
||||||
|
}
|
||||||
|
return yy::parser::make_number(number);
|
||||||
|
} else if (isDecDigit(c)) {
|
||||||
|
uint32_t number = c - '0';
|
||||||
|
for (c = context.file.sgetc(); isDecDigit(c); c = context.file.sgetc()) {
|
||||||
|
number = number * 10 + (c - '0');
|
||||||
|
}
|
||||||
|
return yy::parser::make_number(number);
|
||||||
|
} else if (isIdentChar(c)) { // Note that we match these *after* digit characters!
|
||||||
|
std::string ident;
|
||||||
|
auto strUpperCmp = [](char cmp, char ref) {
|
||||||
|
// `locale::classic()` yields the "C" locale.
|
||||||
|
assert(!std::use_facet<std::ctype<char>>(std::locale::classic())
|
||||||
|
.is(std::ctype_base::lower, ref));
|
||||||
|
return std::use_facet<std::ctype<char>>(std::locale::classic())
|
||||||
|
.toupper(cmp) == ref;
|
||||||
|
};
|
||||||
|
|
||||||
|
ident.push_back(c);
|
||||||
|
for (c = context.file.sgetc(); isIdentChar(c); c = context.file.snextc()) {
|
||||||
|
ident.push_back(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SectionType type : EnumSeq(SECTTYPE_INVALID)) {
|
||||||
|
if (std::equal(ident.begin(), ident.end(),
|
||||||
|
sectionTypeInfo[type].name.begin(), sectionTypeInfo[type].name.end(),
|
||||||
|
strUpperCmp)) {
|
||||||
|
return yy::parser::make_section_type(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Keyword const &keyword : keywords) {
|
||||||
|
if (std::equal(ident.begin(), ident.end(),
|
||||||
|
keyword.name.begin(), keyword.name.end(),
|
||||||
|
strUpperCmp)) {
|
||||||
|
return keyword.tokenGen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptError(context, "Unknown keyword \"%s\"", ident.c_str());
|
||||||
|
goto try_again; // Try lexing another token.
|
||||||
|
} else {
|
||||||
|
scriptError(context, "Unexpected character '%s'", printChar(c));
|
||||||
|
// Keep reading characters until the EOL, to avoid reporting too many errors.
|
||||||
|
for (c = context.file.sgetc(); !isNewline(c); c = context.file.sgetc()) {
|
||||||
|
if (c == EOF) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goto try_again;
|
||||||
|
}
|
||||||
|
// Not marking as unreachable; this will generate a warning if any codepath forgets to return.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Semantic actions.
|
||||||
|
|
||||||
|
static std::array<std::vector<uint16_t>, SECTTYPE_INVALID> curAddr;
|
||||||
|
static SectionType activeType; // Index into curAddr
|
||||||
|
static uint32_t activeBankIdx; // Index into curAddr[activeType]
|
||||||
|
|
||||||
|
static void setActiveTypeAndIdx(SectionType type, uint32_t idx) {
|
||||||
|
activeType = type;
|
||||||
|
activeBankIdx = idx;
|
||||||
|
if (curAddr[activeType].size() <= activeBankIdx) {
|
||||||
|
curAddr[activeType].resize(activeBankIdx + 1, sectionTypeInfo[type].startAddr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setSectionType(SectionType type) {
|
||||||
|
auto const &context = lexerStack.back();
|
||||||
|
|
||||||
|
if (nbbanks(type) != 1) {
|
||||||
|
scriptError(context, "A bank number must be specified for %s",
|
||||||
|
sectionTypeInfo[type].name.c_str());
|
||||||
|
// Keep going with a default value for the bank index.
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveTypeAndIdx(type, 0); // There is only a single bank anyway, so just set the index to 0.
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setSectionType(SectionType type, uint32_t bank) {
|
||||||
|
auto const &context = lexerStack.back();
|
||||||
|
auto const &typeInfo = sectionTypeInfo[type];
|
||||||
|
|
||||||
|
if (bank < typeInfo.firstBank) {
|
||||||
|
scriptError(context, "%s bank %" PRIu32 " doesn't exist, the minimum is %" PRIu32,
|
||||||
|
typeInfo.name.c_str(), bank, typeInfo.firstBank);
|
||||||
|
bank = typeInfo.firstBank;
|
||||||
|
} else if (bank > typeInfo.lastBank) {
|
||||||
|
scriptError(context, "%s bank %" PRIu32 " doesn't exist, the maximum is %" PRIu32,
|
||||||
|
typeInfo.name.c_str(), bank, typeInfo.lastBank);
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveTypeAndIdx(type, bank - typeInfo.firstBank);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setAddr(uint32_t addr) {
|
||||||
|
auto const &context = lexerStack.back();
|
||||||
|
auto &pc = curAddr[activeType][activeBankIdx];
|
||||||
|
auto const &typeInfo = sectionTypeInfo[activeType];
|
||||||
|
|
||||||
|
if (addr < pc) {
|
||||||
|
scriptError(context, "ORG cannot be used to go backwards (from $%04x to $%04x)", pc, addr);
|
||||||
|
} else if (addr > endaddr(activeType)) { // Allow "one past the end" sections.
|
||||||
|
scriptError(context, "Cannot go to $%04" PRIx32 ": %s ends at $%04" PRIx16 "",
|
||||||
|
addr, typeInfo.name.c_str(), endaddr(activeType));
|
||||||
|
pc = endaddr(activeType);
|
||||||
|
} else {
|
||||||
|
pc = addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void alignTo(uint32_t alignment, uint32_t alignOfs) {
|
||||||
|
auto const &context = lexerStack.back();
|
||||||
|
auto const &typeInfo = sectionTypeInfo[activeType];
|
||||||
|
auto &pc = curAddr[activeType][activeBankIdx];
|
||||||
|
|
||||||
|
// TODO: maybe warn if truncating?
|
||||||
|
alignOfs %= 1 << alignment;
|
||||||
|
|
||||||
|
assert(pc >= typeInfo.startAddr);
|
||||||
|
uint16_t length = alignment < 16 ? (uint16_t)(alignOfs - pc) % (1u << alignment)
|
||||||
|
: alignOfs - pc; // Let it wrap around, this'll trip the check.
|
||||||
|
if (uint16_t offset = pc - typeInfo.startAddr; length > typeInfo.size - offset) {
|
||||||
|
scriptError(context, "Cannot align: the next suitable address after $%04" PRIx16 " is $%04" PRIx16 ", past $%04" PRIx16,
|
||||||
|
pc, (uint16_t)(pc + length), (uint16_t)(endaddr(activeType) + 1));
|
||||||
|
} else {
|
||||||
|
pc += length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pad(uint32_t length) {
|
||||||
|
auto const &context = lexerStack.back();
|
||||||
|
auto const &typeInfo = sectionTypeInfo[activeType];
|
||||||
|
auto &pc = curAddr[activeType][activeBankIdx];
|
||||||
|
|
||||||
|
assert(pc >= typeInfo.startAddr);
|
||||||
|
if (uint16_t offset = pc - typeInfo.startAddr; length + offset > typeInfo.size) {
|
||||||
|
scriptError(context, "Cannot pad by %u bytes: only %u bytes to $%04" PRIx16,
|
||||||
|
length, typeInfo.size - offset, (uint16_t)(endaddr(activeType) + 1));
|
||||||
|
} else {
|
||||||
|
pc += length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void placeSection(std::string const &name) {
|
||||||
|
auto const &context = lexerStack.back();
|
||||||
|
auto const &typeInfo = sectionTypeInfo[activeType];
|
||||||
|
|
||||||
|
// A type *must* be active.
|
||||||
|
if (activeType == SECTTYPE_INVALID) {
|
||||||
|
scriptError(context, "No memory region has been specified to place section \"%s\" in",
|
||||||
|
name.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *section = sect_GetSection(name.c_str());
|
||||||
|
if (!section) {
|
||||||
|
scriptError(context, "Unknown section \"%s\"", name.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(section->offset == 0);
|
||||||
|
// Check that the linker script doesn't contradict what the code says.
|
||||||
|
if (section->type == SECTTYPE_INVALID) {
|
||||||
|
// SDCC areas don't have a type assigned yet, so the linker script is used to give them one.
|
||||||
|
for (Section *fragment = section; fragment; fragment = fragment->nextu) {
|
||||||
|
fragment->type = activeType;
|
||||||
|
}
|
||||||
|
} else if (section->type != activeType) {
|
||||||
|
scriptError(context, "\"%s\" is specified to be a %s section, but it is already a %s section",
|
||||||
|
name.c_str(), typeInfo.name.c_str(), sectionTypeInfo[section->type].name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t bank = activeBankIdx + typeInfo.firstBank;
|
||||||
|
if (section->isBankFixed && bank != section->bank) {
|
||||||
|
scriptError(context, "The linker script places section \"%s\" in %s bank %" PRIu32 ", but it was already defined in bank %" PRIu32,
|
||||||
|
name.c_str(), sectionTypeInfo[section->type].name.c_str(), bank, section->bank);
|
||||||
|
}
|
||||||
|
section->isBankFixed = true;
|
||||||
|
section->bank = bank;
|
||||||
|
|
||||||
|
uint16_t &org = curAddr[activeType][activeBankIdx];
|
||||||
|
if (section->isAddressFixed && org != section->org) {
|
||||||
|
scriptError(context, "The linker script assigns section \"%s\" to address $%04" PRIx16 ", but it was already at $%04" PRIx16,
|
||||||
|
name.c_str(), org, section->org);
|
||||||
|
} else if (section->isAlignFixed && (org & section->alignMask) != section->alignOfs) {
|
||||||
|
uint8_t alignment = std::countr_one(section->alignMask);
|
||||||
|
scriptError(context, "The linker script assigns section \"%s\" to address $%04" PRIx16 ", but that would be ALIGN[%" PRIu8 ", %" PRIu16 "] instead of the requested ALIGN[%" PRIu8 ", %" PRIu16 "]",
|
||||||
|
name.c_str(), org, alignment, (uint16_t)(org & section->alignMask), alignment, section->alignOfs);
|
||||||
|
}
|
||||||
|
section->isAddressFixed = true;
|
||||||
|
section->isAlignFixed = false; // This can't be set when the above is.
|
||||||
|
section->org = org;
|
||||||
|
|
||||||
|
uint16_t curOfs = org - typeInfo.startAddr;
|
||||||
|
if (section->size > typeInfo.size - curOfs) {
|
||||||
|
scriptError(context, "The linker script assigns section \"%s\" to address $%04" PRIx16 ", but then it would overflow %s by %" PRIx16 " bytes",
|
||||||
|
name.c_str(), org, typeInfo.name.c_str(),
|
||||||
|
(uint16_t)(section->size - (typeInfo.size - curOfs)));
|
||||||
|
// Fill as much as possible without going out of bounds.
|
||||||
|
org = typeInfo.startAddr + typeInfo.size;
|
||||||
|
} else {
|
||||||
|
org += section->size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// External API.
|
||||||
|
|
||||||
|
void script_ProcessScript(char const *path) {
|
||||||
|
activeType = SECTTYPE_INVALID;
|
||||||
|
|
||||||
|
lexerStack.clear();
|
||||||
|
atEof = false;
|
||||||
|
auto &newContext = lexerStack.emplace_back(std::string(path));
|
||||||
|
|
||||||
|
if (!newContext.file.open(newContext.path, std::ios_base::in)) {
|
||||||
|
error(NULL, 0, "Could not open linker script \"%s\"", newContext.path.c_str());
|
||||||
|
lexerStack.clear();
|
||||||
|
} else {
|
||||||
|
yy::parser linkerScriptParser;
|
||||||
|
// We don't care about the return value, as any error increments the global error count,
|
||||||
|
// which is what `main` checks.
|
||||||
|
(void)linkerScriptParser.parse();
|
||||||
|
|
||||||
|
// Free up working memory.
|
||||||
|
for (auto ®ion : curAddr) {
|
||||||
|
region.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -128,7 +128,8 @@ static void mergeSections(struct Section *target, struct Section *other, enum Se
|
|||||||
|
|
||||||
if (target->type != other->type)
|
if (target->type != other->type)
|
||||||
errx("Section \"%s\" is defined with conflicting types %s and %s",
|
errx("Section \"%s\" is defined with conflicting types %s and %s",
|
||||||
other->name, sectionTypeInfo[target->type].name, sectionTypeInfo[other->type].name);
|
other->name, sectionTypeInfo[target->type].name.c_str(),
|
||||||
|
sectionTypeInfo[other->type].name.c_str());
|
||||||
|
|
||||||
if (other->isBankFixed) {
|
if (other->isBankFixed) {
|
||||||
if (!target->isBankFixed) {
|
if (!target->isBankFixed) {
|
||||||
@@ -202,7 +203,7 @@ void sect_AddSection(struct Section *section)
|
|||||||
mergeSections(other, section, section->modifier);
|
mergeSections(other, section, section->modifier);
|
||||||
} else if (section->modifier == SECTION_UNION && sect_HasData(section->type)) {
|
} else if (section->modifier == SECTION_UNION && sect_HasData(section->type)) {
|
||||||
errx("Section \"%s\" is of type %s, which cannot be unionized",
|
errx("Section \"%s\" is of type %s, which cannot be unionized",
|
||||||
section->name, sectionTypeInfo[section->type].name);
|
section->name, sectionTypeInfo[section->type].name.c_str());
|
||||||
} else {
|
} else {
|
||||||
// If not, add it
|
// If not, add it
|
||||||
hash_AddElement(sections, section->name, section);
|
hash_AddElement(sections, section->name, section);
|
||||||
@@ -254,7 +255,7 @@ static void doSanityChecks(struct Section *section, void *)
|
|||||||
// Too large an alignment may not be satisfiable
|
// Too large an alignment may not be satisfiable
|
||||||
if (section->isAlignFixed && (section->alignMask & sectionTypeInfo[section->type].startAddr))
|
if (section->isAlignFixed && (section->alignMask & sectionTypeInfo[section->type].startAddr))
|
||||||
error(NULL, 0, "%s: %s sections cannot be aligned to $%04x bytes",
|
error(NULL, 0, "%s: %s sections cannot be aligned to $%04x bytes",
|
||||||
section->name, sectionTypeInfo[section->type].name, section->alignMask + 1);
|
section->name, sectionTypeInfo[section->type].name.c_str(), section->alignMask + 1);
|
||||||
|
|
||||||
uint32_t minbank = sectionTypeInfo[section->type].firstBank, maxbank = sectionTypeInfo[section->type].lastBank;
|
uint32_t minbank = sectionTypeInfo[section->type].firstBank, maxbank = sectionTypeInfo[section->type].lastBank;
|
||||||
|
|
||||||
|
|||||||
@@ -3,60 +3,62 @@
|
|||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
|
|
||||||
|
using namespace std::literals;
|
||||||
|
|
||||||
// The default values are the most lax, as they are used as-is by RGBASM; only RGBLINK has the full info,
|
// The default values are the most lax, as they are used as-is by RGBASM; only RGBLINK has the full info,
|
||||||
// so RGBASM's job is only to catch unconditional errors earlier.
|
// so RGBASM's job is only to catch unconditional errors earlier.
|
||||||
struct SectionTypeInfo sectionTypeInfo[SECTTYPE_INVALID] = {
|
struct SectionTypeInfo sectionTypeInfo[SECTTYPE_INVALID] = {
|
||||||
AT(SECTTYPE_WRAM0) {
|
AT(SECTTYPE_WRAM0) {
|
||||||
.name = "WRAM0",
|
.name = "WRAM0"s,
|
||||||
.startAddr = 0xC000,
|
.startAddr = 0xC000,
|
||||||
.size = 0x2000, // Patched to 0x1000 if !isWRA0Mode
|
.size = 0x2000, // Patched to 0x1000 if !isWRA0Mode
|
||||||
.firstBank = 0,
|
.firstBank = 0,
|
||||||
.lastBank = 0,
|
.lastBank = 0,
|
||||||
},
|
},
|
||||||
AT(SECTTYPE_VRAM) {
|
AT(SECTTYPE_VRAM) {
|
||||||
.name = "VRAM",
|
.name = "VRAM"s,
|
||||||
.startAddr = 0x8000,
|
.startAddr = 0x8000,
|
||||||
.size = 0x2000,
|
.size = 0x2000,
|
||||||
.firstBank = 0,
|
.firstBank = 0,
|
||||||
.lastBank = 1, // Patched to 0 if isDmgMode
|
.lastBank = 1, // Patched to 0 if isDmgMode
|
||||||
},
|
},
|
||||||
AT(SECTTYPE_ROMX) {
|
AT(SECTTYPE_ROMX) {
|
||||||
.name = "ROMX",
|
.name = "ROMX"s,
|
||||||
.startAddr = 0x4000,
|
.startAddr = 0x4000,
|
||||||
.size = 0x4000,
|
.size = 0x4000,
|
||||||
.firstBank = 1,
|
.firstBank = 1,
|
||||||
.lastBank = 65535,
|
.lastBank = 65535,
|
||||||
},
|
},
|
||||||
AT(SECTTYPE_ROM0) {
|
AT(SECTTYPE_ROM0) {
|
||||||
.name = "ROM0",
|
.name = "ROM0"s,
|
||||||
.startAddr = 0x0000,
|
.startAddr = 0x0000,
|
||||||
.size = 0x8000, // Patched to 0x4000 if !is32kMode
|
.size = 0x8000, // Patched to 0x4000 if !is32kMode
|
||||||
.firstBank = 0,
|
.firstBank = 0,
|
||||||
.lastBank = 0,
|
.lastBank = 0,
|
||||||
},
|
},
|
||||||
AT(SECTTYPE_HRAM) {
|
AT(SECTTYPE_HRAM) {
|
||||||
.name = "HRAM",
|
.name = "HRAM"s,
|
||||||
.startAddr = 0xFF80,
|
.startAddr = 0xFF80,
|
||||||
.size = 0x007F,
|
.size = 0x007F,
|
||||||
.firstBank = 0,
|
.firstBank = 0,
|
||||||
.lastBank = 0,
|
.lastBank = 0,
|
||||||
},
|
},
|
||||||
AT(SECTTYPE_WRAMX) {
|
AT(SECTTYPE_WRAMX) {
|
||||||
.name = "WRAMX",
|
.name = "WRAMX"s,
|
||||||
.startAddr = 0xD000,
|
.startAddr = 0xD000,
|
||||||
.size = 0x1000,
|
.size = 0x1000,
|
||||||
.firstBank = 1,
|
.firstBank = 1,
|
||||||
.lastBank = 7,
|
.lastBank = 7,
|
||||||
},
|
},
|
||||||
AT(SECTTYPE_SRAM) {
|
AT(SECTTYPE_SRAM) {
|
||||||
.name = "SRAM",
|
.name = "SRAM"s,
|
||||||
.startAddr = 0xA000,
|
.startAddr = 0xA000,
|
||||||
.size = 0x2000,
|
.size = 0x2000,
|
||||||
.firstBank = 0,
|
.firstBank = 0,
|
||||||
.lastBank = 255,
|
.lastBank = 255,
|
||||||
},
|
},
|
||||||
AT(SECTTYPE_OAM) {
|
AT(SECTTYPE_OAM) {
|
||||||
.name = "OAM",
|
.name = "OAM"s,
|
||||||
.startAddr = 0xFE00,
|
.startAddr = 0xFE00,
|
||||||
.size = 0x00A0,
|
.size = 0x00A0,
|
||||||
.firstBank = 0,
|
.firstBank = 0,
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "asm/main.hpp"
|
#include "util.hpp"
|
||||||
#include "asm/util.hpp"
|
|
||||||
#include "asm/warning.hpp"
|
|
||||||
|
|
||||||
#include "extern/utf8decoder.hpp"
|
#include "extern/utf8decoder.hpp"
|
||||||
|
|
||||||
@@ -1 +1,2 @@
|
|||||||
error: ./linkerscript-escapes-test.link(4): Illegal character escape
|
error: ./linkerscript-escapes-test.link(4): Cannot escape character '{'
|
||||||
|
Linking failed with 1 error
|
||||||
|
|||||||
2
test/link/linkerscript-include.asm
Normal file
2
test/link/linkerscript-include.asm
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
SECTION "test", ROM0[42]
|
||||||
|
DB 1, 2, 3, 4, 5
|
||||||
1
test/link/linkerscript-include.inc
Normal file
1
test/link/linkerscript-include.inc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"test"
|
||||||
3
test/link/linkerscript-include.link
Normal file
3
test/link/linkerscript-include.link
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
; This has no newline at the end of the file
|
||||||
|
ROM0
|
||||||
|
INCLUDE "linkerscript-include.inc"
|
||||||
2
test/link/linkerscript-include.out
Normal file
2
test/link/linkerscript-include.out
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
error: linkerscript-include.inc(1): The linker script assigns section "test" to address $0000, but it was already at $002a
|
||||||
|
Linking failed with 1 error
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
error: Linker script contradicts "sec"'s alignment
|
error: ./section-attributes-mismatch.link(3): The linker script assigns section "sec" to address $0018, but that would be ALIGN[4, 8] instead of the requested ALIGN[4, 0]
|
||||||
Linking failed with 1 error
|
Linking failed with 1 error
|
||||||
|
|||||||
Reference in New Issue
Block a user