Run clang-format on everything (#1332)

This commit is contained in:
Sylvie
2024-03-04 14:22:49 -05:00
committed by GitHub
parent b004648a13
commit e74073e480
66 changed files with 6091 additions and 4957 deletions

View File

@@ -1,13 +1,13 @@
AccessModifierOffset: -4 AccessModifierOffset: -4
AlignAfterOpenBracket: Align AlignAfterOpenBracket: BlockIndent
AlignArrayOfStructures: Left AlignArrayOfStructures: Left
AlignConsecutiveAssignments: None AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: Consecutive AlignConsecutiveBitFields: Consecutive
AlignConsecutiveDeclarations: None AlignConsecutiveDeclarations: None
AlignConsecutiveMacros: Consecutive AlignConsecutiveMacros: Consecutive
AlignEscapedNewlines: Left AlignEscapedNewlines: DontAlign
AlignOperands: Align AlignOperands: Align
AlignTrailingComments: false AlignTrailingComments: true
AllowShortBlocksOnASingleLine: Empty AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: true AllowShortEnumsOnASingleLine: true
@@ -21,8 +21,8 @@ AlwaysBreakTemplateDeclarations: Yes
AttributeMacros: AttributeMacros:
- format_ - format_
- attr_ - attr_
BinPackArguments: true BinPackArguments: false
BinPackParameters: true BinPackParameters: false
BitFieldColonSpacing: Both BitFieldColonSpacing: Both
BreakBeforeBinaryOperators: NonAssignment BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Attach BreakBeforeBraces: Attach
@@ -60,8 +60,8 @@ IndentPPDirectives: BeforeHash
IndentRequires: true IndentRequires: true
IndentWidth: 4 IndentWidth: 4
IndentWrappedFunctionNames: true IndentWrappedFunctionNames: true
# Only support for Javascript as of clang-format 13... # Only support for Javascript as of clang-format 14...
# InsertTrailingCommas: true # InsertTrailingCommas: Wrapped
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature LambdaBodyIndentation: Signature
Language: Cpp Language: Cpp

View File

@@ -3,6 +3,7 @@
#ifndef RGBDS_FORMAT_SPEC_H #ifndef RGBDS_FORMAT_SPEC_H
#define RGBDS_FORMAT_SPEC_H #define RGBDS_FORMAT_SPEC_H
#include <stddef.h>
#include <stdint.h> #include <stdint.h>
enum FormatState { enum FormatState {
@@ -30,7 +31,7 @@ class FormatSpec {
public: public:
bool isEmpty() const { return !state; } bool isEmpty() const { return !state; }
bool isValid() const { return valid || state == FORMAT_DONE; } bool isValid() const { return valid || state == FORMAT_DONE; }
bool isFinished() const { return state >= FORMAT_DONE;} bool isFinished() const { return state >= FORMAT_DONE; }
void useCharacter(int c); void useCharacter(int c);
void finishCharacters(); void finishCharacters();

View File

@@ -11,24 +11,25 @@
#include <variant> #include <variant>
#include <vector> #include <vector>
#include "asm/lexer.hpp"
#include "linkdefs.hpp" #include "linkdefs.hpp"
#include "asm/lexer.hpp"
struct FileStackNode { struct FileStackNode {
FileStackNode *parent; // Pointer to parent node, for error reporting FileStackNode *parent; // Pointer to parent node, for error reporting
// Line at which the parent context was exited; meaningless for the root level // Line at which the parent context was exited; meaningless for the root level
uint32_t lineNo; uint32_t lineNo;
bool referenced; // If referenced by a Symbol, Section, or Patch's `src`, don't `delete`! bool referenced; // If referenced by a Symbol, Section, or Patch's `src`, don't `delete`!
uint32_t ID; // Set only if referenced: ID within the object file, -1 if not output yet uint32_t ID; // Set only if referenced: ID within the object file, -1 if not output yet
enum FileStackNodeType type; enum FileStackNodeType type;
std::variant< std::variant<
std::monostate, // Default constructed; `.type` and `.data` must be set manually std::monostate, // Default constructed; `.type` and `.data` must be set manually
std::vector<uint32_t>, // NODE_REPT std::vector<uint32_t>, // NODE_REPT
std::string // NODE_FILE, NODE_MACRO std::string // NODE_FILE, NODE_MACRO
> data; >
data;
// REPT iteration counts since last named node, in reverse depth order // REPT iteration counts since last named node, in reverse depth order
std::vector<uint32_t> &iters(); std::vector<uint32_t> &iters();
@@ -62,8 +63,15 @@ bool yywrap();
void fstk_RunInclude(char const *path); void fstk_RunInclude(char const *path);
void fstk_RunMacro(char const *macroName, MacroArgs &args); void fstk_RunMacro(char const *macroName, MacroArgs &args);
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char const *body, size_t size); void fstk_RunRept(uint32_t count, int32_t reptLineNo, char const *body, size_t size);
void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step, void fstk_RunFor(
int32_t reptLineNo, char const *body, size_t size); char const *symName,
int32_t start,
int32_t stop,
int32_t step,
int32_t reptLineNo,
char const *body,
size_t size
);
void fstk_StopRept(); void fstk_StopRept();
bool fstk_Break(); bool fstk_Break();

View File

@@ -34,13 +34,13 @@ struct Expansion {
char const *unowned; char const *unowned;
char *owned; // Non-`const` only so it can be `delete []`d char *owned; // Non-`const` only so it can be `delete []`d
} contents; } contents;
size_t size; // Length of the contents size_t size; // Length of the contents
size_t offset; // Cursor into the contents size_t offset; // Cursor into the contents
bool owned; // Whether or not to free contents when this expansion is freed bool owned; // Whether or not to free contents when this expansion is freed
}; };
struct IfStackEntry { struct IfStackEntry {
bool ranIfBlock; // Whether an IF/ELIF/ELSE block ran already bool ranIfBlock; // Whether an IF/ELIF/ELSE block ran already
bool reachedElseBlock; // Whether an ELSE block ran already bool reachedElseBlock; // Whether an ELSE block ran already
}; };
@@ -59,9 +59,9 @@ struct ViewedLexerState {
struct BufferedLexerState { struct BufferedLexerState {
int fd; int fd;
size_t index; // Read index into the buffer size_t index; // Read index into the buffer
char buf[LEXER_BUF_SIZE]; // Circular buffer char buf[LEXER_BUF_SIZE]; // Circular buffer
size_t nbChars; // Number of "fresh" chars in the buffer size_t nbChars; // Number of "fresh" chars in the buffer
}; };
struct LexerState { struct LexerState {
@@ -75,8 +75,8 @@ struct LexerState {
std::deque<IfStackEntry> ifStack; std::deque<IfStackEntry> ifStack;
bool capturing; // Whether the text being lexed should be captured bool capturing; // Whether the text being lexed should be captured
size_t captureSize; // Amount of text captured size_t captureSize; // Amount of text captured
std::vector<char> *captureBuf; // Buffer to send the captured text to if non-null std::vector<char> *captureBuf; // Buffer to send the captured text to if non-null
bool disableMacroArgs; bool disableMacroArgs;
@@ -85,38 +85,29 @@ struct LexerState {
bool expandStrings; bool expandStrings;
std::deque<Expansion> expansions; // Front is the innermost current expansion std::deque<Expansion> expansions; // Front is the innermost current expansion
std::variant< std::variant<std::monostate, MmappedLexerState, ViewedLexerState, BufferedLexerState> content;
std::monostate,
MmappedLexerState,
ViewedLexerState,
BufferedLexerState
> content;
}; };
extern LexerState *lexerState; extern LexerState *lexerState;
extern LexerState *lexerStateEOL; extern LexerState *lexerStateEOL;
static inline void lexer_SetState(LexerState *state) static inline void lexer_SetState(LexerState *state) {
{
lexerState = state; lexerState = state;
} }
static inline void lexer_SetStateAtEOL(LexerState *state) static inline void lexer_SetStateAtEOL(LexerState *state) {
{
lexerStateEOL = state; lexerStateEOL = state;
} }
extern char binDigits[2]; extern char binDigits[2];
extern char gfxDigits[4]; extern char gfxDigits[4];
static inline void lexer_SetBinDigits(char const digits[2]) static inline void lexer_SetBinDigits(char const digits[2]) {
{
binDigits[0] = digits[0]; binDigits[0] = digits[0];
binDigits[1] = digits[1]; binDigits[1] = digits[1];
} }
static inline void lexer_SetGfxDigits(char const digits[4]) static inline void lexer_SetGfxDigits(char const digits[4]) {
{
gfxDigits[0] = digits[0]; gfxDigits[0] = digits[0];
gfxDigits[1] = digits[1]; gfxDigits[1] = digits[1];
gfxDigits[2] = digits[2]; gfxDigits[2] = digits[2];
@@ -125,8 +116,9 @@ static inline void lexer_SetGfxDigits(char const digits[4])
// `path` is referenced, but not held onto..! // `path` is referenced, but not held onto..!
bool lexer_OpenFile(LexerState &state, char const *path); bool lexer_OpenFile(LexerState &state, char const *path);
void lexer_OpenFileView(LexerState &state, char const *path, char const *buf, size_t size, void lexer_OpenFileView(
uint32_t lineNo); LexerState &state, char const *path, char const *buf, size_t size, uint32_t lineNo
);
void lexer_RestartRept(uint32_t lineNo); void lexer_RestartRept(uint32_t lineNo);
void lexer_CleanupState(LexerState &state); void lexer_CleanupState(LexerState &state);
void lexer_Init(); void lexer_Init();

View File

@@ -8,10 +8,10 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "asm/warning.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "asm/warning.hpp"
struct MacroArgs { struct MacroArgs {
unsigned int shift; unsigned int shift;
std::vector<std::string> args; std::vector<std::string> args;

View File

@@ -16,7 +16,9 @@ void out_RegisterNode(FileStackNode *node);
void out_ReplaceNode(FileStackNode *node); void out_ReplaceNode(FileStackNode *node);
void out_SetFileName(char *s); void out_SetFileName(char *s);
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift); void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift);
void out_CreateAssert(enum AssertionType type, Expression const &expr, char const *message, uint32_t ofs); void out_CreateAssert(
enum AssertionType type, Expression const &expr, char const *message, uint32_t ofs
);
void out_WriteObject(); void out_WriteObject();
#endif // RGBDS_ASM_OUTPUT_H #endif // RGBDS_ASM_OUTPUT_H

View File

@@ -5,18 +5,19 @@
#include <stdint.h> #include <stdint.h>
#include <string> #include <string>
#include <vector>
#include "linkdefs.hpp" #include "linkdefs.hpp"
struct Symbol; struct Symbol;
struct Expression { struct Expression {
int32_t val; // If the expression's value is known, it's here int32_t val; // If the expression's value is known, it's here
std::string *reason; // Why the expression is not known, if it isn't std::string *reason; // Why the expression is not known, if it isn't
bool isKnown; // Whether the expression's value is known at assembly time bool isKnown; // Whether the expression's value is known at assembly time
bool isSymbol; // Whether the expression represents a symbol suitable for const diffing bool isSymbol; // Whether the expression represents a symbol suitable for const diffing
std::vector<uint8_t> *rpn; // Bytes serializing the RPN expression std::vector<uint8_t> *rpn; // Bytes serializing the RPN expression
uint32_t rpnPatchSize; // Size the expression will take in the object file uint32_t rpnPatchSize; // Size the expression will take in the object file
int32_t getConstVal() const; int32_t getConstVal() const;
Symbol const *symbolOf() const; Symbol const *symbolOf() const;
@@ -26,7 +27,9 @@ struct Expression {
void rpn_Number(Expression &expr, uint32_t val); void rpn_Number(Expression &expr, uint32_t val);
void rpn_Symbol(Expression &expr, char const *symName); void rpn_Symbol(Expression &expr, char const *symName);
void rpn_LOGNOT(Expression &expr, const Expression &src); void rpn_LOGNOT(Expression &expr, const Expression &src);
void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1, const Expression &src2); void rpn_BinaryOp(
enum RPNCommand op, Expression &expr, const Expression &src1, const Expression &src2
);
void rpn_HIGH(Expression &expr, const Expression &src); void rpn_HIGH(Expression &expr, const Expression &src);
void rpn_LOW(Expression &expr, const Expression &src); void rpn_LOW(Expression &expr, const Expression &src);
void rpn_ISCONST(Expression &expr, const Expression &src); void rpn_ISCONST(Expression &expr, const Expression &src);

View File

@@ -30,7 +30,7 @@ struct Section {
enum SectionType type; enum SectionType type;
enum SectionModifier modifier; enum SectionModifier modifier;
FileStackNode const *src; // Where the section was defined FileStackNode const *src; // Where the section was defined
uint32_t fileLine; // Line where the section was defined uint32_t fileLine; // Line where the section was defined
uint32_t size; uint32_t size;
uint32_t org; uint32_t org;
uint32_t bank; uint32_t bank;
@@ -52,10 +52,20 @@ extern std::deque<Section> sectionList;
extern Section *currentSection; extern Section *currentSection;
Section *sect_FindSectionByName(char const *name); Section *sect_FindSectionByName(char const *name);
void sect_NewSection(char const *name, enum SectionType type, uint32_t org, void sect_NewSection(
SectionSpec const &attrs, enum SectionModifier mod); char const *name,
void sect_SetLoadSection(char const *name, enum SectionType type, uint32_t org, enum SectionType type,
SectionSpec const &attrs, enum SectionModifier mod); uint32_t org,
SectionSpec const &attrs,
enum SectionModifier mod
);
void sect_SetLoadSection(
char const *name,
enum SectionType type,
uint32_t org,
SectionSpec const &attrs,
enum SectionModifier mod
);
void sect_EndLoadSection(); void sect_EndLoadSection();
Section *sect_GetSymbolSection(); Section *sect_GetSymbolSection();

View File

@@ -4,8 +4,8 @@
#define RGBDS_SYMBOL_H #define RGBDS_SYMBOL_H
#include <stdint.h> #include <stdint.h>
#include <string>
#include <string.h> #include <string.h>
#include <string>
#include <string_view> #include <string_view>
#include <time.h> #include <time.h>
#include <variant> #include <variant>
@@ -23,7 +23,7 @@ enum SymbolType {
SYM_REF // Forward reference to a label SYM_REF // Forward reference to a label
}; };
struct Symbol; // For the `sym_IsPC` forward declaration struct Symbol; // For the `sym_IsPC` forward declaration
bool sym_IsPC(Symbol const *sym); // For the inline `getSection` method bool sym_IsPC(Symbol const *sym); // For the inline `getSection` method
struct Symbol { struct Symbol {
@@ -33,14 +33,15 @@ struct Symbol {
bool isBuiltin; // Whether the symbol is a built-in bool isBuiltin; // Whether the symbol is a built-in
Section *section; Section *section;
FileStackNode *src; // Where the symbol was defined FileStackNode *src; // Where the symbol was defined
uint32_t fileLine; // Line where the symbol was defined uint32_t fileLine; // Line where the symbol was defined
std::variant< std::variant<
int32_t, // If isNumeric() int32_t, // If isNumeric()
int32_t (*)(), // If isNumeric() and has a callback int32_t (*)(), // If isNumeric() and has a callback
std::string_view *, // For SYM_MACRO std::string_view *, // For SYM_MACRO
std::string * // For SYM_EQUS std::string * // For SYM_EQUS
> data; >
data;
uint32_t ID; // ID of the symbol in the object file (-1 if none) uint32_t ID; // ID of the symbol in the object file (-1 if none)

View File

@@ -7,34 +7,29 @@
extern unsigned int nbErrors, maxErrors; extern unsigned int nbErrors, maxErrors;
enum WarningState { enum WarningState { WARNING_DEFAULT, WARNING_DISABLED, WARNING_ENABLED, WARNING_ERROR };
WARNING_DEFAULT,
WARNING_DISABLED,
WARNING_ENABLED,
WARNING_ERROR
};
enum WarningID { enum WarningID {
WARNING_ASSERT, // Assertions WARNING_ASSERT, // Assertions
WARNING_BACKWARDS_FOR, // `for` loop with backwards range WARNING_BACKWARDS_FOR, // `for` loop with backwards range
WARNING_BUILTIN_ARG, // Invalid args to builtins WARNING_BUILTIN_ARG, // Invalid args to builtins
WARNING_CHARMAP_REDEF, // Charmap entry re-definition WARNING_CHARMAP_REDEF, // Charmap entry re-definition
WARNING_DIV, // Division undefined behavior WARNING_DIV, // Division undefined behavior
WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
WARNING_EMPTY_MACRO_ARG, // Empty macro argument WARNING_EMPTY_MACRO_ARG, // Empty macro argument
WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL` WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
WARNING_LARGE_CONSTANT, // Constants too large WARNING_LARGE_CONSTANT, // Constants too large
WARNING_LONG_STR, // String too long for internal buffers WARNING_LONG_STR, // String too long for internal buffers
WARNING_MACRO_SHIFT, // Shift past available arguments in macro WARNING_MACRO_SHIFT, // Shift past available arguments in macro
WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment
WARNING_OBSOLETE, // Obsolete things WARNING_OBSOLETE, // Obsolete things
WARNING_SHIFT, // Shifting undefined behavior WARNING_SHIFT, // Shifting undefined behavior
WARNING_SHIFT_AMOUNT, // Strange shift amount WARNING_SHIFT_AMOUNT, // Strange shift amount
WARNING_USER, // User warnings WARNING_USER, // User warnings
NB_PLAIN_WARNINGS, NB_PLAIN_WARNINGS,
// Warnings past this point are "parametric" warnings, only mapping to a single flag // Warnings past this point are "parametric" warnings, only mapping to a single flag
#define PARAM_WARNINGS_START NB_PLAIN_WARNINGS #define PARAM_WARNINGS_START NB_PLAIN_WARNINGS
// Treating string as number may lose some bits // Treating string as number may lose some bits
WARNING_NUMERIC_STRING_1 = PARAM_WARNINGS_START, WARNING_NUMERIC_STRING_1 = PARAM_WARNINGS_START,
@@ -49,7 +44,7 @@ enum WarningID {
NB_PLAIN_AND_PARAM_WARNINGS, NB_PLAIN_AND_PARAM_WARNINGS,
#define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START) #define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START)
// Warnings past this point are "meta" warnings // Warnings past this point are "meta" warnings
#define META_WARNINGS_START NB_PLAIN_AND_PARAM_WARNINGS #define META_WARNINGS_START NB_PLAIN_AND_PARAM_WARNINGS
WARNING_ALL = META_WARNINGS_START, WARNING_ALL = META_WARNINGS_START,
WARNING_EXTRA, WARNING_EXTRA,

View File

@@ -8,12 +8,11 @@
extern "C" { extern "C" {
void warn(char const *fmt ...) format_(printf, 1, 2); void warn(char const *fmt...) format_(printf, 1, 2);
void warnx(char const *fmt, ...) format_(printf, 1, 2); void warnx(char const *fmt, ...) format_(printf, 1, 2);
[[noreturn]] void err(char const *fmt, ...) format_(printf, 1, 2); [[noreturn]] void err(char const *fmt, ...) format_(printf, 1, 2);
[[noreturn]] void errx(char const *fmt, ...) format_(printf, 1, 2); [[noreturn]] void errx(char const *fmt, ...) format_(printf, 1, 2);
} }
#endif // RGBDS_ERROR_H #endif // RGBDS_ERROR_H

View File

@@ -17,12 +17,13 @@ struct option {
int val; int val;
}; };
int musl_getopt_long_only(int argc, char **argv, char const *optstring, int musl_getopt_long_only(
const option *longopts, int *idx); int argc, char **argv, char const *optstring, const option *longopts, int *idx
);
#define no_argument 0 #define no_argument 0
#define required_argument 1 #define required_argument 1
#define optional_argument 2 #define optional_argument 2
} // extern "C" } // extern "C"

View File

@@ -11,8 +11,8 @@
#include <ios> #include <ios>
#include <iostream> #include <iostream>
#include <streambuf> #include <streambuf>
#include <string>
#include <string.h> #include <string.h>
#include <string>
#include <string_view> #include <string_view>
#include <variant> #include <variant>
@@ -40,8 +40,11 @@ public:
assert(!(mode & std::ios_base::out)); assert(!(mode & std::ios_base::out));
_file.emplace<std::streambuf *>(std::cin.rdbuf()); _file.emplace<std::streambuf *>(std::cin.rdbuf());
if (setmode(STDIN_FILENO, (mode & std::ios_base::binary) ? O_BINARY : O_TEXT) == -1) { if (setmode(STDIN_FILENO, (mode & std::ios_base::binary) ? O_BINARY : O_TEXT) == -1) {
fatal("Failed to set stdin to %s mode: %s", fatal(
mode & std::ios_base::binary ? "binary" : "text", strerror(errno)); "Failed to set stdin to %s mode: %s",
mode & std::ios_base::binary ? "binary" : "text",
strerror(errno)
);
} }
} else { } else {
assert(mode & std::ios_base::out); assert(mode & std::ios_base::out);
@@ -50,9 +53,12 @@ public:
return this; return this;
} }
std::streambuf &operator*() { std::streambuf &operator*() {
return std::visit(Visitor{[](std::filebuf &file) -> std::streambuf & { return file; }, return std::visit(
[](std::streambuf *buf) -> std::streambuf & { return *buf; }}, Visitor{
_file); [](std::filebuf &file) -> std::streambuf & { return file; },
[](std::streambuf *buf) -> std::streambuf & { return *buf; }},
_file
);
} }
std::streambuf const &operator*() const { std::streambuf const &operator*() const {
// The non-`const` version does not perform any modifications, so it's okay. // The non-`const` version does not perform any modifications, so it's okay.
@@ -65,25 +71,32 @@ public:
} }
File *close() { File *close() {
return std::visit(Visitor{[this](std::filebuf &file) { return std::visit(
// This is called by the destructor, and an explicit `close` Visitor{
// shouldn't close twice. [this](std::filebuf &file) {
_file.emplace<std::streambuf *>(nullptr); // This is called by the destructor, and an explicit `close`
return file.close() != nullptr; // shouldn't close twice.
}, _file.emplace<std::streambuf *>(nullptr);
[](std::streambuf *buf) { return buf != nullptr; }}, return file.close() != nullptr;
_file) },
[](std::streambuf *buf) { return buf != nullptr; },
},
_file
)
? this ? this
: nullptr; : nullptr;
} }
char const *c_str(std::string const &path) const { char const *c_str(std::string const &path) const {
return std::visit(Visitor{[&path](std::filebuf const &) { return path.c_str(); }, return std::visit(
[](std::streambuf const *buf) { Visitor{
return buf == std::cin.rdbuf() [&path](std::filebuf const &) { return path.c_str(); },
? "<stdin>" : "<stdout>"; [](std::streambuf const *buf) {
}}, return buf == std::cin.rdbuf() ? "<stdin>" : "<stdout>";
_file); },
},
_file
);
} }
}; };

View File

@@ -19,13 +19,13 @@ struct Options {
uint16_t reversedWidth = 0; // -r, in tiles uint16_t reversedWidth = 0; // -r, in tiles
bool reverse() const { return reversedWidth != 0; } bool reverse() const { return reversedWidth != 0; }
bool useColorCurve = false; // -C bool useColorCurve = false; // -C
bool allowMirroring = false; // -m bool allowMirroring = false; // -m
bool allowDedup = false; // -u bool allowDedup = false; // -u
bool columnMajor = false; // -Z, previously -h bool columnMajor = false; // -Z, previously -h
uint8_t verbosity = 0; // -v uint8_t verbosity = 0; // -v
std::string attrmap{}; // -a, -A std::string attrmap{}; // -a, -A
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
enum { enum {
NO_SPEC, NO_SPEC,
@@ -39,25 +39,25 @@ struct Options {
uint16_t top; uint16_t top;
uint16_t width; uint16_t width;
uint16_t height; uint16_t height;
} inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS) } inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS)
std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N
uint8_t nbPalettes = 8; // -n uint8_t nbPalettes = 8; // -n
std::string output{}; // -o std::string output{}; // -o
std::string palettes{}; // -p, -P std::string palettes{}; // -p, -P
std::string palmap{}; // -q, -Q std::string palmap{}; // -q, -Q
uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth; uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth;
std::string tilemap{}; // -t, -T std::string tilemap{}; // -t, -T
uint64_t trim = 0; // -x uint64_t trim = 0; // -x
std::string input{}; // positional arg std::string input{}; // positional arg
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun? static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
format_(printf, 3, 4) void verbosePrint(uint8_t level, char const *fmt, ...) const; format_(printf, 3, 4) void verbosePrint(uint8_t level, char const *fmt, ...) const;
mutable bool hasTransparentPixels = false; mutable bool hasTransparentPixels = false;

View File

@@ -15,10 +15,16 @@ struct Palette;
namespace sorting { namespace sorting {
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB, void indexed(
int palAlphaSize, png_byte *palAlpha); std::vector<Palette> &palettes,
void grayscale(std::vector<Palette> &palettes, int palSize,
std::array<std::optional<Rgba>, 0x8001> const &colors); png_color const *palRGB,
int palAlphaSize,
png_byte *palAlpha
);
void grayscale(
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
);
void rgb(std::vector<Palette> &palettes); void rgb(std::vector<Palette> &palettes);
} // namespace sorting } // namespace sorting

View File

@@ -25,8 +25,12 @@ struct Rgba {
fiveBpp &= 0b11111; // For caller's convenience fiveBpp &= 0b11111; // For caller's convenience
return fiveBpp << 3 | fiveBpp >> 2; return fiveBpp << 3 | fiveBpp >> 2;
}; };
return {_5to8(cgbColor), _5to8(cgbColor >> 5), _5to8(cgbColor >> 10), return {
(uint8_t)(cgbColor & 0x8000 ? 0x00 : 0xFF)}; _5to8(cgbColor),
_5to8(cgbColor >> 5),
_5to8(cgbColor >> 10),
(uint8_t)(cgbColor & 0x8000 ? 0x00 : 0xFF),
};
} }
/* /*

View File

@@ -6,8 +6,8 @@
// Ideally, we'd use `__has_attribute` and `__has_builtin`, but these were only introduced in GCC 9 // Ideally, we'd use `__has_attribute` and `__has_builtin`, but these were only introduced in GCC 9
#ifdef __GNUC__ // GCC or compatible #ifdef __GNUC__ // GCC or compatible
#define format_(archetype, str_index, first_arg) \ #define format_(archetype, str_index, first_arg) \
__attribute__ ((format (archetype, str_index, first_arg))) __attribute__((format(archetype, str_index, first_arg)))
#define attr_(...) __attribute__ ((__VA_ARGS__)) #define attr_(...) __attribute__((__VA_ARGS__))
// In release builds, define "unreachable" as such, but trap in debug builds // In release builds, define "unreachable" as such, but trap in debug builds
#ifdef NDEBUG #ifdef NDEBUG
#define unreachable_ __builtin_unreachable #define unreachable_ __builtin_unreachable
@@ -18,9 +18,10 @@
// Unsupported, but no need to throw a fit // Unsupported, but no need to throw a fit
#define format_(archetype, str_index, first_arg) #define format_(archetype, str_index, first_arg)
#define attr_(...) #define attr_(...)
// This seems to generate similar code to __builtin_unreachable, despite different semantics // This seems to generate similar code to __builtin_unreachable, despite different semantics
// Note that executing this is undefined behavior (declared [[noreturn]], but does return) // Note that executing this is undefined behavior (declared [[noreturn]], but does return)
[[noreturn]] static inline void unreachable_() {} [[noreturn]] static inline void unreachable_() {
}
#endif #endif
// Use builtins whenever possible, and shim them otherwise // Use builtins whenever possible, and shim them otherwise
@@ -32,50 +33,46 @@
#include <assert.h> #include <assert.h>
#include <intrin.h> #include <intrin.h>
#pragma intrinsic(_BitScanReverse, _BitScanForward) #pragma intrinsic(_BitScanReverse, _BitScanForward)
static inline int ctz(unsigned int x) static inline int ctz(unsigned int x) {
{ unsigned long cnt;
unsigned long cnt;
assert(x != 0); assert(x != 0);
_BitScanForward(&cnt, x); _BitScanForward(&cnt, x);
return cnt; return cnt;
} }
static inline int clz(unsigned int x) static inline int clz(unsigned int x) {
{ unsigned long cnt;
unsigned long cnt;
assert(x != 0); assert(x != 0);
_BitScanReverse(&cnt, x); _BitScanReverse(&cnt, x);
return 31 - cnt; return 31 - cnt;
} }
#else #else
#include <limits.h> #include <limits.h>
static inline int ctz(unsigned int x) static inline int ctz(unsigned int x) {
{ int cnt = 0;
int cnt = 0;
while (!(x & 1)) { while (!(x & 1)) {
x >>= 1; x >>= 1;
cnt++; cnt++;
}
return cnt;
} }
return cnt;
}
static inline int clz(unsigned int x) static inline int clz(unsigned int x) {
{ int cnt = 0;
int cnt = 0;
while (x <= UINT_MAX / 2) { while (x <= UINT_MAX / 2) {
x <<= 1; x <<= 1;
cnt++; cnt++;
}
return cnt;
} }
return cnt;
}
#endif #endif
// Macros for stringification // Macros for stringification
#define STR(x) #x #define STR(x) #x
#define EXPAND_AND_STR(x) STR(x) #define EXPAND_AND_STR(x) STR(x)
// Obtaining the size of an array; `arr` must be an expression, not a type! // Obtaining the size of an array; `arr` must be an expression, not a type!

View File

@@ -64,8 +64,9 @@ public:
} }
auto operator*() const { auto operator*() const {
return std::apply([](auto &&...it) { return std::tuple<decltype(*it)...>(*it...); }, return std::apply(
_iters); [](auto &&...it) { return std::tuple<decltype(*it)...>(*it...); }, _iters
);
} }
friend auto operator==(Zip const &lhs, Zip const &rhs) { friend auto operator==(Zip const &lhs, Zip const &rhs) {
@@ -92,7 +93,8 @@ public:
using std::begin; using std::begin;
return std::make_tuple(begin(containers)...); return std::make_tuple(begin(containers)...);
}, },
_containers)); _containers
));
} }
auto end() { auto end() {
@@ -101,14 +103,15 @@ public:
using std::end; using std::end;
return std::make_tuple(end(containers)...); return std::make_tuple(end(containers)...);
}, },
_containers)); _containers
));
} }
}; };
// Take ownership of objects and rvalue refs passed to us, but not lvalue refs // Take ownership of objects and rvalue refs passed to us, but not lvalue refs
template<typename T> template<typename T>
using Holder = std::conditional_t<std::is_lvalue_reference_v<T>, T, using Holder = std::
std::remove_cv_t<std::remove_reference_t<T>>>; conditional_t<std::is_lvalue_reference_v<T>, T, std::remove_cv_t<std::remove_reference_t<T>>>;
} // namespace detail } // namespace detail
// Does the same number of iterations as the first container's iterator! // Does the same number of iterations as the first container's iterator!

View File

@@ -31,10 +31,11 @@ extern bool isWRAM0Mode;
extern bool disablePadding; extern bool disablePadding;
// Helper macro for printing verbose-mode messages // Helper macro for printing verbose-mode messages
#define verbosePrint(...) do { \ #define verbosePrint(...) \
if (beVerbose) \ do { \
fprintf(stderr, __VA_ARGS__); \ if (beVerbose) \
} while (0) fprintf(stderr, __VA_ARGS__); \
} while (0)
struct FileStackNode { struct FileStackNode {
FileStackNode *parent; FileStackNode *parent;
@@ -43,10 +44,11 @@ struct FileStackNode {
enum FileStackNodeType type; enum FileStackNodeType type;
std::variant< std::variant<
std::monostate, // Default constructed; `.type` and `.data` must be set manually std::monostate, // Default constructed; `.type` and `.data` must be set manually
std::vector<uint32_t>, // NODE_REPT std::vector<uint32_t>, // NODE_REPT
std::string // NODE_FILE, NODE_MACRO std::string // NODE_FILE, NODE_MACRO
> data; >
data;
// REPT iteration counts since last named node, in reverse depth order // REPT iteration counts since last named node, in reverse depth order
std::vector<uint32_t> &iters(); std::vector<uint32_t> &iters();
@@ -58,8 +60,10 @@ struct FileStackNode {
std::string const *dumpFileStack() const; std::string const *dumpFileStack() const;
}; };
void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) format_(printf, 3, 4); void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
format_(printf, 3, 4);
void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) format_(printf, 3, 4); void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) format_(printf, 3, 4);
[[noreturn]] void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) format_(printf, 3, 4); [[noreturn]] void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
format_(printf, 3, 4);
#endif // RGBDS_LINK_MAIN_H #endif // RGBDS_LINK_MAIN_H

View File

@@ -8,10 +8,10 @@
#include <stdint.h> #include <stdint.h>
#include <vector> #include <vector>
#include "link/section.hpp"
#include "linkdefs.hpp" #include "linkdefs.hpp"
#include "link/section.hpp"
struct Assertion { struct Assertion {
Patch patch; // Also used for its `.type` Patch patch; // Also used for its `.type`
std::string message; std::string message;

View File

@@ -10,10 +10,10 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "link/main.hpp"
#include "linkdefs.hpp" #include "linkdefs.hpp"
#include "link/main.hpp"
struct FileStackNode; struct FileStackNode;
struct Section; struct Section;
struct Symbol; struct Symbol;

View File

@@ -30,9 +30,10 @@ struct Symbol {
FileStackNode const *src; FileStackNode const *src;
int32_t lineNo; int32_t lineNo;
std::variant< std::variant<
int32_t, // Constants just have a numeric value int32_t, // Constants just have a numeric value
Label // Label values refer to an offset within a specific section Label // Label values refer to an offset within a specific section
> data; >
data;
Label &label(); Label &label();
Label const &label() const; Label const &label() const;

View File

@@ -8,56 +8,52 @@
#include <string> #include <string>
#define RGBDS_OBJECT_VERSION_STRING "RGBA" #define RGBDS_OBJECT_VERSION_STRING "RGBA"
#define RGBDS_OBJECT_REV 10U #define RGBDS_OBJECT_REV 10U
enum AssertionType { enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
ASSERT_WARN,
ASSERT_ERROR,
ASSERT_FATAL
};
enum RPNCommand { enum RPNCommand {
RPN_ADD = 0x00, RPN_ADD = 0x00,
RPN_SUB = 0x01, RPN_SUB = 0x01,
RPN_MUL = 0x02, RPN_MUL = 0x02,
RPN_DIV = 0x03, RPN_DIV = 0x03,
RPN_MOD = 0x04, RPN_MOD = 0x04,
RPN_NEG = 0x05, RPN_NEG = 0x05,
RPN_EXP = 0x06, RPN_EXP = 0x06,
RPN_OR = 0x10, RPN_OR = 0x10,
RPN_AND = 0x11, RPN_AND = 0x11,
RPN_XOR = 0x12, RPN_XOR = 0x12,
RPN_NOT = 0x13, RPN_NOT = 0x13,
RPN_LOGAND = 0x21, RPN_LOGAND = 0x21,
RPN_LOGOR = 0x22, RPN_LOGOR = 0x22,
RPN_LOGNOT = 0x23, RPN_LOGNOT = 0x23,
RPN_LOGEQ = 0x30, RPN_LOGEQ = 0x30,
RPN_LOGNE = 0x31, RPN_LOGNE = 0x31,
RPN_LOGGT = 0x32, RPN_LOGGT = 0x32,
RPN_LOGLT = 0x33, RPN_LOGLT = 0x33,
RPN_LOGGE = 0x34, RPN_LOGGE = 0x34,
RPN_LOGLE = 0x35, RPN_LOGLE = 0x35,
RPN_SHL = 0x40, RPN_SHL = 0x40,
RPN_SHR = 0x41, RPN_SHR = 0x41,
RPN_USHR = 0x42, RPN_USHR = 0x42,
RPN_BANK_SYM = 0x50, RPN_BANK_SYM = 0x50,
RPN_BANK_SECT = 0x51, RPN_BANK_SECT = 0x51,
RPN_BANK_SELF = 0x52, RPN_BANK_SELF = 0x52,
RPN_SIZEOF_SECT = 0x53, RPN_SIZEOF_SECT = 0x53,
RPN_STARTOF_SECT = 0x54, RPN_STARTOF_SECT = 0x54,
RPN_SIZEOF_SECTTYPE = 0x55, RPN_SIZEOF_SECTTYPE = 0x55,
RPN_STARTOF_SECTTYPE = 0x56, RPN_STARTOF_SECTTYPE = 0x56,
RPN_HRAM = 0x60, RPN_HRAM = 0x60,
RPN_RST = 0x61, RPN_RST = 0x61,
RPN_CONST = 0x80, RPN_CONST = 0x80,
RPN_SYM = 0x81 RPN_SYM = 0x81
}; };
enum SectionType { enum SectionType {
@@ -96,8 +92,7 @@ extern struct SectionTypeInfo {
* @param type The section's type * @param type The section's type
* @return `true` if the section's definition includes data * @return `true` if the section's definition includes data
*/ */
static inline bool sect_HasData(enum SectionType type) static inline bool sect_HasData(enum SectionType type) {
{
assert(type != SECTTYPE_INVALID); assert(type != SECTTYPE_INVALID);
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX; return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
} }
@@ -106,8 +101,7 @@ static inline bool sect_HasData(enum SectionType type)
* Computes a memory region's end address (last byte), eg. 0x7FFF * Computes a memory region's end address (last byte), eg. 0x7FFF
* @return The address of the last byte in that memory region * @return The address of the last byte in that memory region
*/ */
static inline uint16_t endaddr(enum SectionType type) static inline uint16_t endaddr(enum SectionType type) {
{
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1; return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
} }
@@ -115,24 +109,15 @@ static inline uint16_t endaddr(enum SectionType type)
* Computes a memory region's number of banks * Computes a memory region's number of banks
* @return The number of banks, 1 for regions without banking * @return The number of banks, 1 for regions without banking
*/ */
static inline uint32_t nbbanks(enum SectionType type) static inline uint32_t nbbanks(enum SectionType type) {
{
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1; return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
} }
enum SectionModifier { enum SectionModifier { SECTION_NORMAL, SECTION_UNION, SECTION_FRAGMENT };
SECTION_NORMAL,
SECTION_UNION,
SECTION_FRAGMENT
};
extern char const * const sectionModNames[]; extern char const * const sectionModNames[];
enum ExportLevel { enum ExportLevel { SYMTYPE_LOCAL, SYMTYPE_IMPORT, SYMTYPE_EXPORT };
SYMTYPE_LOCAL,
SYMTYPE_IMPORT,
SYMTYPE_EXPORT
};
enum PatchType { enum PatchType {
PATCHTYPE_BYTE, PATCHTYPE_BYTE,

View File

@@ -7,62 +7,62 @@
// MSVC doesn't have str(n)casecmp, use a suitable replacement // MSVC doesn't have str(n)casecmp, use a suitable replacement
#ifdef _MSC_VER #ifdef _MSC_VER
# include <string.h> #include <string.h>
# define strcasecmp _stricmp #define strcasecmp _stricmp
# define strncasecmp _strnicmp #define strncasecmp _strnicmp
#else #else
# include <strings.h> #include <strings.h>
#endif #endif
// MSVC prefixes the names of S_* macros with underscores, // MSVC prefixes the names of S_* macros with underscores,
// and doesn't define any S_IS* macros; define them ourselves // and doesn't define any S_IS* macros; define them ourselves
#ifdef _MSC_VER #ifdef _MSC_VER
# define S_IFMT _S_IFMT #define S_IFMT _S_IFMT
# define S_IFDIR _S_IFDIR #define S_IFDIR _S_IFDIR
# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) #define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR)
#endif #endif
// gcc has __PRETTY_FUNCTION__, MSVC has __FUNCSIG__, __func__ is standard // gcc has __PRETTY_FUNCTION__, MSVC has __FUNCSIG__, __func__ is standard
#ifndef __PRETTY_FUNCTION__ #ifndef __PRETTY_FUNCTION__
# ifdef __FUNCSIG__ #ifdef __FUNCSIG__
# define __PRETTY_FUNCTION__ __FUNCSIG__ #define __PRETTY_FUNCTION__ __FUNCSIG__
# else #else
# define __PRETTY_FUNCTION__ __func__ #define __PRETTY_FUNCTION__ __func__
# endif #endif
#endif #endif
// MSVC doesn't use POSIX types or defines for `read` // MSVC doesn't use POSIX types or defines for `read`
#ifdef _MSC_VER #ifdef _MSC_VER
# include <io.h> #include <io.h>
# define STDIN_FILENO 0 #define STDIN_FILENO 0
# define STDOUT_FILENO 1 #define STDOUT_FILENO 1
# define STDERR_FILENO 2 #define STDERR_FILENO 2
# define ssize_t int #define ssize_t int
# define SSIZE_MAX INT_MAX #define SSIZE_MAX INT_MAX
#else #else
# include <fcntl.h> #include <fcntl.h>
# include <limits.h> #include <limits.h>
# include <unistd.h> #include <unistd.h>
#endif #endif
// MSVC uses a different name for O_RDWR, and needs an additional _O_BINARY flag // MSVC uses a different name for O_RDWR, and needs an additional _O_BINARY flag
#ifdef _MSC_VER #ifdef _MSC_VER
# include <fcntl.h> #include <fcntl.h>
# define O_RDWR _O_RDWR #define O_RDWR _O_RDWR
# define S_ISREG(field) ((field) & _S_IFREG) #define S_ISREG(field) ((field)&_S_IFREG)
# define O_BINARY _O_BINARY #define O_BINARY _O_BINARY
# define O_TEXT _O_TEXT #define O_TEXT _O_TEXT
#elif !defined(O_BINARY) // Cross-compilers define O_BINARY #elif !defined(O_BINARY) // Cross-compilers define O_BINARY
# define O_BINARY 0 // POSIX says we shouldn't care! #define O_BINARY 0 // POSIX says we shouldn't care!
# define O_TEXT 0 // Assume that it's not defined either #define O_TEXT 0 // Assume that it's not defined either
#endif // _MSC_VER #endif // _MSC_VER
// Windows has stdin and stdout open as text by default, which we may not want // Windows has stdin and stdout open as text by default, which we may not want
#if defined(_MSC_VER) || defined(__MINGW32__) #if defined(_MSC_VER) || defined(__MINGW32__)
# include <io.h> #include <io.h>
# define setmode(fd, mode) _setmode(fd, mode) #define setmode(fd, mode) _setmode(fd, mode)
#else #else
# define setmode(fd, mode) (0) #define setmode(fd, mode) (0)
#endif #endif
#endif // RGBDS_PLATFORM_H #endif // RGBDS_PLATFORM_H

View File

@@ -10,7 +10,6 @@ extern "C" {
#define PACKAGE_VERSION_PATCH 0 #define PACKAGE_VERSION_PATCH 0
char const *get_package_version_string(); char const *get_package_version_string();
} }
#endif // EXTERN_VERSION_H #endif // EXTERN_VERSION_H

View File

@@ -1,29 +1,30 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "asm/charmap.hpp"
#include <errno.h> #include <errno.h>
#include <new>
#include <map> #include <map>
#include <new>
#include <stack> #include <stack>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <string> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <string>
#include <vector> #include <vector>
#include "asm/charmap.hpp" #include "util.hpp"
#include "asm/main.hpp" #include "asm/main.hpp"
#include "asm/output.hpp" #include "asm/output.hpp"
#include "asm/warning.hpp" #include "asm/warning.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:
// whether there exists a mapping that ends at the current character, // whether there exists a mapping that ends at the current character,
struct CharmapNode { struct CharmapNode {
bool isTerminal; // Whether there exists a mapping that ends here bool isTerminal; // Whether there exists a mapping that ends here
uint8_t value; // If the above is true, its corresponding value uint8_t value; // If the above is true, its corresponding value
// This MUST be indexes and not pointers, because pointers get invalidated by reallocation! // This MUST be indexes and not pointers, because pointers get invalidated by reallocation!
size_t next[255]; // Indexes of where to go next, 0 = nowhere size_t next[255]; // Indexes of where to go next, 0 = nowhere
}; };
@@ -38,8 +39,7 @@ static std::map<std::string, Charmap> charmaps;
static Charmap *currentCharmap; static Charmap *currentCharmap;
std::stack<Charmap *> charmapStack; std::stack<Charmap *> charmapStack;
void charmap_New(char const *name, char const *baseName) void charmap_New(char const *name, char const *baseName) {
{
Charmap *base = nullptr; Charmap *base = nullptr;
if (baseName != nullptr) { if (baseName != nullptr) {
@@ -68,8 +68,7 @@ void charmap_New(char const *name, char const *baseName)
currentCharmap = &charmap; currentCharmap = &charmap;
} }
void charmap_Set(char const *name) void charmap_Set(char const *name) {
{
auto search = charmaps.find(name); auto search = charmaps.find(name);
if (search == charmaps.end()) if (search == charmaps.end())
@@ -78,13 +77,11 @@ void charmap_Set(char const *name)
currentCharmap = &search->second; currentCharmap = &search->second;
} }
void charmap_Push() void charmap_Push() {
{
charmapStack.push(currentCharmap); charmapStack.push(currentCharmap);
} }
void charmap_Pop() void charmap_Pop() {
{
if (charmapStack.empty()) { if (charmapStack.empty()) {
error("No entries in the charmap stack\n"); error("No entries in the charmap stack\n");
return; return;
@@ -94,8 +91,7 @@ void charmap_Pop()
charmapStack.pop(); charmapStack.pop();
} }
void charmap_Add(char *mapping, uint8_t value) void charmap_Add(char *mapping, uint8_t value) {
{
Charmap &charmap = *currentCharmap; Charmap &charmap = *currentCharmap;
size_t nodeIdx = 0; size_t nodeIdx = 0;
@@ -124,8 +120,7 @@ void charmap_Add(char *mapping, uint8_t value)
node.value = value; node.value = value;
} }
bool charmap_HasChar(char const *input) bool charmap_HasChar(char const *input) {
{
Charmap const &charmap = *currentCharmap; Charmap const &charmap = *currentCharmap;
size_t nodeIdx = 0; size_t nodeIdx = 0;
@@ -139,14 +134,12 @@ bool charmap_HasChar(char const *input)
return charmap.nodes[nodeIdx].isTerminal; return charmap.nodes[nodeIdx].isTerminal;
} }
void charmap_Convert(char const *input, std::vector<uint8_t> &output) void charmap_Convert(char const *input, std::vector<uint8_t> &output) {
{
while (charmap_ConvertNext(input, &output)) while (charmap_ConvertNext(input, &output))
; ;
} }
size_t charmap_ConvertNext(char const *&input, std::vector<uint8_t> *output) size_t charmap_ConvertNext(char const *&input, std::vector<uint8_t> *output) {
{
// The goal is to match the longest mapping possible. // The goal is to match the longest mapping possible.
// For that, advance through the trie with each character read. // For that, advance through the trie with each character read.
// If that would lead to a dead end, rewind characters until the last match, and output. // If that would lead to a dead end, rewind characters until the last match, and output.
@@ -194,11 +187,13 @@ size_t charmap_ConvertNext(char const *&input, std::vector<uint8_t> *output)
// Warn if this character is not mapped but any others are // Warn if this character is not mapped but any others are
if (charmap.nodes.size() > 1) if (charmap.nodes.size() > 1)
warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s\n", warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s\n", printChar(firstChar));
printChar(firstChar));
else if (charmap.name != DEFAULT_CHARMAP_NAME) else if (charmap.name != DEFAULT_CHARMAP_NAME)
warning(WARNING_UNMAPPED_CHAR_2, "Unmapped character %s not in " warning(
DEFAULT_CHARMAP_NAME " charmap\n", printChar(firstChar)); WARNING_UNMAPPED_CHAR_2,
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n",
printChar(firstChar)
);
return codepointLen; return codepointLen;

View File

@@ -2,20 +2,21 @@
// Fixed-point math routines // Fixed-point math routines
#include "asm/fixpoint.hpp"
#include <inttypes.h> #include <inttypes.h>
#include <math.h> #include <math.h>
#include <stdint.h> #include <stdint.h>
#include "asm/fixpoint.hpp"
#include "asm/symbol.hpp" #include "asm/symbol.hpp"
#include "asm/warning.hpp" #include "asm/warning.hpp"
#ifndef M_PI #ifndef M_PI
#define M_PI 3.14159265358979323846 #define M_PI 3.14159265358979323846
#endif #endif
#define fix2double(i, q) ((double)((i) / pow(2.0, q))) #define fix2double(i, q) ((double)((i) / pow(2.0, q)))
#define double2fix(d, q) ((int32_t)round((d) * pow(2.0, q))) #define double2fix(d, q) ((int32_t)round((d)*pow(2.0, q)))
// 2*pi radians == 1 turn // 2*pi radians == 1 turn
#define turn2rad(f) ((f) * (M_PI * 2)) #define turn2rad(f) ((f) * (M_PI * 2))
@@ -23,87 +24,70 @@
uint8_t fixPrecision; uint8_t fixPrecision;
uint8_t fix_Precision() uint8_t fix_Precision() {
{
return fixPrecision; return fixPrecision;
} }
double fix_PrecisionFactor() double fix_PrecisionFactor() {
{
return pow(2.0, fixPrecision); return pow(2.0, fixPrecision);
} }
int32_t fix_Sin(int32_t i, int32_t q) int32_t fix_Sin(int32_t i, int32_t q) {
{
return double2fix(sin(turn2rad(fix2double(i, q))), q); return double2fix(sin(turn2rad(fix2double(i, q))), q);
} }
int32_t fix_Cos(int32_t i, int32_t q) int32_t fix_Cos(int32_t i, int32_t q) {
{
return double2fix(cos(turn2rad(fix2double(i, q))), q); return double2fix(cos(turn2rad(fix2double(i, q))), q);
} }
int32_t fix_Tan(int32_t i, int32_t q) int32_t fix_Tan(int32_t i, int32_t q) {
{
return double2fix(tan(turn2rad(fix2double(i, q))), q); return double2fix(tan(turn2rad(fix2double(i, q))), q);
} }
int32_t fix_ASin(int32_t i, int32_t q) int32_t fix_ASin(int32_t i, int32_t q) {
{
return double2fix(rad2turn(asin(fix2double(i, q))), q); return double2fix(rad2turn(asin(fix2double(i, q))), q);
} }
int32_t fix_ACos(int32_t i, int32_t q) int32_t fix_ACos(int32_t i, int32_t q) {
{
return double2fix(rad2turn(acos(fix2double(i, q))), q); return double2fix(rad2turn(acos(fix2double(i, q))), q);
} }
int32_t fix_ATan(int32_t i, int32_t q) int32_t fix_ATan(int32_t i, int32_t q) {
{
return double2fix(rad2turn(atan(fix2double(i, q))), q); return double2fix(rad2turn(atan(fix2double(i, q))), q);
} }
int32_t fix_ATan2(int32_t i, int32_t j, int32_t q) int32_t fix_ATan2(int32_t i, int32_t j, int32_t q) {
{
return double2fix(rad2turn(atan2(fix2double(i, q), fix2double(j, q))), q); return double2fix(rad2turn(atan2(fix2double(i, q), fix2double(j, q))), q);
} }
int32_t fix_Mul(int32_t i, int32_t j, int32_t q) int32_t fix_Mul(int32_t i, int32_t j, int32_t q) {
{
return double2fix(fix2double(i, q) * fix2double(j, q), q); return double2fix(fix2double(i, q) * fix2double(j, q), q);
} }
int32_t fix_Div(int32_t i, int32_t j, int32_t q) int32_t fix_Div(int32_t i, int32_t j, int32_t q) {
{
return double2fix(fix2double(i, q) / fix2double(j, q), q); return double2fix(fix2double(i, q) / fix2double(j, q), q);
} }
int32_t fix_Mod(int32_t i, int32_t j, int32_t q) int32_t fix_Mod(int32_t i, int32_t j, int32_t q) {
{
return double2fix(fmod(fix2double(i, q), fix2double(j, q)), q); return double2fix(fmod(fix2double(i, q), fix2double(j, q)), q);
} }
int32_t fix_Pow(int32_t i, int32_t j, int32_t q) int32_t fix_Pow(int32_t i, int32_t j, int32_t q) {
{
return double2fix(pow(fix2double(i, q), fix2double(j, q)), q); return double2fix(pow(fix2double(i, q), fix2double(j, q)), q);
} }
int32_t fix_Log(int32_t i, int32_t j, int32_t q) int32_t fix_Log(int32_t i, int32_t j, int32_t q) {
{
return double2fix(log(fix2double(i, q)) / log(fix2double(j, q)), q); return double2fix(log(fix2double(i, q)) / log(fix2double(j, q)), q);
} }
int32_t fix_Round(int32_t i, int32_t q) int32_t fix_Round(int32_t i, int32_t q) {
{
return double2fix(round(fix2double(i, q)), q); return double2fix(round(fix2double(i, q)), q);
} }
int32_t fix_Ceil(int32_t i, int32_t q) int32_t fix_Ceil(int32_t i, int32_t q) {
{
return double2fix(ceil(fix2double(i, q)), q); return double2fix(ceil(fix2double(i, q)), q);
} }
int32_t fix_Floor(int32_t i, int32_t q) int32_t fix_Floor(int32_t i, int32_t q) {
{
return double2fix(floor(fix2double(i, q)), q); return double2fix(floor(fix2double(i, q)), q);
} }

View File

@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "asm/format.hpp"
#include <assert.h> #include <assert.h>
#include <inttypes.h> #include <inttypes.h>
#include <math.h> #include <math.h>
@@ -9,11 +11,9 @@
#include <string.h> #include <string.h>
#include "asm/fixpoint.hpp" #include "asm/fixpoint.hpp"
#include "asm/format.hpp"
#include "asm/warning.hpp" #include "asm/warning.hpp"
void FormatSpec::useCharacter(int c) void FormatSpec::useCharacter(int c) {
{
if (state == FORMAT_INVALID) if (state == FORMAT_INVALID)
return; return;
@@ -99,14 +99,12 @@ invalid:
} }
} }
void FormatSpec::finishCharacters() void FormatSpec::finishCharacters() {
{
if (!isValid()) if (!isValid())
state = FORMAT_INVALID; state = FORMAT_INVALID;
} }
void FormatSpec::printString(char *buf, size_t bufLen, char const *value) void FormatSpec::printString(char *buf, size_t bufLen, char const *value) {
{
if (isEmpty()) { if (isEmpty()) {
// No format was specified // No format was specified
type = 's'; type = 's';
@@ -149,8 +147,7 @@ void FormatSpec::printString(char *buf, size_t bufLen, char const *value)
buf[totalLen] = '\0'; buf[totalLen] = '\0';
} }
void FormatSpec::printNumber(char *buf, size_t bufLen, uint32_t value) void FormatSpec::printNumber(char *buf, size_t bufLen, uint32_t value) {
{
if (isEmpty()) { if (isEmpty()) {
// No format was specified; default to uppercase $hex // No format was specified; default to uppercase $hex
type = 'X'; type = 'X';
@@ -175,12 +172,12 @@ void FormatSpec::printNumber(char *buf, size_t bufLen, uint32_t value)
} }
} }
char prefixChar = !prefix ? 0 char prefixChar = !prefix ? 0
: type == 'X' ? '$' : type == 'X' ? '$'
: type == 'x' ? '$' : type == 'x' ? '$'
: type == 'b' ? '%' : type == 'b' ? '%'
: type == 'o' ? '&' : type == 'o' ? '&'
: 0; : 0;
char valueBuf[262]; // Max 5 digits + decimal + 255 fraction digits + terminator char valueBuf[262]; // Max 5 digits + decimal + 255 fraction digits + terminator
@@ -215,15 +212,16 @@ void FormatSpec::printNumber(char *buf, size_t bufLen, uint32_t value)
cappedFracWidth = 255; cappedFracWidth = 255;
} }
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)cappedFracWidth, snprintf(
value / fix_PrecisionFactor()); valueBuf, sizeof(valueBuf), "%.*f", (int)cappedFracWidth, value / fix_PrecisionFactor()
);
} else { } else {
char const *spec = type == 'd' ? "%" PRId32 char const *spec = type == 'd' ? "%" PRId32
: type == 'u' ? "%" PRIu32 : type == 'u' ? "%" PRIu32
: type == 'X' ? "%" PRIX32 : type == 'X' ? "%" PRIX32
: type == 'x' ? "%" PRIx32 : type == 'x' ? "%" PRIx32
: type == 'o' ? "%" PRIo32 : type == 'o' ? "%" PRIo32
: "%" PRId32; : "%" PRId32;
snprintf(valueBuf, sizeof(valueBuf), spec, value); snprintf(valueBuf, sizeof(valueBuf), spec, value);
} }

View File

@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include <sys/stat.h> #include <sys/stat.h>
#include <new>
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
@@ -15,13 +15,14 @@
#include <variant> #include <variant>
#include <vector> #include <vector>
#include "error.hpp"
#include "platform.hpp" // S_ISDIR (stat macro)
#include "asm/fstack.hpp" #include "asm/fstack.hpp"
#include "asm/macro.hpp" #include "asm/macro.hpp"
#include "asm/main.hpp" #include "asm/main.hpp"
#include "asm/symbol.hpp" #include "asm/symbol.hpp"
#include "asm/warning.hpp" #include "asm/warning.hpp"
#include "error.hpp"
#include "platform.hpp" // S_ISDIR (stat macro)
struct Context { struct Context {
FileStackNode *fileInfo; FileStackNode *fileInfo;
@@ -39,7 +40,7 @@ static std::stack<Context> contextStack;
size_t maxRecursionDepth; size_t maxRecursionDepth;
// The first include path for `fstk_FindFile` to try is none at all // The first include path for `fstk_FindFile` to try is none at all
static std::vector<std::string> includePaths = { "" }; static std::vector<std::string> includePaths = {""};
static const char *preIncludeName; static const char *preIncludeName;
@@ -63,8 +64,7 @@ std::string const &FileStackNode::name() const {
return std::get<std::string>(data); return std::get<std::string>(data);
} }
static const char *dumpNodeAndParents(FileStackNode const &node) static const char *dumpNodeAndParents(FileStackNode const &node) {
{
char const *name; char const *name;
if (node.type == NODE_REPT) { if (node.type == NODE_REPT) {
@@ -73,7 +73,7 @@ static const char *dumpNodeAndParents(FileStackNode const &node)
name = dumpNodeAndParents(*node.parent); name = dumpNodeAndParents(*node.parent);
fprintf(stderr, "(%" PRIu32 ") -> %s", node.lineNo, name); fprintf(stderr, "(%" PRIu32 ") -> %s", node.lineNo, name);
for (uint32_t i = nodeIters.size(); i--; ) for (uint32_t i = nodeIters.size(); i--;)
fprintf(stderr, "::REPT~%" PRIu32, nodeIters[i]); fprintf(stderr, "::REPT~%" PRIu32, nodeIters[i]);
} else { } else {
name = node.name().c_str(); name = node.name().c_str();
@@ -87,14 +87,12 @@ static const char *dumpNodeAndParents(FileStackNode const &node)
return name; return name;
} }
void FileStackNode::dump(uint32_t curLineNo) const void FileStackNode::dump(uint32_t curLineNo) const {
{
dumpNodeAndParents(*this); dumpNodeAndParents(*this);
fprintf(stderr, "(%" PRIu32 ")", curLineNo); fprintf(stderr, "(%" PRIu32 ")", curLineNo);
} }
void fstk_DumpCurrent() void fstk_DumpCurrent() {
{
if (contextStack.empty()) { if (contextStack.empty()) {
fputs("at top level", stderr); fputs("at top level", stderr);
return; return;
@@ -102,8 +100,7 @@ void fstk_DumpCurrent()
contextStack.top().fileInfo->dump(lexer_GetLineNo()); contextStack.top().fileInfo->dump(lexer_GetLineNo());
} }
FileStackNode *fstk_GetFileStack() FileStackNode *fstk_GetFileStack() {
{
if (contextStack.empty()) if (contextStack.empty())
return nullptr; return nullptr;
@@ -117,8 +114,7 @@ FileStackNode *fstk_GetFileStack()
return topNode; return topNode;
} }
char const *fstk_GetFileName() char const *fstk_GetFileName() {
{
// Iterating via the nodes themselves skips nested REPTs // Iterating via the nodes themselves skips nested REPTs
FileStackNode const *node = contextStack.top().fileInfo; FileStackNode const *node = contextStack.top().fileInfo;
@@ -127,8 +123,7 @@ char const *fstk_GetFileName()
return node->name().c_str(); return node->name().c_str();
} }
void fstk_AddIncludePath(char const *path) void fstk_AddIncludePath(char const *path) {
{
if (path[0] == '\0') if (path[0] == '\0')
return; return;
@@ -138,8 +133,7 @@ void fstk_AddIncludePath(char const *path)
str += '/'; str += '/';
} }
void fstk_SetPreIncludeFile(char const *path) void fstk_SetPreIncludeFile(char const *path) {
{
if (preIncludeName) if (preIncludeName)
warnx("Overriding pre-included filename %s", preIncludeName); warnx("Overriding pre-included filename %s", preIncludeName);
preIncludeName = path; preIncludeName = path;
@@ -147,8 +141,7 @@ void fstk_SetPreIncludeFile(char const *path)
printf("Pre-included filename %s\n", preIncludeName); printf("Pre-included filename %s\n", preIncludeName);
} }
static void printDep(char const *path) static void printDep(char const *path) {
{
if (dependfile) { if (dependfile) {
fprintf(dependfile, "%s: %s\n", targetFileName.c_str(), path); fprintf(dependfile, "%s: %s\n", targetFileName.c_str(), path);
if (generatePhonyDeps) if (generatePhonyDeps)
@@ -156,8 +149,7 @@ static void printDep(char const *path)
} }
} }
static bool isPathValid(char const *path) static bool isPathValid(char const *path) {
{
struct stat statbuf; struct stat statbuf;
if (stat(path, &statbuf) != 0) if (stat(path, &statbuf) != 0)
@@ -167,9 +159,8 @@ static bool isPathValid(char const *path)
return !S_ISDIR(statbuf.st_mode); return !S_ISDIR(statbuf.st_mode);
} }
std::string *fstk_FindFile(char const *path) std::string *fstk_FindFile(char const *path) {
{ std::string *fullPath = new (std::nothrow) std::string();
std::string *fullPath = new(std::nothrow) std::string();
if (!fullPath) { if (!fullPath) {
error("Failed to allocate string during include path search: %s\n", strerror(errno)); error("Failed to allocate string during include path search: %s\n", strerror(errno));
@@ -189,20 +180,22 @@ std::string *fstk_FindFile(char const *path)
return nullptr; return nullptr;
} }
bool yywrap() bool yywrap() {
{
uint32_t ifDepth = lexer_GetIFDepth(); uint32_t ifDepth = lexer_GetIFDepth();
if (ifDepth != 0) if (ifDepth != 0)
fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n", fatalerror(
ifDepth, ifDepth == 1 ? "" : "s"); "Ended block with %" PRIu32 " unterminated IF construct%s\n",
ifDepth,
ifDepth == 1 ? "" : "s"
);
if (Context &context = contextStack.top(); context.fileInfo->type == NODE_REPT) { if (Context &context = contextStack.top(); context.fileInfo->type == NODE_REPT) {
// The context is a REPT or FOR block, which may loop // The context is a REPT or FOR block, which may loop
// If the node is referenced, we can't edit it; duplicate it // If the node is referenced, we can't edit it; duplicate it
if (context.fileInfo->referenced) { if (context.fileInfo->referenced) {
context.fileInfo = new(std::nothrow) FileStackNode(*context.fileInfo); context.fileInfo = new (std::nothrow) FileStackNode(*context.fileInfo);
if (!context.fileInfo) if (!context.fileInfo)
fatalerror("Failed to duplicate REPT file node: %s\n", strerror(errno)); fatalerror("Failed to duplicate REPT file node: %s\n", strerror(errno));
// Copy all info but the referencing // Copy all info but the referencing
@@ -255,8 +248,7 @@ bool yywrap()
// Make sure not to switch the lexer state before calling this, so the saved line no is correct. // Make sure not to switch the lexer state before calling this, so the saved line no is correct.
// BE CAREFUL! This modifies the file stack directly, you should have set up the file info first. // BE CAREFUL! This modifies the file stack directly, you should have set up the file info first.
// Callers should set `contextStack.top().lexerState` after this so it is not `nullptr`. // Callers should set `contextStack.top().lexerState` after this so it is not `nullptr`.
static Context &newContext(FileStackNode &fileInfo) static Context &newContext(FileStackNode &fileInfo) {
{
if (contextStack.size() > maxRecursionDepth) if (contextStack.size() > maxRecursionDepth)
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth); fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
@@ -277,15 +269,13 @@ static Context &newContext(FileStackNode &fileInfo)
return context; return context;
} }
void fstk_RunInclude(char const *path) void fstk_RunInclude(char const *path) {
{
std::string *fullPath = fstk_FindFile(path); std::string *fullPath = fstk_FindFile(path);
if (!fullPath) { if (!fullPath) {
if (generatedMissingIncludes) { if (generatedMissingIncludes) {
if (verbose) if (verbose)
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path, strerror(errno));
path, strerror(errno));
failedOnMissingInclude = true; failedOnMissingInclude = true;
} else { } else {
error("Unable to open included file '%s': %s\n", path, strerror(errno)); error("Unable to open included file '%s': %s\n", path, strerror(errno));
@@ -293,7 +283,7 @@ void fstk_RunInclude(char const *path)
return; return;
} }
FileStackNode *fileInfo = new(std::nothrow) FileStackNode(); FileStackNode *fileInfo = new (std::nothrow) FileStackNode();
if (!fileInfo) { if (!fileInfo) {
error("Failed to alloc file info for INCLUDE: %s\n", strerror(errno)); error("Failed to alloc file info for INCLUDE: %s\n", strerror(errno));
@@ -317,8 +307,7 @@ void fstk_RunInclude(char const *path)
// Similar to `fstk_RunInclude`, but not subject to `-MG`, and // Similar to `fstk_RunInclude`, but not subject to `-MG`, and
// calling `lexer_SetState` instead of `lexer_SetStateAtEOL`. // calling `lexer_SetState` instead of `lexer_SetStateAtEOL`.
static void runPreIncludeFile() static void runPreIncludeFile() {
{
if (!preIncludeName) if (!preIncludeName)
return; return;
@@ -329,7 +318,7 @@ static void runPreIncludeFile()
return; return;
} }
FileStackNode *fileInfo = new(std::nothrow) FileStackNode(); FileStackNode *fileInfo = new (std::nothrow) FileStackNode();
if (!fileInfo) { if (!fileInfo) {
error("Failed to alloc file info for pre-include: %s\n", strerror(errno)); error("Failed to alloc file info for pre-include: %s\n", strerror(errno));
@@ -348,8 +337,7 @@ static void runPreIncludeFile()
context.uniqueID = macro_UndefUniqueID(); context.uniqueID = macro_UndefUniqueID();
} }
void fstk_RunMacro(char const *macroName, MacroArgs &args) void fstk_RunMacro(char const *macroName, MacroArgs &args) {
{
Symbol *macro = sym_FindExactSymbol(macroName); Symbol *macro = sym_FindExactSymbol(macroName);
if (!macro) { if (!macro) {
@@ -362,7 +350,7 @@ void fstk_RunMacro(char const *macroName, MacroArgs &args)
} }
contextStack.top().macroArgs = macro_GetCurrentArgs(); contextStack.top().macroArgs = macro_GetCurrentArgs();
FileStackNode *fileInfo = new(std::nothrow) FileStackNode(); FileStackNode *fileInfo = new (std::nothrow) FileStackNode();
if (!fileInfo) { if (!fileInfo) {
error("Failed to alloc file info for \"%s\": %s\n", macro->name, strerror(errno)); error("Failed to alloc file info for \"%s\": %s\n", macro->name, strerror(errno));
@@ -383,12 +371,11 @@ void fstk_RunMacro(char const *macroName, MacroArgs &args)
if (macro->src->type == NODE_REPT) { if (macro->src->type == NODE_REPT) {
std::vector<uint32_t> const &srcIters = macro->src->iters(); std::vector<uint32_t> const &srcIters = macro->src->iters();
for (uint32_t i = srcIters.size(); i--; ) { for (uint32_t i = srcIters.size(); i--;) {
char buf[sizeof("::REPT~4294967295")]; // UINT32_MAX char buf[sizeof("::REPT~4294967295")]; // UINT32_MAX
if (sprintf(buf, "::REPT~%" PRIu32, srcIters[i]) < 0) if (sprintf(buf, "::REPT~%" PRIu32, srcIters[i]) < 0)
fatalerror("Failed to write macro invocation info: %s\n", fatalerror("Failed to write macro invocation info: %s\n", strerror(errno));
strerror(errno));
fileInfoName.append(buf); fileInfoName.append(buf);
} }
} }
@@ -398,19 +385,19 @@ void fstk_RunMacro(char const *macroName, MacroArgs &args)
Context &context = newContext(*fileInfo); Context &context = newContext(*fileInfo);
std::string_view *macroView = macro->getMacro(); std::string_view *macroView = macro->getMacro();
lexer_OpenFileView(context.lexerState, "MACRO", macroView->data(), macroView->size(), lexer_OpenFileView(
macro->fileLine); context.lexerState, "MACRO", macroView->data(), macroView->size(), macro->fileLine
);
lexer_SetStateAtEOL(&context.lexerState); lexer_SetStateAtEOL(&context.lexerState);
context.uniqueID = macro_UseNewUniqueID(); context.uniqueID = macro_UseNewUniqueID();
macro_UseNewArgs(&args); macro_UseNewArgs(&args);
} }
static bool newReptContext(int32_t reptLineNo, char const *body, size_t size) static bool newReptContext(int32_t reptLineNo, char const *body, size_t size) {
{
uint32_t reptDepth = contextStack.top().fileInfo->type == NODE_REPT uint32_t reptDepth = contextStack.top().fileInfo->type == NODE_REPT
? contextStack.top().fileInfo->iters().size() ? contextStack.top().fileInfo->iters().size()
: 0; : 0;
FileStackNode *fileInfo = new(std::nothrow) FileStackNode(); FileStackNode *fileInfo = new (std::nothrow) FileStackNode();
if (!fileInfo) { if (!fileInfo) {
error("Failed to alloc file info for REPT: %s\n", strerror(errno)); error("Failed to alloc file info for REPT: %s\n", strerror(errno));
@@ -420,7 +407,9 @@ static bool newReptContext(int32_t reptLineNo, char const *body, size_t size)
fileInfo->data = std::vector<uint32_t>{1}; fileInfo->data = std::vector<uint32_t>{1};
if (reptDepth) { if (reptDepth) {
// Append all parent iter counts // Append all parent iter counts
fileInfo->iters().insert(fileInfo->iters().end(), RANGE(contextStack.top().fileInfo->iters())); fileInfo->iters().insert(
fileInfo->iters().end(), RANGE(contextStack.top().fileInfo->iters())
);
} }
Context &context = newContext(*fileInfo); Context &context = newContext(*fileInfo);
@@ -434,8 +423,7 @@ static bool newReptContext(int32_t reptLineNo, char const *body, size_t size)
return true; return true;
} }
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char const *body, size_t size) void fstk_RunRept(uint32_t count, int32_t reptLineNo, char const *body, size_t size) {
{
if (count == 0) if (count == 0)
return; return;
if (!newReptContext(reptLineNo, body, size)) if (!newReptContext(reptLineNo, body, size))
@@ -444,9 +432,15 @@ void fstk_RunRept(uint32_t count, int32_t reptLineNo, char const *body, size_t s
contextStack.top().nbReptIters = count; contextStack.top().nbReptIters = count;
} }
void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step, void fstk_RunFor(
int32_t reptLineNo, char const *body, size_t size) char const *symName,
{ int32_t start,
int32_t stop,
int32_t step,
int32_t reptLineNo,
char const *body,
size_t size
) {
Symbol *sym = sym_AddVar(symName, start); Symbol *sym = sym_AddVar(symName, start);
if (sym->type != SYM_VAR) if (sym->type != SYM_VAR)
@@ -462,8 +456,9 @@ void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
error("FOR cannot have a step value of 0\n"); error("FOR cannot have a step value of 0\n");
if ((step > 0 && start > stop) || (step < 0 && start < stop)) if ((step > 0 && start > stop) || (step < 0 && start < stop))
warning(WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", warning(
start, stop, step); WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step
);
if (count == 0) if (count == 0)
return; return;
@@ -479,14 +474,12 @@ void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
context.forName = symName; context.forName = symName;
} }
void fstk_StopRept() void fstk_StopRept() {
{
// Prevent more iterations // Prevent more iterations
contextStack.top().nbReptIters = 0; contextStack.top().nbReptIters = 0;
} }
bool fstk_Break() bool fstk_Break() {
{
if (contextStack.top().fileInfo->type != NODE_REPT) { if (contextStack.top().fileInfo->type != NODE_REPT) {
error("BREAK can only be used inside a REPT/FOR block\n"); error("BREAK can only be used inside a REPT/FOR block\n");
return false; return false;
@@ -496,22 +489,20 @@ bool fstk_Break()
return true; return true;
} }
void fstk_NewRecursionDepth(size_t newDepth) void fstk_NewRecursionDepth(size_t newDepth) {
{
if (contextStack.size() > newDepth + 1) if (contextStack.size() > newDepth + 1)
fatalerror("Recursion limit (%zu) exceeded\n", newDepth); fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
maxRecursionDepth = newDepth; maxRecursionDepth = newDepth;
} }
void fstk_Init(char const *mainPath, size_t maxDepth) void fstk_Init(char const *mainPath, size_t maxDepth) {
{
Context &context = contextStack.emplace(); Context &context = contextStack.emplace();
if (!lexer_OpenFile(context.lexerState, mainPath)) if (!lexer_OpenFile(context.lexerState, mainPath))
fatalerror("Failed to open main file\n"); fatalerror("Failed to open main file\n");
lexer_SetState(&context.lexerState); lexer_SetState(&context.lexerState);
FileStackNode *fileInfo = new(std::nothrow) FileStackNode(); FileStackNode *fileInfo = new (std::nothrow) FileStackNode();
if (!fileInfo) if (!fileInfo)
fatalerror("Failed to allocate memory for main file info: %s\n", strerror(errno)); fatalerror("Failed to allocate memory for main file info: %s\n", strerror(errno));

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,15 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "asm/macro.hpp"
#include <errno.h> #include <errno.h>
#include <new>
#include <inttypes.h> #include <inttypes.h>
#include <new>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include "asm/macro.hpp"
#include "asm/warning.hpp" #include "asm/warning.hpp"
#define MAXMACROARGS 99999 #define MAXMACROARGS 99999
@@ -19,8 +20,7 @@ static uint32_t maxUniqueID = 0;
static char uniqueIDBuf[sizeof("_u4294967295")] = {}; // UINT32_MAX static char uniqueIDBuf[sizeof("_u4294967295")] = {}; // UINT32_MAX
static char *uniqueIDPtr = nullptr; static char *uniqueIDPtr = nullptr;
void MacroArgs::append(std::string s) void MacroArgs::append(std::string s) {
{
if (s.empty()) if (s.empty())
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n"); warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
if (args.size() == MAXMACROARGS) if (args.size() == MAXMACROARGS)
@@ -28,18 +28,15 @@ void MacroArgs::append(std::string s)
args.push_back(s); args.push_back(s);
} }
MacroArgs *macro_GetCurrentArgs() MacroArgs *macro_GetCurrentArgs() {
{
return macroArgs; return macroArgs;
} }
void macro_UseNewArgs(MacroArgs *args) void macro_UseNewArgs(MacroArgs *args) {
{
macroArgs = args; macroArgs = args;
} }
char const *macro_GetArg(uint32_t i) char const *macro_GetArg(uint32_t i) {
{
if (!macroArgs) if (!macroArgs)
return nullptr; return nullptr;
@@ -48,8 +45,7 @@ char const *macro_GetArg(uint32_t i)
return realIndex >= macroArgs->args.size() ? nullptr : macroArgs->args[realIndex].c_str(); return realIndex >= macroArgs->args.size() ? nullptr : macroArgs->args[realIndex].c_str();
} }
char const *macro_GetAllArgs() char const *macro_GetAllArgs() {
{
if (!macroArgs) if (!macroArgs)
return nullptr; return nullptr;
@@ -85,13 +81,11 @@ char const *macro_GetAllArgs()
return str; return str;
} }
uint32_t macro_GetUniqueID() uint32_t macro_GetUniqueID() {
{
return uniqueID; return uniqueID;
} }
char const *macro_GetUniqueIDStr() char const *macro_GetUniqueIDStr() {
{
// Generate a new unique ID on the first use of `\@` // Generate a new unique ID on the first use of `\@`
if (uniqueID == 0) if (uniqueID == 0)
macro_SetUniqueID(++maxUniqueID); macro_SetUniqueID(++maxUniqueID);
@@ -99,8 +93,7 @@ char const *macro_GetUniqueIDStr()
return uniqueIDPtr; return uniqueIDPtr;
} }
void macro_SetUniqueID(uint32_t id) void macro_SetUniqueID(uint32_t id) {
{
uniqueID = id; uniqueID = id;
if (id == 0 || id == (uint32_t)-1) { if (id == 0 || id == (uint32_t)-1) {
uniqueIDPtr = nullptr; uniqueIDPtr = nullptr;
@@ -112,26 +105,23 @@ void macro_SetUniqueID(uint32_t id)
} }
} }
uint32_t macro_UseNewUniqueID() uint32_t macro_UseNewUniqueID() {
{
// A new ID will be generated on the first use of `\@` // A new ID will be generated on the first use of `\@`
macro_SetUniqueID(0); macro_SetUniqueID(0);
return uniqueID; return uniqueID;
} }
uint32_t macro_UndefUniqueID() uint32_t macro_UndefUniqueID() {
{
// No ID will be generated; use of `\@` is an error // No ID will be generated; use of `\@` is an error
macro_SetUniqueID((uint32_t)-1); macro_SetUniqueID((uint32_t)-1);
return uniqueID; return uniqueID;
} }
void macro_ShiftCurrentArgs(int32_t count) void macro_ShiftCurrentArgs(int32_t count) {
{
if (!macroArgs) { if (!macroArgs) {
error("Cannot shift macro arguments outside of a macro\n"); error("Cannot shift macro arguments outside of a macro\n");
} else if (size_t nbArgs = macroArgs->args.size(); } else if (size_t nbArgs = macroArgs->args.size();
count > 0 && ((uint32_t)count > nbArgs || macroArgs->shift > nbArgs - count)) { count > 0 && ((uint32_t)count > nbArgs || macroArgs->shift > nbArgs - count)) {
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end\n"); warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end\n");
macroArgs->shift = nbArgs; macroArgs->shift = nbArgs;
} else if (count < 0 && macroArgs->shift < (uint32_t)-count) { } else if (count < 0 && macroArgs->shift < (uint32_t)-count) {
@@ -142,7 +132,6 @@ void macro_ShiftCurrentArgs(int32_t count)
} }
} }
uint32_t macro_NbArgs() uint32_t macro_NbArgs() {
{
return macroArgs->args.size() - macroArgs->shift; return macroArgs->args.size() - macroArgs->shift;
} }

View File

@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "asm/main.hpp"
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <float.h> #include <float.h>
@@ -7,43 +9,43 @@
#include <limits.h> #include <limits.h>
#include <math.h> #include <math.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string>
#include <string.h> #include <string.h>
#include <string>
#include <time.h> #include <time.h>
#include "error.hpp"
#include "extern/getopt.hpp"
#include "helpers.hpp"
#include "parser.hpp"
#include "version.hpp"
#include "asm/charmap.hpp" #include "asm/charmap.hpp"
#include "asm/fixpoint.hpp" #include "asm/fixpoint.hpp"
#include "asm/format.hpp" #include "asm/format.hpp"
#include "asm/fstack.hpp" #include "asm/fstack.hpp"
#include "asm/lexer.hpp" #include "asm/lexer.hpp"
#include "asm/main.hpp"
#include "asm/opt.hpp" #include "asm/opt.hpp"
#include "asm/output.hpp" #include "asm/output.hpp"
#include "asm/rpn.hpp" #include "asm/rpn.hpp"
#include "asm/symbol.hpp" #include "asm/symbol.hpp"
#include "asm/warning.hpp" #include "asm/warning.hpp"
#include "parser.hpp"
#include "extern/getopt.hpp"
#include "helpers.hpp"
#include "error.hpp"
#include "version.hpp"
#ifdef __clang__ #ifdef __clang__
#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__) #if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
#define __SANITIZE_ADDRESS__ #define __SANITIZE_ADDRESS__
#endif // __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__) #endif // __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
#endif // __clang__ #endif // __clang__
#ifdef __SANITIZE_ADDRESS__ #ifdef __SANITIZE_ADDRESS__
// There are known, non-trivial to fix leaks. We would still like to have `make develop' // There are known, non-trivial to fix leaks. We would still like to have `make develop'
// detect memory corruption, though. // detect memory corruption, though.
extern "C" { extern "C" {
char const *__asan_default_options() { return "detect_leaks=0"; } char const *__asan_default_options() {
return "detect_leaks=0";
}
} }
#endif #endif
@@ -65,8 +67,7 @@ bool verbose;
bool warnings; // True to enable warnings, false to disable them. bool warnings; // True to enable warnings, false to disable them.
// Escapes Make-special chars from a string // Escapes Make-special chars from a string
static std::string make_escape(std::string &str) static std::string make_escape(std::string &str) {
{
std::string escaped; std::string escaped;
size_t pos = 0; size_t pos = 0;
@@ -98,54 +99,53 @@ static int depType; // Variants of `-M`
// This is because long opt matching, even to a single char, is prioritized // This is because long opt matching, even to a single char, is prioritized
// over short opt matching // over short opt matching
static option const longopts[] = { static option const longopts[] = {
{ "binary-digits", required_argument, nullptr, 'b' }, {"binary-digits", required_argument, nullptr, 'b'},
{ "define", required_argument, nullptr, 'D' }, {"define", required_argument, nullptr, 'D'},
{ "export-all", no_argument, nullptr, 'E' }, {"export-all", no_argument, nullptr, 'E'},
{ "gfx-chars", required_argument, nullptr, 'g' }, {"gfx-chars", required_argument, nullptr, 'g'},
{ "nop-after-halt", no_argument, nullptr, 'H' }, {"nop-after-halt", no_argument, nullptr, 'H'},
{ "halt-without-nop", no_argument, nullptr, 'h' }, {"halt-without-nop", no_argument, nullptr, 'h'},
{ "include", required_argument, nullptr, 'I' }, {"include", required_argument, nullptr, 'I'},
{ "preserve-ld", no_argument, nullptr, 'L' }, {"preserve-ld", no_argument, nullptr, 'L'},
{ "auto-ldh", no_argument, nullptr, 'l' }, {"auto-ldh", no_argument, nullptr, 'l'},
{ "dependfile", required_argument, nullptr, 'M' }, {"dependfile", required_argument, nullptr, 'M'},
{ "MG", no_argument, &depType, 'G' }, {"MG", no_argument, &depType, 'G'},
{ "MP", no_argument, &depType, 'P' }, {"MP", no_argument, &depType, 'P'},
{ "MT", required_argument, &depType, 'T' }, {"MT", required_argument, &depType, 'T'},
{ "warning", required_argument, nullptr, 'W' }, {"warning", required_argument, nullptr, 'W'},
{ "MQ", required_argument, &depType, 'Q' }, {"MQ", required_argument, &depType, 'Q'},
{ "output", required_argument, nullptr, 'o' }, {"output", required_argument, nullptr, 'o'},
{ "preinclude", required_argument, nullptr, 'P' }, {"preinclude", required_argument, nullptr, 'P'},
{ "pad-value", required_argument, nullptr, 'p' }, {"pad-value", required_argument, nullptr, 'p'},
{ "q-precision", required_argument, nullptr, 'Q' }, {"q-precision", required_argument, nullptr, 'Q'},
{ "recursion-depth", required_argument, nullptr, 'r' }, {"recursion-depth", required_argument, nullptr, 'r'},
{ "version", no_argument, nullptr, 'V' }, {"version", no_argument, nullptr, 'V'},
{ "verbose", no_argument, nullptr, 'v' }, {"verbose", no_argument, nullptr, 'v'},
{ "warning", required_argument, nullptr, 'W' }, {"warning", required_argument, nullptr, 'W'},
{ "max-errors", required_argument, nullptr, 'X' }, {"max-errors", required_argument, nullptr, 'X'},
{ nullptr, no_argument, nullptr, 0 } {nullptr, no_argument, nullptr, 0 }
}; };
static void printUsage() static void printUsage() {
{
fputs( fputs(
"Usage: rgbasm [-EHhLlVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n" "Usage: rgbasm [-EHhLlVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n" " [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n" " [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
" [-r depth] [-W warning] [-X max_errors] <file>\n" " [-r depth] [-W warning] [-X max_errors] <file>\n"
"Useful options:\n" "Useful options:\n"
" -E, --export-all export all labels\n" " -E, --export-all export all labels\n"
" -M, --dependfile <path> set the output dependency file\n" " -M, --dependfile <path> set the output dependency file\n"
" -o, --output <path> set the output object file\n" " -o, --output <path> set the output object file\n"
" -p, --pad-value <value> set the value to use for `ds'\n" " -p, --pad-value <value> set the value to use for `ds'\n"
" -V, --version print RGBASM version and exit\n" " -V, --version print RGBASM version and exit\n"
" -W, --warning <warning> enable or disable warnings\n" " -W, --warning <warning> enable or disable warnings\n"
"\n" "\n"
"For help, use `man rgbasm' or go to https://rgbds.gbdev.io/docs/\n", "For help, use `man rgbasm' or go to https://rgbds.gbdev.io/docs/\n",
stderr); stderr
);
} }
int main(int argc, char *argv[]) int main(int argc, char *argv[]) {
{
time_t now = time(nullptr); time_t now = time(nullptr);
char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH");
@@ -213,8 +213,9 @@ int main(int argc, char *argv[])
case 'H': case 'H':
if (warnOnHaltNop) if (warnOnHaltNop)
warning(WARNING_OBSOLETE, warning(
"Automatic `nop` after `halt` (the `-H` flag) is deprecated\n"); WARNING_OBSOLETE, "Automatic `nop` after `halt` (the `-H` flag) is deprecated\n"
);
else else
errx("`-H` and `-h` don't make sense together"); errx("`-H` and `-h` don't make sense together");
haltNop = true; haltNop = true;
@@ -240,8 +241,10 @@ int main(int argc, char *argv[])
break; break;
case 'l': case 'l':
if (warnOnLdOpt) if (warnOnLdOpt)
warning(WARNING_OBSOLETE, warning(
"Automatic `ld` to `ldh` optimization (the `-l` flag) is deprecated\n"); WARNING_OBSOLETE,
"Automatic `ld` to `ldh` optimization (the `-l` flag) is deprecated\n"
);
else else
errx("`-L` and `-l` don't make sense together"); errx("`-L` and `-l` don't make sense together");
optimizeLoads = true; optimizeLoads = true;
@@ -371,7 +374,9 @@ int main(int argc, char *argv[])
targetFileName = objectName; targetFileName = objectName;
if (argc == musl_optind) { if (argc == musl_optind) {
fputs("FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr); fputs(
"FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr
);
printUsage(); printUsage();
exit(1); exit(1);
} else if (argc != musl_optind + 1) { } else if (argc != musl_optind + 1) {
@@ -387,7 +392,8 @@ int main(int argc, char *argv[])
if (dependfile) { if (dependfile) {
if (targetFileName.empty()) if (targetFileName.empty())
errx("Dependency files can only be created if a target file is specified with either -o, -MQ or -MT"); errx("Dependency files can only be created if a target file is specified with either "
"-o, -MQ or -MT");
fprintf(dependfile, "%s: %s\n", targetFileName.c_str(), mainFileName); fprintf(dependfile, "%s: %s\n", targetFileName.c_str(), mainFileName);
} }

View File

@@ -33,59 +33,48 @@ struct OptStackEntry {
static std::stack<OptStackEntry> stack; static std::stack<OptStackEntry> stack;
void opt_B(char const chars[2]) void opt_B(char const chars[2]) {
{
lexer_SetBinDigits(chars); lexer_SetBinDigits(chars);
} }
void opt_G(char const chars[4]) void opt_G(char const chars[4]) {
{
lexer_SetGfxDigits(chars); lexer_SetGfxDigits(chars);
} }
void opt_P(uint8_t padByte) void opt_P(uint8_t padByte) {
{
fillByte = padByte; fillByte = padByte;
} }
void opt_Q(uint8_t precision) void opt_Q(uint8_t precision) {
{
fixPrecision = precision; fixPrecision = precision;
} }
void opt_R(size_t newDepth) void opt_R(size_t newDepth) {
{
fstk_NewRecursionDepth(newDepth); fstk_NewRecursionDepth(newDepth);
lexer_CheckRecursionDepth(); lexer_CheckRecursionDepth();
} }
void opt_H(bool warn) void opt_H(bool warn) {
{
warnOnHaltNop = warn; warnOnHaltNop = warn;
} }
void opt_h(bool halt) void opt_h(bool halt) {
{
haltNop = halt; haltNop = halt;
} }
void opt_L(bool optimize) void opt_L(bool optimize) {
{
optimizeLoads = optimize; optimizeLoads = optimize;
} }
void opt_l(bool warn) void opt_l(bool warn) {
{
warnOnLdOpt = warn; warnOnLdOpt = warn;
} }
void opt_W(char *flag) void opt_W(char *flag) {
{
processWarningFlag(flag); processWarningFlag(flag);
} }
void opt_Parse(char *s) void opt_Parse(char *s) {
{
switch (s[0]) { switch (s[0]) {
case 'b': case 'b':
if (strlen(&s[1]) == 2) if (strlen(&s[1]) == 2)
@@ -239,8 +228,7 @@ void opt_Parse(char *s)
} }
} }
void opt_Push() void opt_Push() {
{
OptStackEntry entry; OptStackEntry entry;
// Both of these are pulled from lexer.hpp // Both of these are pulled from lexer.hpp
@@ -273,8 +261,7 @@ void opt_Push()
stack.push(entry); stack.push(entry);
} }
void opt_Pop() void opt_Pop() {
{
if (stack.empty()) { if (stack.empty()) {
error("No entries in the option stack\n"); error("No entries in the option stack\n");
return; return;

View File

@@ -2,30 +2,31 @@
// Outputs an objectfile // Outputs an objectfile
#include "asm/output.hpp"
#include <algorithm> #include <algorithm>
#include <assert.h> #include <assert.h>
#include <deque> #include <deque>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string>
#include <string.h> #include <string.h>
#include <string>
#include <vector> #include <vector>
#include "error.hpp"
#include "linkdefs.hpp"
#include "asm/charmap.hpp" #include "asm/charmap.hpp"
#include "asm/fstack.hpp" #include "asm/fstack.hpp"
#include "asm/main.hpp" #include "asm/main.hpp"
#include "asm/output.hpp"
#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/warning.hpp" #include "asm/warning.hpp"
#include "error.hpp"
#include "linkdefs.hpp"
struct Assertion { struct Assertion {
Patch patch; Patch patch;
Section *section; Section *section;
@@ -42,8 +43,7 @@ static std::deque<Assertion> assertions;
static std::deque<FileStackNode *> fileStackNodes; static std::deque<FileStackNode *> fileStackNodes;
// Write a long to a file (little-endian) // Write a long to a file (little-endian)
static void putlong(uint32_t i, FILE *f) static void putlong(uint32_t i, FILE *f) {
{
putc(i, f); putc(i, f);
putc(i >> 8, f); putc(i >> 8, f);
putc(i >> 16, f); putc(i >> 16, f);
@@ -51,15 +51,13 @@ static void putlong(uint32_t i, FILE *f)
} }
// Write a NUL-terminated string to a file // Write a NUL-terminated string to a file
static void putstring(char const *s, FILE *f) static void putstring(char const *s, FILE *f) {
{
while (*s) while (*s)
putc(*s++, f); putc(*s++, f);
putc(0, f); putc(0, f);
} }
void out_RegisterNode(FileStackNode *node) void out_RegisterNode(FileStackNode *node) {
{
// If node is not already registered, register it (and parents), and give it a unique ID // If node is not already registered, register it (and parents), and give it a unique ID
for (; node && node->ID == (uint32_t)-1; node = node->parent) { for (; node && node->ID == (uint32_t)-1; node = node->parent) {
node->ID = fileStackNodes.size(); node->ID = fileStackNodes.size();
@@ -67,8 +65,7 @@ void out_RegisterNode(FileStackNode *node)
} }
} }
void out_ReplaceNode(FileStackNode * /* node */) void out_ReplaceNode(FileStackNode * /* node */) {
{
#if 0 #if 0
This is code intended to replace a node, which is pretty useless until ref counting is added... This is code intended to replace a node, which is pretty useless until ref counting is added...
@@ -84,8 +81,7 @@ This is code intended to replace a node, which is pretty useless until ref count
} }
// Return a section's ID, or -1 if the section is not in the list // Return a section's ID, or -1 if the section is not in the list
static uint32_t getSectIDIfAny(Section *sect) static uint32_t getSectIDIfAny(Section *sect) {
{
if (!sect) if (!sect)
return (uint32_t)-1; return (uint32_t)-1;
@@ -98,8 +94,7 @@ static uint32_t getSectIDIfAny(Section *sect)
} }
// Write a patch to a file // Write a patch to a file
static void writepatch(Patch const &patch, FILE *f) static void writepatch(Patch const &patch, FILE *f) {
{
assert(patch.src->ID != (uint32_t)-1); assert(patch.src->ID != (uint32_t)-1);
putlong(patch.src->ID, f); putlong(patch.src->ID, f);
putlong(patch.lineNo, f); putlong(patch.lineNo, f);
@@ -112,8 +107,7 @@ static void writepatch(Patch const &patch, FILE *f)
} }
// Write a section to a file // Write a section to a file
static void writesection(Section const &sect, FILE *f) static void writesection(Section const &sect, FILE *f) {
{
putstring(sect.name.c_str(), f); putstring(sect.name.c_str(), f);
putlong(sect.size, f); putlong(sect.size, f);
@@ -138,8 +132,7 @@ static void writesection(Section const &sect, FILE *f)
} }
// Write a symbol to a file // Write a symbol to a file
static void writesymbol(Symbol const &sym, FILE *f) static void writesymbol(Symbol const &sym, FILE *f) {
{
putstring(sym.name, f); putstring(sym.name, f);
if (!sym.isDefined()) { if (!sym.isDefined()) {
putc(SYMTYPE_IMPORT, f); putc(SYMTYPE_IMPORT, f);
@@ -154,8 +147,7 @@ static void writesymbol(Symbol const &sym, FILE *f)
} }
} }
static void registerSymbol(Symbol &sym) static void registerSymbol(Symbol &sym) {
{
sym.ID = objectSymbols.size(); sym.ID = objectSymbols.size();
objectSymbols.push_back(&sym); objectSymbols.push_back(&sym);
out_RegisterNode(sym.src); out_RegisterNode(sym.src);
@@ -163,19 +155,17 @@ static void registerSymbol(Symbol &sym)
// Returns a symbol's ID within the object file // Returns a symbol's ID within the object file
// If the symbol does not have one, one is assigned by registering the symbol // If the symbol does not have one, one is assigned by registering the symbol
static uint32_t getSymbolID(Symbol &sym) static uint32_t getSymbolID(Symbol &sym) {
{
if (sym.ID == (uint32_t)-1 && !sym_IsPC(&sym)) if (sym.ID == (uint32_t)-1 && !sym_IsPC(&sym))
registerSymbol(sym); registerSymbol(sym);
return sym.ID; return sym.ID;
} }
static void writerpn(std::vector<uint8_t> &rpnexpr, const std::vector<uint8_t> &rpn) static void writerpn(std::vector<uint8_t> &rpnexpr, const std::vector<uint8_t> &rpn) {
{
char symName[512]; char symName[512];
size_t rpnptr = 0; size_t rpnptr = 0;
for (size_t offset = 0; offset < rpn.size(); ) { for (size_t offset = 0; offset < rpn.size();) {
uint8_t rpndata = rpn[offset++]; uint8_t rpndata = rpn[offset++];
switch (rpndata) { switch (rpndata) {
@@ -262,8 +252,7 @@ static void writerpn(std::vector<uint8_t> &rpnexpr, const std::vector<uint8_t> &
} }
} }
static void initpatch(Patch &patch, uint32_t type, Expression const &expr, uint32_t ofs) static void initpatch(Patch &patch, uint32_t type, Expression const &expr, uint32_t ofs) {
{
FileStackNode *node = fstk_GetFileStack(); FileStackNode *node = fstk_GetFileStack();
patch.type = type; patch.type = type;
@@ -290,8 +279,7 @@ static void initpatch(Patch &patch, uint32_t type, Expression const &expr, uint3
} }
// Create a new patch (includes the rpn expr) // Create a new patch (includes the rpn expr)
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift) void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift) {
{
// Add the patch to the list // Add the patch to the list
Patch &patch = currentSection->patches.emplace_front(); Patch &patch = currentSection->patches.emplace_front();
@@ -304,22 +292,21 @@ void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32
} }
// Creates an assert that will be written to the object file // Creates an assert that will be written to the object file
void out_CreateAssert(enum AssertionType type, Expression const &expr, char const *message, uint32_t ofs) void out_CreateAssert(
{ enum AssertionType type, Expression const &expr, char const *message, uint32_t ofs
) {
Assertion &assertion = assertions.emplace_front(); Assertion &assertion = assertions.emplace_front();
initpatch(assertion.patch, type, expr, ofs); initpatch(assertion.patch, type, expr, ofs);
assertion.message = message; assertion.message = message;
} }
static void writeassert(Assertion &assert, FILE *f) static void writeassert(Assertion &assert, FILE *f) {
{
writepatch(assert.patch, f); writepatch(assert.patch, f);
putstring(assert.message.c_str(), f); putstring(assert.message.c_str(), f);
} }
static void writeFileStackNode(FileStackNode const &node, FILE *f) static void writeFileStackNode(FileStackNode const &node, FILE *f) {
{
putlong(node.parent ? node.parent->ID : (uint32_t)-1, f); putlong(node.parent ? node.parent->ID : (uint32_t)-1, f);
putlong(node.lineNo, f); putlong(node.lineNo, f);
putc(node.type, f); putc(node.type, f);
@@ -330,13 +317,12 @@ static void writeFileStackNode(FileStackNode const &node, FILE *f)
putlong(nodeIters.size(), f); putlong(nodeIters.size(), f);
// Iters are stored by decreasing depth, so reverse the order for output // Iters are stored by decreasing depth, so reverse the order for output
for (uint32_t i = nodeIters.size(); i--; ) for (uint32_t i = nodeIters.size(); i--;)
putlong(nodeIters[i], f); putlong(nodeIters[i], f);
} }
} }
static void registerUnregisteredSymbol(Symbol &sym) static void registerUnregisteredSymbol(Symbol &sym) {
{
// Check for symbol->src, to skip any built-in symbol from rgbasm // Check for symbol->src, to skip any built-in symbol from rgbasm
if (sym.src && sym.ID == (uint32_t)-1) { if (sym.src && sym.ID == (uint32_t)-1) {
registerSymbol(sym); registerSymbol(sym);
@@ -344,8 +330,7 @@ static void registerUnregisteredSymbol(Symbol &sym)
} }
// Write an objectfile // Write an objectfile
void out_WriteObject() void out_WriteObject() {
{
FILE *f; FILE *f;
if (strcmp(objectName, "-")) { if (strcmp(objectName, "-")) {
@@ -374,9 +359,12 @@ void out_WriteObject()
// The list is supposed to have decrementing IDs // The list is supposed to have decrementing IDs
if (it + 1 != fileStackNodes.end() && it[1]->ID != node->ID - 1) if (it + 1 != fileStackNodes.end() && it[1]->ID != node->ID - 1)
fatalerror("Internal error: fstack node #%" PRIu32 " follows #%" PRIu32 fatalerror(
". Please report this to the developers!\n", "Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
it[1]->ID, node->ID); ". Please report this to the developers!\n",
it[1]->ID,
node->ID
);
} }
for (Symbol const *sym : objectSymbols) for (Symbol const *sym : objectSymbols)
@@ -394,8 +382,7 @@ void out_WriteObject()
} }
// Set the objectfilename // Set the objectfilename
void out_SetFileName(char *s) void out_SetFileName(char *s) {
{
if (objectName) if (objectName)
warnx("Overriding output filename %s", objectName); warnx("Overriding output filename %s", objectName);
objectName = s; objectName = s;

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,8 @@
// Controls RPN expressions for objectfiles // Controls RPN expressions for objectfiles
#include "asm/rpn.hpp"
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
@@ -12,18 +14,16 @@
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include "opmath.hpp"
#include "asm/main.hpp" #include "asm/main.hpp"
#include "asm/output.hpp" #include "asm/output.hpp"
#include "asm/rpn.hpp"
#include "asm/section.hpp" #include "asm/section.hpp"
#include "asm/symbol.hpp" #include "asm/symbol.hpp"
#include "asm/warning.hpp" #include "asm/warning.hpp"
#include "opmath.hpp"
// Init a RPN expression // Init a RPN expression
static void initExpression(Expression &expr) static void initExpression(Expression &expr) {
{
expr.reason = nullptr; expr.reason = nullptr;
expr.isKnown = true; expr.isKnown = true;
expr.isSymbol = false; expr.isSymbol = false;
@@ -33,8 +33,7 @@ static void initExpression(Expression &expr)
// Makes an expression "not known", also setting its error message // Makes an expression "not known", also setting its error message
template<typename... Ts> template<typename... Ts>
static void makeUnknown(Expression &expr, Ts ...parts) static void makeUnknown(Expression &expr, Ts... parts) {
{
expr.isKnown = false; expr.isKnown = false;
expr.reason = new std::string(); expr.reason = new std::string();
if (!expr.reason) if (!expr.reason)
@@ -42,10 +41,9 @@ static void makeUnknown(Expression &expr, Ts ...parts)
(expr.reason->append(parts), ...); (expr.reason->append(parts), ...);
} }
static uint8_t *reserveSpace(Expression &expr, uint32_t size) static uint8_t *reserveSpace(Expression &expr, uint32_t size) {
{
if (!expr.rpn) { if (!expr.rpn) {
expr.rpn = new(std::nothrow) std::vector<uint8_t>(); expr.rpn = new (std::nothrow) std::vector<uint8_t>();
if (!expr.rpn) if (!expr.rpn)
fatalerror("Failed to allocate RPN expression: %s\n", strerror(errno)); fatalerror("Failed to allocate RPN expression: %s\n", strerror(errno));
} }
@@ -57,22 +55,19 @@ static uint8_t *reserveSpace(Expression &expr, uint32_t size)
} }
// Free the RPN expression // Free the RPN expression
void rpn_Free(Expression &expr) void rpn_Free(Expression &expr) {
{
delete expr.rpn; delete expr.rpn;
delete expr.reason; delete expr.reason;
initExpression(expr); initExpression(expr);
} }
// Add symbols, constants and operators to expression // Add symbols, constants and operators to expression
void rpn_Number(Expression &expr, uint32_t val) void rpn_Number(Expression &expr, uint32_t val) {
{
initExpression(expr); initExpression(expr);
expr.val = val; expr.val = val;
} }
void rpn_Symbol(Expression &expr, char const *symName) void rpn_Symbol(Expression &expr, char const *symName) {
{
Symbol *sym = sym_FindScopedSymbol(symName); Symbol *sym = sym_FindScopedSymbol(symName);
if (sym_IsPC(sym) && !sect_GetSymbolSection()) { if (sym_IsPC(sym) && !sect_GetSymbolSection()) {
@@ -98,8 +93,7 @@ void rpn_Symbol(Expression &expr, char const *symName)
} }
} }
static void bankSelf(Expression &expr) static void bankSelf(Expression &expr) {
{
initExpression(expr); initExpression(expr);
if (!currentSection) { if (!currentSection) {
@@ -114,8 +108,7 @@ static void bankSelf(Expression &expr)
} }
} }
void rpn_BankSymbol(Expression &expr, char const *symName) void rpn_BankSymbol(Expression &expr, char const *symName) {
{
Symbol const *sym = sym_FindScopedSymbol(symName); Symbol const *sym = sym_FindScopedSymbol(symName);
// The @ symbol is treated differently. // The @ symbol is treated differently.
@@ -147,8 +140,7 @@ void rpn_BankSymbol(Expression &expr, char const *symName)
} }
} }
void rpn_BankSection(Expression &expr, char const *sectionName) void rpn_BankSection(Expression &expr, char const *sectionName) {
{
initExpression(expr); initExpression(expr);
Section *section = sect_FindSectionByName(sectionName); Section *section = sect_FindSectionByName(sectionName);
@@ -167,8 +159,7 @@ void rpn_BankSection(Expression &expr, char const *sectionName)
} }
} }
void rpn_SizeOfSection(Expression &expr, char const *sectionName) void rpn_SizeOfSection(Expression &expr, char const *sectionName) {
{
initExpression(expr); initExpression(expr);
Section *section = sect_FindSectionByName(sectionName); Section *section = sect_FindSectionByName(sectionName);
@@ -187,8 +178,7 @@ void rpn_SizeOfSection(Expression &expr, char const *sectionName)
} }
} }
void rpn_StartOfSection(Expression &expr, char const *sectionName) void rpn_StartOfSection(Expression &expr, char const *sectionName) {
{
initExpression(expr); initExpression(expr);
Section *section = sect_FindSectionByName(sectionName); Section *section = sect_FindSectionByName(sectionName);
@@ -207,8 +197,7 @@ void rpn_StartOfSection(Expression &expr, char const *sectionName)
} }
} }
void rpn_SizeOfSectionType(Expression &expr, enum SectionType type) void rpn_SizeOfSectionType(Expression &expr, enum SectionType type) {
{
initExpression(expr); initExpression(expr);
makeUnknown(expr, "Section type's size is not known"); makeUnknown(expr, "Section type's size is not known");
@@ -219,8 +208,7 @@ void rpn_SizeOfSectionType(Expression &expr, enum SectionType type)
*ptr++ = type; *ptr++ = type;
} }
void rpn_StartOfSectionType(Expression &expr, enum SectionType type) void rpn_StartOfSectionType(Expression &expr, enum SectionType type) {
{
initExpression(expr); initExpression(expr);
makeUnknown(expr, "Section type's start is not known"); makeUnknown(expr, "Section type's start is not known");
@@ -231,8 +219,7 @@ void rpn_StartOfSectionType(Expression &expr, enum SectionType type)
*ptr++ = type; *ptr++ = type;
} }
void rpn_CheckHRAM(Expression &expr, const Expression &src) void rpn_CheckHRAM(Expression &expr, const Expression &src) {
{
expr = src; expr = src;
expr.isSymbol = false; expr.isSymbol = false;
@@ -247,8 +234,7 @@ void rpn_CheckHRAM(Expression &expr, const Expression &src)
} }
} }
void rpn_CheckRST(Expression &expr, const Expression &src) void rpn_CheckRST(Expression &expr, const Expression &src) {
{
expr = src; expr = src;
if (expr.isKnown) { if (expr.isKnown) {
@@ -264,9 +250,8 @@ void rpn_CheckRST(Expression &expr, const Expression &src)
} }
// Checks that an RPN expression's value fits within N bits (signed or unsigned) // Checks that an RPN expression's value fits within N bits (signed or unsigned)
void rpn_CheckNBit(Expression const &expr, uint8_t n) void rpn_CheckNBit(Expression const &expr, uint8_t n) {
{ assert(n != 0); // That doesn't make sense
assert(n != 0); // That doesn't make sense
assert(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB assert(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
if (expr.isKnown) { if (expr.isKnown) {
@@ -279,8 +264,7 @@ void rpn_CheckNBit(Expression const &expr, uint8_t n)
} }
} }
int32_t Expression::getConstVal() const int32_t Expression::getConstVal() const {
{
if (!isKnown) { if (!isKnown) {
error("Expected constant expression: %s\n", reason->c_str()); error("Expected constant expression: %s\n", reason->c_str());
return 0; return 0;
@@ -288,8 +272,7 @@ int32_t Expression::getConstVal() const
return val; return val;
} }
void rpn_LOGNOT(Expression &expr, const Expression &src) void rpn_LOGNOT(Expression &expr, const Expression &src) {
{
expr = src; expr = src;
expr.isSymbol = false; expr.isSymbol = false;
@@ -301,15 +284,13 @@ void rpn_LOGNOT(Expression &expr, const Expression &src)
} }
} }
Symbol const *Expression::symbolOf() const Symbol const *Expression::symbolOf() const {
{
if (!isSymbol) if (!isSymbol)
return nullptr; return nullptr;
return sym_FindScopedSymbol((char const *)&(*rpn)[1]); return sym_FindScopedSymbol((char const *)&(*rpn)[1]);
} }
bool Expression::isDiffConstant(Symbol const *sym) const bool Expression::isDiffConstant(Symbol const *sym) const {
{
// Check if both expressions only refer to a single symbol // Check if both expressions only refer to a single symbol
Symbol const *sym1 = symbolOf(); Symbol const *sym1 = symbolOf();
@@ -328,8 +309,7 @@ bool Expression::isDiffConstant(Symbol const *sym) const
* *
* @return The constant result if it can be computed, or -1 otherwise. * @return The constant result if it can be computed, or -1 otherwise.
*/ */
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
{
Symbol const *lhsSymbol = lhs.symbolOf(); Symbol const *lhsSymbol = lhs.symbolOf();
Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf(); Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf();
bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection(); bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection();
@@ -362,8 +342,9 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs)
return (symbolOfs + sect.alignOfs) & ~unknownBits; return (symbolOfs + sect.alignOfs) & ~unknownBits;
} }
void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1, const Expression &src2) void rpn_BinaryOp(
{ enum RPNCommand op, Expression &expr, const Expression &src1, const Expression &src2
) {
expr.isSymbol = false; expr.isSymbol = false;
int32_t constMaskVal; int32_t constMaskVal;
@@ -417,43 +398,47 @@ void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1,
break; break;
case RPN_SHL: case RPN_SHL:
if (src2.val < 0) if (src2.val < 0)
warning(WARNING_SHIFT_AMOUNT, warning(
"Shifting left by negative amount %" PRId32 "\n", WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", src2.val
src2.val); );
if (src2.val >= 32) if (src2.val >= 32)
warning(WARNING_SHIFT_AMOUNT, warning(
"Shifting left by large amount %" PRId32 "\n", src2.val); WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", src2.val
);
expr.val = op_shift_left(src1.val, src2.val); expr.val = op_shift_left(src1.val, src2.val);
break; break;
case RPN_SHR: case RPN_SHR:
if (src1.val < 0) if (src1.val < 0)
warning(WARNING_SHIFT, warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", src1.val);
"Shifting right negative value %" PRId32 "\n", src1.val);
if (src2.val < 0) if (src2.val < 0)
warning(WARNING_SHIFT_AMOUNT, warning(
"Shifting right by negative amount %" PRId32 "\n", WARNING_SHIFT_AMOUNT,
src2.val); "Shifting right by negative amount %" PRId32 "\n",
src2.val
);
if (src2.val >= 32) if (src2.val >= 32)
warning(WARNING_SHIFT_AMOUNT, warning(
"Shifting right by large amount %" PRId32 "\n", WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", src2.val
src2.val); );
expr.val = op_shift_right(src1.val, src2.val); expr.val = op_shift_right(src1.val, src2.val);
break; break;
case RPN_USHR: case RPN_USHR:
if (src2.val < 0) if (src2.val < 0)
warning(WARNING_SHIFT_AMOUNT, warning(
"Shifting right by negative amount %" PRId32 "\n", WARNING_SHIFT_AMOUNT,
src2.val); "Shifting right by negative amount %" PRId32 "\n",
src2.val
);
if (src2.val >= 32) if (src2.val >= 32)
warning(WARNING_SHIFT_AMOUNT, warning(
"Shifting right by large amount %" PRId32 "\n", WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", src2.val
src2.val); );
expr.val = op_shift_right_unsigned(src1.val, src2.val); expr.val = op_shift_right_unsigned(src1.val, src2.val);
break; break;
@@ -465,9 +450,12 @@ void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1,
fatalerror("Division by zero\n"); fatalerror("Division by zero\n");
if (src1.val == INT32_MIN && src2.val == -1) { if (src1.val == INT32_MIN && src2.val == -1) {
warning(WARNING_DIV, warning(
"Division of %" PRId32 " by -1 yields %" PRId32 "\n", WARNING_DIV,
INT32_MIN, INT32_MIN); "Division of %" PRId32 " by -1 yields %" PRId32 "\n",
INT32_MIN,
INT32_MIN
);
expr.val = INT32_MIN; expr.val = INT32_MIN;
} else { } else {
expr.val = op_divide(src1.val, src2.val); expr.val = op_divide(src1.val, src2.val);
@@ -521,8 +509,13 @@ void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1,
// Convert the left-hand expression if it's constant // Convert the left-hand expression if it's constant
if (src1.isKnown) { if (src1.isKnown) {
uint32_t lval = src1.val; uint32_t lval = src1.val;
uint8_t bytes[] = {RPN_CONST, (uint8_t)lval, (uint8_t)(lval >> 8), uint8_t bytes[] = {
(uint8_t)(lval >> 16), (uint8_t)(lval >> 24)}; RPN_CONST,
(uint8_t)lval,
(uint8_t)(lval >> 8),
(uint8_t)(lval >> 16),
(uint8_t)(lval >> 24),
};
expr.rpnPatchSize = sizeof(bytes); expr.rpnPatchSize = sizeof(bytes);
expr.rpn = nullptr; expr.rpn = nullptr;
memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes)); memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes));
@@ -545,8 +538,13 @@ void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1,
// If the right expression is constant, merge a shim instead // If the right expression is constant, merge a shim instead
uint32_t rval = src2.val; uint32_t rval = src2.val;
uint8_t bytes[] = {RPN_CONST, (uint8_t)rval, (uint8_t)(rval >> 8), uint8_t bytes[] = {
(uint8_t)(rval >> 16), (uint8_t)(rval >> 24)}; RPN_CONST,
(uint8_t)rval,
(uint8_t)(rval >> 8),
(uint8_t)(rval >> 16),
(uint8_t)(rval >> 24),
};
if (src2.isKnown) { if (src2.isKnown) {
ptr = bytes; ptr = bytes;
len = sizeof(bytes); len = sizeof(bytes);
@@ -569,23 +567,20 @@ void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1,
} }
} }
void rpn_HIGH(Expression &expr, const Expression &src) void rpn_HIGH(Expression &expr, const Expression &src) {
{
expr = src; expr = src;
expr.isSymbol = false; expr.isSymbol = false;
if (expr.isKnown) { if (expr.isKnown) {
expr.val = (uint32_t)expr.val >> 8 & 0xFF; expr.val = (uint32_t)expr.val >> 8 & 0xFF;
} else { } else {
uint8_t bytes[] = {RPN_CONST, 8, 0, 0, 0, RPN_SHR, uint8_t bytes[] = {RPN_CONST, 8, 0, 0, 0, RPN_SHR, RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
expr.rpnPatchSize += sizeof(bytes); expr.rpnPatchSize += sizeof(bytes);
memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes)); memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes));
} }
} }
void rpn_LOW(Expression &expr, const Expression &src) void rpn_LOW(Expression &expr, const Expression &src) {
{
expr = src; expr = src;
expr.isSymbol = false; expr.isSymbol = false;
@@ -599,16 +594,14 @@ void rpn_LOW(Expression &expr, const Expression &src)
} }
} }
void rpn_ISCONST(Expression &expr, const Expression &src) void rpn_ISCONST(Expression &expr, const Expression &src) {
{
initExpression(expr); initExpression(expr);
expr.val = src.isKnown; expr.val = src.isKnown;
expr.isKnown = true; expr.isKnown = true;
expr.isSymbol = false; expr.isSymbol = false;
} }
void rpn_NEG(Expression &expr, const Expression &src) void rpn_NEG(Expression &expr, const Expression &src) {
{
expr = src; expr = src;
expr.isSymbol = false; expr.isSymbol = false;
@@ -620,8 +613,7 @@ void rpn_NEG(Expression &expr, const Expression &src)
} }
} }
void rpn_NOT(Expression &expr, const Expression &src) void rpn_NOT(Expression &expr, const Expression &src) {
{
expr = src; expr = src;
expr.isSymbol = false; expr.isSymbol = false;

View File

@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "asm/section.hpp"
#include <algorithm> #include <algorithm>
#include <assert.h> #include <assert.h>
#include <deque> #include <deque>
@@ -9,21 +11,20 @@
#include <stack> #include <stack>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string>
#include <string.h> #include <string.h>
#include <string>
#include <vector> #include <vector>
#include "error.hpp"
#include "linkdefs.hpp"
#include "asm/fstack.hpp" #include "asm/fstack.hpp"
#include "asm/main.hpp" #include "asm/main.hpp"
#include "asm/output.hpp" #include "asm/output.hpp"
#include "asm/rpn.hpp" #include "asm/rpn.hpp"
#include "asm/section.hpp"
#include "asm/symbol.hpp" #include "asm/symbol.hpp"
#include "asm/warning.hpp" #include "asm/warning.hpp"
#include "error.hpp"
#include "linkdefs.hpp"
uint8_t fillByte; uint8_t fillByte;
struct UnionStackEntry { struct UnionStackEntry {
@@ -50,8 +51,7 @@ char const *currentLoadScope = nullptr;
int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset) int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
// A quick check to see if we have an initialized section // A quick check to see if we have an initialized section
attr_(warn_unused_result) static bool checksection() attr_(warn_unused_result) static bool checksection() {
{
if (currentSection) if (currentSection)
return true; return true;
@@ -61,35 +61,38 @@ attr_(warn_unused_result) static bool checksection()
// A quick check to see if we have an initialized section that can contain // A quick check to see if we have an initialized section that can contain
// this much initialized data // this much initialized data
attr_(warn_unused_result) static bool checkcodesection() attr_(warn_unused_result) static bool checkcodesection() {
{
if (!checksection()) if (!checksection())
return false; return false;
if (sect_HasData(currentSection->type)) if (sect_HasData(currentSection->type))
return true; return true;
error("Section '%s' cannot contain code or data (not ROM0 or ROMX)\n", error(
currentSection->name.c_str()); "Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
currentSection->name.c_str()
);
return false; return false;
} }
attr_(warn_unused_result) static bool checkSectionSize(Section const &sect, uint32_t size) attr_(warn_unused_result) static bool checkSectionSize(Section const &sect, uint32_t size) {
{
uint32_t maxSize = sectionTypeInfo[sect.type].size; uint32_t maxSize = sectionTypeInfo[sect.type].size;
// If the new size is reasonable, keep going // If the new size is reasonable, keep going
if (size <= maxSize) if (size <= maxSize)
return true; return true;
error("Section '%s' grew too big (max size = 0x%" PRIX32 error(
" bytes, reached 0x%" PRIX32 ").\n", sect.name.c_str(), maxSize, size); "Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32 ").\n",
sect.name.c_str(),
maxSize,
size
);
return false; return false;
} }
// Check if the section has grown too much. // Check if the section has grown too much.
attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size) attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size) {
{
// This check is here to trap broken code that generates sections that are too big and to // This check is here to trap broken code that generates sections that are too big and to
// prevent the assembler from generating huge object files or trying to allocate too much // prevent the assembler from generating huge object files or trying to allocate too much
// memory. // memory.
@@ -97,20 +100,19 @@ attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size)
// If the section has already overflowed, skip the check to avoid erroring out ad nauseam // If the section has already overflowed, skip the check to avoid erroring out ad nauseam
if (currentSection->size != UINT32_MAX if (currentSection->size != UINT32_MAX
&& !checkSectionSize(*currentSection, curOffset + loadOffset + delta_size)) && !checkSectionSize(*currentSection, curOffset + loadOffset + delta_size))
// Mark the section as overflowed, to avoid repeating the error // Mark the section as overflowed, to avoid repeating the error
currentSection->size = UINT32_MAX; currentSection->size = UINT32_MAX;
if (currentLoadSection && currentLoadSection->size != UINT32_MAX if (currentLoadSection && currentLoadSection->size != UINT32_MAX
&& !checkSectionSize(*currentLoadSection, curOffset + delta_size)) && !checkSectionSize(*currentLoadSection, curOffset + delta_size))
currentLoadSection->size = UINT32_MAX; currentLoadSection->size = UINT32_MAX;
return currentSection->size != UINT32_MAX return currentSection->size != UINT32_MAX
&& (!currentLoadSection || currentLoadSection->size != UINT32_MAX); && (!currentLoadSection || currentLoadSection->size != UINT32_MAX);
} }
Section *sect_FindSectionByName(char const *name) Section *sect_FindSectionByName(char const *name) {
{
for (Section &sect : sectionList) { for (Section &sect : sectionList) {
if (sect.name == name) if (sect.name == name)
return &sect; return &sect;
@@ -119,14 +121,15 @@ Section *sect_FindSectionByName(char const *name)
} }
#define mask(align) ((1U << (align)) - 1) #define mask(align) ((1U << (align)) - 1)
#define fail(...) do { \ #define fail(...) \
error(__VA_ARGS__); \ do { \
nbSectErrors++; \ error(__VA_ARGS__); \
} while (0) nbSectErrors++; \
} while (0)
static unsigned int mergeSectUnion(Section &sect, enum SectionType type, uint32_t org, static unsigned int mergeSectUnion(
uint8_t alignment, uint16_t alignOffset) Section &sect, enum SectionType type, uint32_t org, uint8_t alignment, uint16_t alignOffset
{ ) {
assert(alignment < 16); // Should be ensured by the caller assert(alignment < 16); // Should be ensured by the caller
unsigned int nbSectErrors = 0; unsigned int nbSectErrors = 0;
@@ -138,11 +141,15 @@ static unsigned int mergeSectUnion(Section &sect, enum SectionType type, uint32_
if (org != (uint32_t)-1) { if (org != (uint32_t)-1) {
// If both are fixed, they must be the same // If both are fixed, they must be the same
if (sect.org != (uint32_t)-1 && sect.org != org) if (sect.org != (uint32_t)-1 && sect.org != org)
fail("Section already declared as fixed at different address $%04" fail(
PRIx32 "\n", sect.org); "Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org
);
else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs))) else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs)))
fail("Section already declared as aligned to %u bytes (offset %" fail(
PRIu16 ")\n", 1U << sect.align, sect.alignOfs); "Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
1U << sect.align,
sect.alignOfs
);
else else
// Otherwise, just override // Otherwise, just override
sect.org = org; sect.org = org;
@@ -151,13 +158,18 @@ static unsigned int mergeSectUnion(Section &sect, enum SectionType type, uint32_
// Make sure any fixed address given is compatible // Make sure any fixed address given is compatible
if (sect.org != (uint32_t)-1) { if (sect.org != (uint32_t)-1) {
if ((sect.org - alignOffset) & mask(alignment)) if ((sect.org - alignOffset) & mask(alignment))
fail("Section already declared as fixed at incompatible address $%04" fail(
PRIx32 "\n", sect.org); "Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
// Check if alignment offsets are compatible sect.org
);
// Check if alignment offsets are compatible
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) { } else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
fail("Section already declared with incompatible %u" fail(
"-byte alignment (offset %" PRIu16 ")\n", "Section already declared with incompatible %u"
1U << sect.align, sect.alignOfs); "-byte alignment (offset %" PRIu16 ")\n",
1U << sect.align,
sect.alignOfs
);
} else if (alignment > sect.align) { } else if (alignment > sect.align) {
// If the section is not fixed, its alignment is the largest of both // If the section is not fixed, its alignment is the largest of both
sect.align = alignment; sect.align = alignment;
@@ -168,8 +180,8 @@ static unsigned int mergeSectUnion(Section &sect, enum SectionType type, uint32_
return nbSectErrors; return nbSectErrors;
} }
static unsigned int mergeFragments(Section &sect, uint32_t org, uint8_t alignment, uint16_t alignOffset) static unsigned int
{ mergeFragments(Section &sect, uint32_t org, uint8_t alignment, uint16_t alignOffset) {
assert(alignment < 16); // Should be ensured by the caller assert(alignment < 16); // Should be ensured by the caller
unsigned int nbSectErrors = 0; unsigned int nbSectErrors = 0;
@@ -181,11 +193,16 @@ static unsigned int mergeFragments(Section &sect, uint32_t org, uint8_t alignmen
// If both are fixed, they must be the same // If both are fixed, they must be the same
if (sect.org != (uint32_t)-1 && sect.org != curOrg) if (sect.org != (uint32_t)-1 && sect.org != curOrg)
fail("Section already declared as fixed at incompatible address $%04" fail(
PRIx32 "\n", sect.org); "Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
sect.org
);
else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs))) else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs)))
fail("Section already declared as aligned to %u bytes (offset %" fail(
PRIu16 ")\n", 1U << sect.align, sect.alignOfs); "Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
1U << sect.align,
sect.alignOfs
);
else else
// Otherwise, just override // Otherwise, just override
sect.org = curOrg; sect.org = curOrg;
@@ -199,13 +216,18 @@ static unsigned int mergeFragments(Section &sect, uint32_t org, uint8_t alignmen
// Make sure any fixed address given is compatible // Make sure any fixed address given is compatible
if (sect.org != (uint32_t)-1) { if (sect.org != (uint32_t)-1) {
if ((sect.org - curOfs) & mask(alignment)) if ((sect.org - curOfs) & mask(alignment))
fail("Section already declared as fixed at incompatible address $%04" fail(
PRIx32 "\n", sect.org); "Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
// Check if alignment offsets are compatible sect.org
);
// Check if alignment offsets are compatible
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) { } else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
fail("Section already declared with incompatible %u" fail(
"-byte alignment (offset %" PRIu16 ")\n", "Section already declared with incompatible %u"
1U << sect.align, sect.alignOfs); "-byte alignment (offset %" PRIu16 ")\n",
1U << sect.align,
sect.alignOfs
);
} else if (alignment > sect.align) { } else if (alignment > sect.align) {
// If the section is not fixed, its alignment is the largest of both // If the section is not fixed, its alignment is the largest of both
sect.align = alignment; sect.align = alignment;
@@ -216,9 +238,15 @@ static unsigned int mergeFragments(Section &sect, uint32_t org, uint8_t alignmen
return nbSectErrors; return nbSectErrors;
} }
static void mergeSections(Section &sect, enum SectionType type, uint32_t org, uint32_t bank, static void mergeSections(
uint8_t alignment, uint16_t alignOffset, enum SectionModifier mod) Section &sect,
{ enum SectionType type,
uint32_t org,
uint32_t bank,
uint8_t alignment,
uint16_t alignOffset,
enum SectionModifier mod
) {
unsigned int nbSectErrors = 0; unsigned int nbSectErrors = 0;
if (type != sect.type) if (type != sect.type)
@@ -230,9 +258,9 @@ static void mergeSections(Section &sect, enum SectionType type, uint32_t org, ui
switch (mod) { switch (mod) {
case SECTION_UNION: case SECTION_UNION:
case SECTION_FRAGMENT: case SECTION_FRAGMENT:
nbSectErrors += mod == SECTION_UNION ? nbSectErrors += mod == SECTION_UNION
mergeSectUnion(sect, type, org, alignment, alignOffset) : ? mergeSectUnion(sect, type, org, alignment, alignOffset)
mergeFragments(sect, org, alignment, alignOffset); : mergeFragments(sect, org, alignment, alignOffset);
// Common checks // Common checks
@@ -241,8 +269,7 @@ static void mergeSections(Section &sect, enum SectionType type, uint32_t org, ui
sect.bank = bank; sect.bank = bank;
// If both specify a bank, it must be the same one // If both specify a bank, it must be the same one
else if (bank != (uint32_t)-1 && sect.bank != bank) else if (bank != (uint32_t)-1 && sect.bank != bank)
fail("Section already declared with different bank %" PRIu32 "\n", fail("Section already declared with different bank %" PRIu32 "\n", sect.bank);
sect.bank);
break; break;
case SECTION_NORMAL: case SECTION_NORMAL:
@@ -254,16 +281,26 @@ static void mergeSections(Section &sect, enum SectionType type, uint32_t org, ui
} }
if (nbSectErrors) if (nbSectErrors)
fatalerror("Cannot create section \"%s\" (%u error%s)\n", fatalerror(
sect.name.c_str(), nbSectErrors, nbSectErrors == 1 ? "" : "s"); "Cannot create section \"%s\" (%u error%s)\n",
sect.name.c_str(),
nbSectErrors,
nbSectErrors == 1 ? "" : "s"
);
} }
#undef fail #undef fail
// Create a new section, not yet in the list. // Create a new section, not yet in the list.
static Section *createSection(char const *name, enum SectionType type, uint32_t org, uint32_t bank, static Section *createSection(
uint8_t alignment, uint16_t alignOffset, enum SectionModifier mod) char const *name,
{ enum SectionType type,
uint32_t org,
uint32_t bank,
uint8_t alignment,
uint16_t alignOffset,
enum SectionModifier mod
) {
// Add the new section to the list (order doesn't matter) // Add the new section to the list (order doesn't matter)
Section &sect = sectionList.emplace_front(); Section &sect = sectionList.emplace_front();
@@ -286,9 +323,13 @@ static Section *createSection(char const *name, enum SectionType type, uint32_t
} }
// Find a section by name and type. If it doesn't exist, create it. // Find a section by name and type. If it doesn't exist, create it.
static Section *getSection(char const *name, enum SectionType type, uint32_t org, static Section *getSection(
SectionSpec const &attrs, enum SectionModifier mod) char const *name,
{ enum SectionType type,
uint32_t org,
SectionSpec const &attrs,
enum SectionModifier mod
) {
uint32_t bank = attrs.bank; uint32_t bank = attrs.bank;
uint8_t alignment = attrs.alignment; uint8_t alignment = attrs.alignment;
uint16_t alignOffset = attrs.alignOfs; uint16_t alignOffset = attrs.alignOfs;
@@ -296,29 +337,41 @@ static Section *getSection(char const *name, enum SectionType type, uint32_t org
// First, validate parameters, and normalize them if applicable // First, validate parameters, and normalize them if applicable
if (bank != (uint32_t)-1) { if (bank != (uint32_t)-1) {
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
&& type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX) && type != SECTTYPE_WRAMX)
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(
PRIx32 ")\n", sectionTypeInfo[type].name.c_str(), bank, "%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n",
sectionTypeInfo[type].firstBank, sectionTypeInfo[type].lastBank); sectionTypeInfo[type].name.c_str(),
bank,
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
bank = sectionTypeInfo[type].firstBank; bank = sectionTypeInfo[type].firstBank;
} }
if (alignOffset >= 1 << alignment) { if (alignOffset >= 1 << alignment) {
error("Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)\n", error(
alignOffset, 1U << alignment); "Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)\n",
alignOffset,
1U << alignment
);
alignOffset = 0; alignOffset = 0;
} }
if (org != (uint32_t)-1) { if (org != (uint32_t)-1) {
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
error("Section \"%s\"'s fixed address $%04" PRIx32 error(
" is outside of range [$%04" PRIx16 "; $%04" PRIx16 "]\n", "Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
name, org, sectionTypeInfo[type].startAddr, endaddr(type)); "; $%04" PRIx16 "]\n",
name,
org,
sectionTypeInfo[type].startAddr,
endaddr(type)
);
} }
if (alignment != 0) { if (alignment != 0) {
@@ -331,12 +384,14 @@ static Section *getSection(char const *name, enum SectionType type, uint32_t org
if (org != (uint32_t)-1) { if (org != (uint32_t)-1) {
if ((org - alignOffset) & mask) if ((org - alignOffset) & mask)
error("Section \"%s\"'s fixed address doesn't match its alignment\n", error("Section \"%s\"'s fixed address doesn't match its alignment\n", name);
name);
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(
name, sectionTypeInfo[type].name.c_str()); "Section \"%s\"'s alignment cannot be attained in %s\n",
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) {
@@ -361,16 +416,14 @@ static Section *getSection(char const *name, enum SectionType type, uint32_t org
} }
// Set the current section // Set the current section
static void changeSection() static void changeSection() {
{
if (!currentUnionStack.empty()) if (!currentUnionStack.empty())
fatalerror("Cannot change the section within a UNION\n"); fatalerror("Cannot change the section within a UNION\n");
sym_SetCurrentSymbolScope(nullptr); sym_SetCurrentSymbolScope(nullptr);
} }
bool Section::isSizeKnown() const bool Section::isSizeKnown() const {
{
// SECTION UNION and SECTION FRAGMENT can still grow // SECTION UNION and SECTION FRAGMENT can still grow
if (modifier != SECTION_NORMAL) if (modifier != SECTION_NORMAL)
return false; return false;
@@ -389,9 +442,13 @@ bool Section::isSizeKnown() const
} }
// Set the current section by name and type // Set the current section by name and type
void sect_NewSection(char const *name, enum SectionType type, uint32_t org, void sect_NewSection(
SectionSpec const &attrs, enum SectionModifier mod) char const *name,
{ enum SectionType type,
uint32_t org,
SectionSpec const &attrs,
enum SectionModifier mod
) {
if (currentLoadSection) if (currentLoadSection)
fatalerror("Cannot change the section within a `LOAD` block\n"); fatalerror("Cannot change the section within a `LOAD` block\n");
@@ -409,9 +466,13 @@ void sect_NewSection(char const *name, enum SectionType type, uint32_t org,
} }
// Set the current section by name and type // Set the current section by name and type
void sect_SetLoadSection(char const *name, enum SectionType type, uint32_t org, void sect_SetLoadSection(
SectionSpec const &attrs, enum SectionModifier mod) char const *name,
{ enum SectionType type,
uint32_t org,
SectionSpec const &attrs,
enum SectionModifier mod
) {
// Important info: currently, UNION and LOAD cannot interact, since UNION is prohibited in // Important info: currently, UNION and LOAD cannot interact, since UNION is prohibited in
// "code" sections, whereas LOAD is restricted to them. // "code" sections, whereas LOAD is restricted to them.
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at // Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
@@ -444,8 +505,7 @@ void sect_SetLoadSection(char const *name, enum SectionType type, uint32_t org,
currentLoadSection = sect; currentLoadSection = sect;
} }
void sect_EndLoadSection() void sect_EndLoadSection() {
{
if (!currentLoadSection) { if (!currentLoadSection) {
error("Found `ENDL` outside of a `LOAD` block\n"); error("Found `ENDL` outside of a `LOAD` block\n");
return; return;
@@ -458,25 +518,21 @@ void sect_EndLoadSection()
sym_SetCurrentSymbolScope(currentLoadScope); sym_SetCurrentSymbolScope(currentLoadScope);
} }
Section *sect_GetSymbolSection() Section *sect_GetSymbolSection() {
{
return currentLoadSection ? currentLoadSection : currentSection; return currentLoadSection ? currentLoadSection : currentSection;
} }
// The offset into the section above // The offset into the section above
uint32_t sect_GetSymbolOffset() uint32_t sect_GetSymbolOffset() {
{
return curOffset; return curOffset;
} }
uint32_t sect_GetOutputOffset() uint32_t sect_GetOutputOffset() {
{
return curOffset + loadOffset; return curOffset + loadOffset;
} }
// Returns how many bytes need outputting for the specified alignment and offset to succeed // Returns how many bytes need outputting for the specified alignment and offset to succeed
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
{
Section *sect = sect_GetSymbolSection(); Section *sect = sect_GetSymbolSection();
if (!sect) if (!sect)
return 0; return 0;
@@ -492,11 +548,10 @@ uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset)
// We need `(pcValue + curOffset + return value) % (1 << alignment) == offset` // We need `(pcValue + curOffset + return value) % (1 << alignment) == offset`
uint16_t pcValue = isFixed ? sect->org : sect->alignOfs; uint16_t pcValue = isFixed ? sect->org : sect->alignOfs;
return static_cast<uint16_t>(offset - curOffset - pcValue) return static_cast<uint16_t>(offset - curOffset - pcValue)
% (1u << std::min(alignment, curAlignment)); % (1u << std::min(alignment, curAlignment));
} }
void sect_AlignPC(uint8_t alignment, uint16_t offset) void sect_AlignPC(uint8_t alignment, uint16_t offset) {
{
if (!checksection()) if (!checksection())
return; return;
@@ -505,12 +560,16 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset)
if (sect->org != (uint32_t)-1) { if (sect->org != (uint32_t)-1) {
if ((sect->org + curOffset - offset) % alignSize) if ((sect->org + curOffset - offset) % alignSize)
error("Section's fixed address fails required alignment (PC = $%04" PRIx32 error(
")\n", sect->org + curOffset); "Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
} else if (sect->align != 0 && (((sect->alignOfs + curOffset) % (1u << sect->align)) sect->org + curOffset
- offset) % alignSize) { );
error("Section's alignment fails required alignment (offset from section start = $%04" } else if (sect->align != 0 && (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
PRIx32 ")\n", curOffset); error(
"Section's alignment fails required alignment (offset from section start = $%04" PRIx32
")\n",
curOffset
);
} else if (alignment >= 16) { } else if (alignment >= 16) {
// Treat an alignment large enough as fixing the address. // Treat an alignment large enough as fixing the address.
// Note that this also ensures that a section's alignment never becomes 16 or greater. // Note that this also ensures that a section's alignment never becomes 16 or greater.
@@ -526,8 +585,7 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset)
} }
} }
static void growSection(uint32_t growth) static void growSection(uint32_t growth) {
{
curOffset += growth; curOffset += growth;
if (curOffset + loadOffset > currentSection->size) if (curOffset + loadOffset > currentSection->size)
currentSection->size = curOffset + loadOffset; currentSection->size = curOffset + loadOffset;
@@ -535,33 +593,28 @@ static void growSection(uint32_t growth)
currentLoadSection->size = curOffset; currentLoadSection->size = curOffset;
} }
static void writebyte(uint8_t byte) static void writebyte(uint8_t byte) {
{
currentSection->data[sect_GetOutputOffset()] = byte; currentSection->data[sect_GetOutputOffset()] = byte;
growSection(1); growSection(1);
} }
static void writeword(uint16_t b) static void writeword(uint16_t b) {
{
writebyte(b & 0xFF); writebyte(b & 0xFF);
writebyte(b >> 8); writebyte(b >> 8);
} }
static void writelong(uint32_t b) static void writelong(uint32_t b) {
{
writebyte(b & 0xFF); writebyte(b & 0xFF);
writebyte(b >> 8); writebyte(b >> 8);
writebyte(b >> 16); writebyte(b >> 16);
writebyte(b >> 24); writebyte(b >> 24);
} }
static void createPatch(enum PatchType type, Expression const &expr, uint32_t pcShift) static void createPatch(enum PatchType type, Expression const &expr, uint32_t pcShift) {
{
out_CreatePatch(type, expr, sect_GetOutputOffset(), pcShift); out_CreatePatch(type, expr, sect_GetOutputOffset(), pcShift);
} }
void sect_StartUnion() void sect_StartUnion() {
{
// Important info: currently, UNION and LOAD cannot interact, since UNION is prohibited in // Important info: currently, UNION and LOAD cannot interact, since UNION is prohibited in
// "code" sections, whereas LOAD is restricted to them. // "code" sections, whereas LOAD is restricted to them.
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at // Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
@@ -576,11 +629,10 @@ void sect_StartUnion()
return; return;
} }
currentUnionStack.push({ .start = curOffset, .size = 0 }); currentUnionStack.push({.start = curOffset, .size = 0});
} }
static void endUnionMember() static void endUnionMember() {
{
UnionStackEntry &member = currentUnionStack.top(); UnionStackEntry &member = currentUnionStack.top();
uint32_t memberSize = curOffset - member.start; uint32_t memberSize = curOffset - member.start;
@@ -589,8 +641,7 @@ static void endUnionMember()
curOffset = member.start; curOffset = member.start;
} }
void sect_NextUnionMember() void sect_NextUnionMember() {
{
if (currentUnionStack.empty()) { if (currentUnionStack.empty()) {
error("Found NEXTU outside of a UNION construct\n"); error("Found NEXTU outside of a UNION construct\n");
return; return;
@@ -598,8 +649,7 @@ void sect_NextUnionMember()
endUnionMember(); endUnionMember();
} }
void sect_EndUnion() void sect_EndUnion() {
{
if (currentUnionStack.empty()) { if (currentUnionStack.empty()) {
error("Found ENDU outside of a UNION construct\n"); error("Found ENDU outside of a UNION construct\n");
return; return;
@@ -609,15 +659,13 @@ void sect_EndUnion()
currentUnionStack.pop(); currentUnionStack.pop();
} }
void sect_CheckUnionClosed() void sect_CheckUnionClosed() {
{
if (!currentUnionStack.empty()) if (!currentUnionStack.empty())
error("Unterminated UNION construct\n"); error("Unterminated UNION construct\n");
} }
// Output an absolute byte // Output an absolute byte
void sect_AbsByte(uint8_t b) void sect_AbsByte(uint8_t b) {
{
if (!checkcodesection()) if (!checkcodesection())
return; return;
if (!reserveSpace(1)) if (!reserveSpace(1))
@@ -626,8 +674,7 @@ void sect_AbsByte(uint8_t b)
writebyte(b); writebyte(b);
} }
void sect_AbsByteGroup(uint8_t const *s, size_t length) void sect_AbsByteGroup(uint8_t const *s, size_t length) {
{
if (!checkcodesection()) if (!checkcodesection())
return; return;
if (!reserveSpace(length)) if (!reserveSpace(length))
@@ -637,8 +684,7 @@ void sect_AbsByteGroup(uint8_t const *s, size_t length)
writebyte(*s++); writebyte(*s++);
} }
void sect_AbsWordGroup(uint8_t const *s, size_t length) void sect_AbsWordGroup(uint8_t const *s, size_t length) {
{
if (!checkcodesection()) if (!checkcodesection())
return; return;
if (!reserveSpace(length * 2)) if (!reserveSpace(length * 2))
@@ -648,8 +694,7 @@ void sect_AbsWordGroup(uint8_t const *s, size_t length)
writeword(*s++); writeword(*s++);
} }
void sect_AbsLongGroup(uint8_t const *s, size_t length) void sect_AbsLongGroup(uint8_t const *s, size_t length) {
{
if (!checkcodesection()) if (!checkcodesection())
return; return;
if (!reserveSpace(length * 4)) if (!reserveSpace(length * 4))
@@ -660,8 +705,7 @@ void sect_AbsLongGroup(uint8_t const *s, size_t length)
} }
// Skip this many bytes // Skip this many bytes
void sect_Skip(uint32_t skip, bool ds) void sect_Skip(uint32_t skip, bool ds) {
{
if (!checksection()) if (!checksection())
return; return;
if (!reserveSpace(skip)) if (!reserveSpace(skip))
@@ -671,8 +715,13 @@ void sect_Skip(uint32_t skip, bool ds)
growSection(skip); growSection(skip);
} else { } else {
if (!ds) if (!ds)
warning(WARNING_EMPTY_DATA_DIRECTIVE, "%s directive without data in ROM\n", warning(
(skip == 4) ? "DL" : (skip == 2) ? "DW" : "DB"); WARNING_EMPTY_DATA_DIRECTIVE,
"%s directive without data in ROM\n",
(skip == 4) ? "DL"
: (skip == 2) ? "DW"
: "DB"
);
// We know we're in a code SECTION // We know we're in a code SECTION
while (skip--) while (skip--)
writebyte(fillByte); writebyte(fillByte);
@@ -681,8 +730,7 @@ void sect_Skip(uint32_t skip, bool ds)
// Output a relocatable byte. Checking will be done to see if it // Output a relocatable byte. Checking will be done to see if it
// is an absolute value in disguise. // is an absolute value in disguise.
void sect_RelByte(Expression &expr, uint32_t pcShift) void sect_RelByte(Expression &expr, uint32_t pcShift) {
{
if (!checkcodesection()) if (!checkcodesection())
return; return;
if (!reserveSpace(1)) if (!reserveSpace(1))
@@ -699,8 +747,7 @@ void sect_RelByte(Expression &expr, uint32_t pcShift)
// Output several copies of a relocatable byte. Checking will be done to see if // Output several copies of a relocatable byte. Checking will be done to see if
// it is an absolute value in disguise. // it is an absolute value in disguise.
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
{
if (!checkcodesection()) if (!checkcodesection())
return; return;
if (!reserveSpace(n)) if (!reserveSpace(n))
@@ -723,8 +770,7 @@ void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs)
// Output a relocatable word. Checking will be done to see if // Output a relocatable word. Checking will be done to see if
// it's an absolute value in disguise. // it's an absolute value in disguise.
void sect_RelWord(Expression &expr, uint32_t pcShift) void sect_RelWord(Expression &expr, uint32_t pcShift) {
{
if (!checkcodesection()) if (!checkcodesection())
return; return;
if (!reserveSpace(2)) if (!reserveSpace(2))
@@ -741,8 +787,7 @@ void sect_RelWord(Expression &expr, uint32_t pcShift)
// Output a relocatable longword. Checking will be done to see if // Output a relocatable longword. Checking will be done to see if
// is an absolute value in disguise. // is an absolute value in disguise.
void sect_RelLong(Expression &expr, uint32_t pcShift) void sect_RelLong(Expression &expr, uint32_t pcShift) {
{
if (!checkcodesection()) if (!checkcodesection())
return; return;
if (!reserveSpace(2)) if (!reserveSpace(2))
@@ -759,8 +804,7 @@ void sect_RelLong(Expression &expr, uint32_t pcShift)
// Output a PC-relative relocatable byte. Checking will be done to see if it // Output a PC-relative relocatable byte. Checking will be done to see if it
// is an absolute value in disguise. // is an absolute value in disguise.
void sect_PCRelByte(Expression &expr, uint32_t pcShift) void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
{
if (!checkcodesection()) if (!checkcodesection())
return; return;
if (!reserveSpace(1)) if (!reserveSpace(1))
@@ -782,8 +826,7 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift)
offset = sym->getValue() - (pc->getValue() + 1); offset = sym->getValue() - (pc->getValue() + 1);
if (offset < -128 || offset > 127) { if (offset < -128 || offset > 127) {
error("jr target out of reach (expected -129 < %" PRId16 " < 128)\n", error("jr target out of reach (expected -129 < %" PRId16 " < 128)\n", offset);
offset);
writebyte(0); writebyte(0);
} else { } else {
writebyte(offset); writebyte(offset);
@@ -793,8 +836,7 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift)
} }
// Output a binary file // Output a binary file
void sect_BinaryFile(char const *s, int32_t startPos) void sect_BinaryFile(char const *s, int32_t startPos) {
{
if (startPos < 0) { if (startPos < 0) {
error("Start position cannot be negative (%" PRId32 ")\n", startPos); error("Start position cannot be negative (%" PRId32 ")\n", startPos);
startPos = 0; startPos = 0;
@@ -834,8 +876,7 @@ void sect_BinaryFile(char const *s, int32_t startPos)
goto cleanup; goto cleanup;
} else { } else {
if (errno != ESPIPE) if (errno != ESPIPE)
error("Error determining size of INCBIN file '%s': %s\n", error("Error determining size of INCBIN file '%s': %s\n", s, strerror(errno));
s, strerror(errno));
// The file isn't seekable, so we'll just skip bytes // The file isn't seekable, so we'll just skip bytes
while (startPos--) while (startPos--)
(void)fgetc(f); (void)fgetc(f);
@@ -854,8 +895,7 @@ cleanup:
fclose(f); fclose(f);
} }
void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length) void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length) {
{
if (start_pos < 0) { if (start_pos < 0) {
error("Start position cannot be negative (%" PRId32 ")\n", start_pos); error("Start position cannot be negative (%" PRId32 ")\n", start_pos);
start_pos = 0; start_pos = 0;
@@ -900,16 +940,20 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
} }
if ((start_pos + length) > fsize) { if ((start_pos + length) > fsize) {
error("Specified range in INCBIN is out of bounds (%" PRIu32 " + %" PRIu32 error(
" > %" PRIu32 ")\n", start_pos, length, fsize); "Specified range in INCBIN is out of bounds (%" PRIu32 " + %" PRIu32 " > %" PRIu32
")\n",
start_pos,
length,
fsize
);
goto cleanup; goto cleanup;
} }
fseek(f, start_pos, SEEK_SET); fseek(f, start_pos, SEEK_SET);
} else { } else {
if (errno != ESPIPE) if (errno != ESPIPE)
error("Error determining size of INCBIN file '%s': %s\n", error("Error determining size of INCBIN file '%s': %s\n", s, strerror(errno));
s, strerror(errno));
// The file isn't seekable, so we'll just skip bytes // The file isn't seekable, so we'll just skip bytes
while (start_pos--) while (start_pos--)
(void)fgetc(f); (void)fgetc(f);
@@ -923,8 +967,7 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
} else if (ferror(f)) { } else if (ferror(f)) {
error("Error reading INCBIN file '%s': %s\n", s, strerror(errno)); error("Error reading INCBIN file '%s': %s\n", s, strerror(errno));
} else { } else {
error("Premature end of file (%" PRId32 " bytes left to read)\n", error("Premature end of file (%" PRId32 " bytes left to read)\n", length + 1);
length + 1);
} }
} }
@@ -933,15 +976,14 @@ cleanup:
} }
// Section stack routines // Section stack routines
void sect_PushSection() void sect_PushSection() {
{
sectionStack.push_front({ sectionStack.push_front({
.section = currentSection, .section = currentSection,
.loadSection = currentLoadSection, .loadSection = currentLoadSection,
.scope = sym_GetCurrentSymbolScope(), .scope = sym_GetCurrentSymbolScope(),
.offset = curOffset, .offset = curOffset,
.loadOffset = loadOffset, .loadOffset = loadOffset,
.unionStack = {}, .unionStack = {},
}); });
// Reset the section scope // Reset the section scope
@@ -951,8 +993,7 @@ void sect_PushSection()
std::swap(currentUnionStack, sectionStack.front().unionStack); std::swap(currentUnionStack, sectionStack.front().unionStack);
} }
void sect_PopSection() void sect_PopSection() {
{
if (sectionStack.empty()) if (sectionStack.empty())
fatalerror("No entries in the section stack\n"); fatalerror("No entries in the section stack\n");
@@ -971,8 +1012,7 @@ void sect_PopSection()
std::swap(currentUnionStack, entry.unionStack); std::swap(currentUnionStack, entry.unionStack);
} }
void sect_EndSection() void sect_EndSection() {
{
if (!currentSection) if (!currentSection)
fatalerror("Cannot end the section outside of a SECTION\n"); fatalerror("Cannot end the section outside of a SECTION\n");

View File

@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "asm/symbol.hpp"
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
@@ -7,26 +9,25 @@
#include <map> #include <map>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <string>
#include <string.h> #include <string.h>
#include <string>
#include <string_view> #include <string_view>
#include <time.h> #include <time.h>
#include <variant> #include <variant>
#include "error.hpp"
#include "helpers.hpp"
#include "util.hpp"
#include "version.hpp"
#include "asm/fixpoint.hpp" #include "asm/fixpoint.hpp"
#include "asm/fstack.hpp" #include "asm/fstack.hpp"
#include "asm/macro.hpp" #include "asm/macro.hpp"
#include "asm/main.hpp" #include "asm/main.hpp"
#include "asm/output.hpp" #include "asm/output.hpp"
#include "asm/section.hpp" #include "asm/section.hpp"
#include "asm/symbol.hpp"
#include "util.hpp"
#include "asm/warning.hpp" #include "asm/warning.hpp"
#include "error.hpp"
#include "helpers.hpp"
#include "version.hpp"
std::map<std::string, Symbol> symbols; std::map<std::string, Symbol> symbols;
static const char *labelScope; // Current section's label scope static const char *labelScope; // Current section's label scope
@@ -38,19 +39,16 @@ static char savedTIMESTAMP_ISO8601_LOCAL[256];
static char savedTIMESTAMP_ISO8601_UTC[256]; static char savedTIMESTAMP_ISO8601_UTC[256];
static bool exportAll; static bool exportAll;
bool sym_IsPC(Symbol const *sym) bool sym_IsPC(Symbol const *sym) {
{
return sym == PCSymbol; return sym == PCSymbol;
} }
void sym_ForEach(void (*callback)(Symbol &)) void sym_ForEach(void (*callback)(Symbol &)) {
{
for (auto &it : symbols) for (auto &it : symbols)
callback(it.second); callback(it.second);
} }
static int32_t Callback_NARG() static int32_t Callback_NARG() {
{
if (!macro_GetCurrentArgs()) { if (!macro_GetCurrentArgs()) {
error("_NARG does not make sense outside of a macro\n"); error("_NARG does not make sense outside of a macro\n");
return 0; return 0;
@@ -58,15 +56,13 @@ static int32_t Callback_NARG()
return macro_NbArgs(); return macro_NbArgs();
} }
static int32_t CallbackPC() static int32_t CallbackPC() {
{
Section const *section = sect_GetSymbolSection(); Section const *section = sect_GetSymbolSection();
return section ? section->org + sect_GetSymbolOffset() : 0; return section ? section->org + sect_GetSymbolOffset() : 0;
} }
int32_t Symbol::getValue() const int32_t Symbol::getValue() const {
{
assert(std::holds_alternative<int32_t>(data) || std::holds_alternative<int32_t (*)()>(data)); assert(std::holds_alternative<int32_t>(data) || std::holds_alternative<int32_t (*)()>(data));
if (int32_t const *value = std::get_if<int32_t>(&data); value) { if (int32_t const *value = std::get_if<int32_t>(&data); value) {
// TODO: do not use section's org directly // TODO: do not use section's org directly
@@ -75,29 +71,28 @@ int32_t Symbol::getValue() const
return getOutputValue(); return getOutputValue();
} }
int32_t Symbol::getOutputValue() const int32_t Symbol::getOutputValue() const {
{ return std::visit(
return std::visit(Visitor{ Visitor{
[](int32_t value) -> int32_t { return value; }, [](int32_t value) -> int32_t { return value; },
[](int32_t (*callback)()) -> int32_t { return callback(); }, [](int32_t (*callback)()) -> int32_t { return callback(); },
[](auto &) -> int32_t { return 0; } [](auto &) -> int32_t { return 0; },
}, data); },
data
);
} }
std::string_view *Symbol::getMacro() const std::string_view *Symbol::getMacro() const {
{
assert(std::holds_alternative<std::string_view *>(data)); assert(std::holds_alternative<std::string_view *>(data));
return std::get<std::string_view *>(data); return std::get<std::string_view *>(data);
} }
std::string *Symbol::getEqus() const std::string *Symbol::getEqus() const {
{
assert(std::holds_alternative<std::string *>(data)); assert(std::holds_alternative<std::string *>(data));
return std::get<std::string *>(data); return std::get<std::string *>(data);
} }
static void dumpFilename(Symbol const &sym) static void dumpFilename(Symbol const &sym) {
{
if (sym.src) if (sym.src)
sym.src->dump(sym.fileLine); sym.src->dump(sym.fileLine);
else if (sym.fileLine == 0) else if (sym.fileLine == 0)
@@ -107,15 +102,13 @@ static void dumpFilename(Symbol const &sym)
} }
// Set a symbol's definition filename and line // Set a symbol's definition filename and line
static void setSymbolFilename(Symbol &sym) static void setSymbolFilename(Symbol &sym) {
{ sym.src = fstk_GetFileStack(); // This is `nullptr` for built-ins
sym.src = fstk_GetFileStack(); // This is `nullptr` for built-ins
sym.fileLine = sym.src ? lexer_GetLineNo() : 0; // This is 1 for built-ins sym.fileLine = sym.src ? lexer_GetLineNo() : 0; // This is 1 for built-ins
} }
// Update a symbol's definition filename and line // Update a symbol's definition filename and line
static void updateSymbolFilename(Symbol &sym) static void updateSymbolFilename(Symbol &sym) {
{
FileStackNode *oldSrc = sym.src; FileStackNode *oldSrc = sym.src;
setSymbolFilename(sym); setSymbolFilename(sym);
@@ -126,8 +119,7 @@ static void updateSymbolFilename(Symbol &sym)
} }
// Create a new symbol by name // Create a new symbol by name
static Symbol &createsymbol(char const *symName) static Symbol &createsymbol(char const *symName) {
{
Symbol &sym = symbols[symName]; Symbol &sym = symbols[symName];
if (snprintf(sym.name, MAXSYMLEN + 1, "%s", symName) > MAXSYMLEN) if (snprintf(sym.name, MAXSYMLEN + 1, "%s", symName) > MAXSYMLEN)
@@ -144,9 +136,8 @@ static Symbol &createsymbol(char const *symName)
// Creates the full name of a local symbol in a given scope, by prepending // Creates the full name of a local symbol in a given scope, by prepending
// the name with the parent symbol's name. // the name with the parent symbol's name.
static void fullSymbolName(char *output, size_t outputSize, static void
char const *localName, char const *scopeName) fullSymbolName(char *output, size_t outputSize, char const *localName, char const *scopeName) {
{
int ret = snprintf(output, outputSize, "%s%s", scopeName, localName); int ret = snprintf(output, outputSize, "%s%s", scopeName, localName);
if (ret < 0) if (ret < 0)
@@ -155,27 +146,23 @@ static void fullSymbolName(char *output, size_t outputSize,
fatalerror("Symbol name is too long: '%s%s'\n", scopeName, localName); fatalerror("Symbol name is too long: '%s%s'\n", scopeName, localName);
} }
static void assignStringSymbol(Symbol &sym, char const *value) static void assignStringSymbol(Symbol &sym, char const *value) {
{ std::string *equs = new (std::nothrow) std::string(value);
std::string *equs = new(std::nothrow) std::string(value);
if (!equs) if (!equs)
fatalerror("No memory for string equate: %s\n", strerror(errno)); fatalerror("No memory for string equate: %s\n", strerror(errno));
sym.type = SYM_EQUS; sym.type = SYM_EQUS;
sym.data = equs; sym.data = equs;
} }
Symbol *sym_FindExactSymbol(char const *symName) Symbol *sym_FindExactSymbol(char const *symName) {
{
auto search = symbols.find(symName); auto search = symbols.find(symName);
return search != symbols.end() ? &search->second : nullptr; return search != symbols.end() ? &search->second : nullptr;
} }
Symbol *sym_FindScopedSymbol(char const *symName) Symbol *sym_FindScopedSymbol(char const *symName) {
{
if (char const *localName = strchr(symName, '.'); localName) { if (char const *localName = strchr(symName, '.'); localName) {
if (strchr(localName + 1, '.')) if (strchr(localName + 1, '.'))
fatalerror("'%s' is a nonsensical reference to a nested local symbol\n", fatalerror("'%s' is a nonsensical reference to a nested local symbol\n", symName);
symName);
// If auto-scoped local label, expand the name // If auto-scoped local label, expand the name
if (localName == symName) { // Meaning, the name begins with the dot if (localName == symName) { // Meaning, the name begins with the dot
char fullName[MAXSYMLEN + 1]; char fullName[MAXSYMLEN + 1];
@@ -187,8 +174,7 @@ Symbol *sym_FindScopedSymbol(char const *symName)
return sym_FindExactSymbol(symName); return sym_FindExactSymbol(symName);
} }
Symbol *sym_FindScopedValidSymbol(char const *symName) Symbol *sym_FindScopedValidSymbol(char const *symName) {
{
Symbol *sym = sym_FindScopedSymbol(symName); Symbol *sym = sym_FindScopedSymbol(symName);
// `@` has no value outside a section // `@` has no value outside a section
@@ -202,14 +188,12 @@ Symbol *sym_FindScopedValidSymbol(char const *symName)
return sym; return sym;
} }
Symbol const *sym_GetPC() Symbol const *sym_GetPC() {
{
return PCSymbol; return PCSymbol;
} }
// Purge a symbol // Purge a symbol
void sym_Purge(std::string const &symName) void sym_Purge(std::string const &symName) {
{
Symbol *sym = sym_FindScopedValidSymbol(symName.c_str()); Symbol *sym = sym_FindScopedValidSymbol(symName.c_str());
if (!sym) { if (!sym) {
@@ -230,8 +214,7 @@ void sym_Purge(std::string const &symName)
} }
} }
uint32_t sym_GetPCValue() uint32_t sym_GetPCValue() {
{
Section const *sect = sect_GetSymbolSection(); Section const *sect = sect_GetSymbolSection();
if (!sect) if (!sect)
@@ -244,8 +227,7 @@ uint32_t sym_GetPCValue()
} }
// Return a constant symbol's value, assuming it's defined // Return a constant symbol's value, assuming it's defined
uint32_t Symbol::getConstantValue() const uint32_t Symbol::getConstantValue() const {
{
if (sym_IsPC(this)) if (sym_IsPC(this))
return sym_GetPCValue(); return sym_GetPCValue();
@@ -257,8 +239,7 @@ uint32_t Symbol::getConstantValue() const
} }
// Return a constant symbol's value // Return a constant symbol's value
uint32_t sym_GetConstantValue(char const *symName) uint32_t sym_GetConstantValue(char const *symName) {
{
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym) if (Symbol const *sym = sym_FindScopedSymbol(symName); sym)
return sym->getConstantValue(); return sym->getConstantValue();
@@ -266,13 +247,11 @@ uint32_t sym_GetConstantValue(char const *symName)
return 0; return 0;
} }
char const *sym_GetCurrentSymbolScope() char const *sym_GetCurrentSymbolScope() {
{
return labelScope; return labelScope;
} }
void sym_SetCurrentSymbolScope(char const *newScope) void sym_SetCurrentSymbolScope(char const *newScope) {
{
labelScope = newScope; labelScope = newScope;
} }
@@ -283,8 +262,7 @@ void sym_SetCurrentSymbolScope(char const *newScope)
* @param symName The name of the symbol to create * @param symName The name of the symbol to create
* @param numeric If false, the symbol may not have been referenced earlier * @param numeric If false, the symbol may not have been referenced earlier
*/ */
static Symbol *createNonrelocSymbol(char const *symName, bool numeric) static Symbol *createNonrelocSymbol(char const *symName, bool numeric) {
{
Symbol *sym = sym_FindExactSymbol(symName); Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) { if (!sym) {
@@ -306,8 +284,7 @@ static Symbol *createNonrelocSymbol(char const *symName, bool numeric)
} }
// Add an equated symbol // Add an equated symbol
Symbol *sym_AddEqu(char const *symName, int32_t value) Symbol *sym_AddEqu(char const *symName, int32_t value) {
{
Symbol *sym = createNonrelocSymbol(symName, true); Symbol *sym = createNonrelocSymbol(symName, true);
if (!sym) if (!sym)
@@ -319,8 +296,7 @@ Symbol *sym_AddEqu(char const *symName, int32_t value)
return sym; return sym;
} }
Symbol *sym_RedefEqu(char const *symName, int32_t value) Symbol *sym_RedefEqu(char const *symName, int32_t value) {
{
Symbol *sym = sym_FindExactSymbol(symName); Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) if (!sym)
@@ -355,8 +331,7 @@ Symbol *sym_RedefEqu(char const *symName, int32_t value)
* of the string are enough: sym_AddString("M_PI", "3.1415"). This is the same * of the string are enough: sym_AddString("M_PI", "3.1415"). This is the same
* as ``` M_PI EQUS "3.1415" ``` * as ``` M_PI EQUS "3.1415" ```
*/ */
Symbol *sym_AddString(char const *symName, char const *value) Symbol *sym_AddString(char const *symName, char const *value) {
{
Symbol *sym = createNonrelocSymbol(symName, false); Symbol *sym = createNonrelocSymbol(symName, false);
if (!sym) if (!sym)
@@ -366,8 +341,7 @@ Symbol *sym_AddString(char const *symName, char const *value)
return sym; return sym;
} }
Symbol *sym_RedefString(char const *symName, char const *value) Symbol *sym_RedefString(char const *symName, char const *value) {
{
Symbol *sym = sym_FindExactSymbol(symName); Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) if (!sym)
@@ -395,15 +369,15 @@ Symbol *sym_RedefString(char const *symName, char const *value)
} }
// Alter a mutable symbol's value // Alter a mutable symbol's value
Symbol *sym_AddVar(char const *symName, int32_t value) Symbol *sym_AddVar(char const *symName, int32_t value) {
{
Symbol *sym = sym_FindExactSymbol(symName); Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) { if (!sym) {
sym = &createsymbol(symName); sym = &createsymbol(symName);
} else if (sym->isDefined() && sym->type != SYM_VAR) { } else if (sym->isDefined() && sym->type != SYM_VAR) {
error("'%s' already defined as %s at ", error(
symName, sym->type == SYM_LABEL ? "label" : "constant"); "'%s' already defined as %s at ", symName, sym->type == SYM_LABEL ? "label" : "constant"
);
dumpFilename(*sym); dumpFilename(*sym);
putc('\n', stderr); putc('\n', stderr);
return sym; return sym;
@@ -422,8 +396,7 @@ Symbol *sym_AddVar(char const *symName, int32_t value)
* @param symName The label's full name (so `.name` is invalid) * @param symName The label's full name (so `.name` is invalid)
* @return The created symbol * @return The created symbol
*/ */
static Symbol *addLabel(char const *symName) static Symbol *addLabel(char const *symName) {
{
assert(symName[0] != '.'); // The symbol name must have been expanded prior assert(symName[0] != '.'); // The symbol name must have been expanded prior
Symbol *sym = sym_FindExactSymbol(symName); Symbol *sym = sym_FindExactSymbol(symName);
@@ -452,8 +425,7 @@ static Symbol *addLabel(char const *symName)
} }
// Add a local (`.name` or `Parent.name`) relocatable symbol // Add a local (`.name` or `Parent.name`) relocatable symbol
Symbol *sym_AddLocalLabel(char const *symName) Symbol *sym_AddLocalLabel(char const *symName) {
{
// Assuming no dots in `labelScope` if defined // Assuming no dots in `labelScope` if defined
assert(!labelScope || !strchr(labelScope, '.')); assert(!labelScope || !strchr(labelScope, '.'));
@@ -464,13 +436,11 @@ Symbol *sym_AddLocalLabel(char const *symName)
// Check for something after the dot in `localName` // Check for something after the dot in `localName`
if (localName[1] == '\0') { if (localName[1] == '\0') {
fatalerror("'%s' is a nonsensical reference to an empty local label\n", fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName);
symName);
} }
// Check for more than one dot in `localName` // Check for more than one dot in `localName`
if (strchr(localName + 1, '.')) if (strchr(localName + 1, '.'))
fatalerror("'%s' is a nonsensical reference to a nested local label\n", fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName);
symName);
if (localName == symName) { if (localName == symName) {
if (!labelScope) { if (!labelScope) {
@@ -486,8 +456,7 @@ Symbol *sym_AddLocalLabel(char const *symName)
} }
// Add a relocatable symbol // Add a relocatable symbol
Symbol *sym_AddLabel(char const *symName) Symbol *sym_AddLabel(char const *symName) {
{
Symbol *sym = addLabel(symName); Symbol *sym = addLabel(symName);
// Set the symbol as the new scope // Set the symbol as the new scope
@@ -499,8 +468,7 @@ Symbol *sym_AddLabel(char const *symName)
static uint32_t anonLabelID; static uint32_t anonLabelID;
// Add an anonymous label // Add an anonymous label
Symbol *sym_AddAnonLabel() Symbol *sym_AddAnonLabel() {
{
if (anonLabelID == UINT32_MAX) { if (anonLabelID == UINT32_MAX) {
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID); error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
return nullptr; return nullptr;
@@ -513,22 +481,29 @@ Symbol *sym_AddAnonLabel()
} }
// Write an anonymous label's name to a buffer // Write an anonymous label's name to a buffer
void sym_WriteAnonLabelName(char buf[MAXSYMLEN + 1], uint32_t ofs, bool neg) void sym_WriteAnonLabelName(char buf[MAXSYMLEN + 1], uint32_t ofs, bool neg) {
{
uint32_t id = 0; uint32_t id = 0;
if (neg) { if (neg) {
if (ofs > anonLabelID) if (ofs > anonLabelID)
error("Reference to anonymous label %" PRIu32 " before, when only %" PRIu32 error(
" ha%s been created so far\n", "Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
ofs, anonLabelID, anonLabelID == 1 ? "s" : "ve"); " ha%s been created so far\n",
ofs,
anonLabelID,
anonLabelID == 1 ? "s" : "ve"
);
else else
id = anonLabelID - ofs; id = anonLabelID - ofs;
} else { } else {
ofs--; // We're referencing symbols that haven't been created yet... ofs--; // We're referencing symbols that haven't been created yet...
if (ofs > UINT32_MAX - anonLabelID) if (ofs > UINT32_MAX - anonLabelID)
error("Reference to anonymous label %" PRIu32 " after, when only %" PRIu32 error(
" may still be created\n", ofs + 1, UINT32_MAX - anonLabelID); "Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
" may still be created\n",
ofs + 1,
UINT32_MAX - anonLabelID
);
else else
id = anonLabelID + ofs; id = anonLabelID + ofs;
} }
@@ -537,8 +512,7 @@ void sym_WriteAnonLabelName(char buf[MAXSYMLEN + 1], uint32_t ofs, bool neg)
} }
// Export a symbol // Export a symbol
void sym_Export(char const *symName) void sym_Export(char const *symName) {
{
if (symName[0] == '!') { if (symName[0] == '!') {
error("Anonymous labels cannot be exported\n"); error("Anonymous labels cannot be exported\n");
return; return;
@@ -553,14 +527,13 @@ void sym_Export(char const *symName)
} }
// Add a macro definition // Add a macro definition
Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char const *body, size_t size) Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char const *body, size_t size) {
{
Symbol *sym = createNonrelocSymbol(symName, false); Symbol *sym = createNonrelocSymbol(symName, false);
if (!sym) if (!sym)
return nullptr; return nullptr;
std::string_view *macro = new(std::nothrow) std::string_view(body, size); std::string_view *macro = new (std::nothrow) std::string_view(body, size);
if (!macro) if (!macro)
fatalerror("No memory for macro: %s\n", strerror(errno)); fatalerror("No memory for macro: %s\n", strerror(errno));
sym->type = SYM_MACRO; sym->type = SYM_MACRO;
@@ -576,8 +549,7 @@ Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char const *body, s
// Flag that a symbol is referenced in an RPN expression // Flag that a symbol is referenced in an RPN expression
// and create it if it doesn't exist yet // and create it if it doesn't exist yet
Symbol *sym_Ref(char const *symName) Symbol *sym_Ref(char const *symName) {
{
Symbol *sym = sym_FindScopedSymbol(symName); Symbol *sym = sym_FindScopedSymbol(symName);
if (!sym) { if (!sym) {
@@ -598,13 +570,11 @@ Symbol *sym_Ref(char const *symName)
} }
// Set whether to export all relocatable symbols by default // Set whether to export all relocatable symbols by default
void sym_SetExportAll(bool set) void sym_SetExportAll(bool set) {
{
exportAll = set; exportAll = set;
} }
static Symbol *createBuiltinSymbol(char const *symName) static Symbol *createBuiltinSymbol(char const *symName) {
{
Symbol *sym = &createsymbol(symName); Symbol *sym = &createsymbol(symName);
sym->isBuiltin = true; sym->isBuiltin = true;
@@ -615,8 +585,7 @@ static Symbol *createBuiltinSymbol(char const *symName)
} }
// Initialize the symboltable // Initialize the symboltable
void sym_Init(time_t now) void sym_Init(time_t now) {
{
PCSymbol = createBuiltinSymbol("@"); PCSymbol = createBuiltinSymbol("@");
PCSymbol->type = SYM_LABEL; PCSymbol->type = SYM_LABEL;
PCSymbol->data = CallbackPC; PCSymbol->data = CallbackPC;
@@ -627,11 +596,12 @@ void sym_Init(time_t now)
sym_AddVar("_RS", 0)->isBuiltin = true; sym_AddVar("_RS", 0)->isBuiltin = true;
#define addSym(fn, name, val) do { \ #define addSym(fn, name, val) \
Symbol *sym = fn(name, val); \ do { \
assert(sym); \ Symbol *sym = fn(name, val); \
sym->isBuiltin = true; \ assert(sym); \
} while (0) sym->isBuiltin = true; \
} while (0)
#define addNumber(name, val) addSym(sym_AddEqu, name, val) #define addNumber(name, val) addSym(sym_AddEqu, name, val)
#define addString(name, val) addSym(sym_AddString, name, val) #define addString(name, val) addSym(sym_AddString, name, val)
@@ -653,15 +623,21 @@ void sym_Init(time_t now)
strftime(savedTIME, sizeof(savedTIME), "\"%H:%M:%S\"", time_local); strftime(savedTIME, sizeof(savedTIME), "\"%H:%M:%S\"", time_local);
strftime(savedDATE, sizeof(savedDATE), "\"%d %B %Y\"", time_local); strftime(savedDATE, sizeof(savedDATE), "\"%d %B %Y\"", time_local);
strftime(savedTIMESTAMP_ISO8601_LOCAL, strftime(
sizeof(savedTIMESTAMP_ISO8601_LOCAL), "\"%Y-%m-%dT%H:%M:%S%z\"", savedTIMESTAMP_ISO8601_LOCAL,
time_local); sizeof(savedTIMESTAMP_ISO8601_LOCAL),
"\"%Y-%m-%dT%H:%M:%S%z\"",
time_local
);
const tm *time_utc = gmtime(&now); const tm *time_utc = gmtime(&now);
strftime(savedTIMESTAMP_ISO8601_UTC, strftime(
sizeof(savedTIMESTAMP_ISO8601_UTC), "\"%Y-%m-%dT%H:%M:%SZ\"", savedTIMESTAMP_ISO8601_UTC,
time_utc); sizeof(savedTIMESTAMP_ISO8601_UTC),
"\"%Y-%m-%dT%H:%M:%SZ\"",
time_utc
);
addString("__TIME__", savedTIME); addString("__TIME__", savedTIME);
addString("__DATE__", savedDATE); addString("__DATE__", savedDATE);

View File

@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "asm/warning.hpp"
#include <inttypes.h> #include <inttypes.h>
#include <limits.h> #include <limits.h>
#include <stdarg.h> #include <stdarg.h>
@@ -8,48 +10,46 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "asm/fstack.hpp"
#include "asm/main.hpp"
#include "asm/warning.hpp"
#include "error.hpp" #include "error.hpp"
#include "itertools.hpp" #include "itertools.hpp"
#include "asm/fstack.hpp"
#include "asm/main.hpp"
unsigned int nbErrors = 0; unsigned int nbErrors = 0;
unsigned int maxErrors = 0; unsigned int maxErrors = 0;
static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = { static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = {
WARNING_ENABLED, // WARNING_ASSERT WARNING_ENABLED, // WARNING_ASSERT
WARNING_DISABLED, // WARNING_BACKWARDS_FOR WARNING_DISABLED, // WARNING_BACKWARDS_FOR
WARNING_DISABLED, // WARNING_BUILTIN_ARG WARNING_DISABLED, // WARNING_BUILTIN_ARG
WARNING_DISABLED, // WARNING_CHARMAP_REDEF WARNING_DISABLED, // WARNING_CHARMAP_REDEF
WARNING_DISABLED, // WARNING_DIV WARNING_DISABLED, // WARNING_DIV
WARNING_DISABLED, // WARNING_EMPTY_DATA_DIRECTIVE WARNING_DISABLED, // WARNING_EMPTY_DATA_DIRECTIVE
WARNING_DISABLED, // WARNING_EMPTY_MACRO_ARG WARNING_DISABLED, // WARNING_EMPTY_MACRO_ARG
WARNING_DISABLED, // WARNING_EMPTY_STRRPL WARNING_DISABLED, // WARNING_EMPTY_STRRPL
WARNING_DISABLED, // WARNING_LARGE_CONSTANT WARNING_DISABLED, // WARNING_LARGE_CONSTANT
WARNING_DISABLED, // WARNING_LONG_STR WARNING_DISABLED, // WARNING_LONG_STR
WARNING_DISABLED, // WARNING_MACRO_SHIFT WARNING_DISABLED, // WARNING_MACRO_SHIFT
WARNING_ENABLED, // WARNING_NESTED_COMMENT WARNING_ENABLED, // WARNING_NESTED_COMMENT
WARNING_ENABLED, // WARNING_OBSOLETE WARNING_ENABLED, // WARNING_OBSOLETE
WARNING_DISABLED, // WARNING_SHIFT WARNING_DISABLED, // WARNING_SHIFT
WARNING_DISABLED, // WARNING_SHIFT_AMOUNT WARNING_DISABLED, // WARNING_SHIFT_AMOUNT
WARNING_ENABLED, // WARNING_USER WARNING_ENABLED, // WARNING_USER
WARNING_ENABLED, // WARNING_NUMERIC_STRING_1 WARNING_ENABLED, // WARNING_NUMERIC_STRING_1
WARNING_DISABLED, // WARNING_NUMERIC_STRING_2 WARNING_DISABLED, // WARNING_NUMERIC_STRING_2
WARNING_ENABLED, // WARNING_TRUNCATION_1 WARNING_ENABLED, // WARNING_TRUNCATION_1
WARNING_DISABLED, // WARNING_TRUNCATION_2 WARNING_DISABLED, // WARNING_TRUNCATION_2
WARNING_ENABLED, // WARNING_UNMAPPED_CHAR_1 WARNING_ENABLED, // WARNING_UNMAPPED_CHAR_1
WARNING_DISABLED, // WARNING_UNMAPPED_CHAR_2 WARNING_DISABLED, // WARNING_UNMAPPED_CHAR_2
}; };
enum WarningState warningStates[ARRAY_SIZE(warningStates)]; enum WarningState warningStates[ARRAY_SIZE(warningStates)];
bool warningsAreErrors; // Set if `-Werror` was specified bool warningsAreErrors; // Set if `-Werror` was specified
static enum WarningState warningState(enum WarningID id) static enum WarningState warningState(enum WarningID id) {
{
// Check if warnings are globally disabled // Check if warnings are globally disabled
if (!warnings) if (!warnings)
return WARNING_DISABLED; return WARNING_DISABLED;
@@ -68,35 +68,35 @@ static enum WarningState warningState(enum WarningID id)
} }
static const char * const warningFlags[NB_WARNINGS] = { static const char * const warningFlags[NB_WARNINGS] = {
"assert", "assert",
"backwards-for", "backwards-for",
"builtin-args", "builtin-args",
"charmap-redef", "charmap-redef",
"div", "div",
"empty-data-directive", "empty-data-directive",
"empty-macro-arg", "empty-macro-arg",
"empty-strrpl", "empty-strrpl",
"large-constant", "large-constant",
"long-string", "long-string",
"macro-shift", "macro-shift",
"nested-comment", "nested-comment",
"obsolete", "obsolete",
"shift", "shift",
"shift-amount", "shift-amount",
"user", "user",
// Parametric warnings // Parametric warnings
"numeric-string", "numeric-string",
"numeric-string", "numeric-string",
"truncation", "truncation",
"truncation", "truncation",
"unmapped-char", "unmapped-char",
"unmapped-char", "unmapped-char",
// Meta warnings // Meta warnings
"all", "all",
"extra", "extra",
"everything", // Especially useful for testing "everything", // Especially useful for testing
}; };
static const struct { static const struct {
@@ -104,13 +104,12 @@ static const struct {
uint8_t nbLevels; uint8_t nbLevels;
uint8_t defaultLevel; uint8_t defaultLevel;
} paramWarnings[] = { } paramWarnings[] = {
{ "numeric-string", 2, 1 }, {"numeric-string", 2, 1},
{ "truncation", 2, 2 }, {"truncation", 2, 2},
{ "unmapped-char", 2, 1 }, {"unmapped-char", 2, 1},
}; };
static bool tryProcessParamWarning(char const *flag, uint8_t param, enum WarningState state) static bool tryProcessParamWarning(char const *flag, uint8_t param, enum WarningState state) {
{
enum WarningID baseID = PARAM_WARNINGS_START; enum WarningID baseID = PARAM_WARNINGS_START;
for (size_t i = 0; i < ARRAY_SIZE(paramWarnings); i++) { for (size_t i = 0; i < ARRAY_SIZE(paramWarnings); i++) {
@@ -126,17 +125,19 @@ static bool tryProcessParamWarning(char const *flag, uint8_t param, enum Warning
param = paramWarnings[i].defaultLevel; param = paramWarnings[i].defaultLevel;
} else if (param > maxParam) { } else if (param > maxParam) {
if (param != 255) // Don't warn if already capped if (param != 255) // Don't warn if already capped
warnx("Got parameter %" PRIu8 warnx(
" for warning flag \"%s\", but the maximum is %" "Got parameter %" PRIu8
PRIu8 "; capping.\n", " for warning flag \"%s\", but the maximum is %" PRIu8 "; capping.\n",
param, flag, maxParam); param,
flag,
maxParam
);
param = maxParam; param = maxParam;
} }
// Set the first <param> to enabled/error, and disable the rest // Set the first <param> to enabled/error, and disable the rest
for (uint8_t ofs = 0; ofs < maxParam; ofs++) { for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
warningStates[baseID + ofs] = warningStates[baseID + ofs] = ofs < param ? state : WARNING_DISABLED;
ofs < param ? state : WARNING_DISABLED;
} }
return true; return true;
} }
@@ -146,73 +147,70 @@ static bool tryProcessParamWarning(char const *flag, uint8_t param, enum Warning
return false; return false;
} }
enum MetaWarningCommand { enum MetaWarningCommand { META_WARNING_DONE = NB_WARNINGS };
META_WARNING_DONE = NB_WARNINGS
};
// Warnings that probably indicate an error // Warnings that probably indicate an error
static uint8_t const _wallCommands[] = { static uint8_t const _wallCommands[] = {
WARNING_BACKWARDS_FOR, WARNING_BACKWARDS_FOR,
WARNING_BUILTIN_ARG, WARNING_BUILTIN_ARG,
WARNING_CHARMAP_REDEF, WARNING_CHARMAP_REDEF,
WARNING_EMPTY_DATA_DIRECTIVE, WARNING_EMPTY_DATA_DIRECTIVE,
WARNING_EMPTY_STRRPL, WARNING_EMPTY_STRRPL,
WARNING_LARGE_CONSTANT, WARNING_LARGE_CONSTANT,
WARNING_LONG_STR, WARNING_LONG_STR,
WARNING_NESTED_COMMENT, WARNING_NESTED_COMMENT,
WARNING_OBSOLETE, WARNING_OBSOLETE,
WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_1,
WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_1,
META_WARNING_DONE META_WARNING_DONE,
}; };
// Warnings that are less likely to indicate an error // Warnings that are less likely to indicate an error
static uint8_t const _wextraCommands[] = { static uint8_t const _wextraCommands[] = {
WARNING_EMPTY_MACRO_ARG, WARNING_EMPTY_MACRO_ARG,
WARNING_MACRO_SHIFT, WARNING_MACRO_SHIFT,
WARNING_NESTED_COMMENT, WARNING_NESTED_COMMENT,
WARNING_OBSOLETE, WARNING_OBSOLETE,
WARNING_NUMERIC_STRING_2, WARNING_NUMERIC_STRING_2,
WARNING_TRUNCATION_1, WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2, WARNING_TRUNCATION_2,
WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_1,
WARNING_UNMAPPED_CHAR_2, WARNING_UNMAPPED_CHAR_2,
META_WARNING_DONE META_WARNING_DONE,
}; };
// Literally everything. Notably useful for testing // Literally everything. Notably useful for testing
static uint8_t const _weverythingCommands[] = { static uint8_t const _weverythingCommands[] = {
WARNING_BACKWARDS_FOR, WARNING_BACKWARDS_FOR,
WARNING_BUILTIN_ARG, WARNING_BUILTIN_ARG,
WARNING_DIV, WARNING_DIV,
WARNING_EMPTY_DATA_DIRECTIVE, WARNING_EMPTY_DATA_DIRECTIVE,
WARNING_EMPTY_MACRO_ARG, WARNING_EMPTY_MACRO_ARG,
WARNING_EMPTY_STRRPL, WARNING_EMPTY_STRRPL,
WARNING_LARGE_CONSTANT, WARNING_LARGE_CONSTANT,
WARNING_LONG_STR, WARNING_LONG_STR,
WARNING_MACRO_SHIFT, WARNING_MACRO_SHIFT,
WARNING_NESTED_COMMENT, WARNING_NESTED_COMMENT,
WARNING_OBSOLETE, WARNING_OBSOLETE,
WARNING_SHIFT, WARNING_SHIFT,
WARNING_SHIFT_AMOUNT, WARNING_SHIFT_AMOUNT,
WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_1,
WARNING_NUMERIC_STRING_2, WARNING_NUMERIC_STRING_2,
WARNING_TRUNCATION_1, WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2, WARNING_TRUNCATION_2,
WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_1,
WARNING_UNMAPPED_CHAR_2, WARNING_UNMAPPED_CHAR_2,
// WARNING_USER, // WARNING_USER,
META_WARNING_DONE META_WARNING_DONE,
}; };
static uint8_t const *metaWarningCommands[NB_META_WARNINGS] = { static uint8_t const *metaWarningCommands[NB_META_WARNINGS] = {
_wallCommands, _wallCommands,
_wextraCommands, _wextraCommands,
_weverythingCommands _weverythingCommands,
}; };
void processWarningFlag(char *flag) void processWarningFlag(char *flag) {
{
static bool setError = false; static bool setError = false;
// First, try to match against a "meta" warning // First, try to match against a "meta" warning
@@ -221,11 +219,11 @@ void processWarningFlag(char *flag)
if (!strcmp(flag, warningFlags[id])) { if (!strcmp(flag, warningFlags[id])) {
// We got a match! // We got a match!
if (setError) if (setError)
errx("Cannot make meta warning \"%s\" into an error", errx("Cannot make meta warning \"%s\" into an error", flag);
flag);
for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START]; for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
*ptr != META_WARNING_DONE; ptr++) { *ptr != META_WARNING_DONE;
ptr++) {
// Warning flag, set without override // Warning flag, set without override
if (warningStates[*ptr] == WARNING_DEFAULT) if (warningStates[*ptr] == WARNING_DEFAULT)
warningStates[*ptr] = WARNING_ENABLED; warningStates[*ptr] = WARNING_ENABLED;
@@ -252,16 +250,16 @@ void processWarningFlag(char *flag)
setError = false; setError = false;
return; return;
// Otherwise, allow parsing as another flag // Otherwise, allow parsing as another flag
} }
} }
// Well, it's either a normal warning or a mistake // Well, it's either a normal warning or a mistake
enum WarningState state = setError ? WARNING_ERROR enum WarningState state = setError ? WARNING_ERROR
// Not an error, then check if this is a negation // Not an error, then check if this is a negation
: strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED : strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED
: WARNING_DISABLED; : WARNING_DISABLED;
char *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag; char *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag;
// Is this a "parametric" warning? // Is this a "parametric" warning?
@@ -284,8 +282,7 @@ void processWarningFlag(char *flag)
// Avoid overflowing! // Avoid overflowing!
if (param > UINT8_MAX - (*ptr - '0')) { if (param > UINT8_MAX - (*ptr - '0')) {
if (!warned) if (!warned)
warnx("Invalid warning flag \"%s\": capping parameter at 255\n", warnx("Invalid warning flag \"%s\": capping parameter at 255\n", flag);
flag);
warned = true; // Only warn once, cap always warned = true; // Only warn once, cap always
param = 255; param = 255;
continue; continue;
@@ -303,8 +300,7 @@ void processWarningFlag(char *flag)
return; return;
} }
*equals = '\0'; // Truncate the param at the '=' *equals = '\0'; // Truncate the param at the '='
if (tryProcessParamWarning(rootFlag, param, if (tryProcessParamWarning(rootFlag, param, param == 0 ? WARNING_DISABLED : state))
param == 0 ? WARNING_DISABLED : state))
return; return;
} }
} }
@@ -327,9 +323,9 @@ void processWarningFlag(char *flag)
warnx("Unknown warning `%s`", flag); warnx("Unknown warning `%s`", flag);
} }
void printDiag(char const *fmt, va_list args, char const *type, void printDiag(
char const *flagfmt, char const *flag) char const *fmt, va_list args, char const *type, char const *flagfmt, char const *flag
{ ) {
fputs(type, stderr); fputs(type, stderr);
fputs(": ", stderr); fputs(": ", stderr);
fstk_DumpCurrent(); fstk_DumpCurrent();
@@ -339,8 +335,7 @@ void printDiag(char const *fmt, va_list args, char const *type,
lexer_DumpStringExpansions(); lexer_DumpStringExpansions();
} }
void error(char const *fmt, ...) void error(char const *fmt, ...) {
{
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
@@ -350,12 +345,15 @@ void error(char const *fmt, ...)
// This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)") // This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)")
nbErrors++; nbErrors++;
if (nbErrors == maxErrors) if (nbErrors == maxErrors)
errx("The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly aborted!", errx(
maxErrors, maxErrors == 1 ? "" : "s"); "The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly "
"aborted!",
maxErrors,
maxErrors == 1 ? "" : "s"
);
} }
[[noreturn]] void fatalerror(char const *fmt, ...) [[noreturn]] void fatalerror(char const *fmt, ...) {
{
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
@@ -365,8 +363,7 @@ void error(char const *fmt, ...)
exit(1); exit(1);
} }
void warning(enum WarningID id, char const *fmt, ...) void warning(enum WarningID id, char const *fmt, ...) {
{
char const *flag = warningFlags[id]; char const *flag = warningFlags[id];
va_list args; va_list args;

View File

@@ -1,15 +1,14 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "error.hpp"
#include <errno.h> #include <errno.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "error.hpp" static void vwarn(char const *fmt, va_list ap) {
static void vwarn(char const *fmt, va_list ap)
{
const char *error = strerror(errno); const char *error = strerror(errno);
fprintf(stderr, "warning: "); fprintf(stderr, "warning: ");
@@ -17,15 +16,13 @@ static void vwarn(char const *fmt, va_list ap)
fprintf(stderr, ": %s\n", error); fprintf(stderr, ": %s\n", error);
} }
static void vwarnx(char const *fmt, va_list ap) static void vwarnx(char const *fmt, va_list ap) {
{
fprintf(stderr, "warning: "); fprintf(stderr, "warning: ");
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
putc('\n', stderr); putc('\n', stderr);
} }
[[noreturn]] static void verr(char const *fmt, va_list ap) [[noreturn]] static void verr(char const *fmt, va_list ap) {
{
const char *error = strerror(errno); const char *error = strerror(errno);
fprintf(stderr, "error: "); fprintf(stderr, "error: ");
@@ -35,8 +32,7 @@ static void vwarnx(char const *fmt, va_list ap)
exit(1); exit(1);
} }
[[noreturn]] static void verrx(char const *fmt, va_list ap) [[noreturn]] static void verrx(char const *fmt, va_list ap) {
{
fprintf(stderr, "error: "); fprintf(stderr, "error: ");
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
putc('\n', stderr); putc('\n', stderr);
@@ -44,8 +40,7 @@ static void vwarnx(char const *fmt, va_list ap)
exit(1); exit(1);
} }
void warn(char const *fmt, ...) void warn(char const *fmt, ...) {
{
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
@@ -53,8 +48,7 @@ void warn(char const *fmt, ...)
va_end(ap); va_end(ap);
} }
void warnx(char const *fmt, ...) void warnx(char const *fmt, ...) {
{
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
@@ -62,16 +56,14 @@ void warnx(char const *fmt, ...)
va_end(ap); va_end(ap);
} }
[[noreturn]] void err(char const *fmt, ...) [[noreturn]] void err(char const *fmt, ...) {
{
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
verr(fmt, ap); verr(fmt, ap);
} }
[[noreturn]] void errx(char const *fmt, ...) [[noreturn]] void errx(char const *fmt, ...) {
{
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);

90
src/extern/getopt.cpp vendored
View File

@@ -2,32 +2,28 @@
/* This implementation was taken from musl and modified for RGBDS */ /* This implementation was taken from musl and modified for RGBDS */
#include <stddef.h> #include "extern/getopt.hpp"
#include <stdlib.h>
#include <limits.h> #include <limits.h>
#include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <wchar.h> #include <wchar.h>
#include "extern/getopt.hpp"
char *musl_optarg; char *musl_optarg;
int musl_optind = 1, musl_opterr = 1, musl_optopt; int musl_optind = 1, musl_opterr = 1, musl_optopt;
int musl_optreset = 0; int musl_optreset = 0;
static int musl_optpos; static int musl_optpos;
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) {
{
FILE *f = stderr; FILE *f = stderr;
if (fputs(a, f) >= 0 && if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l)
fwrite(b, strlen(b), 1, f) &&
fwrite(c, 1, l, f) == l)
putc('\n', f); putc('\n', f);
} }
static int getopt(int argc, char *argv[], char const *optstring) static int getopt(int argc, char *argv[], char const *optstring) {
{
int i; int i;
wchar_t c, d; wchar_t c, d;
int k, l; int k, l;
@@ -77,7 +73,7 @@ static int getopt(int argc, char *argv[], char const *optstring)
i = 0; i = 0;
d = 0; d = 0;
do { do {
l = mbtowc(&d, optstring+i, MB_LEN_MAX); l = mbtowc(&d, optstring + i, MB_LEN_MAX);
if (l > 0) if (l > 0)
i += l; i += l;
else else
@@ -101,30 +97,29 @@ static int getopt(int argc, char *argv[], char const *optstring)
if (optstring[0] == ':') if (optstring[0] == ':')
return ':'; return ':';
if (musl_opterr) if (musl_opterr)
musl_getopt_msg(argv[0], ": option requires an argument: ", musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
optchar, k);
return '?'; return '?';
} }
} }
return c; return c;
} }
static void permute(char **argv, int dest, int src) static void permute(char **argv, int dest, int src) {
{
char *tmp = argv[src]; char *tmp = argv[src];
int i; int i;
for (i = src; i > dest; i--) for (i = src; i > dest; i--)
argv[i] = argv[i-1]; argv[i] = argv[i - 1];
argv[dest] = tmp; argv[dest] = tmp;
} }
static int musl_getopt_long_core(int argc, char **argv, char const *optstring, static int musl_getopt_long_core(
const option *longopts, int *idx, int longonly); int argc, char **argv, char const *optstring, const option *longopts, int *idx, int longonly
);
static int musl_getopt_long(int argc, char **argv, char const *optstring, static int musl_getopt_long(
const option *longopts, int *idx, int longonly) int argc, char **argv, char const *optstring, const option *longopts, int *idx, int longonly
{ ) {
int ret, skipped, resumed; int ret, skipped, resumed;
if (!musl_optind || musl_optreset) { if (!musl_optind || musl_optreset) {
@@ -139,7 +134,7 @@ static int musl_getopt_long(int argc, char **argv, char const *optstring,
skipped = musl_optind; skipped = musl_optind;
if (optstring[0] != '+' && optstring[0] != '-') { if (optstring[0] != '+' && optstring[0] != '-') {
int i; int i;
for (i = musl_optind; ; i++) { for (i = musl_optind;; i++) {
if (i >= argc || !argv[i]) if (i >= argc || !argv[i])
return -1; return -1;
if (argv[i][0] == '-' && argv[i][1]) if (argv[i][0] == '-' && argv[i][1])
@@ -159,13 +154,13 @@ static int musl_getopt_long(int argc, char **argv, char const *optstring,
return ret; return ret;
} }
static int musl_getopt_long_core(int argc, char **argv, char const *optstring, static int musl_getopt_long_core(
const option *longopts, int *idx, int longonly) int argc, char **argv, char const *optstring, const option *longopts, int *idx, int longonly
{ ) {
musl_optarg = 0; musl_optarg = 0;
if (longopts && argv[musl_optind][0] == '-' && if (longopts && argv[musl_optind][0] == '-'
((longonly && argv[musl_optind][1] && argv[musl_optind][1] != '-') || && ((longonly && argv[musl_optind][1] && argv[musl_optind][1] != '-')
(argv[musl_optind][1] == '-' && argv[musl_optind][2]))) { || (argv[musl_optind][1] == '-' && argv[musl_optind][2]))) {
int colon = optstring[optstring[0] == '+' || optstring[0] == '-'] == ':'; int colon = optstring[optstring[0] == '+' || optstring[0] == '-'] == ':';
int i, cnt, match = 0; int i, cnt, match = 0;
char *arg = 0, *opt, *start = argv[musl_optind] + 1; char *arg = 0, *opt, *start = argv[musl_optind] + 1;
@@ -213,10 +208,12 @@ static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
musl_optopt = longopts[i].val; musl_optopt = longopts[i].val;
if (colon || !musl_opterr) if (colon || !musl_opterr)
return '?'; return '?';
musl_getopt_msg(argv[0], musl_getopt_msg(
": option does not take an argument: ", argv[0],
longopts[i].name, ": option does not take an argument: ",
strlen(longopts[i].name)); longopts[i].name,
strlen(longopts[i].name)
);
return '?'; return '?';
} }
musl_optarg = opt + 1; musl_optarg = opt + 1;
@@ -228,10 +225,12 @@ static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
return ':'; return ':';
if (!musl_opterr) if (!musl_opterr)
return '?'; return '?';
musl_getopt_msg(argv[0], musl_getopt_msg(
": option requires an argument: ", argv[0],
longopts[i].name, ": option requires an argument: ",
strlen(longopts[i].name)); longopts[i].name,
strlen(longopts[i].name)
);
return '?'; return '?';
} }
musl_optind++; musl_optind++;
@@ -247,11 +246,12 @@ static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
if (argv[musl_optind][1] == '-') { if (argv[musl_optind][1] == '-') {
musl_optopt = 0; musl_optopt = 0;
if (!colon && musl_opterr) if (!colon && musl_opterr)
musl_getopt_msg(argv[0], cnt ? musl_getopt_msg(
": option is ambiguous: " : argv[0],
": unrecognized option: ", cnt ? ": option is ambiguous: " : ": unrecognized option: ",
argv[musl_optind] + 2, argv[musl_optind] + 2,
strlen(argv[musl_optind] + 2)); strlen(argv[musl_optind] + 2)
);
musl_optind++; musl_optind++;
return '?'; return '?';
} }
@@ -259,8 +259,8 @@ static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
return getopt(argc, argv, optstring); return getopt(argc, argv, optstring);
} }
int musl_getopt_long_only(int argc, char **argv, char const *optstring, int musl_getopt_long_only(
const option *longopts, int *idx) int argc, char **argv, char const *optstring, const option *longopts, int *idx
{ ) {
return musl_getopt_long(argc, argv, optstring, longopts, idx, 1); return musl_getopt_long(argc, argv, optstring, longopts, idx, 1);
} }

View File

@@ -5,40 +5,37 @@
#include <stdint.h> #include <stdint.h>
static const uint8_t utf8d[] = { static const uint8_t utf8d[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00..0f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00..0f */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10..1f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10..1f */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20..2f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20..2f */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 30..3f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 30..3f */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40..4f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40..4f */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50..5f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50..5f */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60..6f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60..6f */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70..7f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70..7f */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80..8f */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80..8f */
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, /* 90..9f */ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, /* 90..9f */
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* a0..af */ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* a0..af */
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* b0..bf */ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* b0..bf */
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* c0..cf */ 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* c0..cf */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* d0..df */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* d0..df */
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, /* e0..ef */ 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, /* e0..ef */
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* f0..ff */ 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* f0..ff */
0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, /* s0 */ 0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, /* s0 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s1 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s1 */
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, /* s1 */ 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, /* s1 */
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, /* s3 */ 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, /* s3 */
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, /* s4 */ 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, /* s4 */
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, /* s5 */ 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, /* s5 */
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s6 */ 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s6 */
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s7 */ 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s7 */
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s8 */ 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s8 */
}; };
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte) uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte) {
{
uint32_t type = utf8d[byte]; uint32_t type = utf8d[byte];
*codep = (*state != 0) ? *codep = (*state != 0) ? (byte & 0x3FU) | (*codep << 6) : (0xFF >> type) & (byte);
(byte & 0x3FU) | (*codep << 6) :
(0xFF >> type) & (byte);
*state = utf8d[256 + *state * 16 + type]; *state = utf8d[256 + *state * 16 + type];
return *state; return *state;

View File

@@ -1,7 +1,8 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h>
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
@@ -14,7 +15,6 @@
#include <vector> #include <vector>
#include "extern/getopt.hpp" #include "extern/getopt.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "platform.hpp" #include "platform.hpp"
#include "version.hpp" #include "version.hpp"
@@ -37,47 +37,46 @@ static const char *optstring = "Ccf:i:jk:l:m:n:Op:r:st:Vv";
* over short opt matching * over short opt matching
*/ */
static option const longopts[] = { static option const longopts[] = {
{ "color-only", no_argument, nullptr, 'C' }, {"color-only", no_argument, nullptr, 'C'},
{ "color-compatible", no_argument, nullptr, 'c' }, {"color-compatible", no_argument, nullptr, 'c'},
{ "fix-spec", required_argument, nullptr, 'f' }, {"fix-spec", required_argument, nullptr, 'f'},
{ "game-id", required_argument, nullptr, 'i' }, {"game-id", required_argument, nullptr, 'i'},
{ "non-japanese", no_argument, nullptr, 'j' }, {"non-japanese", no_argument, nullptr, 'j'},
{ "new-licensee", required_argument, nullptr, 'k' }, {"new-licensee", required_argument, nullptr, 'k'},
{ "old-licensee", required_argument, nullptr, 'l' }, {"old-licensee", required_argument, nullptr, 'l'},
{ "mbc-type", required_argument, nullptr, 'm' }, {"mbc-type", required_argument, nullptr, 'm'},
{ "rom-version", required_argument, nullptr, 'n' }, {"rom-version", required_argument, nullptr, 'n'},
{ "overwrite", no_argument, nullptr, 'O' }, {"overwrite", no_argument, nullptr, 'O'},
{ "pad-value", required_argument, nullptr, 'p' }, {"pad-value", required_argument, nullptr, 'p'},
{ "ram-size", required_argument, nullptr, 'r' }, {"ram-size", required_argument, nullptr, 'r'},
{ "sgb-compatible", no_argument, nullptr, 's' }, {"sgb-compatible", no_argument, nullptr, 's'},
{ "title", required_argument, nullptr, 't' }, {"title", required_argument, nullptr, 't'},
{ "version", no_argument, nullptr, 'V' }, {"version", no_argument, nullptr, 'V'},
{ "validate", no_argument, nullptr, 'v' }, {"validate", no_argument, nullptr, 'v'},
{ nullptr, no_argument, nullptr, 0 } {nullptr, no_argument, nullptr, 0 }
}; };
static void printUsage() static void printUsage() {
{
fputs( fputs(
"Usage: rgbfix [-jOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n" "Usage: rgbfix [-jOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
" [-l <licensee_byte>] [-m <mbc_type>] [-n <rom_version>]\n" " [-l <licensee_byte>] [-m <mbc_type>] [-n <rom_version>]\n"
" [-p <pad_value>] [-r <ram_size>] [-t <title_str>] <file> ...\n" " [-p <pad_value>] [-r <ram_size>] [-t <title_str>] <file> ...\n"
"Useful options:\n" "Useful options:\n"
" -m, --mbc-type <value> set the MBC type byte to this value; refer\n" " -m, --mbc-type <value> set the MBC type byte to this value; refer\n"
" to the man page for a list of values\n" " to the man page for a list of values\n"
" -p, --pad-value <value> pad to the next valid size using this value\n" " -p, --pad-value <value> pad to the next valid size using this value\n"
" -r, --ram-size <code> set the cart RAM size byte to this value\n" " -r, --ram-size <code> set the cart RAM size byte to this value\n"
" -V, --version print RGBFIX version and exit\n" " -V, --version print RGBFIX version and exit\n"
" -v, --validate fix the header logo and both checksums (-f lhg)\n" " -v, --validate fix the header logo and both checksums (-f lhg)\n"
"\n" "\n"
"For help, use `man rgbfix' or go to https://rgbds.gbdev.io/docs/\n", "For help, use `man rgbfix' or go to https://rgbds.gbdev.io/docs/\n",
stderr); stderr
);
} }
static uint8_t nbErrors; static uint8_t nbErrors;
static format_(printf, 1, 2) void report(char const *fmt, ...) static format_(printf, 1, 2) void report(char const *fmt, ...) {
{
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
@@ -89,7 +88,7 @@ static format_(printf, 1, 2) void report(char const *fmt, ...)
} }
enum MbcType { enum MbcType {
ROM = 0x00, ROM = 0x00,
ROM_RAM = 0x08, ROM_RAM = 0x08,
ROM_RAM_BATTERY = 0x09, ROM_RAM_BATTERY = 0x09,
@@ -153,13 +152,12 @@ enum MbcType {
// Error values // Error values
MBC_NONE = UNSPECIFIED, // No MBC specified, do not act on it MBC_NONE = UNSPECIFIED, // No MBC specified, do not act on it
MBC_BAD, // Specified MBC does not exist / syntax error MBC_BAD, // Specified MBC does not exist / syntax error
MBC_WRONG_FEATURES, // MBC incompatible with specified features MBC_WRONG_FEATURES, // MBC incompatible with specified features
MBC_BAD_RANGE, // MBC number out of range MBC_BAD_RANGE, // MBC number out of range
}; };
static void printAcceptedMBCNames() static void printAcceptedMBCNames() {
{
fputs("\tROM ($00) [aka ROM_ONLY]\n", stderr); fputs("\tROM ($00) [aka ROM_ONLY]\n", stderr);
fputs("\tMBC1 ($01), MBC1+RAM ($02), MBC1+RAM+BATTERY ($03)\n", stderr); fputs("\tMBC1 ($01), MBC1+RAM ($02), MBC1+RAM+BATTERY ($03)\n", stderr);
fputs("\tMBC2 ($05), MBC2+BATTERY ($06)\n", stderr); fputs("\tMBC2 ($05), MBC2+BATTERY ($06)\n", stderr);
@@ -188,8 +186,7 @@ static uint8_t tpp1Rev[2];
/* /*
* @return False on failure * @return False on failure
*/ */
static bool readMBCSlice(char const *&name, char const *expected) static bool readMBCSlice(char const *&name, char const *expected) {
{
while (*expected) { while (*expected) {
char c = *name++; char c = *name++;
@@ -207,8 +204,7 @@ static bool readMBCSlice(char const *&name, char const *expected)
return true; return true;
} }
static enum MbcType parseMBC(char const *name) static enum MbcType parseMBC(char const *name) {
{
if (!strcasecmp(name, "help")) { if (!strcasecmp(name, "help")) {
fputs("Accepted MBC names:\n", stderr); fputs("Accepted MBC names:\n", stderr);
printAcceptedMBCNames(); printAcceptedMBCNames();
@@ -242,10 +238,10 @@ static enum MbcType parseMBC(char const *name)
ptr++; ptr++;
#define tryReadSlice(expected) \ #define tryReadSlice(expected) \
do { \ do { \
if (!readMBCSlice(ptr, expected)) \ if (!readMBCSlice(ptr, expected)) \
return MBC_BAD; \ return MBC_BAD; \
} while (0) } while (0)
switch (*ptr++) { switch (*ptr++) {
case 'R': // ROM / ROM_ONLY case 'R': // ROM / ROM_ONLY
@@ -386,11 +382,11 @@ do { \
// Read "additional features" // Read "additional features"
uint8_t features = 0; uint8_t features = 0;
#define RAM 0x80 #define RAM 0x80
#define BATTERY 0x40 #define BATTERY 0x40
#define TIMER 0x20 #define TIMER 0x20
#define RUMBLE 0x10 #define RUMBLE 0x10
#define SENSOR 0x08 #define SENSOR 0x08
#define MULTIRUMBLE 0x04 #define MULTIRUMBLE 0x04
for (;;) { for (;;) {
@@ -498,8 +494,9 @@ do { \
} }
static_assert(MBC3 + 1 == MBC3_RAM, "Enum sanity check failed!"); static_assert(MBC3 + 1 == MBC3_RAM, "Enum sanity check failed!");
static_assert(MBC3 + 2 == MBC3_RAM_BATTERY, "Enum sanity check failed!"); static_assert(MBC3 + 2 == MBC3_RAM_BATTERY, "Enum sanity check failed!");
static_assert(MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, static_assert(
"Enum sanity check failed!"); MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, "Enum sanity check failed!"
);
if (features == RAM) if (features == RAM)
mbc++; mbc++;
else if (features == (RAM | BATTERY)) else if (features == (RAM | BATTERY))
@@ -516,8 +513,7 @@ do { \
static_assert(MBC5 + 1 == MBC5_RAM, "Enum sanity check failed!"); static_assert(MBC5 + 1 == MBC5_RAM, "Enum sanity check failed!");
static_assert(MBC5 + 2 == MBC5_RAM_BATTERY, "Enum sanity check failed!"); static_assert(MBC5 + 2 == MBC5_RAM_BATTERY, "Enum sanity check failed!");
static_assert(MBC5_RUMBLE + 1 == MBC5_RUMBLE_RAM, "Enum sanity check failed!"); static_assert(MBC5_RUMBLE + 1 == MBC5_RUMBLE_RAM, "Enum sanity check failed!");
static_assert(MBC5_RUMBLE + 2 == MBC5_RUMBLE_RAM_BATTERY, static_assert(MBC5_RUMBLE + 2 == MBC5_RUMBLE_RAM_BATTERY, "Enum sanity check failed!");
"Enum sanity check failed!");
if (features == RAM) if (features == RAM)
mbc++; mbc++;
else if (features == (RAM | BATTERY)) else if (features == (RAM | BATTERY))
@@ -547,8 +543,9 @@ do { \
case TPP1: case TPP1:
if (features & RAM) if (features & RAM)
fprintf(stderr, fprintf(
"warning: TPP1 requests RAM implicitly if given a non-zero RAM size"); stderr, "warning: TPP1 requests RAM implicitly if given a non-zero RAM size"
);
if (features & BATTERY) if (features & BATTERY)
mbc |= 0x08; mbc |= 0x08;
if (features & TIMER) if (features & TIMER)
@@ -574,8 +571,7 @@ do { \
} }
} }
static char const *mbcName(enum MbcType type) static char const *mbcName(enum MbcType type) {
{
switch (type) { switch (type) {
case ROM: case ROM:
return "ROM"; return "ROM";
@@ -673,8 +669,7 @@ static char const *mbcName(enum MbcType type)
unreachable_(); unreachable_();
} }
static bool hasRAM(enum MbcType type) static bool hasRAM(enum MbcType type) {
{
switch (type) { switch (type) {
case ROM: case ROM:
case MBC1: case MBC1:
@@ -685,7 +680,7 @@ static bool hasRAM(enum MbcType type)
case MBC3_TIMER_BATTERY: case MBC3_TIMER_BATTERY:
case MBC5: case MBC5:
case MBC5_RUMBLE: case MBC5_RUMBLE:
case MBC6: // TODO: not sure case MBC6: // TODO: not sure
case BANDAI_TAMA5: // TODO: not sure case BANDAI_TAMA5: // TODO: not sure
case MBC_NONE: case MBC_NONE:
case MBC_BAD: case MBC_BAD:
@@ -736,30 +731,28 @@ static bool hasRAM(enum MbcType type)
} }
static const uint8_t ninLogo[] = { static const uint8_t ninLogo[] = {
0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D,
0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99,
0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E,
0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99,
0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC,
0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E
}; };
static const uint8_t trashLogo[] = { static const uint8_t trashLogo[] = {
0xFF^0xCE, 0xFF^0xED, 0xFF^0x66, 0xFF^0x66, 0xFF^0xCC, 0xFF^0x0D, 0xFF^0x00, 0xFF^0x0B, 0xFF ^ 0xCE, 0xFF ^ 0xED, 0xFF ^ 0x66, 0xFF ^ 0x66, 0xFF ^ 0xCC, 0xFF ^ 0x0D, 0xFF ^ 0x00,
0xFF^0x03, 0xFF^0x73, 0xFF^0x00, 0xFF^0x83, 0xFF^0x00, 0xFF^0x0C, 0xFF^0x00, 0xFF^0x0D, 0xFF ^ 0x0B, 0xFF ^ 0x03, 0xFF ^ 0x73, 0xFF ^ 0x00, 0xFF ^ 0x83, 0xFF ^ 0x00, 0xFF ^ 0x0C,
0xFF^0x00, 0xFF^0x08, 0xFF^0x11, 0xFF^0x1F, 0xFF^0x88, 0xFF^0x89, 0xFF^0x00, 0xFF^0x0E, 0xFF ^ 0x00, 0xFF ^ 0x0D, 0xFF ^ 0x00, 0xFF ^ 0x08, 0xFF ^ 0x11, 0xFF ^ 0x1F, 0xFF ^ 0x88,
0xFF^0xDC, 0xFF^0xCC, 0xFF^0x6E, 0xFF^0xE6, 0xFF^0xDD, 0xFF^0xDD, 0xFF^0xD9, 0xFF^0x99, 0xFF ^ 0x89, 0xFF ^ 0x00, 0xFF ^ 0x0E, 0xFF ^ 0xDC, 0xFF ^ 0xCC, 0xFF ^ 0x6E, 0xFF ^ 0xE6,
0xFF^0xBB, 0xFF^0xBB, 0xFF^0x67, 0xFF^0x63, 0xFF^0x6E, 0xFF^0x0E, 0xFF^0xEC, 0xFF^0xCC, 0xFF ^ 0xDD, 0xFF ^ 0xDD, 0xFF ^ 0xD9, 0xFF ^ 0x99, 0xFF ^ 0xBB, 0xFF ^ 0xBB, 0xFF ^ 0x67,
0xFF^0xDD, 0xFF^0xDC, 0xFF^0x99, 0xFF^0x9F, 0xFF^0xBB, 0xFF^0xB9, 0xFF^0x33, 0xFF^0x3E 0xFF ^ 0x63, 0xFF ^ 0x6E, 0xFF ^ 0x0E, 0xFF ^ 0xEC, 0xFF ^ 0xCC, 0xFF ^ 0xDD, 0xFF ^ 0xDC,
0xFF ^ 0x99, 0xFF ^ 0x9F, 0xFF ^ 0xBB, 0xFF ^ 0xB9, 0xFF ^ 0x33, 0xFF ^ 0x3E,
}; };
static enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone static enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone
#define FIX_LOGO 0x80 #define FIX_LOGO 0x80
#define TRASH_LOGO 0x40 #define TRASH_LOGO 0x40
#define FIX_HEADER_SUM 0x20 #define FIX_HEADER_SUM 0x20
#define TRASH_HEADER_SUM 0x10 #define TRASH_HEADER_SUM 0x10
#define FIX_GLOBAL_SUM 0x08 #define FIX_GLOBAL_SUM 0x08
#define TRASH_GLOBAL_SUM 0x04 #define TRASH_GLOBAL_SUM 0x04
static uint8_t fixSpec = 0; static uint8_t fixSpec = 0;
static const char *gameID = nullptr; static const char *gameID = nullptr;
static uint8_t gameIDLen; static uint8_t gameIDLen;
@@ -776,13 +769,11 @@ static bool sgb = false; // If false, SGB flags are left alone
static const char *title = nullptr; static const char *title = nullptr;
static uint8_t titleLen; static uint8_t titleLen;
static uint8_t maxTitleLen() static uint8_t maxTitleLen() {
{
return gameID ? 11 : model != DMG ? 15 : 16; return gameID ? 11 : model != DMG ? 15 : 16;
} }
static ssize_t readBytes(int fd, uint8_t *buf, size_t len) static ssize_t readBytes(int fd, uint8_t *buf, size_t len) {
{
// POSIX specifies that lengths greater than SSIZE_MAX yield implementation-defined results // POSIX specifies that lengths greater than SSIZE_MAX yield implementation-defined results
assert(len <= SSIZE_MAX); assert(len <= SSIZE_MAX);
@@ -807,8 +798,7 @@ static ssize_t readBytes(int fd, uint8_t *buf, size_t len)
return total; return total;
} }
static ssize_t writeBytes(int fd, uint8_t *buf, size_t len) static ssize_t writeBytes(int fd, uint8_t *buf, size_t len) {
{
// POSIX specifies that lengths greater than SSIZE_MAX yield implementation-defined results // POSIX specifies that lengths greater than SSIZE_MAX yield implementation-defined results
assert(len <= SSIZE_MAX); assert(len <= SSIZE_MAX);
@@ -838,8 +828,7 @@ static ssize_t writeBytes(int fd, uint8_t *buf, size_t len)
* @param fixedByte The fixed byte at the address * @param fixedByte The fixed byte at the address
* @param areaName Name to be displayed in the warning message * @param areaName Name to be displayed in the warning message
*/ */
static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName) static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName) {
{
uint8_t origByte = rom0[addr]; uint8_t origByte = rom0[addr];
if (!overwriteRom && origByte != 0 && origByte != fixedByte) if (!overwriteRom && origByte != 0 && origByte != fixedByte)
@@ -855,16 +844,15 @@ static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char
* @param size How many bytes to check * @param size How many bytes to check
* @param areaName Name to be displayed in the warning message * @param areaName Name to be displayed in the warning message
*/ */
static void overwriteBytes(uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size, static void overwriteBytes(
char const *areaName) uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size, char const *areaName
{ ) {
if (!overwriteRom) { if (!overwriteRom) {
for (uint8_t i = 0; i < size; i++) { for (uint8_t i = 0; i < size; i++) {
uint8_t origByte = rom0[i + startAddr]; uint8_t origByte = rom0[i + startAddr];
if (origByte != 0 && origByte != fixed[i]) { if (origByte != 0 && origByte != fixed[i]) {
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName);
areaName);
break; break;
} }
} }
@@ -879,8 +867,7 @@ static void overwriteBytes(uint8_t *rom0, uint16_t startAddr, uint8_t const *fix
* @param name The file's name, to be displayed for error output * @param name The file's name, to be displayed for error output
* @param fileSize The file's size if known, 0 if not. * @param fileSize The file's size if known, 0 if not.
*/ */
static void processFile(int input, int output, char const *name, off_t fileSize) static void processFile(int input, int output, char const *name, off_t fileSize) {
{
// Both of these should be true for seekable files, and neither otherwise // Both of these should be true for seekable files, and neither otherwise
if (input == output) if (input == output)
assert(fileSize != 0); assert(fileSize != 0);
@@ -896,8 +883,13 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno)); report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno));
return; return;
} else if (rom0Len < headerSize) { } else if (rom0Len < headerSize) {
report("FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n", report(
name, (intmax_t)headerSize, (intmax_t)headerSize, (intmax_t)rom0Len); "FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
name,
(intmax_t)headerSize,
(intmax_t)headerSize,
(intmax_t)rom0Len
);
return; return;
} }
// Accept partial reads if the file contains at least the header // Accept partial reads if the file contains at least the header
@@ -919,8 +911,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag"); overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag");
if (newLicensee) if (newLicensee)
overwriteBytes(rom0, 0x144, (uint8_t const *)newLicensee, newLicenseeLen, overwriteBytes(
"new licensee code"); rom0, 0x144, (uint8_t const *)newLicensee, newLicenseeLen, "new licensee code"
);
if (sgb) if (sgb)
overwriteByte(rom0, 0x146, 0x03, "SGB flag"); overwriteByte(rom0, 0x146, 0x03, "SGB flag");
@@ -963,9 +956,11 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (oldLicensee != UNSPECIFIED) if (oldLicensee != UNSPECIFIED)
overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code"); overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code");
else if (sgb && rom0[0x14B] != 0x33) else if (sgb && rom0[0x14B] != 0x33)
fprintf(stderr, fprintf(
"warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n", stderr,
rom0[0x14B]); "warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n",
rom0[0x14B]
);
if (romVersion != UNSPECIFIED) if (romVersion != UNSPECIFIED)
overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number"); overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number");
@@ -981,9 +976,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
// 65536 banks = 1 GiB. // 65536 banks = 1 GiB.
// This should be reasonable for the time being, and may be extended later. // This should be reasonable for the time being, and may be extended later.
std::vector<uint8_t> romx; // Buffer of ROMX bank data std::vector<uint8_t> romx; // Buffer of ROMX bank data
uint32_t nbBanks = 1; // Number of banks *targeted*, including ROM0 uint32_t nbBanks = 1; // Number of banks *targeted*, including ROM0
size_t totalRomxLen = 0; // *Actual* size of ROMX data size_t totalRomxLen = 0; // *Actual* size of ROMX data
uint8_t bank[BANK_SIZE]; // Temp buffer used to store a whole bank's worth of data uint8_t bank[BANK_SIZE]; // Temp buffer used to store a whole bank's worth of data
// Handle ROMX // Handle ROMX
if (input == output) { if (input == output) {
@@ -1006,7 +1001,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
// Update bank count, ONLY IF at least one byte was read // Update bank count, ONLY IF at least one byte was read
if (bankLen) { if (bankLen) {
// We're gonna read another bank, check that it won't be too much // We're gonna read another bank, check that it won't be too much
static_assert(0x10000 * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS"); static_assert(
0x10000 * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS"
);
if (nbBanks == 0x10000) { if (nbBanks == 0x10000) {
report("FATAL: \"%s\" has more than 65536 banks\n", name); report("FATAL: \"%s\" has more than 65536 banks\n", name);
return; return;
@@ -1062,8 +1059,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
for (uint16_t i = 0x134; i < 0x14D; i++) for (uint16_t i = 0x134; i < 0x14D; i++)
sum -= rom0[i] + 1; sum -= rom0[i] + 1;
overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum, overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum, "header checksum");
"header checksum");
} }
if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) { if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) {
@@ -1113,8 +1109,12 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno)); report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno));
return; return;
} else if (writeLen < rom0Len) { } else if (writeLen < rom0Len) {
report("FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n", report(
(intmax_t)writeLen, name, (intmax_t)rom0Len); "FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
(intmax_t)writeLen,
name,
(intmax_t)rom0Len
);
return; return;
} }
@@ -1127,8 +1127,12 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno)); report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
return; return;
} else if ((size_t)writeLen < totalRomxLen) { } else if ((size_t)writeLen < totalRomxLen) {
report("FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n", report(
(intmax_t)writeLen, name, totalRomxLen); "FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
(intmax_t)writeLen,
name,
totalRomxLen
);
return; return;
} }
} }
@@ -1137,8 +1141,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (padValue != UNSPECIFIED) { if (padValue != UNSPECIFIED) {
if (input == output) { if (input == output) {
if (lseek(output, 0, SEEK_END) == (off_t)-1) { if (lseek(output, 0, SEEK_END) == (off_t)-1) {
report("FATAL: Failed to seek to end of \"%s\": %s\n", report("FATAL: Failed to seek to end of \"%s\": %s\n", name, strerror(errno));
name, strerror(errno));
return; return;
} }
} }
@@ -1153,8 +1156,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
// The return value is either -1, or at most `thisLen`, // The return value is either -1, or at most `thisLen`,
// so it's fine to cast to `size_t` // so it's fine to cast to `size_t`
if ((size_t)ret != thisLen) { if ((size_t)ret != thisLen) {
report("FATAL: Failed to write \"%s\"'s padding: %s\n", report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
name, strerror(errno));
break; break;
} }
len -= thisLen; len -= thisLen;
@@ -1162,8 +1164,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
} }
} }
static bool processFilename(char const *name) static bool processFilename(char const *name) {
{
nbErrors = 0; nbErrors = 0;
if (!strcmp(name, "-")) { if (!strcmp(name, "-")) {
(void)setmode(STDIN_FILENO, O_BINARY); (void)setmode(STDIN_FILENO, O_BINARY);
@@ -1181,21 +1182,24 @@ static bool processFilename(char const *name)
struct stat stat; struct stat stat;
if (input == -1) { if (input == -1) {
report("FATAL: Failed to open \"%s\" for reading+writing: %s\n", report("FATAL: Failed to open \"%s\" for reading+writing: %s\n", name, strerror(errno));
name, strerror(errno));
goto finish; goto finish;
} }
if (fstat(input, &stat) == -1) { if (fstat(input, &stat) == -1) {
report("FATAL: Failed to stat \"%s\": %s\n", name, strerror(errno)); report("FATAL: Failed to stat \"%s\": %s\n", name, strerror(errno));
} else if (!S_ISREG(stat.st_mode)) { // TODO: Do we want to support other types? } else if (!S_ISREG(stat.st_mode)) { // TODO: Do we want to support other types?
report("FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n", report(
name); "FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n", name
);
} else if (stat.st_size < 0x150) { } else if (stat.st_size < 0x150) {
// This check is in theory redundant with the one in `processFile`, but it // This check is in theory redundant with the one in `processFile`, but it
// prevents passing a file size of 0, which usually indicates pipes // prevents passing a file size of 0, which usually indicates pipes
report("FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n", report(
name, (intmax_t)stat.st_size); "FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n",
name,
(intmax_t)stat.st_size
);
} else { } else {
processFile(input, input, name, stat.st_size); processFile(input, input, name, stat.st_size);
} }
@@ -1204,48 +1208,53 @@ static bool processFilename(char const *name)
} }
finish: finish:
if (nbErrors) if (nbErrors)
fprintf(stderr, "Fixing \"%s\" failed with %u error%s\n", fprintf(
name, nbErrors, nbErrors == 1 ? "" : "s"); stderr,
"Fixing \"%s\" failed with %u error%s\n",
name,
nbErrors,
nbErrors == 1 ? "" : "s"
);
return nbErrors; return nbErrors;
} }
int main(int argc, char *argv[]) int main(int argc, char *argv[]) {
{
nbErrors = 0; nbErrors = 0;
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) { for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
switch (ch) { switch (ch) {
size_t len; size_t len;
#define parseByte(output, name) \ #define parseByte(output, name) \
do { \ do { \
char *endptr; \ char *endptr; \
unsigned long tmp; \ unsigned long tmp; \
\ \
if (musl_optarg[0] == 0) { \ if (musl_optarg[0] == 0) { \
report("error: Argument to option '" name "' may not be empty\n"); \ report("error: Argument to option '" name "' may not be empty\n"); \
} else { \
if (musl_optarg[0] == '$') { \
tmp = strtoul(&musl_optarg[1], &endptr, 16); \
} else { \ } else { \
tmp = strtoul(musl_optarg, &endptr, 0); \ if (musl_optarg[0] == '$') { \
tmp = strtoul(&musl_optarg[1], &endptr, 16); \
} else { \
tmp = strtoul(musl_optarg, &endptr, 0); \
} \
if (*endptr) \
report( \
"error: Expected number as argument to option '" name "', got %s\n", \
musl_optarg \
); \
else if (tmp > 0xFF) \
report("error: Argument to option '" name "' is larger than 255: %lu\n", tmp); \
else \
output = tmp; \
} \ } \
if (*endptr) \ } while (0)
report("error: Expected number as argument to option '" name "', got %s\n", \
musl_optarg); \
else if (tmp > 0xFF) \
report("error: Argument to option '" name "' is larger than 255: %lu\n", tmp); \
else \
output = tmp; \
} \
} while (0)
case 'C': case 'C':
case 'c': case 'c':
model = ch == 'c' ? BOTH : CGB; model = ch == 'c' ? BOTH : CGB;
if (titleLen > 15) { if (titleLen > 15) {
titleLen = 15; titleLen = 15;
fprintf(stderr, "warning: Truncating title \"%s\" to 15 chars\n", fprintf(stderr, "warning: Truncating title \"%s\" to 15 chars\n", title);
title);
} }
break; break;
@@ -1260,12 +1269,11 @@ do { \
#define SPEC_g FIX_GLOBAL_SUM #define SPEC_g FIX_GLOBAL_SUM
#define SPEC_G TRASH_GLOBAL_SUM #define SPEC_G TRASH_GLOBAL_SUM
#define overrideSpec(cur, bad) \ #define overrideSpec(cur, bad) \
do { \ do { \
if (fixSpec & SPEC_##bad) \ if (fixSpec & SPEC_##bad) \
fprintf(stderr, \ fprintf(stderr, "warning: '" #cur "' overriding '" #bad "' in fix spec\n"); \
"warning: '" #cur "' overriding '" #bad "' in fix spec\n"); \ fixSpec = (fixSpec & ~SPEC_##bad) | SPEC_##cur; \
fixSpec = (fixSpec & ~SPEC_##bad) | SPEC_##cur; \ } while (0)
} while (0)
case 'l': case 'l':
overrideSpec(l, L); overrideSpec(l, L);
break; break;
@@ -1288,8 +1296,7 @@ do { \
break; break;
default: default:
fprintf(stderr, "warning: Ignoring '%c' in fix spec\n", fprintf(stderr, "warning: Ignoring '%c' in fix spec\n", *musl_optarg);
*musl_optarg);
#undef overrideSpec #undef overrideSpec
} }
musl_optarg++; musl_optarg++;
@@ -1301,14 +1308,12 @@ do { \
len = strlen(gameID); len = strlen(gameID);
if (len > 4) { if (len > 4) {
len = 4; len = 4;
fprintf(stderr, "warning: Truncating game ID \"%s\" to 4 chars\n", fprintf(stderr, "warning: Truncating game ID \"%s\" to 4 chars\n", gameID);
gameID);
} }
gameIDLen = len; gameIDLen = len;
if (titleLen > 11) { if (titleLen > 11) {
titleLen = 11; titleLen = 11;
fprintf(stderr, "warning: Truncating title \"%s\" to 11 chars\n", fprintf(stderr, "warning: Truncating title \"%s\" to 11 chars\n", title);
title);
} }
break; break;
@@ -1321,9 +1326,9 @@ do { \
len = strlen(newLicensee); len = strlen(newLicensee);
if (len > 2) { if (len > 2) {
len = 2; len = 2;
fprintf(stderr, fprintf(
"warning: Truncating new licensee \"%s\" to 2 chars\n", stderr, "warning: Truncating new licensee \"%s\" to 2 chars\n", newLicensee
newLicensee); );
} }
newLicenseeLen = len; newLicenseeLen = len;
break; break;
@@ -1335,18 +1340,22 @@ do { \
case 'm': case 'm':
cartridgeType = parseMBC(musl_optarg); cartridgeType = parseMBC(musl_optarg);
if (cartridgeType == MBC_BAD) { if (cartridgeType == MBC_BAD) {
report("error: Unknown MBC \"%s\"\nAccepted MBC names:\n", report("error: Unknown MBC \"%s\"\nAccepted MBC names:\n", musl_optarg);
musl_optarg);
printAcceptedMBCNames(); printAcceptedMBCNames();
} else if (cartridgeType == MBC_WRONG_FEATURES) { } else if (cartridgeType == MBC_WRONG_FEATURES) {
report("error: Features incompatible with MBC (\"%s\")\nAccepted combinations:\n", report(
musl_optarg); "error: Features incompatible with MBC (\"%s\")\nAccepted combinations:\n",
musl_optarg
);
printAcceptedMBCNames(); printAcceptedMBCNames();
} else if (cartridgeType == MBC_BAD_RANGE) { } else if (cartridgeType == MBC_BAD_RANGE) {
report("error: Specified MBC ID out of range 0-255: %s\n", report("error: Specified MBC ID out of range 0-255: %s\n", musl_optarg);
musl_optarg);
} else if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) { } else if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
fprintf(stderr, "warning: ROM+RAM / ROM+RAM+BATTERY are under-specified and poorly supported\n"); fprintf(
stderr,
"warning: ROM+RAM / ROM+RAM+BATTERY are under-specified and poorly "
"supported\n"
);
} }
break; break;
@@ -1377,8 +1386,7 @@ do { \
if (len > maxLen) { if (len > maxLen) {
len = maxLen; len = maxLen;
fprintf(stderr, "warning: Truncating title \"%s\" to %u chars\n", fprintf(stderr, "warning: Truncating title \"%s\" to %u chars\n", title, maxLen);
title, maxLen);
} }
titleLen = len; titleLen = len;
break; break;
@@ -1401,42 +1409,58 @@ do { \
} }
if ((cartridgeType & 0xFF00) == TPP1 && !japanese) if ((cartridgeType & 0xFF00) == TPP1 && !japanese)
fprintf(stderr, "warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n"); fprintf(
stderr,
"warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n"
);
// Check that RAM size is correct for "standard" mappers // Check that RAM size is correct for "standard" mappers
if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) { if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) {
if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) { if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
if (ramSize != 1) if (ramSize != 1)
fprintf(stderr, "warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n", fprintf(
mbcName(cartridgeType)); stderr,
"warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n",
mbcName(cartridgeType)
);
} else if (hasRAM(cartridgeType)) { } else if (hasRAM(cartridgeType)) {
if (!ramSize) { if (!ramSize) {
fprintf(stderr, fprintf(
"warning: MBC \"%s\" has RAM, but RAM size was set to 0\n", stderr,
mbcName(cartridgeType)); "warning: MBC \"%s\" has RAM, but RAM size was set to 0\n",
mbcName(cartridgeType)
);
} else if (ramSize == 1) { } else if (ramSize == 1) {
fprintf(stderr, fprintf(
"warning: RAM size 1 (2 KiB) was specified for MBC \"%s\"\n", stderr,
mbcName(cartridgeType)); "warning: RAM size 1 (2 KiB) was specified for MBC \"%s\"\n",
mbcName(cartridgeType)
);
} // TODO: check possible values? } // TODO: check possible values?
} else if (ramSize) { } else if (ramSize) {
fprintf(stderr, fprintf(
"warning: MBC \"%s\" has no RAM, but RAM size was set to %u\n", stderr,
mbcName(cartridgeType), ramSize); "warning: MBC \"%s\" has no RAM, but RAM size was set to %u\n",
mbcName(cartridgeType),
ramSize
);
} }
} }
if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33) if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33)
fprintf(stderr, fprintf(
"warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n", stderr,
oldLicensee); "warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n",
oldLicensee
);
argv += musl_optind; argv += musl_optind;
bool failed = nbErrors; bool failed = nbErrors;
if (!*argv) { if (!*argv) {
fputs("FATAL: Please specify an input file (pass `-` to read from standard input)\n", fputs(
stderr); "FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr
);
printUsage(); printUsage();
exit(1); exit(1);
} }

View File

@@ -118,52 +118,54 @@ static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvx:Z";
* over short opt matching * over short opt matching
*/ */
static option const longopts[] = { static option const longopts[] = {
{"auto-attr-map", no_argument, nullptr, 'A'}, {"auto-attr-map", no_argument, nullptr, 'A' },
{"output-attr-map", no_argument, nullptr, -'A'}, // Deprecated {"output-attr-map", no_argument, nullptr, -'A'}, // Deprecated
{"attr-map", required_argument, nullptr, 'a'}, {"attr-map", required_argument, nullptr, 'a' },
{"base-tiles", required_argument, nullptr, 'b'}, {"base-tiles", required_argument, nullptr, 'b' },
{"color-curve", no_argument, nullptr, 'C'}, {"color-curve", no_argument, nullptr, 'C' },
{"colors", required_argument, nullptr, 'c'}, {"colors", required_argument, nullptr, 'c' },
{"depth", required_argument, nullptr, 'd'}, {"depth", required_argument, nullptr, 'd' },
{"slice", required_argument, nullptr, 'L'}, {"slice", required_argument, nullptr, 'L' },
{"mirror-tiles", no_argument, nullptr, 'm'}, {"mirror-tiles", no_argument, nullptr, 'm' },
{"nb-tiles", required_argument, nullptr, 'N'}, {"nb-tiles", required_argument, nullptr, 'N' },
{"nb-palettes", required_argument, nullptr, 'n'}, {"nb-palettes", required_argument, nullptr, 'n' },
{"group-outputs", no_argument, nullptr, 'O'}, {"group-outputs", no_argument, nullptr, 'O' },
{"output", required_argument, nullptr, 'o'}, {"output", required_argument, nullptr, 'o' },
{"auto-palette", no_argument, nullptr, 'P'}, {"auto-palette", no_argument, nullptr, 'P' },
{"output-palette", no_argument, nullptr, -'P'}, // Deprecated {"output-palette", no_argument, nullptr, -'P'}, // Deprecated
{"palette", required_argument, nullptr, 'p'}, {"palette", required_argument, nullptr, 'p' },
{"auto-palette-map", no_argument, nullptr, 'Q'}, {"auto-palette-map", no_argument, nullptr, 'Q' },
{"output-palette-map", no_argument, nullptr, -'Q'}, // Deprecated {"output-palette-map", no_argument, nullptr, -'Q'}, // Deprecated
{"palette-map", required_argument, nullptr, 'q'}, {"palette-map", required_argument, nullptr, 'q' },
{"reverse", required_argument, nullptr, 'r'}, {"reverse", required_argument, nullptr, 'r' },
{"auto-tilemap", no_argument, nullptr, 'T'}, {"auto-tilemap", no_argument, nullptr, 'T' },
{"output-tilemap", no_argument, nullptr, -'T'}, // Deprecated {"output-tilemap", no_argument, nullptr, -'T'}, // Deprecated
{"tilemap", required_argument, nullptr, 't'}, {"tilemap", required_argument, nullptr, 't' },
{"unit-size", required_argument, nullptr, 'U'}, {"unit-size", required_argument, nullptr, 'U' },
{"unique-tiles", no_argument, nullptr, 'u'}, {"unique-tiles", no_argument, nullptr, 'u' },
{"version", no_argument, nullptr, 'V'}, {"version", no_argument, nullptr, 'V' },
{"verbose", no_argument, nullptr, 'v'}, {"verbose", no_argument, nullptr, 'v' },
{"trim-end", required_argument, nullptr, 'x'}, {"trim-end", required_argument, nullptr, 'x' },
{"columns", no_argument, nullptr, 'Z'}, {"columns", no_argument, nullptr, 'Z' },
{nullptr, no_argument, nullptr, 0 } {nullptr, no_argument, nullptr, 0 }
}; };
static void printUsage() { static void printUsage() {
fputs("Usage: rgbgfx [-r stride] [-CmOuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n" fputs(
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n" "Usage: rgbgfx [-r stride] [-CmOuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
" [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n" " [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n"
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n" " [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
"Useful options:\n" " [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
" -m, --mirror-tiles optimize out mirrored tiles\n" "Useful options:\n"
" -o, --output <path> output the tile data to this path\n" " -m, --mirror-tiles optimize out mirrored tiles\n"
" -t, --tilemap <path> output the tile map to this path\n" " -o, --output <path> output the tile data to this path\n"
" -u, --unique-tiles optimize out identical tiles\n" " -t, --tilemap <path> output the tile map to this path\n"
" -V, --version print RGBGFX version and exit\n" " -u, --unique-tiles optimize out identical tiles\n"
"\n" " -V, --version print RGBGFX version and exit\n"
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n", "\n"
stderr); "For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n",
stderr
);
} }
/* /*
@@ -215,8 +217,9 @@ static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVa
}; };
if (charIndex(*string) == 255) { if (charIndex(*string) == 255) {
error("%s: expected digit%s, but found nothing", errPrefix, error(
base != 10 ? " after base" : ""); "%s: expected digit%s, but found nothing", errPrefix, base != 10 ? " after base" : ""
);
return errVal; return errVal;
} }
uint16_t number = 0; uint16_t number = 0;
@@ -246,10 +249,13 @@ static void skipWhitespace(char *&arg) {
static void registerInput(char const *arg) { static void registerInput(char const *arg) {
if (!options.input.empty()) { if (!options.input.empty()) {
fprintf(stderr, fprintf(
"FATAL: input image specified more than once! (first \"%s\", then " stderr,
"\"%s\")\n", "FATAL: input image specified more than once! (first \"%s\", then "
options.input.c_str(), arg); "\"%s\")\n",
options.input.c_str(),
arg
);
printUsage(); printUsage();
exit(1); exit(1);
} else if (arg[0] == '\0') { // Empty input path } else if (arg[0] == '\0') { // Empty input path
@@ -272,8 +278,10 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
} }
// We only filter out `EOF`, but calling `isblank()` on anything else is UB! // We only filter out `EOF`, but calling `isblank()` on anything else is UB!
static_assert(std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF, static_assert(
"isblank(char_traits<...>::eof()) is UB!"); std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF,
"isblank(char_traits<...>::eof()) is UB!"
);
std::vector<size_t> argvOfs; std::vector<size_t> argvOfs;
for (;;) { for (;;) {
@@ -296,7 +304,7 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
} }
continue; // Start processing the next line continue; // Start processing the next line
// If it's an empty line, ignore it // If it's an empty line, ignore it
case '\r': // Assuming CRLF here case '\r': // Assuming CRLF here
file->sbumpc(); // Discard the upcoming '\n' file->sbumpc(); // Discard the upcoming '\n'
[[fallthrough]]; [[fallthrough]];
case '\n': case '\n':
@@ -371,8 +379,10 @@ static char *parseArgv(int argc, char *argv[]) {
} }
skipWhitespace(arg); skipWhitespace(arg);
if (*arg != ',') { if (*arg != ',') {
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"", error(
musl_optarg); "Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
musl_optarg
);
break; break;
} }
++arg; // Skip comma ++arg; // Skip comma
@@ -384,8 +394,10 @@ static char *parseArgv(int argc, char *argv[]) {
options.baseTileIDs[1] = number; options.baseTileIDs[1] = number;
} }
if (*arg != '\0') { if (*arg != '\0') {
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"", error(
musl_optarg); "Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
musl_optarg
);
break; break;
} }
break; break;
@@ -474,8 +486,10 @@ static char *parseArgv(int argc, char *argv[]) {
} }
skipWhitespace(arg); skipWhitespace(arg);
if (*arg != ',') { if (*arg != ',') {
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"", error(
musl_optarg); "Bank capacity must be one or two comma-separated numbers, not \"%s\"",
musl_optarg
);
break; break;
} }
++arg; // Skip comma ++arg; // Skip comma
@@ -485,8 +499,10 @@ static char *parseArgv(int argc, char *argv[]) {
error("Bank 1 cannot contain more than 256 tiles"); error("Bank 1 cannot contain more than 256 tiles");
} }
if (*arg != '\0') { if (*arg != '\0') {
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"", error(
musl_optarg); "Bank capacity must be one or two comma-separated numbers, not \"%s\"",
musl_optarg
);
break; break;
} }
break; break;
@@ -604,7 +620,7 @@ static char *parseArgv(int argc, char *argv[]) {
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
struct AtFileStackEntry { struct AtFileStackEntry {
int parentInd; // Saved offset into parent argv int parentInd; // Saved offset into parent argv
std::vector<char *> argv; // This context's arg pointer vec std::vector<char *> argv; // This context's arg pointer vec
std::vector<char> argPool; std::vector<char> argPool;
@@ -633,7 +649,7 @@ int main(int argc, char *argv[]) {
curArgc = stackEntry.argv.size() - 1; curArgc = stackEntry.argv.size() - 1;
curArgv = stackEntry.argv.data(); curArgv = stackEntry.argv.data();
musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se
continue; // Begin scanning that arg vector continue; // Begin scanning that arg vector
} }
if (musl_optind != curArgc) { if (musl_optind != curArgc) {
@@ -665,16 +681,23 @@ int main(int argc, char *argv[]) {
if (options.nbColorsPerPal == 0) { if (options.nbColorsPerPal == 0) {
options.nbColorsPerPal = 1u << options.bitDepth; options.nbColorsPerPal = 1u << options.bitDepth;
} else if (options.nbColorsPerPal > 1u << options.bitDepth) { } else if (options.nbColorsPerPal > 1u << options.bitDepth) {
error("%" PRIu8 "bpp palettes can only contain %u colors, not %" PRIu8, options.bitDepth, error(
1u << options.bitDepth, options.nbColorsPerPal); "%" PRIu8 "bpp palettes can only contain %u colors, not %" PRIu8,
options.bitDepth,
1u << options.bitDepth,
options.nbColorsPerPal
);
} }
auto autoOutPath = [](bool autoOptEnabled, std::string &path, char const *extension) { auto autoOutPath = [](bool autoOptEnabled, std::string &path, char const *extension) {
if (autoOptEnabled) { if (autoOptEnabled) {
auto &image = localOptions.groupOutputs ? options.output : options.input; auto &image = localOptions.groupOutputs ? options.output : options.input;
if (image.empty()) { if (image.empty()) {
fprintf(stderr, "FATAL: No %s specified\n", localOptions.groupOutputs fprintf(
? "output tile data file" : "input image"); stderr,
"FATAL: No %s specified\n",
localOptions.groupOutputs ? "output tile data file" : "input image"
);
printUsage(); printUsage();
exit(1); exit(1);
} }
@@ -723,7 +746,8 @@ int main(int argc, char *argv[]) {
static std::array<char const *, 3> textbox{ static std::array<char const *, 3> textbox{
" ,----------------------------------------.", " ,----------------------------------------.",
" | Augh, dimensional interference again?! |", " | Augh, dimensional interference again?! |",
" `----------------------------------------'"}; " `----------------------------------------'",
};
for (size_t i = 0; i < gfx.size(); ++i) { for (size_t i = 0; i < gfx.size(); ++i) {
uint16_t row = gfx[i]; uint16_t row = gfx[i];
for (uint8_t _ = 0; _ < 10; ++_) { for (uint8_t _ = 0; _ < 10; ++_) {
@@ -781,15 +805,27 @@ int main(int argc, char *argv[]) {
} }
fputs("\t]\n", stderr); fputs("\t]\n", stderr);
} }
fprintf(stderr, fprintf(
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32 stderr,
", %" PRIi32 ")\n", "\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32 ", %" PRIi32
options.inputSlice.width, options.inputSlice.height, options.inputSlice.left, ")\n",
options.inputSlice.top); options.inputSlice.width,
fprintf(stderr, "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", options.baseTileIDs[0], options.inputSlice.height,
options.baseTileIDs[1]); options.inputSlice.left,
fprintf(stderr, "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n", options.inputSlice.top
options.maxNbTiles[0], options.maxNbTiles[1]); );
fprintf(
stderr,
"\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n",
options.baseTileIDs[0],
options.baseTileIDs[1]
);
fprintf(
stderr,
"\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n",
options.maxNbTiles[0],
options.maxNbTiles[1]
);
auto printPath = [](char const *name, std::string const &path) { auto printPath = [](char const *name, std::string const &path) {
if (!path.empty()) { if (!path.empty()) {
fprintf(stderr, "\t%s: %s\n", name, path.c_str()); fprintf(stderr, "\t%s: %s\n", name, path.c_str());
@@ -814,8 +850,7 @@ int main(int argc, char *argv[]) {
} else { } else {
process(); process();
} }
} else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT } else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT && !options.reverse()) {
&& !options.reverse()) {
processPalettes(); processPalettes();
} else { } else {
fputs("FATAL: No input image specified\n", stderr); fputs("FATAL: No input image specified\n", stderr);
@@ -832,7 +867,7 @@ int main(int argc, char *argv[]) {
void Palette::addColor(uint16_t color) { void Palette::addColor(uint16_t color) {
for (size_t i = 0; true; ++i) { for (size_t i = 0; true; ++i) {
assert(i < colors.size()); // The packing should guarantee this assert(i < colors.size()); // The packing should guarantee this
if (colors[i] == color) { // The color is already present if (colors[i] == color) { // The color is already present
break; break;
} else if (colors[i] == UINT16_MAX) { // Empty slot } else if (colors[i] == UINT16_MAX) { // Empty slot
colors[i] = color; colors[i] = color;
@@ -858,9 +893,9 @@ auto Palette::begin() -> decltype(colors)::iterator {
auto Palette::end() -> decltype(colors)::iterator { auto Palette::end() -> decltype(colors)::iterator {
// Return an iterator pointing past the last non-empty element. // Return an iterator pointing past the last non-empty element.
// Since the palette may contain gaps, we must scan from the end. // Since the palette may contain gaps, we must scan from the end.
return std::find_if(colors.rbegin(), colors.rend(), return std::find_if(
[](uint16_t c) { return c != UINT16_MAX; }) colors.rbegin(), colors.rend(), [](uint16_t c) { return c != UINT16_MAX; }
.base(); ).base();
} }
auto Palette::begin() const -> decltype(colors)::const_iterator { auto Palette::begin() const -> decltype(colors)::const_iterator {
@@ -870,9 +905,9 @@ auto Palette::begin() const -> decltype(colors)::const_iterator {
auto Palette::end() const -> decltype(colors)::const_iterator { auto Palette::end() const -> decltype(colors)::const_iterator {
// Same as the non-const end(). // Same as the non-const end().
return std::find_if(colors.rbegin(), colors.rend(), return std::find_if(
[](uint16_t c) { return c != UINT16_MAX; }) colors.rbegin(), colors.rend(), [](uint16_t c) { return c != UINT16_MAX; }
.base(); ).base();
} }
uint8_t Palette::size() const { uint8_t Palette::size() const {

View File

@@ -140,9 +140,10 @@ public:
*/ */
template<typename... Ts> template<typename... Ts>
void assign(Ts &&...args) { void assign(Ts &&...args) {
auto freeSlot = std::find_if_not( auto freeSlot =
RANGE(_assigned), std::find_if_not(RANGE(_assigned), [](std::optional<ProtoPalAttrs> const &slot) {
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); }); return slot.has_value();
});
if (freeSlot == _assigned.end()) { // We are full, use a new slot if (freeSlot == _assigned.end()) { // We are full, use a new slot
_assigned.emplace_back(std::forward<Ts>(args)...); _assigned.emplace_back(std::forward<Ts>(args)...);
@@ -158,15 +159,20 @@ public:
bool empty() const { bool empty() const {
return std::find_if( return std::find_if(
RANGE(_assigned), RANGE(_assigned),
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); }) [](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); }
)
== _assigned.end(); == _assigned.end();
} }
size_t nbProtoPals() const { return std::distance(RANGE(*this)); } size_t nbProtoPals() const { return std::distance(RANGE(*this)); }
private: private:
template<typename Iter> template<typename Iter>
static void addUniqueColors(std::unordered_set<uint16_t> &colors, Iter iter, Iter const &end, static void addUniqueColors(
std::vector<ProtoPalette> const &protoPals) { std::unordered_set<uint16_t> &colors,
Iter iter,
Iter const &end,
std::vector<ProtoPalette> const &protoPals
) {
for (; iter != end; ++iter) { for (; iter != end; ++iter) {
ProtoPalette const &protoPal = protoPals[iter->protoPalIndex]; ProtoPalette const &protoPal = protoPals[iter->protoPalIndex];
colors.insert(RANGE(protoPal)); colors.insert(RANGE(protoPal));
@@ -226,8 +232,8 @@ public:
* Computes the "relative size" of a set of proto-palettes on this palette * Computes the "relative size" of a set of proto-palettes on this palette
*/ */
template<typename Iter> template<typename Iter>
auto combinedVolume(Iter &&begin, Iter const &end, auto combinedVolume(Iter &&begin, Iter const &end, std::vector<ProtoPalette> const &protoPals)
std::vector<ProtoPalette> const &protoPals) const { const {
auto &colors = uniqueColors(); auto &colors = uniqueColors();
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals); addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
return colors.size(); return colors.size();
@@ -243,8 +249,9 @@ public:
} }
}; };
static void decant(std::vector<AssignedProtos> &assignments, static void decant(
std::vector<ProtoPalette> const &protoPalettes) { std::vector<AssignedProtos> &assignments, std::vector<ProtoPalette> const &protoPalettes
) {
// "Decanting" is the process of moving all *things* that can fit in a lower index there // "Decanting" is the process of moving all *things* that can fit in a lower index there
auto decantOn = [&assignments](auto const &tryDecanting) { auto decantOn = [&assignments](auto const &tryDecanting) {
// No need to attempt decanting on palette #0, as there are no palettes to decant to // No need to attempt decanting on palette #0, as there are no palettes to decant to
@@ -268,8 +275,9 @@ static void decant(std::vector<AssignedProtos> &assignments,
} }
}; };
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes before decanting\n", options.verbosePrint(
assignments.size()); Options::VERB_DEBUG, "%zu palettes before decanting\n", assignments.size()
);
// Decant on palettes // Decant on palettes
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) { decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
@@ -281,8 +289,9 @@ static void decant(std::vector<AssignedProtos> &assignments,
from.clear(); from.clear();
} }
}); });
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n", options.verbosePrint(
assignments.size()); Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n", assignments.size()
);
// Decant on "components" (= proto-pals sharing colors) // Decant on "components" (= proto-pals sharing colors)
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) { decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
@@ -331,8 +340,9 @@ static void decant(std::vector<AssignedProtos> &assignments,
} }
} }
}); });
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n", options.verbosePrint(
assignments.size()); Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n", assignments.size()
);
// Decant on individual proto-palettes // Decant on individual proto-palettes
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) { decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
@@ -343,14 +353,16 @@ static void decant(std::vector<AssignedProtos> &assignments,
} }
} }
}); });
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on proto-palettes\n", options.verbosePrint(
assignments.size()); Options::VERB_DEBUG, "%zu palettes after decanting on proto-palettes\n", assignments.size()
);
} }
std::tuple<DefaultInitVec<size_t>, size_t> std::tuple<DefaultInitVec<size_t>, size_t>
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes) { overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes) {
options.verbosePrint(Options::VERB_LOG_ACT, options.verbosePrint(
"Paginating palettes using \"overload-and-remove\" strategy...\n"); Options::VERB_LOG_ACT, "Paginating palettes using \"overload-and-remove\" strategy...\n"
);
// Sort the proto-palettes by size, which improves the packing algorithm's efficiency // Sort the proto-palettes by size, which improves the packing algorithm's efficiency
DefaultInitVec<size_t> sortedProtoPalIDs(protoPalettes.size()); DefaultInitVec<size_t> sortedProtoPalIDs(protoPalettes.size());
@@ -379,9 +391,14 @@ std::tuple<DefaultInitVec<size_t>, size_t>
continue; continue;
} }
options.verbosePrint(Options::VERB_DEBUG, "%zu/%zu: Rel size: %f (size = %zu)\n", i + 1, options.verbosePrint(
assignments.size(), assignments[i].relSizeOf(protoPal), Options::VERB_DEBUG,
protoPal.size()); "%zu/%zu: Rel size: %f (size = %zu)\n",
i + 1,
assignments.size(),
assignments[i].relSizeOf(protoPal),
protoPal.size()
);
if (assignments[i].relSizeOf(protoPal) < bestRelSize) { if (assignments[i].relSizeOf(protoPal) < bestRelSize) {
bestPalIndex = i; bestPalIndex = i;
} }
@@ -397,21 +414,26 @@ std::tuple<DefaultInitVec<size_t>, size_t>
// If this overloads the palette, get it back to normal (if possible) // If this overloads the palette, get it back to normal (if possible)
while (bestPal.volume() > options.maxOpaqueColors()) { while (bestPal.volume() > options.maxOpaqueColors()) {
options.verbosePrint(Options::VERB_DEBUG, options.verbosePrint(
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n", Options::VERB_DEBUG,
bestPalIndex, bestPal.volume(), options.maxOpaqueColors()); "Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
bestPalIndex,
bestPal.volume(),
options.maxOpaqueColors()
);
// Look for a proto-pal minimizing "efficiency" (size / rel_size) // Look for a proto-pal minimizing "efficiency" (size / rel_size)
auto efficiency = [&bestPal](ProtoPalette const &pal) { auto efficiency = [&bestPal](ProtoPalette const &pal) {
return pal.size() / bestPal.relSizeOf(pal); return pal.size() / bestPal.relSizeOf(pal);
}; };
auto [minEfficiencyIter, maxEfficiencyIter] = auto [minEfficiencyIter, maxEfficiencyIter] = std::minmax_element(
std::minmax_element(RANGE(bestPal), RANGE(bestPal),
[&efficiency, &protoPalettes](ProtoPalAttrs const &lhs, [&efficiency,
ProtoPalAttrs const &rhs) { &protoPalettes](ProtoPalAttrs const &lhs, ProtoPalAttrs const &rhs) {
return efficiency(protoPalettes[lhs.protoPalIndex]) return efficiency(protoPalettes[lhs.protoPalIndex])
< efficiency(protoPalettes[rhs.protoPalIndex]); < efficiency(protoPalettes[rhs.protoPalIndex]);
}); }
);
// All efficiencies are identical iff min equals max // All efficiencies are identical iff min equals max
// TODO: maybe not ideal to re-compute these two? // TODO: maybe not ideal to re-compute these two?
@@ -443,18 +465,24 @@ std::tuple<DefaultInitVec<size_t>, size_t>
while (!queue.empty()) { while (!queue.empty()) {
ProtoPalAttrs const &attrs = queue.front(); ProtoPalAttrs const &attrs = queue.front();
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex]; ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex];
auto iter = auto iter = std::find_if(RANGE(assignments), [&protoPal](AssignedProtos const &pal) {
std::find_if(RANGE(assignments), return pal.canFit(protoPal);
[&protoPal](AssignedProtos const &pal) { return pal.canFit(protoPal); }); });
if (iter == assignments.end()) { // No such page, create a new one if (iter == assignments.end()) { // No such page, create a new one
options.verbosePrint(Options::VERB_DEBUG, options.verbosePrint(
"Adding new palette (%zu) for overflowing proto-pal %zu\n", Options::VERB_DEBUG,
assignments.size(), attrs.protoPalIndex); "Adding new palette (%zu) for overflowing proto-pal %zu\n",
assignments.size(),
attrs.protoPalIndex
);
assignments.emplace_back(protoPalettes, std::move(attrs)); assignments.emplace_back(protoPalettes, std::move(attrs));
} else { } else {
options.verbosePrint(Options::VERB_DEBUG, options.verbosePrint(
"Assigning overflowing proto-pal %zu to palette %zu\n", Options::VERB_DEBUG,
attrs.protoPalIndex, iter - assignments.begin()); "Assigning overflowing proto-pal %zu to palette %zu\n",
attrs.protoPalIndex,
iter - assignments.begin()
);
iter->assign(std::move(attrs)); iter->assign(std::move(attrs));
} }
queue.pop(); queue.pop();

View File

@@ -13,13 +13,20 @@
namespace sorting { namespace sorting {
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB, void indexed(
int palAlphaSize, png_byte *palAlpha) { std::vector<Palette> &palettes,
int palSize,
png_color const *palRGB,
int palAlphaSize,
png_byte *palAlpha
) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes using embedded palette...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes using embedded palette...\n");
auto pngToRgb = [&palRGB, &palAlphaSize, &palAlpha](int index) { auto pngToRgb = [&palRGB, &palAlphaSize, &palAlpha](int index) {
auto const &c = palRGB[index]; auto const &c = palRGB[index];
return Rgba(c.red, c.green, c.blue, palAlpha && index < palAlphaSize ? palAlpha[index] : 0xFF); return Rgba(
c.red, c.green, c.blue, palAlpha && index < palAlphaSize ? palAlpha[index] : 0xFF
);
}; };
for (Palette &pal : palettes) { for (Palette &pal : palettes) {
@@ -43,8 +50,9 @@ void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRG
} }
} }
void grayscale(std::vector<Palette> &palettes, void grayscale(
std::array<std::optional<Rgba>, 0x8001> const &colors) { std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting grayscale-only palette...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Sorting grayscale-only palette...\n");
// This method is only applicable if there are at most as many colors as colors per palette, so // This method is only applicable if there are at most as many colors as colors per palette, so

View File

@@ -66,10 +66,12 @@ void parseInlinePalSpec(char const * const rawArg) {
assert(len <= arg.length()); assert(len <= arg.length());
errorMessage(msg); errorMessage(msg);
fprintf(stderr, fprintf(
"In inline palette spec: %s\n" stderr,
" ", "In inline palette spec: %s\n"
rawArg); " ",
rawArg
);
for (auto i = ofs; i; --i) { for (auto i = ofs; i; --i) {
putc(' ', stderr); putc(' ', stderr);
} }
@@ -97,12 +99,17 @@ void parseInlinePalSpec(char const * const rawArg) {
auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length()); auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length());
switch (pos - n) { switch (pos - n) {
case 3: case 3:
color = Rgba(singleToHex(arg[n + 0]), singleToHex(arg[n + 1]), singleToHex(arg[n + 2]), color = Rgba(
0xFF); singleToHex(arg[n + 0]), singleToHex(arg[n + 1]), singleToHex(arg[n + 2]), 0xFF
);
break; break;
case 6: case 6:
color = Rgba(toHex(arg[n + 0], arg[n + 1]), toHex(arg[n + 2], arg[n + 3]), color = Rgba(
toHex(arg[n + 4], arg[n + 5]), 0xFF); toHex(arg[n + 0], arg[n + 1]),
toHex(arg[n + 2], arg[n + 3]),
toHex(arg[n + 4], arg[n + 5]),
0xFF
);
break; break;
case 0: case 0:
parseError(n - 1, 1, "Missing color after '#'"); parseError(n - 1, 1, "Missing color after '#'");
@@ -239,36 +246,31 @@ static std::optional<U> parseDec(std::string const &str, std::string::size_type
return std::optional<U>{value}; return std::optional<U>{value};
} }
static std::optional<Rgba> parseColor(std::string const &str, std::string::size_type &n, static std::optional<Rgba>
uint16_t i) { parseColor(std::string const &str, std::string::size_type &n, uint16_t i) {
std::optional<uint8_t> r = parseDec<uint8_t>(str, n); std::optional<uint8_t> r = parseDec<uint8_t>(str, n);
if (!r) { if (!r) {
error("Failed to parse color #%d (\"%s\"): invalid red component", i + 1, error("Failed to parse color #%d (\"%s\"): invalid red component", i + 1, str.c_str());
str.c_str());
return std::nullopt; return std::nullopt;
} }
skipWhitespace(str, n); skipWhitespace(str, n);
if (n == str.length()) { if (n == str.length()) {
error("Failed to parse color #%d (\"%s\"): missing green component", i + 1, error("Failed to parse color #%d (\"%s\"): missing green component", i + 1, str.c_str());
str.c_str());
return std::nullopt; return std::nullopt;
} }
std::optional<uint8_t> g = parseDec<uint8_t>(str, n); std::optional<uint8_t> g = parseDec<uint8_t>(str, n);
if (!g) { if (!g) {
error("Failed to parse color #%d (\"%s\"): invalid green component", i + 1, error("Failed to parse color #%d (\"%s\"): invalid green component", i + 1, str.c_str());
str.c_str());
return std::nullopt; return std::nullopt;
} }
skipWhitespace(str, n); skipWhitespace(str, n);
if (n == str.length()) { if (n == str.length()) {
error("Failed to parse color #%d (\"%s\"): missing blue component", i + 1, error("Failed to parse color #%d (\"%s\"): missing blue component", i + 1, str.c_str());
str.c_str());
return std::nullopt; return std::nullopt;
} }
std::optional<uint8_t> b = parseDec<uint8_t>(str, n); std::optional<uint8_t> b = parseDec<uint8_t>(str, n);
if (!b) { if (!b) {
error("Failed to parse color #%d (\"%s\"): invalid blue component", i + 1, error("Failed to parse color #%d (\"%s\"): invalid blue component", i + 1, str.c_str());
str.c_str());
return std::nullopt; return std::nullopt;
} }
@@ -301,10 +303,14 @@ static void parsePSPFile(std::filebuf &file) {
return; return;
} }
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; *nbColors > nbPalColors) { if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes;
warning("PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16 *nbColors > nbPalColors) {
"; ignoring extra", warning(
*nbColors, nbPalColors); "PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
*nbColors,
nbPalColors
);
nbColors = nbPalColors; nbColors = nbPalColors;
} }
@@ -320,8 +326,11 @@ static void parsePSPFile(std::filebuf &file) {
return; return;
} }
if (n != line.length()) { if (n != line.length()) {
error("Failed to parse color #%d (\"%s\"): trailing characters after blue component", error(
i + 1, line.c_str()); "Failed to parse color #%d (\"%s\"): trailing characters after blue component",
i + 1,
line.c_str()
);
return; return;
} }
@@ -372,9 +381,12 @@ static void parseGPLFile(std::filebuf &file) {
} }
if (nbColors > maxNbColors) { if (nbColors > maxNbColors) {
warning("GPL file contains %" PRIu16 " colors, but there can only be %" PRIu16 warning(
"; ignoring extra", "GPL file contains %" PRIu16 " colors, but there can only be %" PRIu16
nbColors, maxNbColors); "; ignoring extra",
nbColors,
maxNbColors
);
} }
} }
@@ -393,8 +405,11 @@ static void parseHEXFile(std::filebuf &file) {
if (line.length() != 6 if (line.length() != 6
|| line.find_first_not_of("0123456789ABCDEFabcdef"sv) != std::string::npos) { || line.find_first_not_of("0123456789ABCDEFabcdef"sv) != std::string::npos) {
error("Failed to parse color #%d (\"%s\"): invalid \"rrggbb\" line", error(
nbColors + 1, line.c_str()); "Failed to parse color #%d (\"%s\"): invalid \"rrggbb\" line",
nbColors + 1,
line.c_str()
);
return; return;
} }
@@ -411,9 +426,12 @@ static void parseHEXFile(std::filebuf &file) {
} }
if (nbColors > maxNbColors) { if (nbColors > maxNbColors) {
warning("HEX file contains %" PRIu16 " colors, but there can only be %" PRIu16 warning(
"; ignoring extra", "HEX file contains %" PRIu16 " colors, but there can only be %" PRIu16
nbColors, maxNbColors); "; ignoring extra",
nbColors,
maxNbColors
);
} }
} }
@@ -436,10 +454,14 @@ static void parseACTFile(std::filebuf &file) {
return; return;
} }
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; nbColors > nbPalColors) { if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes;
warning("ACT file contains %" PRIu16 " colors, but there can only be %" PRIu16 nbColors > nbPalColors) {
"; ignoring extra", warning(
nbColors, nbPalColors); "ACT file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors,
nbPalColors
);
nbColors = nbPalColors; nbColors = nbPalColors;
} }
@@ -486,10 +508,14 @@ static void parseACOFile(std::filebuf &file) {
} }
uint16_t nbColors = readBE<uint16_t>(buf); uint16_t nbColors = readBE<uint16_t>(buf);
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; nbColors > nbPalColors) { if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes;
warning("ACO file contains %" PRIu16 " colors, but there can only be %" PRIu16 nbColors > nbPalColors) {
"; ignoring extra", warning(
nbColors, nbPalColors); "ACO file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors,
nbPalColors
);
nbColors = nbPalColors; nbColors = nbPalColors;
} }
@@ -543,16 +569,22 @@ static void parseGBCFile(std::filebuf &file) {
if (len == 0) { if (len == 0) {
break; break;
} else if (len != sizeof(buf)) { } else if (len != sizeof(buf)) {
error("GBC palette dump contains %zu 8-byte palette%s, plus %zu byte%s", error(
options.palSpec.size(), options.palSpec.size() == 1 ? "" : "s", len, "GBC palette dump contains %zu 8-byte palette%s, plus %zu byte%s",
len == 1 ? "" : "s"); options.palSpec.size(),
options.palSpec.size() == 1 ? "" : "s",
len,
len == 1 ? "" : "s"
);
break; break;
} }
options.palSpec.push_back({Rgba::fromCGBColor(readLE<uint16_t>(&buf[0])), options.palSpec.push_back(
Rgba::fromCGBColor(readLE<uint16_t>(&buf[2])), {Rgba::fromCGBColor(readLE<uint16_t>(&buf[0])),
Rgba::fromCGBColor(readLE<uint16_t>(&buf[4])), Rgba::fromCGBColor(readLE<uint16_t>(&buf[2])),
Rgba::fromCGBColor(readLE<uint16_t>(&buf[6]))}); Rgba::fromCGBColor(readLE<uint16_t>(&buf[4])),
Rgba::fromCGBColor(readLE<uint16_t>(&buf[6]))}
);
} }
} }
@@ -576,14 +608,16 @@ void parseExternalPalSpec(char const *arg) {
std::tuple{"GBC", &parseGBCFile, std::ios::binary}, std::tuple{"GBC", &parseGBCFile, std::ios::binary},
}; };
auto iter = std::find_if(RANGE(parsers), auto iter =
[&arg, &ptr](decltype(parsers)::value_type const &parser) { std::find_if(RANGE(parsers), [&arg, &ptr](decltype(parsers)::value_type const &parser) {
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0; return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
}); });
if (iter == parsers.end()) { if (iter == parsers.end()) {
error("Unknown external palette format \"%.*s\"", error(
static_cast<int>(std::min(ptr - arg, static_cast<decltype(ptr - arg)>(INT_MAX))), "Unknown external palette format \"%.*s\"",
arg); static_cast<int>(std::min(ptr - arg, static_cast<decltype(ptr - arg)>(INT_MAX))),
arg
);
return; return;
} }

View File

@@ -59,10 +59,9 @@ public:
} }
size_t size() const { size_t size() const {
return std::count_if(RANGE(_colors), return std::count_if(RANGE(_colors), [](decltype(_colors)::value_type const &slot) {
[](decltype(_colors)::value_type const &slot) { return slot.has_value() && !slot->isTransparent();
return slot.has_value() && !slot->isTransparent(); });
});
} }
decltype(_colors) const &raw() const { return _colors; } decltype(_colors) const &raw() const { return _colors; }
@@ -105,10 +104,13 @@ class Png {
self->file->sgetn(reinterpret_cast<char *>(data), expectedLen); self->file->sgetn(reinterpret_cast<char *>(data), expectedLen);
if (nbBytesRead != expectedLen) { if (nbBytesRead != expectedLen) {
fatal("Error reading input image (\"%s\"): file too short (expected at least %zd more " fatal(
"bytes after reading %zu)", "Error reading input image (\"%s\"): file too short (expected at least %zd more "
self->c_str(), length - nbBytesRead, "bytes after reading %zu)",
(size_t)self->file->pubseekoff(0, std::ios_base::cur)); self->c_str(),
length - nbBytesRead,
(size_t)self->file->pubseekoff(0, std::ios_base::cur)
);
} }
} }
@@ -134,9 +136,12 @@ public:
bool isSuitableForGrayscale() const { bool isSuitableForGrayscale() const {
// Check that all of the grays don't fall into the same "bin" // Check that all of the grays don't fall into the same "bin"
if (colors.size() > options.maxOpaqueColors()) { // Apply the Pigeonhole Principle if (colors.size() > options.maxOpaqueColors()) { // Apply the Pigeonhole Principle
options.verbosePrint(Options::VERB_DEBUG, options.verbosePrint(
"Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n", Options::VERB_DEBUG,
colors.size(), options.maxOpaqueColors()); "Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n",
colors.size(),
options.maxOpaqueColors()
);
return false; return false;
} }
uint8_t bins = 0; uint8_t bins = 0;
@@ -145,9 +150,11 @@ public:
continue; continue;
} }
if (!color->isGray()) { if (!color->isGray()) {
options.verbosePrint(Options::VERB_DEBUG, options.verbosePrint(
"Found non-gray color #%08x, not using grayscale sorting\n", Options::VERB_DEBUG,
color->toCSS()); "Found non-gray color #%08x, not using grayscale sorting\n",
color->toCSS()
);
return false; return false;
} }
uint8_t mask = 1 << color->grayIndex(); uint8_t mask = 1 << color->grayIndex();
@@ -155,7 +162,8 @@ public:
options.verbosePrint( options.verbosePrint(
Options::VERB_DEBUG, Options::VERB_DEBUG,
"Color #%08x conflicts with another one, not using grayscale sorting\n", "Color #%08x conflicts with another one, not using grayscale sorting\n",
color->toCSS()); color->toCSS()
);
return false; return false;
} }
bins |= mask; bins |= mask;
@@ -174,8 +182,7 @@ public:
*/ */
explicit Png(std::string const &filePath) : path(filePath), colors() { explicit Png(std::string const &filePath) : path(filePath), colors() {
if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) { if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) {
fatal("Failed to open input image (\"%s\"): %s", file.c_str(path), fatal("Failed to open input image (\"%s\"): %s", file.c_str(path), strerror(errno));
strerror(errno));
} }
options.verbosePrint(Options::VERB_LOG_ACT, "Opened input file\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Opened input file\n");
@@ -190,8 +197,9 @@ public:
options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n"); options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n");
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError, png = png_create_read_struct(
handleWarning); PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError, handleWarning
);
if (!png) { if (!png) {
fatal("Failed to allocate PNG structure: %s", strerror(errno)); fatal("Failed to allocate PNG structure: %s", strerror(errno));
} }
@@ -215,8 +223,9 @@ public:
int bitDepth, interlaceType; //, compressionType, filterMethod; int bitDepth, interlaceType; //, compressionType, filterMethod;
png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr, png_get_IHDR(
nullptr); png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr, nullptr
);
if (options.inputSlice.width == 0 && width % 8 != 0) { if (options.inputSlice.width == 0 && width % 8 != 0) {
fatal("Image width (%" PRIu32 " pixels) is not a multiple of 8!", width); fatal("Image width (%" PRIu32 " pixels) is not a multiple of 8!", width);
@@ -253,23 +262,35 @@ public:
fatal("Unknown interlace type %d", interlaceType); fatal("Unknown interlace type %d", interlaceType);
} }
}; };
options.verbosePrint(Options::VERB_INTERM, options.verbosePrint(
"Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n", width, Options::VERB_INTERM,
height, bitDepth, colorTypeName(), interlaceTypeName()); "Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n",
width,
height,
bitDepth,
colorTypeName(),
interlaceTypeName()
);
if (png_get_PLTE(png, info, &embeddedPal, &nbColors) != 0) { if (png_get_PLTE(png, info, &embeddedPal, &nbColors) != 0) {
if (png_get_tRNS(png, info, &transparencyPal, &nbTransparentEntries, nullptr)) { if (png_get_tRNS(png, info, &transparencyPal, &nbTransparentEntries, nullptr)) {
assert(nbTransparentEntries <= nbColors); assert(nbTransparentEntries <= nbColors);
} }
options.verbosePrint(Options::VERB_INTERM, "Embedded palette has %d colors: [", options.verbosePrint(
nbColors); Options::VERB_INTERM, "Embedded palette has %d colors: [", nbColors
);
for (int i = 0; i < nbColors; ++i) { for (int i = 0; i < nbColors; ++i) {
auto const &color = embeddedPal[i]; auto const &color = embeddedPal[i];
options.verbosePrint( options.verbosePrint(
Options::VERB_INTERM, "#%02x%02x%02x%02x%s", color.red, color.green, color.blue, Options::VERB_INTERM,
"#%02x%02x%02x%02x%s",
color.red,
color.green,
color.blue,
transparencyPal && i < nbTransparentEntries ? transparencyPal[i] : 0xFF, transparencyPal && i < nbTransparentEntries ? transparencyPal[i] : 0xFF,
i != nbColors - 1 ? ", " : "]\n"); i != nbColors - 1 ? ", " : "]\n"
);
} }
} else { } else {
options.verbosePrint(Options::VERB_INTERM, "No embedded palette\n"); options.verbosePrint(Options::VERB_INTERM, "No embedded palette\n");
@@ -329,40 +350,51 @@ public:
std::vector<uint32_t> indeterminates; std::vector<uint32_t> indeterminates;
// Assign a color to the given position, and register it in the image palette as well // Assign a color to the given position, and register it in the image palette as well
auto assignColor = [this, &conflicts, &indeterminates](png_uint_32 x, png_uint_32 y, auto assignColor =
Rgba &&color) { [this, &conflicts, &indeterminates](png_uint_32 x, png_uint_32 y, Rgba &&color) {
if (!color.isTransparent() && !color.isOpaque()) { if (!color.isTransparent() && !color.isOpaque()) {
uint32_t css = color.toCSS(); uint32_t css = color.toCSS();
if (std::find(RANGE(indeterminates), css) if (std::find(RANGE(indeterminates), css) == indeterminates.end()) {
== indeterminates.end()) { error(
error("Color #%08x is neither transparent (alpha < %u) nor opaque (alpha >= " "Color #%08x is neither transparent (alpha < %u) nor opaque (alpha >= "
"%u) [first seen at x: %" PRIu32 ", y: %" PRIu32 "]", "%u) [first seen at x: %" PRIu32 ", y: %" PRIu32 "]",
css, Rgba::transparency_threshold, Rgba::opacity_threshold, x, y); css,
indeterminates.push_back(css); Rgba::transparency_threshold,
} Rgba::opacity_threshold,
} else if (Rgba const *other = colors.registerColor(color); other) { x,
std::tuple conflicting{color.toCSS(), other->toCSS()}; y
// Do not report combinations twice );
if (std::find(RANGE(conflicts), conflicting) == conflicts.end()) { indeterminates.push_back(css);
warning("Fusing colors #%08x and #%08x into Game Boy color $%04x [first seen " }
} else if (Rgba const *other = colors.registerColor(color); other) {
std::tuple conflicting{color.toCSS(), other->toCSS()};
// Do not report combinations twice
if (std::find(RANGE(conflicts), conflicting) == conflicts.end()) {
warning(
"Fusing colors #%08x and #%08x into Game Boy color $%04x [first seen "
"at x: %" PRIu32 ", y: %" PRIu32 "]", "at x: %" PRIu32 ", y: %" PRIu32 "]",
std::get<0>(conflicting), std::get<1>(conflicting), color.cgbColor(), x, std::get<0>(conflicting),
y); std::get<1>(conflicting),
// Do not report this combination again color.cgbColor(),
conflicts.emplace_back(conflicting); x,
} y
} );
// Do not report this combination again
conflicts.emplace_back(conflicting);
}
}
pixel(x, y) = color; pixel(x, y) = color;
}; };
if (interlaceType == PNG_INTERLACE_NONE) { if (interlaceType == PNG_INTERLACE_NONE) {
for (png_uint_32 y = 0; y < height; ++y) { for (png_uint_32 y = 0; y < height; ++y) {
png_read_row(png, row.data(), nullptr); png_read_row(png, row.data(), nullptr);
for (png_uint_32 x = 0; x < width; ++x) { for (png_uint_32 x = 0; x < width; ++x) {
assignColor(x, y, assignColor(
Rgba(row[x * 4], row[x * 4 + 1], row[x * 4 + 2], row[x * 4 + 3])); x, y, Rgba(row[x * 4], row[x * 4 + 1], row[x * 4 + 2], row[x * 4 + 3])
);
} }
} }
} else { } else {
@@ -454,14 +486,17 @@ public:
iterator begin() const { return {*this, _limit, 0, 0}; } iterator begin() const { return {*this, _limit, 0, 0}; }
iterator end() const { iterator end() const {
iterator it{*this, _limit, _width - 8, _height - 8}; // Last valid one... iterator it{*this, _limit, _width - 8, _height - 8}; // Last valid one...
return ++it; // ...now one-past-last! return ++it; // ...now one-past-last!
} }
}; };
public: public:
TilesVisitor visitAsTiles() const { TilesVisitor visitAsTiles() const {
return {*this, options.columnMajor, return {
options.inputSlice.width ? options.inputSlice.width * 8 : width, *this,
options.inputSlice.height ? options.inputSlice.height * 8 : height}; options.columnMajor,
options.inputSlice.width ? options.inputSlice.width * 8 : width,
options.inputSlice.height ? options.inputSlice.height * 8 : height,
};
} }
}; };
@@ -497,7 +532,7 @@ struct AttrmapEntry {
* attrmap entry while correctly handling the above, use `getPalID`. * attrmap entry while correctly handling the above, use `getPalID`.
*/ */
size_t protoPaletteID; // Only this field is used when outputting "unoptimized" data size_t protoPaletteID; // Only this field is used when outputting "unoptimized" data
uint8_t tileID; // This is the ID as it will be output to the tilemap uint8_t tileID; // This is the ID as it will be output to the tilemap
bool bank; bool bank;
bool yFlip; bool yFlip;
bool xFlip; bool xFlip;
@@ -523,8 +558,12 @@ static void generatePalSpec(Png const &png) {
embPalSize = options.maxOpaqueColors(); embPalSize = options.maxOpaqueColors();
} }
for (int i = 0; i < embPalSize; ++i) { for (int i = 0; i < embPalSize; ++i) {
options.palSpec[0][i] = Rgba(embPalRGB[i].red, embPalRGB[i].green, embPalRGB[i].blue, options.palSpec[0][i] = Rgba(
embPalAlpha && i < embPalAlphaSize ? embPalAlpha[i] : 0xFF); embPalRGB[i].red,
embPalRGB[i].green,
embPalRGB[i].blue,
embPalAlpha && i < embPalAlphaSize ? embPalAlpha[i] : 0xFF
);
} }
} }
@@ -536,8 +575,12 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
assert(mappings.size() == protoPalettes.size()); assert(mappings.size() == protoPalettes.size());
if (options.verbosity >= Options::VERB_INTERM) { if (options.verbosity >= Options::VERB_INTERM) {
fprintf(stderr, "Proto-palette mappings: (%zu palette%s)\n", nbPalettes, fprintf(
nbPalettes != 1 ? "s" : ""); stderr,
"Proto-palette mappings: (%zu palette%s)\n",
nbPalettes,
nbPalettes != 1 ? "s" : ""
);
for (size_t i = 0; i < mappings.size(); ++i) { for (size_t i = 0; i < mappings.size(); ++i) {
fprintf(stderr, "%zu -> %zu\n", i, mappings[i]); fprintf(stderr, "%zu -> %zu\n", i, mappings[i]);
} }
@@ -615,8 +658,11 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
mappings[i] = iter - palettes.begin(); // Bogus value, but whatever mappings[i] = iter - palettes.begin(); // Bogus value, but whatever
} }
if (bad) { if (bad) {
fprintf(stderr, "note: The following palette%s specified:\n", fprintf(
palettes.size() == 1 ? " was" : "s were"); stderr,
"note: The following palette%s specified:\n",
palettes.size() == 1 ? " was" : "s were"
);
for (Palette const &pal : palettes) { for (Palette const &pal : palettes) {
fprintf(stderr, " [%s]\n", listColors(pal)); fprintf(stderr, " [%s]\n", listColors(pal));
} }
@@ -640,15 +686,17 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
if (palettes.size() > options.nbPalettes) { if (palettes.size() > options.nbPalettes) {
// If the palette generation is wrong, other (dependee) operations are likely to be // If the palette generation is wrong, other (dependee) operations are likely to be
// nonsensical, so fatal-error outright // nonsensical, so fatal-error outright
fatal("Generated %zu palettes, over the maximum of %" PRIu8, palettes.size(), fatal(
options.nbPalettes); "Generated %zu palettes, over the maximum of %" PRIu8,
palettes.size(),
options.nbPalettes
);
} }
if (!options.palettes.empty()) { if (!options.palettes.empty()) {
File output; File output;
if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) { if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes), fatal("Failed to open \"%s\": %s", output.c_str(options.palettes), strerror(errno));
strerror(errno));
} }
for (Palette const &palette : palettes) { for (Palette const &palette : palettes) {
@@ -675,8 +723,8 @@ public:
// of altering the element's hash, but the tile ID is not part of it. // of altering the element's hash, but the tile ID is not part of it.
mutable uint16_t tileID; mutable uint16_t tileID;
static uint16_t rowBitplanes(Png::TilesVisitor::Tile const &tile, Palette const &palette, static uint16_t
uint32_t y) { rowBitplanes(Png::TilesVisitor::Tile const &tile, Palette const &palette, uint32_t y) {
uint16_t row = 0; uint16_t row = 0;
for (uint32_t x = 0; x < 8; ++x) { for (uint32_t x = 0; x < 8; ++x) {
row <<= 1; row <<= 1;
@@ -734,8 +782,9 @@ public:
} }
// Check if we have horizontal mirroring, which scans the array forward again // Check if we have horizontal mirroring, which scans the array forward again
if (std::equal(RANGE(_data), other._data.begin(), if (std::equal(RANGE(_data), other._data.begin(), [](uint8_t lhs, uint8_t rhs) {
[](uint8_t lhs, uint8_t rhs) { return lhs == flipTable[rhs]; })) { return lhs == flipTable[rhs];
})) {
return MatchType::HFLIP; return MatchType::HFLIP;
} }
@@ -773,16 +822,20 @@ struct std::hash<TileData> {
namespace unoptimized { namespace unoptimized {
static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap, static void outputTileData(
std::vector<Palette> const &palettes, Png const &png,
DefaultInitVec<size_t> const &mappings) { DefaultInitVec<AttrmapEntry> const &attrmap,
std::vector<Palette> const &palettes,
DefaultInitVec<size_t> const &mappings
) {
File output; File output;
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) { if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", output.c_str(options.output), strerror(errno)); fatal("Failed to open \"%s\": %s", output.c_str(options.output), strerror(errno));
} }
uint16_t widthTiles = options.inputSlice.width ? options.inputSlice.width : png.getWidth() / 8; uint16_t widthTiles = options.inputSlice.width ? options.inputSlice.width : png.getWidth() / 8;
uint16_t heightTiles = options.inputSlice.height ? options.inputSlice.height : png.getHeight() / 8; uint16_t heightTiles =
options.inputSlice.height ? options.inputSlice.height : png.getHeight() / 8;
uint64_t remainingTiles = widthTiles * heightTiles; uint64_t remainingTiles = widthTiles * heightTiles;
if (remainingTiles <= options.trim) { if (remainingTiles <= options.trim) {
return; return;
@@ -808,15 +861,15 @@ static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &a
assert(remainingTiles == 0); assert(remainingTiles == 0);
} }
static void outputMaps(DefaultInitVec<AttrmapEntry> const &attrmap, static void outputMaps(
DefaultInitVec<size_t> const &mappings) { DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
) {
std::optional<File> tilemapOutput, attrmapOutput, palmapOutput; std::optional<File> tilemapOutput, attrmapOutput, palmapOutput;
auto autoOpenPath = [](std::string const &path, std::optional<File> &file) { auto autoOpenPath = [](std::string const &path, std::optional<File> &file) {
if (!path.empty()) { if (!path.empty()) {
file.emplace(); file.emplace();
if (!file->open(path, std::ios_base::out | std::ios_base::binary)) { if (!file->open(path, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", file->c_str(options.tilemap), fatal("Failed to open \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
strerror(errno));
} }
} }
}; };
@@ -864,8 +917,8 @@ struct UniqueTiles {
/* /*
* Adds a tile to the collection, and returns its ID * Adds a tile to the collection, and returns its ID
*/ */
std::tuple<uint16_t, TileData::MatchType> addTile(Png::TilesVisitor::Tile const &tile, std::tuple<uint16_t, TileData::MatchType>
Palette const &palette) { addTile(Png::TilesVisitor::Tile const &tile, Palette const &palette) {
TileData newTile(tile, palette); TileData newTile(tile, palette);
auto [tileData, inserted] = tileset.insert(newTile); auto [tileData, inserted] = tileset.insert(newTile);
@@ -893,9 +946,12 @@ struct UniqueTiles {
* 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially * 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially
* twice) * twice)
*/ */
static UniqueTiles dedupTiles(Png const &png, DefaultInitVec<AttrmapEntry> &attrmap, static UniqueTiles dedupTiles(
std::vector<Palette> const &palettes, Png const &png,
DefaultInitVec<size_t> const &mappings) { DefaultInitVec<AttrmapEntry> &attrmap,
std::vector<Palette> const &palettes,
DefaultInitVec<size_t> const &mappings
) {
// Iterate throughout the image, generating tile data as we go // Iterate throughout the image, generating tile data as we go
// (We don't need the full tile data to be able to dedup tiles, but we don't lose anything // (We don't need the full tile data to be able to dedup tiles, but we don't lose anything
// by caching the full tile data anyway, so we might as well.) // by caching the full tile data anyway, so we might as well.)
@@ -941,8 +997,9 @@ static void outputTilemap(DefaultInitVec<AttrmapEntry> const &attrmap) {
} }
} }
static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap, static void outputAttrmap(
DefaultInitVec<size_t> const &mappings) { DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
) {
File output; File output;
if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) { if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno)); fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno));
@@ -956,8 +1013,9 @@ static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap,
} }
} }
static void outputPalmap(DefaultInitVec<AttrmapEntry> const &attrmap, static void outputPalmap(
DefaultInitVec<size_t> const &mappings) { DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
) {
File output; File output;
if (!output.open(options.palmap, std::ios_base::out | std::ios_base::binary)) { if (!output.open(options.palmap, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to create \"%s\": %s", output.c_str(options.palmap), strerror(errno)); fatal("Failed to create \"%s\": %s", output.c_str(options.palmap), strerror(errno));
@@ -1063,21 +1121,33 @@ void process() {
} }
if (nbColorsInTile > options.maxOpaqueColors()) { if (nbColorsInTile > options.maxOpaqueColors()) {
fatal("Tile at (%" PRIu32 ", %" PRIu32 ") has %" PRIu8 " opaque colors, more than %" PRIu8 "!", fatal(
tile.x, tile.y, nbColorsInTile, options.maxOpaqueColors()); "Tile at (%" PRIu32 ", %" PRIu32 ") has %" PRIu8 " opaque colors, more than %" PRIu8
"!",
tile.x,
tile.y,
nbColorsInTile,
options.maxOpaqueColors()
);
} }
attrs.protoPaletteID = protoPalettes.size(); attrs.protoPaletteID = protoPalettes.size();
if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow
fatal("Reached %zu proto-palettes... sorry, this image is too much for me to handle :(", fatal(
AttrmapEntry::transparent); "Reached %zu proto-palettes... sorry, this image is too much for me to handle :(",
AttrmapEntry::transparent
);
} }
protoPalettes.push_back(tileColors); protoPalettes.push_back(tileColors);
contained:; contained:;
} }
options.verbosePrint(Options::VERB_INTERM, "Image contains %zu proto-palette%s\n", options.verbosePrint(
protoPalettes.size(), protoPalettes.size() != 1 ? "s" : ""); Options::VERB_INTERM,
"Image contains %zu proto-palette%s\n",
protoPalettes.size(),
protoPalettes.size() != 1 ? "s" : ""
);
if (options.verbosity >= Options::VERB_INTERM) { if (options.verbosity >= Options::VERB_INTERM) {
for (auto const &protoPal : protoPalettes) { for (auto const &protoPal : protoPalettes) {
fputs("[ ", stderr); fputs("[ ", stderr);
@@ -1102,8 +1172,12 @@ contained:;
// Check the tile count // Check the tile count
if (nbTilesW * nbTilesH > options.maxNbTiles[0] + options.maxNbTiles[1]) { if (nbTilesW * nbTilesH > options.maxNbTiles[0] + options.maxNbTiles[1]) {
fatal("Image contains %" PRIu32 " tiles, exceeding the limit of %" PRIu16 " + %" PRIu16, fatal(
nbTilesW * nbTilesH, options.maxNbTiles[0], options.maxNbTiles[1]); "Image contains %" PRIu32 " tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
nbTilesW * nbTilesH,
options.maxNbTiles[0],
options.maxNbTiles[1]
);
} }
if (!options.output.empty()) { if (!options.output.empty()) {
@@ -1114,7 +1188,8 @@ contained:;
if (!options.tilemap.empty() || !options.attrmap.empty() || !options.palmap.empty()) { if (!options.tilemap.empty() || !options.attrmap.empty() || !options.palmap.empty()) {
options.verbosePrint( options.verbosePrint(
Options::VERB_LOG_ACT, Options::VERB_LOG_ACT,
"Generating unoptimized tilemap and/or attrmap and/or palmap...\n"); "Generating unoptimized tilemap and/or attrmap and/or palmap...\n"
);
unoptimized::outputMaps(attrmap, mappings); unoptimized::outputMaps(attrmap, mappings);
} }
} else { } else {
@@ -1123,8 +1198,12 @@ contained:;
optimized::UniqueTiles tiles = optimized::dedupTiles(png, attrmap, palettes, mappings); optimized::UniqueTiles tiles = optimized::dedupTiles(png, attrmap, palettes, mappings);
if (tiles.size() > options.maxNbTiles[0] + options.maxNbTiles[1]) { if (tiles.size() > options.maxNbTiles[0] + options.maxNbTiles[1]) {
fatal("Image contains %zu tiles, exceeding the limit of %" PRIu16 " + %" PRIu16, fatal(
tiles.size(), options.maxNbTiles[0], options.maxNbTiles[1]); "Image contains %zu tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
tiles.size(),
options.maxNbTiles[0],
options.maxNbTiles[1]
);
} }
if (!options.output.empty()) { if (!options.output.empty()) {

View File

@@ -1,7 +1,5 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "helpers.hpp"
#include "gfx/proto_palette.hpp" #include "gfx/proto_palette.hpp"
#include <algorithm> #include <algorithm>
@@ -10,6 +8,8 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "helpers.hpp"
bool ProtoPalette::add(uint16_t color) { bool ProtoPalette::add(uint16_t color) {
size_t i = 0; size_t i = 0;

View File

@@ -53,13 +53,19 @@ static DefaultInitVec<uint8_t> readInto(const std::string &path) {
} }
[[noreturn]] static void pngError(png_structp png, char const *msg) { [[noreturn]] static void pngError(png_structp png, char const *msg) {
fatal("Error writing reversed image (\"%s\"): %s", fatal(
static_cast<char const *>(png_get_error_ptr(png)), msg); "Error writing reversed image (\"%s\"): %s",
static_cast<char const *>(png_get_error_ptr(png)),
msg
);
} }
static void pngWarning(png_structp png, char const *msg) { static void pngWarning(png_structp png, char const *msg) {
warning("While writing reversed image (\"%s\"): %s", warning(
static_cast<char const *>(png_get_error_ptr(png)), msg); "While writing reversed image (\"%s\"): %s",
static_cast<char const *>(png_get_error_ptr(png)),
msg
);
} }
void writePng(png_structp png, png_bytep data, size_t length) { void writePng(png_structp png, png_bytep data, size_t length) {
@@ -94,17 +100,23 @@ void reverse() {
warning("\"Sliced-off\" pixels are ignored in reverse mode"); warning("\"Sliced-off\" pixels are ignored in reverse mode");
} }
if (options.inputSlice.width != 0 && options.inputSlice.width != options.reversedWidth * 8) { if (options.inputSlice.width != 0 && options.inputSlice.width != options.reversedWidth * 8) {
warning("Specified input slice width (%" PRIu16 warning(
") doesn't match provided reversing width (%" PRIu16 " * 8)", "Specified input slice width (%" PRIu16
options.inputSlice.width, options.reversedWidth); ") doesn't match provided reversing width (%" PRIu16 " * 8)",
options.inputSlice.width,
options.reversedWidth
);
} }
options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n");
auto const tiles = readInto(options.output); auto const tiles = readInto(options.output);
uint8_t tileSize = 8 * options.bitDepth; uint8_t tileSize = 8 * options.bitDepth;
if (tiles.size() % tileSize != 0) { if (tiles.size() % tileSize != 0) {
fatal("Tile data size (%zu bytes) is not a multiple of %" PRIu8 " bytes", fatal(
tiles.size(), tileSize); "Tile data size (%zu bytes) is not a multiple of %" PRIu8 " bytes",
tiles.size(),
tileSize
);
} }
// By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles // By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles
@@ -121,25 +133,33 @@ void reverse() {
fatal("Cannot generate empty image"); fatal("Cannot generate empty image");
} }
if (nbTileInstances > options.maxNbTiles[0] + options.maxNbTiles[1]) { if (nbTileInstances > options.maxNbTiles[0] + options.maxNbTiles[1]) {
warning("Read %zu tiles, more than the limit of %" PRIu16 " + %" PRIu16, warning(
nbTileInstances, options.maxNbTiles[0], options.maxNbTiles[1]); "Read %zu tiles, more than the limit of %" PRIu16 " + %" PRIu16,
nbTileInstances,
options.maxNbTiles[0],
options.maxNbTiles[1]
);
} }
size_t width = options.reversedWidth, height; // In tiles size_t width = options.reversedWidth, height; // In tiles
if (nbTileInstances % width != 0) { if (nbTileInstances % width != 0) {
fatal("Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)", fatal(
nbTileInstances, width); "Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)",
nbTileInstances,
width
);
} }
height = nbTileInstances / width; height = nbTileInstances / width;
options.verbosePrint(Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width, options.verbosePrint(
height); Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width, height
);
// TODO: `-U` to configure tile size beyond 8x8px ("deduplication units") // TODO: `-U` to configure tile size beyond 8x8px ("deduplication units")
std::vector<std::array<std::optional<Rgba>, 4>> palettes{ std::vector<std::array<std::optional<Rgba>, 4>> palettes{
{Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)} {Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)}
}; };
// If a palette file is used as input, it overrides the default colors. // If a palette file is used as input, it overrides the default colors.
if (!options.palettes.empty()) { if (!options.palettes.empty()) {
File file; File file;
@@ -155,27 +175,37 @@ void reverse() {
if (nbRead == buf.size()) { if (nbRead == buf.size()) {
// Expand the colors // Expand the colors
auto &palette = palettes.emplace_back(); auto &palette = palettes.emplace_back();
std::generate(palette.begin(), palette.begin() + options.nbColorsPerPal, std::generate(
[&buf, i = 0]() mutable { palette.begin(),
i += 2; palette.begin() + options.nbColorsPerPal,
return Rgba::fromCGBColor(buf[i - 2] + (buf[i - 1] << 8)); [&buf, i = 0]() mutable {
}); i += 2;
return Rgba::fromCGBColor(buf[i - 2] + (buf[i - 1] << 8));
}
);
} else if (nbRead != 0) { } else if (nbRead != 0) {
fatal("Palette data size (%zu) is not a multiple of %zu bytes!\n", fatal(
palettes.size() * buf.size() + nbRead, buf.size()); "Palette data size (%zu) is not a multiple of %zu bytes!\n",
palettes.size() * buf.size() + nbRead,
buf.size()
);
} }
} while (nbRead != 0); } while (nbRead != 0);
if (palettes.size() > options.nbPalettes) { if (palettes.size() > options.nbPalettes) {
warning("Read %zu palettes, more than the specified limit of %" PRIu8, warning(
palettes.size(), options.nbPalettes); "Read %zu palettes, more than the specified limit of %" PRIu8,
palettes.size(),
options.nbPalettes
);
} }
if (options.palSpecType == Options::EXPLICIT && palettes != options.palSpec) { if (options.palSpecType == Options::EXPLICIT && palettes != options.palSpec) {
warning("Colors in the palette file do not match those specified with `-c`!"); warning("Colors in the palette file do not match those specified with `-c`!");
} }
} else if (options.palSpecType == Options::EMBEDDED) { } else if (options.palSpecType == Options::EMBEDDED) {
warning("An embedded palette was requested, but no palette file was specified; ignoring request."); warning("An embedded palette was requested, but no palette file was specified; ignoring "
"request.");
} else if (options.palSpecType == Options::EXPLICIT) { } else if (options.palSpecType == Options::EXPLICIT) {
palettes = std::move(options.palSpec); // We won't be using it again. palettes = std::move(options.palSpec); // We won't be using it again.
} }
@@ -184,8 +214,11 @@ void reverse() {
if (!options.attrmap.empty()) { if (!options.attrmap.empty()) {
attrmap = readInto(options.attrmap); attrmap = readInto(options.attrmap);
if (attrmap->size() != nbTileInstances) { if (attrmap->size() != nbTileInstances) {
fatal("Attribute map size (%zu tiles) doesn't match image's (%zu)", attrmap->size(), fatal(
nbTileInstances); "Attribute map size (%zu tiles) doesn't match image's (%zu)",
attrmap->size(),
nbTileInstances
);
} }
// Scan through the attributes for inconsistencies // Scan through the attributes for inconsistencies
@@ -195,8 +228,9 @@ void reverse() {
bool bad = false; bool bad = false;
for (auto attr : *attrmap) { for (auto attr : *attrmap) {
if ((attr & 0b111) > palettes.size()) { if ((attr & 0b111) > palettes.size()) {
error("Referencing palette %u, but there are only %zu!", error(
attr & 0b111, palettes.size()); "Referencing palette %u, but there are only %zu!", attr & 0b111, palettes.size()
);
bad = true; bad = true;
} }
if (attr & 0x08 && !tilemap) { if (attr & 0x08 && !tilemap) {
@@ -213,16 +247,22 @@ void reverse() {
for (auto [id, attr] : zip(*tilemap, *attrmap)) { for (auto [id, attr] : zip(*tilemap, *attrmap)) {
bool bank = attr & 1 << 3; bool bank = attr & 1 << 3;
if (id >= options.maxNbTiles[bank]) { if (id >= options.maxNbTiles[bank]) {
warning("Tile #%" PRIu8 warning(
" was referenced, but the limit for bank %u is %" PRIu16, "Tile #%" PRIu8 " was referenced, but the limit for bank %u is %" PRIu16,
id, bank, options.maxNbTiles[bank]); id,
bank,
options.maxNbTiles[bank]
);
} }
} }
} else { } else {
for (auto id : *tilemap) { for (auto id : *tilemap) {
if (id >= options.maxNbTiles[0]) { if (id >= options.maxNbTiles[0]) {
warning("Tile #%" PRIu8 " was referenced, but the limit is %" PRIu16, id, warning(
options.maxNbTiles[0]); "Tile #%" PRIu8 " was referenced, but the limit is %" PRIu16,
id,
options.maxNbTiles[0]
);
} }
} }
} }
@@ -232,8 +272,11 @@ void reverse() {
if (!options.palmap.empty()) { if (!options.palmap.empty()) {
palmap = readInto(options.palmap); palmap = readInto(options.palmap);
if (palmap->size() != nbTileInstances) { if (palmap->size() != nbTileInstances) {
fatal("Palette map size (%zu tiles) doesn't match image's (%zu)", palmap->size(), fatal(
nbTileInstances); "Palette map size (%zu tiles) doesn't match image's (%zu)",
palmap->size(),
nbTileInstances
);
} }
} }
@@ -244,8 +287,10 @@ void reverse() {
} }
png_structp png = png_create_write_struct( png_structp png = png_create_write_struct(
PNG_LIBPNG_VER_STRING, PNG_LIBPNG_VER_STRING,
const_cast<png_voidp>(static_cast<void const *>(pngFile.c_str(options.input))), pngError, const_cast<png_voidp>(static_cast<void const *>(pngFile.c_str(options.input))),
pngWarning); pngError,
pngWarning
);
if (!png) { if (!png) {
fatal("Failed to create PNG write struct: %s", strerror(errno)); fatal("Failed to create PNG write struct: %s", strerror(errno));
} }
@@ -255,8 +300,17 @@ void reverse() {
} }
png_set_write_fn(png, &pngFile, writePng, flushPng); png_set_write_fn(png, &pngFile, writePng, flushPng);
png_set_IHDR(png, pngInfo, options.reversedWidth * 8, height * 8, 8, PNG_COLOR_TYPE_RGB_ALPHA, png_set_IHDR(
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png,
pngInfo,
options.reversedWidth * 8,
height * 8,
8,
PNG_COLOR_TYPE_RGB_ALPHA,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT
);
png_write_info(png, pngInfo); png_write_info(png, pngInfo);
png_color_8 sbitChunk; png_color_8 sbitChunk;
@@ -270,10 +324,14 @@ void reverse() {
size_t const SIZEOF_ROW = options.reversedWidth * 8 * SIZEOF_PIXEL; size_t const SIZEOF_ROW = options.reversedWidth * 8 * SIZEOF_PIXEL;
std::vector<uint8_t> tileRow(8 * SIZEOF_ROW, 0xFF); // Data for 8 rows of pixels std::vector<uint8_t> tileRow(8 * SIZEOF_ROW, 0xFF); // Data for 8 rows of pixels
uint8_t * const rowPtrs[8] = { uint8_t * const rowPtrs[8] = {
&tileRow.data()[0 * SIZEOF_ROW], &tileRow.data()[1 * SIZEOF_ROW], &tileRow.data()[0 * SIZEOF_ROW],
&tileRow.data()[2 * SIZEOF_ROW], &tileRow.data()[3 * SIZEOF_ROW], &tileRow.data()[1 * SIZEOF_ROW],
&tileRow.data()[4 * SIZEOF_ROW], &tileRow.data()[5 * SIZEOF_ROW], &tileRow.data()[2 * SIZEOF_ROW],
&tileRow.data()[6 * SIZEOF_ROW], &tileRow.data()[7 * SIZEOF_ROW], &tileRow.data()[3 * SIZEOF_ROW],
&tileRow.data()[4 * SIZEOF_ROW],
&tileRow.data()[5 * SIZEOF_ROW],
&tileRow.data()[6 * SIZEOF_ROW],
&tileRow.data()[7 * SIZEOF_ROW],
}; };
for (size_t ty = 0; ty < height; ++ty) { for (size_t ty = 0; ty < height; ++ty) {
@@ -294,8 +352,22 @@ void reverse() {
// We do not have data for tiles trimmed with `-x`, so assume they are "blank" // We do not have data for tiles trimmed with `-x`, so assume they are "blank"
static std::array<uint8_t, 16> const trimmedTile{ static std::array<uint8_t, 16> const trimmedTile{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
}; };
uint8_t const *tileData = tileID > nbTileInstances - options.trim uint8_t const *tileData = tileID > nbTileInstances - options.trim
? trimmedTile.data() ? trimmedTile.data()

View File

@@ -15,11 +15,11 @@
* with gaps in the scale curve filled by polynomial interpolation. * with gaps in the scale curve filled by polynomial interpolation.
*/ */
static std::array<uint8_t, 256> reverse_curve{ static std::array<uint8_t, 256> reverse_curve{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // These 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // These
1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, // comments 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, // comments
3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, // prevent 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, // prevent
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, // clang-format 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, // clang-format
7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, // from 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, // from
10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, // reflowing 10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, // reflowing
13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, // these 13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, // these
16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, // sixteen 16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, // sixteen

View File

@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "link/assign.hpp"
#include <algorithm> #include <algorithm>
#include <deque> #include <deque>
#include <inttypes.h> #include <inttypes.h>
@@ -8,18 +10,17 @@
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include "link/assign.hpp"
#include "link/section.hpp"
#include "link/symbol.hpp"
#include "link/object.hpp"
#include "link/main.hpp"
#include "link/output.hpp"
#include "error.hpp" #include "error.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "itertools.hpp" #include "itertools.hpp"
#include "linkdefs.hpp" #include "linkdefs.hpp"
#include "link/main.hpp"
#include "link/object.hpp"
#include "link/output.hpp"
#include "link/section.hpp"
#include "link/symbol.hpp"
struct MemoryLocation { struct MemoryLocation {
uint16_t address; uint16_t address;
uint32_t bank; uint32_t bank;
@@ -36,15 +37,13 @@ std::vector<std::deque<FreeSpace>> memory[SECTTYPE_INVALID];
uint64_t nbSectionsToAssign; uint64_t nbSectionsToAssign;
// Init the free space-modelling structs // Init the free space-modelling structs
static void initFreeSpace() static void initFreeSpace() {
{
for (enum SectionType type : EnumSeq(SECTTYPE_INVALID)) { for (enum SectionType type : EnumSeq(SECTTYPE_INVALID)) {
memory[type].resize(nbbanks(type)); memory[type].resize(nbbanks(type));
for (std::deque<FreeSpace> &bankMem : memory[type]) { for (std::deque<FreeSpace> &bankMem : memory[type]) {
bankMem.push_back({ bankMem.push_back(
.address = sectionTypeInfo[type].startAddr, {.address = sectionTypeInfo[type].startAddr, .size = sectionTypeInfo[type].size}
.size = sectionTypeInfo[type].size );
});
} }
} }
} }
@@ -54,8 +53,7 @@ static void initFreeSpace()
* @param section The section to assign * @param section The section to assign
* @param location The location to assign the section to * @param location The location to assign the section to
*/ */
static void assignSection(Section &section, MemoryLocation const &location) static void assignSection(Section &section, MemoryLocation const &location) {
{
// Propagate the assigned location to all UNIONs/FRAGMENTs // Propagate the assigned location to all UNIONs/FRAGMENTs
// so `jr` patches in them will have the correct offset // so `jr` patches in them will have the correct offset
for (Section *next = &section; next != nullptr; next = next->nextu) { for (Section *next = &section; next != nullptr; next = next->nextu) {
@@ -77,9 +75,9 @@ static void assignSection(Section &section, MemoryLocation const &location)
* @param location The location to attempt placing the section at * @param location The location to attempt placing the section at
* @return True if the location is suitable, false otherwise. * @return True if the location is suitable, false otherwise.
*/ */
static bool isLocationSuitable(Section const &section, FreeSpace const &freeSpace, static bool isLocationSuitable(
MemoryLocation const &location) Section const &section, FreeSpace const &freeSpace, MemoryLocation const &location
{ ) {
if (section.isAddressFixed && section.org != location.address) if (section.isAddressFixed && section.org != location.address)
return false; return false;
@@ -99,8 +97,7 @@ static bool isLocationSuitable(Section const &section, FreeSpace const &freeSpac
* @return The index into `memory[section->type]` of the free space encompassing the location, * @return The index into `memory[section->type]` of the free space encompassing the location,
* or -1 if none was found * or -1 if none was found
*/ */
static ssize_t getPlacement(Section const &section, MemoryLocation &location) static ssize_t getPlacement(Section const &section, MemoryLocation &location) {
{
SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type]; SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type];
static uint16_t curScrambleROM = 0; static uint16_t curScrambleROM = 0;
@@ -166,16 +163,16 @@ static ssize_t getPlacement(Section const &section, MemoryLocation &location)
// If that location is past the current block's end, // If that location is past the current block's end,
// go forwards until that is no longer the case. // go forwards until that is no longer the case.
while (spaceIdx < bankMem.size() && location.address >= while (spaceIdx < bankMem.size()
bankMem[spaceIdx].address + bankMem[spaceIdx].size) && location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size)
spaceIdx++; spaceIdx++;
// Try again with the new location/free space combo // Try again with the new location/free space combo
} }
// Try again in the next bank, if one is available. // Try again in the next bank, if one is available.
// Try scrambled banks in descending order until no bank in the scrambled range is available. // Try scrambled banks in descending order until no bank in the scrambled range is
// Otherwise, try in ascending order. // available. Otherwise, try in ascending order.
if (section.isBankFixed) { if (section.isBankFixed) {
return -1; return -1;
} else if (scrambleROMX && section.type == SECTTYPE_ROMX && location.bank <= scrambleROMX) { } else if (scrambleROMX && section.type == SECTTYPE_ROMX && location.bank <= scrambleROMX) {
@@ -213,20 +210,17 @@ static ssize_t getPlacement(Section const &section, MemoryLocation &location)
* sections of decreasing size. * sections of decreasing size.
* @param section The section to place * @param section The section to place
*/ */
static void placeSection(Section &section) static void placeSection(Section &section) {
{
MemoryLocation location; MemoryLocation location;
// Specially handle 0-byte SECTIONs, as they can't overlap anything // Specially handle 0-byte SECTIONs, as they can't overlap anything
if (section.size == 0) { if (section.size == 0) {
// Unless the SECTION's address was fixed, the starting address // Unless the SECTION's address was fixed, the starting address
// is fine for any alignment, as checked in sect_DoSanityChecks. // is fine for any alignment, as checked in sect_DoSanityChecks.
location.address = section.isAddressFixed location.address =
? section.org section.isAddressFixed ? section.org : sectionTypeInfo[section.type].startAddr;
: sectionTypeInfo[section.type].startAddr; location.bank =
location.bank = section.isBankFixed section.isBankFixed ? section.bank : sectionTypeInfo[section.type].firstBank;
? section.bank
: sectionTypeInfo[section.type].firstBank;
assignSection(section, location); assignSection(section, location);
return; return;
} }
@@ -234,14 +228,14 @@ static void placeSection(Section &section)
// Place section using first-fit decreasing algorithm // Place section using first-fit decreasing algorithm
// https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm // https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm
if (ssize_t spaceIdx = getPlacement(section, location); spaceIdx != -1) { if (ssize_t spaceIdx = getPlacement(section, location); spaceIdx != -1) {
std::deque<FreeSpace> &bankMem = memory[section.type][location.bank - std::deque<FreeSpace> &bankMem =
sectionTypeInfo[section.type].firstBank]; memory[section.type][location.bank - sectionTypeInfo[section.type].firstBank];
FreeSpace &freeSpace = bankMem[spaceIdx]; FreeSpace &freeSpace = bankMem[spaceIdx];
assignSection(section, location); assignSection(section, location);
// Update the free space // Update the free space
bool noLeftSpace = freeSpace.address == section.org; bool noLeftSpace = freeSpace.address == section.org;
bool noRightSpace = freeSpace.address + freeSpace.size == section.org + section.size; bool noRightSpace = freeSpace.address + freeSpace.size == section.org + section.size;
if (noLeftSpace && noRightSpace) { if (noLeftSpace && noRightSpace) {
// The free space is entirely deleted // The free space is entirely deleted
@@ -249,11 +243,12 @@ static void placeSection(Section &section)
} else if (!noLeftSpace && !noRightSpace) { } else if (!noLeftSpace && !noRightSpace) {
// The free space is split in two // The free space is split in two
// Append the new space after the original one // Append the new space after the original one
bankMem.insert(bankMem.begin() + spaceIdx + 1, { bankMem.insert(
.address = (uint16_t)(section.org + section.size), bankMem.begin() + spaceIdx + 1,
.size = (uint16_t)(freeSpace.address + freeSpace.size - {.address = (uint16_t)(section.org + section.size),
section.org - section.size) .size =
}); (uint16_t)(freeSpace.address + freeSpace.size - section.org - section.size)}
);
// Resize the original space (address is unmodified) // Resize the original space (address is unmodified)
freeSpace.size = section.org - freeSpace.address; freeSpace.size = section.org - freeSpace.address;
} else { } else {
@@ -271,41 +266,66 @@ static void placeSection(Section &section)
if (section.isBankFixed && nbbanks(section.type) != 1) { if (section.isBankFixed && nbbanks(section.type) != 1) {
if (section.isAddressFixed) if (section.isAddressFixed)
snprintf(where, sizeof(where), "at $%02" PRIx32 ":%04" PRIx16, snprintf(
section.bank, section.org); where, sizeof(where), "at $%02" PRIx32 ":%04" PRIx16, section.bank, section.org
);
else if (section.isAlignFixed) else if (section.isAlignFixed)
snprintf(where, sizeof(where), "in bank $%02" PRIx32 " with align mask %" PRIx16, snprintf(
section.bank, (uint16_t)~section.alignMask); where,
sizeof(where),
"in bank $%02" PRIx32 " with align mask %" PRIx16,
section.bank,
(uint16_t)~section.alignMask
);
else else
snprintf(where, sizeof(where), "in bank $%02" PRIx32, section.bank); snprintf(where, sizeof(where), "in bank $%02" PRIx32, section.bank);
} else { } else {
if (section.isAddressFixed) if (section.isAddressFixed)
snprintf(where, sizeof(where), "at address $%04" PRIx16, section.org); snprintf(where, sizeof(where), "at address $%04" PRIx16, section.org);
else if (section.isAlignFixed) else if (section.isAlignFixed)
snprintf(where, sizeof(where), "with align mask %" PRIx16 " and offset %" PRIx16, snprintf(
(uint16_t)~section.alignMask, section.alignOfs); where,
sizeof(where),
"with align mask %" PRIx16 " and offset %" PRIx16,
(uint16_t)~section.alignMask,
section.alignOfs
);
else else
strcpy(where, "anywhere"); strcpy(where, "anywhere");
} }
// 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(
section.name.c_str(), sectionTypeInfo[section.type].name.c_str(), where); "Unable to place \"%s\" (%s section) %s",
section.name.c_str(),
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(
section.name.c_str(), sectionTypeInfo[section.type].name.c_str(), where, "Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > "
section.org + section.size, endaddr(section.type) + 1); "$%04x)",
section.name.c_str(),
sectionTypeInfo[section.type].name.c_str(),
where,
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(
section.name.c_str(), sectionTypeInfo[section.type].name.c_str(), where, "Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
out_OverlappingSection(section)->name.c_str()); section.name.c_str(),
sectionTypeInfo[section.type].name.c_str(),
where,
out_OverlappingSection(section)->name.c_str()
);
} }
#define BANK_CONSTRAINED (1 << 2) #define BANK_CONSTRAINED (1 << 2)
#define ORG_CONSTRAINED (1 << 1) #define ORG_CONSTRAINED (1 << 1)
#define ALIGN_CONSTRAINED (1 << 0) #define ALIGN_CONSTRAINED (1 << 0)
static std::deque<Section *> unassignedSections[1 << 3]; static std::deque<Section *> unassignedSections[1 << 3];
@@ -314,8 +334,7 @@ static std::deque<Section *> unassignedSections[1 << 3];
* This is so the most-constrained sections are placed first * This is so the most-constrained sections are placed first
* @param section The section to categorize * @param section The section to categorize
*/ */
static void categorizeSection(Section &section) static void categorizeSection(Section &section) {
{
uint8_t constraints = 0; uint8_t constraints = 0;
if (section.isBankFixed) if (section.isBankFixed)
@@ -337,8 +356,7 @@ static void categorizeSection(Section &section)
nbSectionsToAssign++; nbSectionsToAssign++;
} }
void assign_AssignSections() void assign_AssignSections() {
{
verbosePrint("Beginning assignment...\n"); verbosePrint("Beginning assignment...\n");
// Initialize assignment // Initialize assignment
@@ -368,8 +386,7 @@ void assign_AssignSections()
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; constraints >= 0; for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; constraints >= 0;
constraints--) { constraints--) {
for (Section *section : unassignedSections[constraints]) { for (Section *section : unassignedSections[constraints]) {
fprintf(stderr, "%c \"%s\"", nbSections == 0 ? ';': ',', fprintf(stderr, "%c \"%s\"", nbSections == 0 ? ';' : ',', section->name.c_str());
section->name.c_str());
nbSections++; nbSections++;
if (nbSections == 10) if (nbSections == 10)
goto max_out; goto max_out;
@@ -384,7 +401,8 @@ max_out:
} }
// Assign all remaining sections by decreasing constraint order // Assign all remaining sections by decreasing constraint order
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; constraints >= 0; constraints--) { for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; constraints >= 0;
constraints--) {
for (Section *section : unassignedSections[constraints]) for (Section *section : unassignedSections[constraints])
placeSection(*section); placeSection(*section);

View File

@@ -1,51 +1,51 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h> #include <assert.h>
#include <inttypes.h> #include <inttypes.h>
#include <limits.h> #include <limits.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <string>
#include <sys/types.h>
#include <variant> #include <variant>
#include <vector> #include <vector>
#include "error.hpp"
#include "extern/getopt.hpp"
#include "itertools.hpp"
#include "linkdefs.hpp"
#include "platform.hpp"
#include "script.hpp"
#include "version.hpp"
#include "link/assign.hpp" #include "link/assign.hpp"
#include "link/object.hpp" #include "link/object.hpp"
#include "link/output.hpp" #include "link/output.hpp"
#include "link/patch.hpp" #include "link/patch.hpp"
#include "link/section.hpp" #include "link/section.hpp"
#include "script.hpp"
#include "link/symbol.hpp" #include "link/symbol.hpp"
#include "extern/getopt.hpp" bool isDmgMode; // -d
char *linkerScriptName; // -l
#include "error.hpp" char const *mapFileName; // -m
#include "itertools.hpp" bool noSymInMap; // -M
#include "linkdefs.hpp" char const *symFileName; // -n
#include "platform.hpp" char const *overlayFileName; // -O
#include "version.hpp" char const *outputFileName; // -o
uint8_t padValue; // -p
bool isDmgMode; // -d
char *linkerScriptName; // -l
char const *mapFileName; // -m
bool noSymInMap; // -M
char const *symFileName; // -n
char const *overlayFileName; // -O
char const *outputFileName; // -o
uint8_t padValue; // -p
// Setting these three to 0 disables the functionality // Setting these three to 0 disables the functionality
uint16_t scrambleROMX = 0; // -S uint16_t scrambleROMX = 0; // -S
uint8_t scrambleWRAMX = 0; uint8_t scrambleWRAMX = 0;
uint8_t scrambleSRAM = 0; uint8_t scrambleSRAM = 0;
bool is32kMode; // -t bool is32kMode; // -t
bool beVerbose; // -v bool beVerbose; // -v
bool isWRAM0Mode; // -w bool isWRAM0Mode; // -w
bool disablePadding; // -x bool disablePadding; // -x
FILE *linkerScript; FILE *linkerScript;
@@ -72,8 +72,7 @@ std::string const &FileStackNode::name() const {
} }
// Helper function to dump a file stack to stderr // Helper function to dump a file stack to stderr
std::string const *FileStackNode::dumpFileStack() const std::string const *FileStackNode::dumpFileStack() const {
{
std::string const *lastName; std::string const *lastName;
if (parent) { if (parent) {
@@ -95,9 +94,9 @@ std::string const *FileStackNode::dumpFileStack() const
return lastName; return lastName;
} }
void printDiag(char const *fmt, va_list args, char const *type, void printDiag(
FileStackNode const *where, uint32_t lineNo) char const *fmt, va_list args, char const *type, FileStackNode const *where, uint32_t lineNo
{ ) {
fputs(type, stderr); fputs(type, stderr);
fputs(": ", stderr); fputs(": ", stderr);
if (where) { if (where) {
@@ -108,8 +107,7 @@ void printDiag(char const *fmt, va_list args, char const *type,
putc('\n', stderr); putc('\n', stderr);
} }
void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) {
{
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
@@ -117,8 +115,7 @@ void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
va_end(args); va_end(args);
} }
void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) {
{
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
@@ -129,8 +126,7 @@ void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
nbErrors++; nbErrors++;
} }
void argErr(char flag, char const *fmt, ...) void argErr(char flag, char const *fmt, ...) {
{
va_list args; va_list args;
fprintf(stderr, "error: Invalid argument for option '%c': ", flag); fprintf(stderr, "error: Invalid argument for option '%c': ", flag);
@@ -143,8 +139,7 @@ void argErr(char flag, char const *fmt, ...)
nbErrors++; nbErrors++;
} }
[[noreturn]] void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) [[noreturn]] void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) {
{
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
@@ -154,8 +149,9 @@ void argErr(char flag, char const *fmt, ...)
if (nbErrors != UINT32_MAX) if (nbErrors != UINT32_MAX)
nbErrors++; nbErrors++;
fprintf(stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors, fprintf(
nbErrors == 1 ? "" : "s"); stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s"
);
exit(1); exit(1);
} }
@@ -173,41 +169,41 @@ static const char *optstring = "dl:m:Mn:O:o:p:S:s:tVvWwx";
* over short opt matching * over short opt matching
*/ */
static option const longopts[] = { static option const longopts[] = {
{ "dmg", no_argument, nullptr, 'd' }, {"dmg", no_argument, nullptr, 'd'},
{ "linkerscript", required_argument, nullptr, 'l' }, {"linkerscript", required_argument, nullptr, 'l'},
{ "map", required_argument, nullptr, 'm' }, {"map", required_argument, nullptr, 'm'},
{ "no-sym-in-map", no_argument, nullptr, 'M' }, {"no-sym-in-map", no_argument, nullptr, 'M'},
{ "sym", required_argument, nullptr, 'n' }, {"sym", required_argument, nullptr, 'n'},
{ "overlay", required_argument, nullptr, 'O' }, {"overlay", required_argument, nullptr, 'O'},
{ "output", required_argument, nullptr, 'o' }, {"output", required_argument, nullptr, 'o'},
{ "pad", required_argument, nullptr, 'p' }, {"pad", required_argument, nullptr, 'p'},
{ "scramble", required_argument, nullptr, 'S' }, {"scramble", required_argument, nullptr, 'S'},
{ "smart", required_argument, nullptr, 's' }, {"smart", required_argument, nullptr, 's'},
{ "tiny", no_argument, nullptr, 't' }, {"tiny", no_argument, nullptr, 't'},
{ "version", no_argument, nullptr, 'V' }, {"version", no_argument, nullptr, 'V'},
{ "verbose", no_argument, nullptr, 'v' }, {"verbose", no_argument, nullptr, 'v'},
{ "wramx", no_argument, nullptr, 'w' }, {"wramx", no_argument, nullptr, 'w'},
{ "nopad", no_argument, nullptr, 'x' }, {"nopad", no_argument, nullptr, 'x'},
{ nullptr, no_argument, nullptr, 0 } {nullptr, no_argument, nullptr, 0 }
}; };
static void printUsage() static void printUsage() {
{
fputs( fputs(
"Usage: rgblink [-dMtVvwx] [-l script] [-m map_file] [-n sym_file]\n" "Usage: rgblink [-dMtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
" [-O overlay_file] [-o out_file] [-p pad_value]\n" " [-O overlay_file] [-o out_file] [-p pad_value]\n"
" [-S spec] [-s symbol] <file> ...\n" " [-S spec] [-s symbol] <file> ...\n"
"Useful options:\n" "Useful options:\n"
" -l, --linkerscript <path> set the input linker script\n" " -l, --linkerscript <path> set the input linker script\n"
" -m, --map <path> set the output map file\n" " -m, --map <path> set the output map file\n"
" -n, --sym <path> set the output symbol list file\n" " -n, --sym <path> set the output symbol list file\n"
" -o, --output <path> set the output file\n" " -o, --output <path> set the output file\n"
" -p, --pad <value> set the value to pad between sections with\n" " -p, --pad <value> set the value to pad between sections with\n"
" -x, --nopad disable padding of output binary\n" " -x, --nopad disable padding of output binary\n"
" -V, --version print RGBLINK version and exits\n" " -V, --version print RGBLINK version and exits\n"
"\n" "\n"
"For help, use `man rgblink' or go to https://rgbds.gbdev.io/docs/\n", "For help, use `man rgblink' or go to https://rgbds.gbdev.io/docs/\n",
stderr); stderr
);
} }
enum ScrambledRegion { enum ScrambledRegion {
@@ -222,13 +218,12 @@ struct {
char const *name; char const *name;
uint16_t max; uint16_t max;
} scrambleSpecs[SCRAMBLE_UNK] = { } scrambleSpecs[SCRAMBLE_UNK] = {
{ "romx", 65535 }, // SCRAMBLE_ROMX {"romx", 65535}, // SCRAMBLE_ROMX
{ "sram", 255 }, // SCRAMBLE_SRAM {"sram", 255 }, // SCRAMBLE_SRAM
{ "wramx", 7 }, // SCRAMBLE_WRAMX {"wramx", 7 }, // SCRAMBLE_WRAMX
}; };
static void parseScrambleSpec(char const *spec) static void parseScrambleSpec(char const *spec) {
{
// Skip any leading whitespace // Skip any leading whitespace
spec += strspn(spec, " \t"); spec += strspn(spec, " \t");
@@ -251,7 +246,7 @@ static void parseScrambleSpec(char const *spec)
if (*spec == '\0') if (*spec == '\0')
break; break;
if (*spec == '=') // Skip the limit, too if (*spec == '=') // Skip the limit, too
spec = strchr(&spec[1], ','); // Skip to next comma, if any spec = strchr(&spec[1], ','); // Skip to next comma, if any
goto next; goto next;
} }
@@ -259,8 +254,9 @@ static void parseScrambleSpec(char const *spec)
// Find the next non-blank char after the region name's end // Find the next non-blank char after the region name's end
spec += regionNameLen + strspn(&spec[regionNameLen], " \t"); spec += regionNameLen + strspn(&spec[regionNameLen], " \t");
if (*spec != '\0' && *spec != ',' && *spec != '=') { if (*spec != '\0' && *spec != ',' && *spec != '=') {
argErr('S', "Unexpected '%c' after region name \"%.*s\"", argErr(
regionNamePrintLen, regionName); 'S', "Unexpected '%c' after region name \"%.*s\"", regionNamePrintLen, regionName
);
// Skip to next ',' or '=' (or NUL) and keep parsing // Skip to next ',' or '=' (or NUL) and keep parsing
spec += 1 + strcspn(&spec[1], ",="); spec += 1 + strcspn(&spec[1], ",=");
} }
@@ -285,22 +281,30 @@ static void parseScrambleSpec(char const *spec)
char *endptr; char *endptr;
if (*spec == '\0' || *spec == ',') { if (*spec == '\0' || *spec == ',') {
argErr('S', "Empty limit for region \"%.*s\"", argErr('S', "Empty limit for region \"%.*s\"", regionNamePrintLen, regionName);
regionNamePrintLen, regionName);
goto next; goto next;
} }
limit = strtoul(spec, &endptr, 10); limit = strtoul(spec, &endptr, 10);
endptr += strspn(endptr, " \t"); endptr += strspn(endptr, " \t");
if (*endptr != '\0' && *endptr != ',') { if (*endptr != '\0' && *endptr != ',') {
argErr('S', "Invalid non-numeric limit for region \"%.*s\"", argErr(
regionNamePrintLen, regionName); 'S',
"Invalid non-numeric limit for region \"%.*s\"",
regionNamePrintLen,
regionName
);
endptr = strchr(endptr, ','); endptr = strchr(endptr, ',');
} }
spec = endptr; spec = endptr;
if (region != SCRAMBLE_UNK && limit > scrambleSpecs[region].max) { if (region != SCRAMBLE_UNK && limit > scrambleSpecs[region].max) {
argErr('S', "Limit for region \"%.*s\" may not exceed %" PRIu16, argErr(
regionNamePrintLen, regionName, scrambleSpecs[region].max); 'S',
"Limit for region \"%.*s\" may not exceed %" PRIu16,
regionNamePrintLen,
regionName,
scrambleSpecs[region].max
);
limit = scrambleSpecs[region].max; limit = scrambleSpecs[region].max;
} }
@@ -321,8 +325,7 @@ static void parseScrambleSpec(char const *spec)
// Only WRAMX can be implied, since ROMX and SRAM size may vary // Only WRAMX can be implied, since ROMX and SRAM size may vary
scrambleWRAMX = 7; scrambleWRAMX = 7;
} else { } else {
argErr('S', "Cannot imply limit for region \"%.*s\"", argErr('S', "Cannot imply limit for region \"%.*s\"", regionNamePrintLen, regionName);
regionNamePrintLen, regionName);
} }
next: next:
@@ -337,13 +340,13 @@ next:
} }
[[noreturn]] void reportErrors() { [[noreturn]] void reportErrors() {
fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n", fprintf(
nbErrors, nbErrors == 1 ? "" : "s"); stderr, "Linking failed with %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s"
);
exit(1); exit(1);
} }
static void freeSection(Section &section) static void freeSection(Section &section) {
{
Section *next = &section; Section *next = &section;
for (Section *nextu; next; next = nextu) { for (Section *nextu; next; next = nextu) {
@@ -352,13 +355,11 @@ static void freeSection(Section &section)
}; };
} }
static void freeSections() static void freeSections() {
{
sect_ForEach(freeSection); sect_ForEach(freeSection);
} }
int main(int argc, char *argv[]) int main(int argc, char *argv[]) {
{
// Parse options // Parse options
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) { for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
switch (ch) { switch (ch) {
@@ -444,8 +445,9 @@ int main(int argc, char *argv[])
// If no input files were specified, the user must have screwed up // If no input files were specified, the user must have screwed up
if (curArgIndex == argc) { if (curArgIndex == argc) {
fputs("FATAL: Please specify an input file (pass `-` to read from standard input)\n", fputs(
stderr); "FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr
);
printUsage(); printUsage();
exit(1); exit(1);
} }

View File

@@ -1,30 +1,31 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "link/object.hpp"
#include <algorithm> #include <algorithm>
#include <deque> #include <deque>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <limits.h> #include <limits.h>
#include <new> #include <new>
#include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <vector> #include <vector>
#include "link/assign.hpp"
#include "link/main.hpp"
#include "link/object.hpp"
#include "link/patch.hpp"
#include "link/sdas_obj.hpp"
#include "link/section.hpp"
#include "link/symbol.hpp"
#include "error.hpp" #include "error.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "linkdefs.hpp" #include "linkdefs.hpp"
#include "version.hpp" #include "version.hpp"
#include "link/assign.hpp"
#include "link/main.hpp"
#include "link/patch.hpp"
#include "link/sdas_obj.hpp"
#include "link/section.hpp"
#include "link/symbol.hpp"
static std::deque<std::vector<Symbol>> symbolLists; static std::deque<std::vector<Symbol>> symbolLists;
static std::vector<std::vector<FileStackNode>> nodes; static std::vector<std::vector<FileStackNode>> nodes;
static std::deque<Assertion> assertions; static std::deque<Assertion> assertions;
@@ -33,25 +34,23 @@ static std::deque<Assertion> assertions;
// Internal, DO NOT USE. // Internal, DO NOT USE.
// For helper wrapper macros defined below, such as `tryReadlong` // For helper wrapper macros defined below, such as `tryReadlong`
#define tryRead(func, type, errval, vartype, var, file, ...) do { \ #define tryRead(func, type, errval, vartype, var, file, ...) \
FILE *tmpFile = file; \ do { \
type tmpVal = func(tmpFile); \ FILE *tmpFile = file; \
/* TODO: maybe mark the condition as `unlikely`; how to do that portably? */ \ type tmpVal = func(tmpFile); \
if (tmpVal == (errval)) { \ /* TODO: maybe mark the condition as `unlikely`; how to do that portably? */ \
errx(__VA_ARGS__, feof(tmpFile) \ if (tmpVal == (errval)) { \
? "Unexpected end of file" \ errx(__VA_ARGS__, feof(tmpFile) ? "Unexpected end of file" : strerror(errno)); \
: strerror(errno)); \ } \
} \ var = (vartype)tmpVal; \
var = (vartype)tmpVal; \ } while (0)
} while (0)
/* /*
* Reads an unsigned long (32-bit) value from a file. * 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. * @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. * @return The value read, cast to a int64_t, or -1 on failure.
*/ */
static int64_t readlong(FILE *file) static int64_t readlong(FILE *file) {
{
uint32_t value = 0; uint32_t value = 0;
// Read the little-endian value byte by byte // Read the little-endian value byte by byte
@@ -92,8 +91,7 @@ static int64_t readlong(FILE *file)
* @param ... A format string and related arguments; note that an extra string * @param ... A format string and related arguments; note that an extra string
* argument is provided, the reason for failure * argument is provided, the reason for failure
*/ */
#define tryGetc(type, var, file, ...) \ #define tryGetc(type, var, file, ...) tryRead(getc, int, EOF, type, var, file, __VA_ARGS__)
tryRead(getc, int, EOF, type, var, file, __VA_ARGS__)
/* /*
* Helper macro for readings '\0'-terminated strings from a file, and errors out if it fails to. * Helper macro for readings '\0'-terminated strings from a file, and errors out if it fails to.
@@ -103,19 +101,18 @@ static int64_t readlong(FILE *file)
* @param ... A format string and related arguments; note that an extra string * @param ... A format string and related arguments; note that an extra string
* argument is provided, the reason for failure * argument is provided, the reason for failure
*/ */
#define tryReadstring(var, file, ...) do { \ #define tryReadstring(var, file, ...) \
FILE *tmpFile = file; \ do { \
std::string &tmpVal = var; \ FILE *tmpFile = file; \
for (int tmpByte = getc(tmpFile); tmpByte != '\0'; tmpByte = getc(tmpFile)) { \ std::string &tmpVal = var; \
if (tmpByte == EOF) { \ for (int tmpByte = getc(tmpFile); tmpByte != '\0'; tmpByte = getc(tmpFile)) { \
errx(__VA_ARGS__, feof(tmpFile) \ if (tmpByte == EOF) { \
? "Unexpected end of file" \ errx(__VA_ARGS__, feof(tmpFile) ? "Unexpected end of file" : strerror(errno)); \
: strerror(errno)); \ } else { \
} else { \ tmpVal.push_back(tmpByte); \
tmpVal.push_back(tmpByte); \ } \
} \ }; \
}; \ } while (0)
} while (0)
// Functions to parse object files // Functions to parse object files
@@ -126,39 +123,55 @@ static int64_t readlong(FILE *file)
* @param i The ID of the node in the array * @param i The ID of the node in the array
* @param fileName The filename to report in errors * @param fileName The filename to report in errors
*/ */
static void readFileStackNode(FILE *file, std::vector<FileStackNode> &fileNodes, uint32_t i, static void readFileStackNode(
char const *fileName) FILE *file, std::vector<FileStackNode> &fileNodes, uint32_t i, char const *fileName
{ ) {
FileStackNode &node = fileNodes[i]; FileStackNode &node = fileNodes[i];
uint32_t parentID; uint32_t parentID;
tryReadlong(parentID, file, tryReadlong(parentID, file, "%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, i);
"%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, i);
node.parent = parentID != (uint32_t)-1 ? &fileNodes[parentID] : nullptr; node.parent = parentID != (uint32_t)-1 ? &fileNodes[parentID] : nullptr;
tryReadlong(node.lineNo, file, tryReadlong(
"%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, i); node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, i
tryGetc(enum FileStackNodeType, node.type, file, );
"%s: Cannot read node #%" PRIu32 "'s type: %s", fileName, i); tryGetc(
enum FileStackNodeType,
node.type,
file,
"%s: Cannot read node #%" PRIu32 "'s type: %s",
fileName,
i
);
switch (node.type) { switch (node.type) {
case NODE_FILE: case NODE_FILE:
case NODE_MACRO: case NODE_MACRO:
node.data = ""; node.data = "";
tryReadstring(node.name(), file, tryReadstring(
"%s: Cannot read node #%" PRIu32 "'s file name: %s", fileName, i); node.name(), file, "%s: Cannot read node #%" PRIu32 "'s file name: %s", fileName, i
);
break; break;
uint32_t depth; uint32_t depth;
case NODE_REPT: case NODE_REPT:
tryReadlong(depth, file, tryReadlong(depth, file, "%s: Cannot read node #%" PRIu32 "'s rept depth: %s", fileName, i);
"%s: Cannot read node #%" PRIu32 "'s rept depth: %s", fileName, i);
node.data = std::vector<uint32_t>(depth); node.data = std::vector<uint32_t>(depth);
for (uint32_t k = 0; k < depth; k++) for (uint32_t k = 0; k < depth; k++)
tryReadlong(node.iters()[k], file, tryReadlong(
"%s: Cannot read node #%" PRIu32 "'s iter #%" PRIu32 ": %s", node.iters()[k],
fileName, i, k); file,
"%s: Cannot read node #%" PRIu32 "'s iter #%" PRIu32 ": %s",
fileName,
i,
k
);
if (!node.parent) if (!node.parent)
fatal(nullptr, 0, "%s is not a valid object file: root node (#%" fatal(
PRIu32 ") may not be REPT", fileName, i); nullptr,
0,
"%s is not a valid object file: root node (#%" PRIu32 ") may not be REPT",
fileName,
i
);
} }
} }
@@ -168,34 +181,52 @@ static void readFileStackNode(FILE *file, std::vector<FileStackNode> &fileNodes,
* @param symbol The symbol to fill * @param symbol The symbol to fill
* @param fileName The filename to report in errors * @param fileName The filename to report in errors
*/ */
static void readSymbol(FILE *file, Symbol &symbol, char const *fileName, static void readSymbol(
std::vector<FileStackNode> const &fileNodes) FILE *file, Symbol &symbol, char const *fileName, std::vector<FileStackNode> const &fileNodes
{ ) {
tryReadstring(symbol.name, file, "%s: Cannot read symbol name: %s", fileName); tryReadstring(symbol.name, file, "%s: Cannot read symbol name: %s", fileName);
tryGetc(enum ExportLevel, symbol.type, file, "%s: Cannot read \"%s\"'s type: %s", tryGetc(
fileName, symbol.name.c_str()); enum ExportLevel,
symbol.type,
file,
"%s: Cannot read \"%s\"'s type: %s",
fileName,
symbol.name.c_str()
);
// If the symbol is defined in this file, read its definition // If the symbol is defined in this file, read its definition
if (symbol.type != SYMTYPE_IMPORT) { if (symbol.type != SYMTYPE_IMPORT) {
symbol.objFileName = fileName; symbol.objFileName = fileName;
uint32_t nodeID; uint32_t nodeID;
tryReadlong(nodeID, file, "%s: Cannot read \"%s\"'s node ID: %s", tryReadlong(
fileName, symbol.name.c_str()); nodeID, file, "%s: Cannot read \"%s\"'s node ID: %s", fileName, symbol.name.c_str()
);
symbol.src = &fileNodes[nodeID]; symbol.src = &fileNodes[nodeID];
tryReadlong(symbol.lineNo, file, "%s: Cannot read \"%s\"'s line number: %s", tryReadlong(
fileName, symbol.name.c_str()); symbol.lineNo,
file,
"%s: Cannot read \"%s\"'s line number: %s",
fileName,
symbol.name.c_str()
);
int32_t sectionID, value; int32_t sectionID, value;
tryReadlong(sectionID, file, "%s: Cannot read \"%s\"'s section ID: %s", tryReadlong(
fileName, symbol.name.c_str()); sectionID,
tryReadlong(value, file, "%s: Cannot read \"%s\"'s value: %s", file,
fileName, symbol.name.c_str()); "%s: Cannot read \"%s\"'s section ID: %s",
fileName,
symbol.name.c_str()
);
tryReadlong(
value, file, "%s: Cannot read \"%s\"'s value: %s", fileName, symbol.name.c_str()
);
if (sectionID == -1) { if (sectionID == -1) {
symbol.data = value; symbol.data = value;
} else { } else {
symbol.data = Label{ symbol.data = Label{
.sectionID = sectionID, .sectionID = sectionID,
.offset = value, .offset = value,
// Set the `.section` later based on the `.sectionID` // Set the `.section` later based on the `.sectionID`
.section = nullptr, .section = nullptr,
}; };
} }
} else { } else {
@@ -210,53 +241,96 @@ static void readSymbol(FILE *file, Symbol &symbol, char const *fileName,
* @param fileName The filename to report in errors * @param fileName The filename to report in errors
* @param i The number of the patch to report in errors * @param i The number of the patch to report in errors
*/ */
static void readPatch(FILE *file, Patch &patch, char const *fileName, std::string const &sectName, static void readPatch(
uint32_t i, std::vector<FileStackNode> const &fileNodes) FILE *file,
{ Patch &patch,
char const *fileName,
std::string const &sectName,
uint32_t i,
std::vector<FileStackNode> const &fileNodes
) {
uint32_t nodeID, rpnSize; uint32_t nodeID, rpnSize;
enum PatchType type; enum PatchType type;
tryReadlong(nodeID, file, tryReadlong(
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s node ID: %s", nodeID,
fileName, sectName.c_str(), i); file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s node ID: %s",
fileName,
sectName.c_str(),
i
);
patch.src = &fileNodes[nodeID]; patch.src = &fileNodes[nodeID];
tryReadlong(patch.lineNo, file, tryReadlong(
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s line number: %s", patch.lineNo,
fileName, sectName.c_str(), i); file,
tryReadlong(patch.offset, file, "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s line number: %s",
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s offset: %s", fileName,
fileName, sectName.c_str(), i); sectName.c_str(),
tryReadlong(patch.pcSectionID, file, i
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s", );
fileName, sectName.c_str(), i); tryReadlong(
tryReadlong(patch.pcOffset, file, patch.offset,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s", file,
fileName, sectName.c_str(), i); "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s offset: %s",
tryGetc(enum PatchType, type, file, fileName,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s type: %s", sectName.c_str(),
fileName, sectName.c_str(), i); i
);
tryReadlong(
patch.pcSectionID,
file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
fileName,
sectName.c_str(),
i
);
tryReadlong(
patch.pcOffset,
file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
fileName,
sectName.c_str(),
i
);
tryGetc(
enum PatchType,
type,
file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s type: %s",
fileName,
sectName.c_str(),
i
);
patch.type = type; patch.type = type;
tryReadlong(rpnSize, file, tryReadlong(
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s", rpnSize,
fileName, sectName.c_str(), i); file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s",
fileName,
sectName.c_str(),
i
);
patch.rpnExpression.resize(rpnSize); patch.rpnExpression.resize(rpnSize);
size_t nbElementsRead = fread(patch.rpnExpression.data(), 1, rpnSize, file); size_t nbElementsRead = fread(patch.rpnExpression.data(), 1, rpnSize, file);
if (nbElementsRead != rpnSize) if (nbElementsRead != rpnSize)
errx("%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s", errx(
fileName, sectName.c_str(), i, "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s",
feof(file) ? "Unexpected end of file" : strerror(errno)); fileName,
sectName.c_str(),
i,
feof(file) ? "Unexpected end of file" : strerror(errno)
);
} }
/* /*
* Sets a patch's pcSection from its pcSectionID. * Sets a patch's pcSection from its pcSectionID.
* @param patch The patch to fix * @param patch The patch to fix
*/ */
static void linkPatchToPCSect(Patch &patch, std::vector<Section *> const &fileSections) static void linkPatchToPCSect(Patch &patch, std::vector<Section *> const &fileSections) {
{ patch.pcSection = patch.pcSectionID != (uint32_t)-1 ? fileSections[patch.pcSectionID] : nullptr;
patch.pcSection = patch.pcSectionID != (uint32_t)-1 ? fileSections[patch.pcSectionID]
: nullptr;
} }
/* /*
@@ -265,9 +339,9 @@ static void linkPatchToPCSect(Patch &patch, std::vector<Section *> const &fileSe
* @param section The section to fill * @param section The section to fill
* @param fileName The filename to report in errors * @param fileName The filename to report in errors
*/ */
static void readSection(FILE *file, Section &section, char const *fileName, static void readSection(
std::vector<FileStackNode> const &fileNodes) FILE *file, Section &section, char const *fileName, std::vector<FileStackNode> const &fileNodes
{ ) {
int32_t tmp; int32_t tmp;
uint8_t byte; uint8_t byte;
@@ -277,8 +351,9 @@ static void readSection(FILE *file, Section &section, char const *fileName,
errx("\"%s\"'s section size (%" PRId32 ") is invalid", section.name.c_str(), tmp); errx("\"%s\"'s section size (%" PRId32 ") is invalid", section.name.c_str(), tmp);
section.size = tmp; section.size = tmp;
section.offset = 0; section.offset = 0;
tryGetc(uint8_t, byte, file, "%s: Cannot read \"%s\"'s type: %s", fileName, tryGetc(
section.name.c_str()); uint8_t, byte, file, "%s: Cannot read \"%s\"'s type: %s", fileName, section.name.c_str()
);
section.type = (enum SectionType)(byte & 0x3F); section.type = (enum SectionType)(byte & 0x3F);
if (byte >> 7) if (byte >> 7)
section.modifier = SECTION_UNION; section.modifier = SECTION_UNION;
@@ -296,17 +371,29 @@ static void readSection(FILE *file, Section &section, char const *fileName,
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s", fileName, section.name.c_str()); tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s", fileName, section.name.c_str());
section.isBankFixed = tmp >= 0; section.isBankFixed = tmp >= 0;
section.bank = tmp; section.bank = tmp;
tryGetc(uint8_t, byte, file, "%s: Cannot read \"%s\"'s alignment: %s", fileName, tryGetc(
section.name.c_str()); uint8_t,
byte,
file,
"%s: Cannot read \"%s\"'s alignment: %s",
fileName,
section.name.c_str()
);
if (byte > 16) if (byte > 16)
byte = 16; byte = 16;
section.isAlignFixed = byte != 0; section.isAlignFixed = byte != 0;
section.alignMask = (1 << byte) - 1; section.alignMask = (1 << byte) - 1;
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s alignment offset: %s", fileName, tryReadlong(
section.name.c_str()); tmp, file, "%s: Cannot read \"%s\"'s alignment offset: %s", fileName, section.name.c_str()
);
if (tmp > UINT16_MAX) { if (tmp > UINT16_MAX) {
error(nullptr, 0, "\"%s\"'s alignment offset is too large (%" PRId32 ")", error(
section.name.c_str(), tmp); nullptr,
0,
"\"%s\"'s alignment offset is too large (%" PRId32 ")",
section.name.c_str(),
tmp
);
tmp = UINT16_MAX; tmp = UINT16_MAX;
} }
section.alignOfs = tmp; section.alignOfs = tmp;
@@ -316,15 +403,23 @@ static void readSection(FILE *file, Section &section, char const *fileName,
section.data.resize(section.size); section.data.resize(section.size);
if (size_t nbRead = fread(section.data.data(), 1, section.size, file); if (size_t nbRead = fread(section.data.data(), 1, section.size, file);
nbRead != section.size) nbRead != section.size)
errx("%s: Cannot read \"%s\"'s data: %s", fileName, section.name.c_str(), errx(
feof(file) ? "Unexpected end of file" : strerror(errno)); "%s: Cannot read \"%s\"'s data: %s",
fileName,
section.name.c_str(),
feof(file) ? "Unexpected end of file" : strerror(errno)
);
} }
uint32_t nbPatches; uint32_t nbPatches;
tryReadlong(nbPatches, file, tryReadlong(
"%s: Cannot read \"%s\"'s number of patches: %s", fileName, nbPatches,
section.name.c_str()); file,
"%s: Cannot read \"%s\"'s number of patches: %s",
fileName,
section.name.c_str()
);
section.patches.resize(nbPatches); section.patches.resize(nbPatches);
for (uint32_t i = 0; i < nbPatches; i++) for (uint32_t i = 0; i < nbPatches; i++)
@@ -337,8 +432,7 @@ static void readSection(FILE *file, Section &section, char const *fileName,
* @param symbol The symbol to link * @param symbol The symbol to link
* @param section The section to link * @param section The section to link
*/ */
static void linkSymToSect(Symbol &symbol, Section &section) static void linkSymToSect(Symbol &symbol, Section &section) {
{
uint32_t a = 0, b = section.symbols.size(); uint32_t a = 0, b = section.symbols.size();
int32_t symbolOffset = symbol.label().offset; int32_t symbolOffset = symbol.label().offset;
@@ -361,9 +455,13 @@ static void linkSymToSect(Symbol &symbol, Section &section)
* @param assert The assertion to fill * @param assert The assertion to fill
* @param fileName The filename to report in errors * @param fileName The filename to report in errors
*/ */
static void readAssertion(FILE *file, Assertion &assert, char const *fileName, uint32_t i, static void readAssertion(
std::vector<FileStackNode> const &fileNodes) FILE *file,
{ Assertion &assert,
char const *fileName,
uint32_t i,
std::vector<FileStackNode> const &fileNodes
) {
char assertName[sizeof("Assertion #4294967295")]; // UINT32_MAX char assertName[sizeof("Assertion #4294967295")]; // UINT32_MAX
snprintf(assertName, sizeof(assertName), "Assertion #%" PRIu32, i); snprintf(assertName, sizeof(assertName), "Assertion #%" PRIu32, i);
@@ -372,13 +470,11 @@ static void readAssertion(FILE *file, Assertion &assert, char const *fileName, u
tryReadstring(assert.message, file, "%s: Cannot read assertion's message: %s", fileName); tryReadstring(assert.message, file, "%s: Cannot read assertion's message: %s", fileName);
} }
static Section *getMainSection(Section &section) static Section *getMainSection(Section &section) {
{
return section.modifier != SECTION_NORMAL ? sect_GetSection(section.name) : &section; return section.modifier != SECTION_NORMAL ? sect_GetSection(section.name) : &section;
} }
void obj_ReadFile(char const *fileName, unsigned int fileID) void obj_ReadFile(char const *fileName, unsigned int fileID) {
{
FILE *file; FILE *file;
if (strcmp(fileName, "-")) { if (strcmp(fileName, "-")) {
@@ -405,12 +501,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
default: // This is (probably) a SDCC object file, defer the rest of detection to it default: // This is (probably) a SDCC object file, defer the rest of detection to it
// Since SDCC does not provide line info, everything will be reported as coming from the // Since SDCC does not provide line info, everything will be reported as coming from the
// object file. It's better than nothing. // object file. It's better than nothing.
nodes[fileID].push_back({ nodes[fileID].push_back(
.parent = nullptr, {.parent = nullptr, .lineNo = 0, .type = NODE_FILE, .data = fileName}
.lineNo = 0, );
.type = NODE_FILE,
.data = fileName
});
std::vector<Symbol> &fileSymbols = symbolLists.emplace_front(); std::vector<Symbol> &fileSymbols = symbolLists.emplace_front();
@@ -422,7 +515,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
int matchedElems; int matchedElems;
if (fscanf(file, RGBDS_OBJECT_VERSION_STRING "%n", &matchedElems) == 1 if (fscanf(file, RGBDS_OBJECT_VERSION_STRING "%n", &matchedElems) == 1
&& matchedElems != strlen(RGBDS_OBJECT_VERSION_STRING)) && matchedElems != strlen(RGBDS_OBJECT_VERSION_STRING))
errx("%s: Not a RGBDS object file", fileName); errx("%s: Not a RGBDS object file", fileName);
verbosePrint("Reading object file %s\n", fileName); verbosePrint("Reading object file %s\n", fileName);
@@ -431,10 +524,16 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
tryReadlong(revNum, file, "%s: Cannot read revision number: %s", fileName); tryReadlong(revNum, file, "%s: Cannot read revision number: %s", fileName);
if (revNum != RGBDS_OBJECT_REV) if (revNum != RGBDS_OBJECT_REV)
errx("%s: Unsupported object file for rgblink %s; try rebuilding \"%s\"%s" errx(
" (expected revision %d, got %d)", fileName, get_package_version_string(), "%s: Unsupported object file for rgblink %s; try rebuilding \"%s\"%s"
fileName, revNum > RGBDS_OBJECT_REV ? " or updating rgblink" : "", " (expected revision %d, got %d)",
RGBDS_OBJECT_REV, revNum); fileName,
get_package_version_string(),
fileName,
revNum > RGBDS_OBJECT_REV ? " or updating rgblink" : "",
RGBDS_OBJECT_REV,
revNum
);
uint32_t nbNodes; uint32_t nbNodes;
uint32_t nbSymbols; uint32_t nbSymbols;
@@ -448,7 +547,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
tryReadlong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName); tryReadlong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
nodes[fileID].resize(nbNodes); nodes[fileID].resize(nbNodes);
verbosePrint("Reading %u nodes...\n", nbNodes); verbosePrint("Reading %u nodes...\n", nbNodes);
for (uint32_t i = nbNodes; i--; ) for (uint32_t i = nbNodes; i--;)
readFileStackNode(file, nodes[fileID], i, fileName); readFileStackNode(file, nodes[fileID], i, fileName);
// This file's symbols, kept to link sections to them // This file's symbols, kept to link sections to them
@@ -474,7 +573,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
verbosePrint("Reading %" PRIu32 " sections...\n", nbSections); verbosePrint("Reading %" PRIu32 " sections...\n", nbSections);
for (uint32_t i = 0; i < nbSections; i++) { for (uint32_t i = 0; i < nbSections; i++) {
// Read section // Read section
fileSections[i] = new(std::nothrow) Section(); fileSections[i] = new (std::nothrow) Section();
if (!fileSections[i]) if (!fileSections[i])
err("%s: Failed to create new section", fileName); err("%s: Failed to create new section", fileName);
@@ -527,12 +626,10 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
fclose(file); fclose(file);
} }
void obj_CheckAssertions() void obj_CheckAssertions() {
{
patch_CheckAssertions(assertions); patch_CheckAssertions(assertions);
} }
void obj_Setup(unsigned int nbFiles) void obj_Setup(unsigned int nbFiles) {
{
nodes.resize(nbFiles); nodes.resize(nbFiles);
} }

View File

@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "link/output.hpp"
#include <algorithm> #include <algorithm>
#include <assert.h> #include <assert.h>
#include <deque> #include <deque>
@@ -10,17 +12,15 @@
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include "link/output.hpp" #include "error.hpp"
#include "extern/utf8decoder.hpp"
#include "itertools.hpp"
#include "linkdefs.hpp"
#include "link/main.hpp" #include "link/main.hpp"
#include "link/section.hpp" #include "link/section.hpp"
#include "link/symbol.hpp" #include "link/symbol.hpp"
#include "extern/utf8decoder.hpp"
#include "error.hpp"
#include "itertools.hpp"
#include "linkdefs.hpp"
#define BANK_SIZE 0x4000 #define BANK_SIZE 0x4000
FILE *outputFile; FILE *outputFile;
@@ -42,42 +42,45 @@ static std::deque<SortedSections> sections[SECTTYPE_INVALID];
// Defines the order in which types are output to the sym and map files // Defines the order in which types are output to the sym and map files
static enum SectionType typeMap[SECTTYPE_INVALID] = { static enum SectionType typeMap[SECTTYPE_INVALID] = {
SECTTYPE_ROM0, SECTTYPE_ROM0,
SECTTYPE_ROMX, SECTTYPE_ROMX,
SECTTYPE_VRAM, SECTTYPE_VRAM,
SECTTYPE_SRAM, SECTTYPE_SRAM,
SECTTYPE_WRAM0, SECTTYPE_WRAM0,
SECTTYPE_WRAMX, SECTTYPE_WRAMX,
SECTTYPE_OAM, SECTTYPE_OAM,
SECTTYPE_HRAM SECTTYPE_HRAM,
}; };
void out_AddSection(Section const &section) void out_AddSection(Section const &section) {
{
static const uint32_t maxNbBanks[SECTTYPE_INVALID] = { static const uint32_t maxNbBanks[SECTTYPE_INVALID] = {
1, // SECTTYPE_WRAM0 1, // SECTTYPE_WRAM0
2, // SECTTYPE_VRAM 2, // SECTTYPE_VRAM
UINT32_MAX, // SECTTYPE_ROMX UINT32_MAX, // SECTTYPE_ROMX
1, // SECTTYPE_ROM0 1, // SECTTYPE_ROM0
1, // SECTTYPE_HRAM 1, // SECTTYPE_HRAM
7, // SECTTYPE_WRAMX 7, // SECTTYPE_WRAMX
UINT32_MAX, // SECTTYPE_SRAM UINT32_MAX, // SECTTYPE_SRAM
1, // SECTTYPE_OAM 1, // SECTTYPE_OAM
}; };
uint32_t targetBank = section.bank - sectionTypeInfo[section.type].firstBank; uint32_t targetBank = section.bank - sectionTypeInfo[section.type].firstBank;
uint32_t minNbBanks = targetBank + 1; uint32_t minNbBanks = targetBank + 1;
if (minNbBanks > maxNbBanks[section.type]) if (minNbBanks > maxNbBanks[section.type])
errx("Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")", errx(
section.name.c_str(), section.bank, maxNbBanks[section.type] - 1); "Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")",
section.name.c_str(),
section.bank,
maxNbBanks[section.type] - 1
);
for (uint32_t i = sections[section.type].size(); i < minNbBanks; i++) for (uint32_t i = sections[section.type].size(); i < minNbBanks; i++)
sections[section.type].emplace_back(); sections[section.type].emplace_back();
std::deque<Section const *> &bankSections = section.size std::deque<Section const *> &bankSections =
? sections[section.type][targetBank].sections section.size ? sections[section.type][targetBank].sections
: sections[section.type][targetBank].zeroLenSections; : sections[section.type][targetBank].zeroLenSections;
auto pos = bankSections.begin(); auto pos = bankSections.begin();
while (pos != bankSections.end() && (*pos)->org < section.org) while (pos != bankSections.end() && (*pos)->org < section.org)
@@ -86,8 +89,7 @@ void out_AddSection(Section const &section)
bankSections.insert(pos, &section); bankSections.insert(pos, &section);
} }
Section const *out_OverlappingSection(Section const &section) Section const *out_OverlappingSection(Section const &section) {
{
uint32_t bank = section.bank - sectionTypeInfo[section.type].firstBank; uint32_t bank = section.bank - sectionTypeInfo[section.type].firstBank;
for (Section const *ptr : sections[section.type][bank].sections) { for (Section const *ptr : sections[section.type][bank].sections) {
@@ -101,8 +103,7 @@ Section const *out_OverlappingSection(Section const &section)
* Performs sanity checks on the overlay file. * Performs sanity checks on the overlay file.
* @return The number of ROM banks in the overlay file * @return The number of ROM banks in the overlay file
*/ */
static uint32_t checkOverlaySize() static uint32_t checkOverlaySize() {
{
if (!overlayFile) if (!overlayFile)
return 0; return 0;
@@ -136,14 +137,13 @@ static uint32_t checkOverlaySize()
* covered by any sections. * covered by any sections.
* @param nbOverlayBanks The number of banks in the overlay file * @param nbOverlayBanks The number of banks in the overlay file
*/ */
static void coverOverlayBanks(uint32_t nbOverlayBanks) static void coverOverlayBanks(uint32_t nbOverlayBanks) {
{
// 2 if is32kMode, 1 otherwise // 2 if is32kMode, 1 otherwise
uint32_t nbRom0Banks = sectionTypeInfo[SECTTYPE_ROM0].size / BANK_SIZE; uint32_t nbRom0Banks = sectionTypeInfo[SECTTYPE_ROM0].size / BANK_SIZE;
// Discount ROM0 banks to avoid outputting too much // Discount ROM0 banks to avoid outputting too much
uint32_t nbUncoveredBanks = nbOverlayBanks - nbRom0Banks > sections[SECTTYPE_ROMX].size() uint32_t nbUncoveredBanks = nbOverlayBanks - nbRom0Banks > sections[SECTTYPE_ROMX].size()
? nbOverlayBanks - nbRom0Banks ? nbOverlayBanks - nbRom0Banks
: 0; : 0;
if (nbUncoveredBanks > sections[SECTTYPE_ROMX].size()) { if (nbUncoveredBanks > sections[SECTTYPE_ROMX].size()) {
for (uint32_t i = sections[SECTTYPE_ROMX].size(); i < nbUncoveredBanks; i++) for (uint32_t i = sections[SECTTYPE_ROMX].size(); i < nbUncoveredBanks; i++)
@@ -157,8 +157,8 @@ static void coverOverlayBanks(uint32_t nbOverlayBanks)
* @param baseOffset The address of the bank's first byte in GB address space * @param baseOffset The address of the bank's first byte in GB address space
* @param size The size of the bank * @param size The size of the bank
*/ */
static void writeBank(std::deque<Section const *> *bankSections, uint16_t baseOffset, uint16_t size) static void
{ writeBank(std::deque<Section const *> *bankSections, uint16_t baseOffset, uint16_t size) {
uint16_t offset = 0; uint16_t offset = 0;
if (bankSections) { if (bankSections) {
@@ -190,8 +190,7 @@ static void writeBank(std::deque<Section const *> *bankSections, uint16_t baseOf
} }
// Writes a ROM file to the output. // Writes a ROM file to the output.
static void writeROM() static void writeROM() {
{
if (outputFileName) { if (outputFileName) {
if (strcmp(outputFileName, "-")) { if (strcmp(outputFileName, "-")) {
outputFile = fopen(outputFileName, "wb"); outputFile = fopen(outputFileName, "wb");
@@ -220,12 +219,18 @@ static void writeROM()
coverOverlayBanks(nbOverlayBanks); coverOverlayBanks(nbOverlayBanks);
if (outputFile) { if (outputFile) {
writeBank(!sections[SECTTYPE_ROM0].empty() ? &sections[SECTTYPE_ROM0][0].sections : nullptr, writeBank(
sectionTypeInfo[SECTTYPE_ROM0].startAddr, sectionTypeInfo[SECTTYPE_ROM0].size); !sections[SECTTYPE_ROM0].empty() ? &sections[SECTTYPE_ROM0][0].sections : nullptr,
sectionTypeInfo[SECTTYPE_ROM0].startAddr,
sectionTypeInfo[SECTTYPE_ROM0].size
);
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].size(); i++) for (uint32_t i = 0; i < sections[SECTTYPE_ROMX].size(); i++)
writeBank(&sections[SECTTYPE_ROMX][i].sections, writeBank(
sectionTypeInfo[SECTTYPE_ROMX].startAddr, sectionTypeInfo[SECTTYPE_ROMX].size); &sections[SECTTYPE_ROMX][i].sections,
sectionTypeInfo[SECTTYPE_ROMX].startAddr,
sectionTypeInfo[SECTTYPE_ROMX].size
);
} }
if (outputFile) if (outputFile)
@@ -235,23 +240,20 @@ static void writeROM()
} }
// Checks whether this character is legal as the first character of a symbol's name in a sym file // Checks whether this character is legal as the first character of a symbol's name in a sym file
static bool canStartSymName(char c) static bool canStartSymName(char c) {
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'; return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
} }
// Checks whether this character is legal in a symbol's name in a sym file // Checks whether this character is legal in a symbol's name in a sym file
static bool isLegalForSymName(char c) static bool isLegalForSymName(char c) {
{ return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || || c == '@' || c == '#' || c == '$' || c == '.';
c == '_' || c == '@' || c == '#' || c == '$' || c == '.';
} }
// Prints a symbol's name to `symFile`, assuming that the first character is legal. // Prints a symbol's name to `symFile`, assuming that the first character is legal.
// Illegal characters are UTF-8-decoded (errors are replaced by U+FFFD) and emitted as `\u`/`\U`. // Illegal characters are UTF-8-decoded (errors are replaced by U+FFFD) and emitted as `\u`/`\U`.
static void printSymName(char const *name) static void printSymName(char const *name) {
{ for (char const *ptr = name; *ptr != '\0';) {
for (char const *ptr = name; *ptr != '\0'; ) {
char c = *ptr; char c = *ptr;
if (isLegalForSymName(c)) { if (isLegalForSymName(c)) {
@@ -277,16 +279,14 @@ static void printSymName(char const *name)
++ptr; ++ptr;
} while (state != 0); } while (state != 0);
fprintf(symFile, codepoint <= 0xFFFF ? "\\u%04" PRIx32 : "\\U%08" PRIx32, fprintf(symFile, codepoint <= 0xFFFF ? "\\u%04" PRIx32 : "\\U%08" PRIx32, codepoint);
codepoint);
} }
} }
} }
// Comparator function for `std::stable_sort` to sort symbols // Comparator function for `std::stable_sort` to sort symbols
// Symbols are ordered by address, then by parentage // Symbols are ordered by address, then by parentage
static int compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2) static int compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2) {
{
if (sym1.addr != sym2.addr) if (sym1.addr != sym2.addr)
return sym1.addr < sym2.addr ? -1 : 1; return sym1.addr < sym2.addr ? -1 : 1;
@@ -315,24 +315,24 @@ static int compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2)
* Write a bank's contents to the sym file * Write a bank's contents to the sym file
* @param bankSections The bank's sections * @param bankSections The bank's sections
*/ */
static void writeSymBank(SortedSections const &bankSections, enum SectionType type, uint32_t bank) static void writeSymBank(SortedSections const &bankSections, enum SectionType type, uint32_t bank) {
{ #define forEachSortedSection(sect, ...) \
#define forEachSortedSection(sect, ...) do { \ do { \
for (auto it = bankSections.zeroLenSections.begin(); it != bankSections.zeroLenSections.end(); it++) { \ for (auto it = bankSections.zeroLenSections.begin(); \
for (Section const *sect = *it; sect; sect = sect->nextu) \ it != bankSections.zeroLenSections.end(); \
__VA_ARGS__ \ it++) { \
} \ for (Section const *sect = *it; sect; sect = sect->nextu) \
for (auto it = bankSections.sections.begin(); it != bankSections.sections.end(); it++) { \ __VA_ARGS__ \
for (Section const *sect = *it; sect; sect = sect->nextu) \ } \
__VA_ARGS__ \ for (auto it = bankSections.sections.begin(); it != bankSections.sections.end(); it++) { \
} \ for (Section const *sect = *it; sect; sect = sect->nextu) \
} while (0) __VA_ARGS__ \
} \
} while (0)
uint32_t nbSymbols = 0; uint32_t nbSymbols = 0;
forEachSortedSection(sect, { forEachSortedSection(sect, { nbSymbols += sect->symbols.size(); });
nbSymbols += sect->symbols.size();
});
if (!nbSymbols) if (!nbSymbols)
return; return;
@@ -345,10 +345,8 @@ static void writeSymBank(SortedSections const &bankSections, enum SectionType ty
for (Symbol const *sym : sect->symbols) { for (Symbol const *sym : sect->symbols) {
// Don't output symbols that begin with an illegal character // Don't output symbols that begin with an illegal character
if (!sym->name.empty() && canStartSymName(sym->name[0])) if (!sym->name.empty() && canStartSymName(sym->name[0]))
symList.push_back({ symList.push_back({.sym = sym, .addr = (uint16_t)(sym->label().offset + sect->org)}
.sym = sym, );
.addr = (uint16_t)(sym->label().offset + sect->org)
});
} }
}); });
@@ -365,23 +363,31 @@ static void writeSymBank(SortedSections const &bankSections, enum SectionType ty
} }
} }
static void writeEmptySpace(uint16_t begin, uint16_t end) static void writeEmptySpace(uint16_t begin, uint16_t end) {
{
if (begin < end) { if (begin < end) {
uint16_t len = end - begin; uint16_t len = end - begin;
fprintf(mapFile, "\tEMPTY: $%04x-$%04x ($%04" PRIx16 " byte%s)\n", fprintf(
begin, end - 1, len, len == 1 ? "" : "s"); mapFile,
"\tEMPTY: $%04x-$%04x ($%04" PRIx16 " byte%s)\n",
begin,
end - 1,
len,
len == 1 ? "" : "s"
);
} }
} }
/* /*
* Write a bank's contents to the map file * Write a bank's contents to the map file
*/ */
static void writeMapBank(SortedSections const &sectList, enum SectionType type, uint32_t bank) static void writeMapBank(SortedSections const &sectList, enum SectionType type, uint32_t bank) {
{ fprintf(
fprintf(mapFile, "\n%s bank #%" PRIu32 ":\n", sectionTypeInfo[type].name.c_str(), mapFile,
bank + sectionTypeInfo[type].firstBank); "\n%s bank #%" PRIu32 ":\n",
sectionTypeInfo[type].name.c_str(),
bank + sectionTypeInfo[type].firstBank
);
uint16_t used = 0; uint16_t used = 0;
auto section = sectList.sections.begin(); auto section = sectList.sections.begin();
@@ -390,9 +396,10 @@ static void writeMapBank(SortedSections const &sectList, enum SectionType type,
while (section != sectList.sections.end() || zeroLenSection != sectList.zeroLenSections.end()) { while (section != sectList.sections.end() || zeroLenSection != sectList.zeroLenSections.end()) {
// Pick the lowest section by address out of the two // Pick the lowest section by address out of the two
auto &pickedSection = section == sectList.sections.end() ? zeroLenSection auto &pickedSection = section == sectList.sections.end() ? zeroLenSection
: zeroLenSection == sectList.zeroLenSections.end() ? section : zeroLenSection == sectList.zeroLenSections.end() ? section
: (*section)->org < (*zeroLenSection)->org ? section : zeroLenSection; : (*section)->org < (*zeroLenSection)->org ? section
: zeroLenSection;
Section const *sect = *pickedSection; Section const *sect = *pickedSection;
used += sect->size; used += sect->size;
@@ -403,31 +410,41 @@ static void writeMapBank(SortedSections const &sectList, enum SectionType type,
prevEndAddr = sect->org + sect->size; prevEndAddr = sect->org + sect->size;
if (sect->size != 0) if (sect->size != 0)
fprintf(mapFile, "\tSECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16 fprintf(
" byte%s) [\"%s\"]\n", mapFile,
sect->org, prevEndAddr - 1, sect->size, sect->size == 1 ? "" : "s", "\tSECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16 " byte%s) [\"%s\"]\n",
sect->name.c_str()); sect->org,
prevEndAddr - 1,
sect->size,
sect->size == 1 ? "" : "s",
sect->name.c_str()
);
else else
fprintf(mapFile, "\tSECTION: $%04" PRIx16 " (0 bytes) [\"%s\"]\n", fprintf(
sect->org, sect->name.c_str()); mapFile,
"\tSECTION: $%04" PRIx16 " (0 bytes) [\"%s\"]\n",
sect->org,
sect->name.c_str()
);
if (!noSymInMap) { if (!noSymInMap) {
// Also print symbols in the following "pieces" // Also print symbols in the following "pieces"
for (uint16_t org = sect->org; sect; sect = sect->nextu) { for (uint16_t org = sect->org; sect; sect = sect->nextu) {
for (Symbol *sym : sect->symbols) for (Symbol *sym : sect->symbols)
// Space matches "\tSECTION: $xxxx ..." // Space matches "\tSECTION: $xxxx ..."
fprintf(mapFile, "\t $%04" PRIx32 " = %s\n", fprintf(
sym->label().offset + org, mapFile,
sym->name.c_str()); "\t $%04" PRIx32 " = %s\n",
sym->label().offset + org,
sym->name.c_str()
);
if (sect->nextu) { if (sect->nextu) {
// Announce the following "piece" // Announce the following "piece"
if (sect->nextu->modifier == SECTION_UNION) if (sect->nextu->modifier == SECTION_UNION)
fprintf(mapFile, fprintf(mapFile, "\t ; Next union\n");
"\t ; Next union\n");
else if (sect->nextu->modifier == SECTION_FRAGMENT) else if (sect->nextu->modifier == SECTION_FRAGMENT)
fprintf(mapFile, fprintf(mapFile, "\t ; Next fragment\n");
"\t ; Next fragment\n");
} }
} }
} }
@@ -444,16 +461,14 @@ static void writeMapBank(SortedSections const &sectList, enum SectionType type,
uint16_t slack = sectionTypeInfo[type].size - used; uint16_t slack = sectionTypeInfo[type].size - used;
fprintf(mapFile, "\tTOTAL EMPTY: $%04" PRIx16 " byte%s\n", slack, fprintf(mapFile, "\tTOTAL EMPTY: $%04" PRIx16 " byte%s\n", slack, slack == 1 ? "" : "s");
slack == 1 ? "" : "s");
} }
} }
/* /*
* Write the total used and free space by section type to the map file * Write the total used and free space by section type to the map file
*/ */
static void writeMapSummary() static void writeMapSummary() {
{
fputs("SUMMARY:\n", mapFile); fputs("SUMMARY:\n", mapFile);
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) { for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
@@ -480,9 +495,9 @@ static void writeMapSummary()
|| zeroLenSection != sectList.zeroLenSections.end()) { || zeroLenSection != sectList.zeroLenSections.end()) {
// Pick the lowest section by address out of the two // Pick the lowest section by address out of the two
auto &pickedSection = section == sectList.sections.end() ? zeroLenSection auto &pickedSection = section == sectList.sections.end() ? zeroLenSection
: zeroLenSection == sectList.zeroLenSections.end() ? section : zeroLenSection == sectList.zeroLenSections.end() ? section
: (*section)->org < (*zeroLenSection)->org ? section : (*section)->org < (*zeroLenSection)->org ? section
: zeroLenSection; : zeroLenSection;
used += (*pickedSection)->size; used += (*pickedSection)->size;
pickedSection++; pickedSection++;
@@ -491,19 +506,22 @@ static void writeMapSummary()
usedTotal += used; usedTotal += used;
} }
fprintf(mapFile, "\t%s: %" PRId32 " byte%s used / %" PRId32 " free", fprintf(
sectionTypeInfo[type].name.c_str(), usedTotal, usedTotal == 1 ? "" : "s", mapFile,
nbBanks * sectionTypeInfo[type].size - usedTotal); "\t%s: %" PRId32 " byte%s used / %" PRId32 " free",
if (sectionTypeInfo[type].firstBank != sectionTypeInfo[type].lastBank sectionTypeInfo[type].name.c_str(),
|| nbBanks > 1) usedTotal,
usedTotal == 1 ? "" : "s",
nbBanks * sectionTypeInfo[type].size - usedTotal
);
if (sectionTypeInfo[type].firstBank != sectionTypeInfo[type].lastBank || nbBanks > 1)
fprintf(mapFile, " in %u bank%s", nbBanks, nbBanks == 1 ? "" : "s"); fprintf(mapFile, " in %u bank%s", nbBanks, nbBanks == 1 ? "" : "s");
putc('\n', mapFile); putc('\n', mapFile);
} }
} }
// Writes the sym file, if applicable. // Writes the sym file, if applicable.
static void writeSym() static void writeSym() {
{
if (!symFileName) if (!symFileName)
return; return;
@@ -529,8 +547,7 @@ static void writeSym()
} }
// Writes the map file, if applicable. // Writes the map file, if applicable.
static void writeMap() static void writeMap() {
{
if (!mapFileName) if (!mapFileName)
return; return;
@@ -555,8 +572,7 @@ static void writeMap()
fclose(mapFile); fclose(mapFile);
} }
void out_WriteFiles() void out_WriteFiles() {
{
writeROM(); writeROM();
writeSym(); writeSym();
writeMap(); writeMap();

View File

@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "link/patch.hpp"
#include <assert.h> #include <assert.h>
#include <deque> #include <deque>
#include <inttypes.h> #include <inttypes.h>
@@ -8,17 +10,16 @@
#include <string.h> #include <string.h>
#include <variant> #include <variant>
#include "link/object.hpp"
#include "link/patch.hpp"
#include "link/section.hpp"
#include "link/symbol.hpp"
#include "error.hpp" #include "error.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "linkdefs.hpp" #include "linkdefs.hpp"
#include "opmath.hpp" #include "opmath.hpp"
#include "platform.hpp" #include "platform.hpp"
#include "link/object.hpp"
#include "link/section.hpp"
#include "link/symbol.hpp"
struct RPNStackEntry { struct RPNStackEntry {
int32_t value; int32_t value;
bool errorFlag; // Whether the value is a placeholder inserted for error recovery bool errorFlag; // Whether the value is a placeholder inserted for error recovery
@@ -26,17 +27,15 @@ struct RPNStackEntry {
std::deque<RPNStackEntry> rpnStack; std::deque<RPNStackEntry> rpnStack;
static void pushRPN(int32_t value, bool comesFromError) static void pushRPN(int32_t value, bool comesFromError) {
{ rpnStack.push_front({.value = value, .errorFlag = comesFromError});
rpnStack.push_front({ .value = value, .errorFlag = comesFromError });
} }
// This flag tracks whether the RPN op that is currently being evaluated // This flag tracks whether the RPN op that is currently being evaluated
// has popped any values with the error flag set. // has popped any values with the error flag set.
static bool isError = false; static bool isError = false;
static int32_t popRPN(FileStackNode const *node, uint32_t lineNo) static int32_t popRPN(FileStackNode const *node, uint32_t lineNo) {
{
if (rpnStack.empty()) if (rpnStack.empty())
fatal(node, lineNo, "Internal error, RPN stack empty"); fatal(node, lineNo, "Internal error, RPN stack empty");
@@ -49,17 +48,16 @@ static int32_t popRPN(FileStackNode const *node, uint32_t lineNo)
// RPN operators // RPN operators
static uint32_t getRPNByte(uint8_t const *&expression, int32_t &size, static uint32_t getRPNByte(
FileStackNode const *node, uint32_t lineNo) uint8_t const *&expression, int32_t &size, FileStackNode const *node, uint32_t lineNo
{ ) {
if (!size--) if (!size--)
fatal(node, lineNo, "Internal error, RPN expression overread"); fatal(node, lineNo, "Internal error, RPN expression overread");
return *expression++; return *expression++;
} }
static Symbol const *getSymbol(std::vector<Symbol> const &symbolList, uint32_t index) static Symbol const *getSymbol(std::vector<Symbol> const &symbolList, uint32_t index) {
{
assert(index != (uint32_t)-1); // PC needs to be handled specially, not here assert(index != (uint32_t)-1); // PC needs to be handled specially, not here
Symbol const &symbol = symbolList[index]; Symbol const &symbol = symbolList[index];
@@ -78,8 +76,7 @@ static Symbol const *getSymbol(std::vector<Symbol> const &symbolList, uint32_t i
* @return isError Set if an error occurred during evaluation, and further * @return isError Set if an error occurred during evaluation, and further
* errors caused by the value should be suppressed. * errors caused by the value should be suppressed.
*/ */
static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fileSymbols) static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fileSymbols) {
{
// Small shortcut to avoid a lot of repetition // Small shortcut to avoid a lot of repetition
#define popRPN() popRPN(patch.src, patch.lineNo) #define popRPN() popRPN(patch.src, patch.lineNo)
@@ -89,8 +86,8 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
rpnStack.clear(); rpnStack.clear();
while (size > 0) { while (size > 0) {
enum RPNCommand command = (enum RPNCommand)getRPNByte(expression, size, enum RPNCommand command =
patch.src, patch.lineNo); (enum RPNCommand)getRPNByte(expression, size, patch.src, patch.lineNo);
int32_t value; int32_t value;
isError = false; isError = false;
@@ -218,22 +215,27 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_BANK_SYM: case RPN_BANK_SYM:
value = 0; value = 0;
for (uint8_t shift = 0; shift < 32; shift += 8) for (uint8_t shift = 0; shift < 32; shift += 8)
value |= getRPNByte(expression, size, value |= getRPNByte(expression, size, patch.src, patch.lineNo) << shift;
patch.src, patch.lineNo) << shift;
symbol = getSymbol(fileSymbols, value); symbol = getSymbol(fileSymbols, value);
if (!symbol) { if (!symbol) {
error(patch.src, patch.lineNo, error(
"Requested BANK() of symbol \"%s\", which was not found", patch.src,
fileSymbols[value].name.c_str()); patch.lineNo,
"Requested BANK() of symbol \"%s\", which was not found",
fileSymbols[value].name.c_str()
);
isError = true; isError = true;
value = 1; value = 1;
} else if (Label const *label = std::get_if<Label>(&symbol->data); label) { } else if (Label const *label = std::get_if<Label>(&symbol->data); label) {
value = label->section->bank; value = label->section->bank;
} else { } else {
error(patch.src, patch.lineNo, error(
"Requested BANK() of non-label symbol \"%s\"", patch.src,
fileSymbols[value].name.c_str()); patch.lineNo,
"Requested BANK() of non-label symbol \"%s\"",
fileSymbols[value].name.c_str()
);
isError = true; isError = true;
value = 1; value = 1;
} }
@@ -249,9 +251,12 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
sect = sect_GetSection(name); sect = sect_GetSection(name);
if (!sect) { if (!sect) {
error(patch.src, patch.lineNo, error(
"Requested BANK() of section \"%s\", which was not found", patch.src,
name); patch.lineNo,
"Requested BANK() of section \"%s\", which was not found",
name
);
isError = true; isError = true;
value = 1; value = 1;
} else { } else {
@@ -261,8 +266,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_BANK_SELF: case RPN_BANK_SELF:
if (!patch.pcSection) { if (!patch.pcSection) {
error(patch.src, patch.lineNo, error(patch.src, patch.lineNo, "PC has no bank outside a section");
"PC has no bank outside a section");
isError = true; isError = true;
value = 1; value = 1;
} else { } else {
@@ -279,9 +283,12 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
sect = sect_GetSection(name); sect = sect_GetSection(name);
if (!sect) { if (!sect) {
error(patch.src, patch.lineNo, error(
"Requested SIZEOF() of section \"%s\", which was not found", patch.src,
name); patch.lineNo,
"Requested SIZEOF() of section \"%s\", which was not found",
name
);
isError = true; isError = true;
value = 1; value = 1;
} else { } else {
@@ -299,9 +306,12 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
assert(sect->offset == 0); assert(sect->offset == 0);
if (!sect) { if (!sect) {
error(patch.src, patch.lineNo, error(
"Requested STARTOF() of section \"%s\", which was not found", patch.src,
name); patch.lineNo,
"Requested STARTOF() of section \"%s\", which was not found",
name
);
isError = true; isError = true;
value = 1; value = 1;
} else { } else {
@@ -312,8 +322,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_SIZEOF_SECTTYPE: case RPN_SIZEOF_SECTTYPE:
value = getRPNByte(expression, size, patch.src, patch.lineNo); value = getRPNByte(expression, size, patch.src, patch.lineNo);
if (value < 0 || value >= SECTTYPE_INVALID) { if (value < 0 || value >= SECTTYPE_INVALID) {
error(patch.src, patch.lineNo, error(patch.src, patch.lineNo, "Requested SIZEOF() an invalid section type");
"Requested SIZEOF() an invalid section type");
isError = true; isError = true;
value = 0; value = 0;
} else { } else {
@@ -324,8 +333,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_STARTOF_SECTTYPE: case RPN_STARTOF_SECTTYPE:
value = getRPNByte(expression, size, patch.src, patch.lineNo); value = getRPNByte(expression, size, patch.src, patch.lineNo);
if (value < 0 || value >= SECTTYPE_INVALID) { if (value < 0 || value >= SECTTYPE_INVALID) {
error(patch.src, patch.lineNo, error(patch.src, patch.lineNo, "Requested STARTOF() an invalid section type");
"Requested STARTOF() an invalid section type");
isError = true; isError = true;
value = 0; value = 0;
} else { } else {
@@ -335,11 +343,8 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_HRAM: case RPN_HRAM:
value = popRPN(); value = popRPN();
if (!isError && (value < 0 if (!isError && (value < 0 || (value > 0xFF && value < 0xFF00) || value > 0xFFFF)) {
|| (value > 0xFF && value < 0xFF00) error(patch.src, patch.lineNo, "Value %" PRId32 " is not in HRAM range", value);
|| value > 0xFFFF)) {
error(patch.src, patch.lineNo,
"Value %" PRId32 " is not in HRAM range", value);
isError = true; isError = true;
} }
value &= 0xFF; value &= 0xFF;
@@ -351,8 +356,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
// They can be easily checked with a bitmask // They can be easily checked with a bitmask
if (value & ~0x38) { if (value & ~0x38) {
if (!isError) if (!isError)
error(patch.src, patch.lineNo, error(patch.src, patch.lineNo, "Value %" PRId32 " is not a RST vector", value);
"Value %" PRId32 " is not a RST vector", value);
isError = true; isError = true;
} }
value |= 0xC7; value |= 0xC7;
@@ -361,20 +365,17 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_CONST: case RPN_CONST:
value = 0; value = 0;
for (uint8_t shift = 0; shift < 32; shift += 8) for (uint8_t shift = 0; shift < 32; shift += 8)
value |= getRPNByte(expression, size, value |= getRPNByte(expression, size, patch.src, patch.lineNo) << shift;
patch.src, patch.lineNo) << shift;
break; break;
case RPN_SYM: case RPN_SYM:
value = 0; value = 0;
for (uint8_t shift = 0; shift < 32; shift += 8) for (uint8_t shift = 0; shift < 32; shift += 8)
value |= getRPNByte(expression, size, value |= getRPNByte(expression, size, patch.src, patch.lineNo) << shift;
patch.src, patch.lineNo) << shift;
if (value == -1) { // PC if (value == -1) { // PC
if (!patch.pcSection) { if (!patch.pcSection) {
error(patch.src, patch.lineNo, error(patch.src, patch.lineNo, "PC has no value outside a section");
"PC has no value outside a section");
value = 0; value = 0;
isError = true; isError = true;
} else { } else {
@@ -384,16 +385,23 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
symbol = getSymbol(fileSymbols, value); symbol = getSymbol(fileSymbols, value);
if (!symbol) { if (!symbol) {
error(patch.src, patch.lineNo, error(
"Unknown symbol \"%s\"", fileSymbols[value].name.c_str()); patch.src,
patch.lineNo,
"Unknown symbol \"%s\"",
fileSymbols[value].name.c_str()
);
isError = true; isError = true;
} else { } else {
value = std::visit(Visitor{ value = std::visit(
[](int32_t val) -> int32_t { return val; }, Visitor{
[](Label label) -> int32_t { [](int32_t val) -> int32_t { return val; },
return label.section->org + label.offset; [](Label label) -> int32_t {
} return label.section->org + label.offset;
}, symbol->data); },
},
symbol->data
);
} }
} }
break; break;
@@ -403,8 +411,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
} }
if (rpnStack.size() > 1) if (rpnStack.size() > 1)
error(patch.src, patch.lineNo, error(patch.src, patch.lineNo, "RPN stack has %zu entries on exit, not 1", rpnStack.size());
"RPN stack has %zu entries on exit, not 1", rpnStack.size());
isError = false; isError = false;
return popRPN(); return popRPN();
@@ -412,8 +419,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
#undef popRPN #undef popRPN
} }
void patch_CheckAssertions(std::deque<Assertion> &assertions) void patch_CheckAssertions(std::deque<Assertion> &assertions) {
{
verbosePrint("Checking assertions...\n"); verbosePrint("Checking assertions...\n");
for (Assertion &assert : assertions) { for (Assertion &assert : assertions) {
@@ -423,24 +429,37 @@ void patch_CheckAssertions(std::deque<Assertion> &assertions)
if (!isError && !value) { if (!isError && !value) {
switch (type) { switch (type) {
case ASSERT_FATAL: case ASSERT_FATAL:
fatal(assert.patch.src, assert.patch.lineNo, "%s", fatal(
!assert.message.empty() ? assert.message.c_str() assert.patch.src,
: "assert failure"); assert.patch.lineNo,
"%s",
!assert.message.empty() ? assert.message.c_str() : "assert failure"
);
case ASSERT_ERROR: case ASSERT_ERROR:
error(assert.patch.src, assert.patch.lineNo, "%s", error(
!assert.message.empty() ? assert.message.c_str() assert.patch.src,
: "assert failure"); assert.patch.lineNo,
"%s",
!assert.message.empty() ? assert.message.c_str() : "assert failure"
);
break; break;
case ASSERT_WARN: case ASSERT_WARN:
warning(assert.patch.src, assert.patch.lineNo, "%s", warning(
!assert.message.empty() ? assert.message.c_str() assert.patch.src,
: "assert failure"); assert.patch.lineNo,
"%s",
!assert.message.empty() ? assert.message.c_str() : "assert failure"
);
break; break;
} }
} else if (isError && type == ASSERT_FATAL) { } else if (isError && type == ASSERT_FATAL) {
fatal(assert.patch.src, assert.patch.lineNo, fatal(
"Failed to evaluate assertion%s%s", assert.patch.src,
!assert.message.empty() ? ": " : "", assert.message.c_str()); assert.patch.lineNo,
"Failed to evaluate assertion%s%s",
!assert.message.empty() ? ": " : "",
assert.message.c_str()
);
} }
} }
} }
@@ -450,8 +469,7 @@ void patch_CheckAssertions(std::deque<Assertion> &assertions)
* @param section The section component to patch * @param section The section component to patch
* @param dataSection The section to patch * @param dataSection The section to patch
*/ */
static void applyFilePatches(Section &section, Section &dataSection) static void applyFilePatches(Section &section, Section &dataSection) {
{
verbosePrint("Patching section \"%s\"...\n", section.name.c_str()); verbosePrint("Patching section \"%s\"...\n", section.name.c_str());
for (Patch &patch : section.patches) { for (Patch &patch : section.patches) {
int32_t value = computeRPNExpr(patch, *section.fileSymbols); int32_t value = computeRPNExpr(patch, *section.fileSymbols);
@@ -465,9 +483,12 @@ static void applyFilePatches(Section &section, Section &dataSection)
int16_t jumpOffset = value - address; int16_t jumpOffset = value - address;
if (!isError && (jumpOffset < -128 || jumpOffset > 127)) if (!isError && (jumpOffset < -128 || jumpOffset > 127))
error(patch.src, patch.lineNo, error(
"jr target out of reach (expected -129 < %" PRId16 " < 128)", patch.src,
jumpOffset); patch.lineNo,
"jr target out of reach (expected -129 < %" PRId16 " < 128)",
jumpOffset
);
dataSection.data[offset] = jumpOffset & 0xFF; dataSection.data[offset] = jumpOffset & 0xFF;
} else { } else {
// Patch a certain number of bytes // Patch a certain number of bytes
@@ -476,17 +497,20 @@ static void applyFilePatches(Section &section, Section &dataSection)
int32_t min; int32_t min;
int32_t max; int32_t max;
} const types[PATCHTYPE_INVALID] = { } const types[PATCHTYPE_INVALID] = {
{ 1, -128, 255 }, // PATCHTYPE_BYTE {1, -128, 255 }, // PATCHTYPE_BYTE
{ 2, -32768, 65536 }, // PATCHTYPE_WORD {2, -32768, 65536 }, // PATCHTYPE_WORD
{ 4, INT32_MIN, INT32_MAX }, // PATCHTYPE_LONG {4, INT32_MIN, INT32_MAX}, // PATCHTYPE_LONG
}; };
if (!isError && (value < types[patch.type].min if (!isError && (value < types[patch.type].min || value > types[patch.type].max))
|| value > types[patch.type].max)) error(
error(patch.src, patch.lineNo, patch.src,
"Value %" PRId32 "%s is not %u-bit", patch.lineNo,
value, value < 0 ? " (maybe negative?)" : "", "Value %" PRId32 "%s is not %u-bit",
types[patch.type].size * 8U); value,
value < 0 ? " (maybe negative?)" : "",
types[patch.type].size * 8U
);
for (uint8_t i = 0; i < types[patch.type].size; i++) { for (uint8_t i = 0; i < types[patch.type].size; i++) {
dataSection.data[offset + i] = value & 0xFF; dataSection.data[offset + i] = value & 0xFF;
value >>= 8; value >>= 8;
@@ -499,8 +523,7 @@ static void applyFilePatches(Section &section, Section &dataSection)
* Applies all of a section's patches, iterating over "components" of unionized sections * Applies all of a section's patches, iterating over "components" of unionized sections
* @param section The section to patch * @param section The section to patch
*/ */
static void applyPatches(Section &section) static void applyPatches(Section &section) {
{
if (!sect_HasData(section.type)) if (!sect_HasData(section.type))
return; return;
@@ -508,7 +531,6 @@ static void applyPatches(Section &section)
applyFilePatches(*component, section); applyFilePatches(*component, section);
} }
void patch_ApplyPatches() void patch_ApplyPatches() {
{
sect_ForEach(applyPatches); sect_ForEach(applyPatches);
} }

View File

@@ -48,7 +48,7 @@
struct Keyword { struct Keyword {
std::string_view name; std::string_view name;
yy::parser::symbol_type (* tokenGen)(); yy::parser::symbol_type (*tokenGen)();
}; };
} }
@@ -79,35 +79,75 @@
%% %%
lines: %empty lines:
| line lines %empty
| line lines
; ;
line: INCLUDE string newline { includeFile(std::move($2)); } // Note: this additionally increments the line number! line:
| directive newline { incLineNo(); } INCLUDE string newline {
| newline { incLineNo(); } includeFile(std::move($2)); // Note: this additionally increments the line number!
| error newline { yyerrok; incLineNo(); } // Error recovery. }
| directive newline {
incLineNo();
}
| newline {
incLineNo();
}
// Error recovery.
| error newline {
yyerrok;
incLineNo();
}
; ;
directive: section_type { setSectionType($1); } directive:
| section_type number { setSectionType($1, $2); } section_type {
| FLOATING { makeAddrFloating(); } setSectionType($1);
| ORG number { setAddr($2); } }
| ALIGN number { alignTo($2, 0); } | section_type number {
| ALIGN number COMMA number { alignTo($2, $4); } setSectionType($1, $2);
| DS number { pad($2); } }
| string optional { placeSection($1, $2); } | FLOATING {
makeAddrFloating();
}
| ORG number {
setAddr($2);
}
| ALIGN number {
alignTo($2, 0);
}
| ALIGN number COMMA number {
alignTo($2, $4);
}
| DS number {
pad($2);
}
| string optional {
placeSection($1, $2);
}
; ;
optional: %empty { $$ = false; } optional:
| OPTIONAL { $$ = true; } %empty {
$$ = false;
}
| OPTIONAL {
$$ = true;
}
; ;
%% %%
#define scriptError(context, fmt, ...) \ #define scriptError(context, fmt, ...) \
::error(nullptr, 0, "%s(%" PRIu32 "): " fmt, \ ::error( \
context.path.c_str(), context.lineNo __VA_OPT__(,) __VA_ARGS__) nullptr, \
0, \
"%s(%" PRIu32 "): " fmt, \
context.path.c_str(), \
context.lineNo __VA_OPT__(, ) \
__VA_ARGS__ \
)
// Lexer. // Lexer.
@@ -134,8 +174,9 @@ static void includeFile(std::string &&path) {
if (!newContext.file.open(newContext.path, std::ios_base::in)) { if (!newContext.file.open(newContext.path, std::ios_base::in)) {
// The order is important: report the error, increment the line number, modify the stack! // The order is important: report the error, increment the line number, modify the stack!
scriptError(prevContext, "Failed to open included linker script \"%s\"", scriptError(
newContext.path.c_str()); prevContext, "Failed to open included linker script \"%s\"", newContext.path.c_str()
);
++prevContext.lineNo; ++prevContext.lineNo;
lexerStack.pop_back(); lexerStack.pop_back();
} else { } else {
@@ -291,9 +332,8 @@ try_again: // Can't use a `do {} while(0)` loop, otherwise compilers (wrongly) t
auto strUpperCmp = [](char cmp, char ref) { auto strUpperCmp = [](char cmp, char ref) {
// `locale::classic()` yields the "C" locale. // `locale::classic()` yields the "C" locale.
assert(!std::use_facet<std::ctype<char>>(std::locale::classic()) assert(!std::use_facet<std::ctype<char>>(std::locale::classic())
.is(std::ctype_base::lower, ref)); .is(std::ctype_base::lower, ref));
return std::use_facet<std::ctype<char>>(std::locale::classic()) return std::use_facet<std::ctype<char>>(std::locale::classic()).toupper(cmp) == ref;
.toupper(cmp) == ref;
}; };
ident.push_back(c); ident.push_back(c);
@@ -351,8 +391,9 @@ static void setSectionType(SectionType type) {
auto const &context = lexerStack.back(); auto const &context = lexerStack.back();
if (nbbanks(type) != 1) { if (nbbanks(type) != 1) {
scriptError(context, "A bank number must be specified for %s", scriptError(
sectionTypeInfo[type].name.c_str()); context, "A bank number must be specified for %s", sectionTypeInfo[type].name.c_str()
);
// Keep going with a default value for the bank index. // Keep going with a default value for the bank index.
} }
@@ -364,12 +405,16 @@ static void setSectionType(SectionType type, uint32_t bank) {
auto const &typeInfo = sectionTypeInfo[type]; auto const &typeInfo = sectionTypeInfo[type];
if (bank < typeInfo.firstBank) { if (bank < typeInfo.firstBank) {
scriptError(context, "%s bank %" PRIu32 " doesn't exist (the minimum is %" PRIu32 ")", scriptError(
typeInfo.name.c_str(), bank, typeInfo.firstBank); context, "%s bank %" PRIu32 " doesn't exist (the minimum is %" PRIu32 ")",
typeInfo.name.c_str(), bank, typeInfo.firstBank
);
bank = typeInfo.firstBank; bank = typeInfo.firstBank;
} else if (bank > typeInfo.lastBank) { } else if (bank > typeInfo.lastBank) {
scriptError(context, "%s bank %" PRIu32 " doesn't exist (the maximum is %" PRIu32 ")", scriptError(
typeInfo.name.c_str(), bank, typeInfo.lastBank); context, "%s bank %" PRIu32 " doesn't exist (the maximum is %" PRIu32 ")",
typeInfo.name.c_str(), bank, typeInfo.lastBank
);
} }
setActiveTypeAndIdx(type, bank - typeInfo.firstBank); setActiveTypeAndIdx(type, bank - typeInfo.firstBank);
@@ -388,8 +433,10 @@ static void setAddr(uint32_t addr) {
if (addr < pc) { if (addr < pc) {
scriptError(context, "Cannot decrease the current address (from $%04x to $%04x)", pc, addr); scriptError(context, "Cannot decrease the current address (from $%04x to $%04x)", pc, addr);
} else if (addr > endaddr(activeType)) { // Allow "one past the end" sections. } else if (addr > endaddr(activeType)) { // Allow "one past the end" sections.
scriptError(context, "Cannot set the current address to $%04" PRIx32 ": %s ends at $%04" PRIx16 "", scriptError(
addr, typeInfo.name.c_str(), endaddr(activeType)); context, "Cannot set the current address to $%04" PRIx32 ": %s ends at $%04" PRIx16 "",
addr, typeInfo.name.c_str(), endaddr(activeType)
);
pc = endaddr(activeType); pc = endaddr(activeType);
} else { } else {
pc = addr; pc = addr;
@@ -400,7 +447,9 @@ static void setAddr(uint32_t addr) {
static void makeAddrFloating() { static void makeAddrFloating() {
auto const &context = lexerStack.back(); auto const &context = lexerStack.back();
if (activeType == SECTTYPE_INVALID) { if (activeType == SECTTYPE_INVALID) {
scriptError(context, "Cannot make the current address floating: no memory region is active"); scriptError(
context, "Cannot make the current address floating: no memory region is active"
);
return; return;
} }
@@ -423,9 +472,12 @@ static void alignTo(uint32_t alignment, uint32_t alignOfs) {
uint32_t alignSize = 1u << alignment; uint32_t alignSize = 1u << alignment;
if (alignOfs >= alignSize) { if (alignOfs >= alignSize) {
scriptError(context, "Cannot align: The alignment offset (%" PRIu32 scriptError(
") must be less than alignment size (%" PRIu32 ")", context,
alignOfs, alignSize); "Cannot align: The alignment offset (%" PRIu32
") must be less than alignment size (%" PRIu32 ")",
alignOfs, alignSize
);
return; return;
} }
@@ -439,8 +491,9 @@ static void alignTo(uint32_t alignment, uint32_t alignOfs) {
auto &pc = curAddr[activeType][activeBankIdx]; auto &pc = curAddr[activeType][activeBankIdx];
if (alignment > 16) { if (alignment > 16) {
scriptError(context, "Cannot align: The alignment (%" PRIu32 ") must be less than 16", scriptError(
alignment); context, "Cannot align: The alignment (%" PRIu32 ") must be less than 16", alignment
);
return; return;
} }
@@ -451,9 +504,12 @@ static void alignTo(uint32_t alignment, uint32_t alignOfs) {
uint32_t alignSize = 1u << alignment; uint32_t alignSize = 1u << alignment;
if (alignOfs >= alignSize) { if (alignOfs >= alignSize) {
scriptError(context, "Cannot align: The alignment offset (%" PRIu32 scriptError(
") must be less than alignment size (%" PRIu32 ")", context,
alignOfs, alignSize); "Cannot align: The alignment offset (%" PRIu32
") must be less than alignment size (%" PRIu32 ")",
alignOfs, alignSize
);
return; return;
} }
@@ -462,9 +518,12 @@ static void alignTo(uint32_t alignment, uint32_t alignOfs) {
} }
if (uint16_t offset = pc - typeInfo.startAddr; length > typeInfo.size - offset) { if (uint16_t offset = pc - typeInfo.startAddr; length > typeInfo.size - offset) {
scriptError(context, "Cannot align: the next suitable address after $%04" scriptError(
PRIx16 " is $%04" PRIx16 ", past $%04" PRIx16, context,
pc, (uint16_t)(pc + length), (uint16_t)(endaddr(activeType) + 1)); "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)
);
return; return;
} }
@@ -488,8 +547,11 @@ static void pad(uint32_t length) {
assert(pc >= typeInfo.startAddr); assert(pc >= typeInfo.startAddr);
if (uint16_t offset = pc - typeInfo.startAddr; length + offset > typeInfo.size) { if (uint16_t offset = pc - typeInfo.startAddr; length + offset > typeInfo.size) {
scriptError(context, "Cannot increase the current address by %u bytes: only %u bytes to $%04" PRIx16, scriptError(
length, typeInfo.size - offset, (uint16_t)(endaddr(activeType) + 1)); context,
"Cannot increase the current address by %u bytes: only %u bytes to $%04" PRIx16, length,
typeInfo.size - offset, (uint16_t)(endaddr(activeType) + 1)
);
} else { } else {
pc += length; pc += length;
} }
@@ -498,8 +560,9 @@ static void pad(uint32_t length) {
static void placeSection(std::string const &name, bool isOptional) { static void placeSection(std::string const &name, bool isOptional) {
auto const &context = lexerStack.back(); auto const &context = lexerStack.back();
if (activeType == SECTTYPE_INVALID) { if (activeType == SECTTYPE_INVALID) {
scriptError(context, "No memory region has been specified to place section \"%s\" in", scriptError(
name.c_str()); context, "No memory region has been specified to place section \"%s\" in", name.c_str()
);
return; return;
} }
@@ -520,14 +583,20 @@ static void placeSection(std::string const &name, bool isOptional) {
fragment->type = activeType; fragment->type = activeType;
} }
} else if (section->type != activeType) { } else if (section->type != activeType) {
scriptError(context, "\"%s\" is specified to be a %s section, but it is already a %s section", scriptError(
name.c_str(), typeInfo.name.c_str(), sectionTypeInfo[section->type].name.c_str()); 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; uint32_t bank = activeBankIdx + typeInfo.firstBank;
if (section->isBankFixed && bank != section->bank) { 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, scriptError(
name.c_str(), sectionTypeInfo[section->type].name.c_str(), bank, section->bank); 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->isBankFixed = true;
section->bank = bank; section->bank = bank;
@@ -535,12 +604,22 @@ static void placeSection(std::string const &name, bool isOptional) {
if (!isPcFloating) { if (!isPcFloating) {
uint16_t &org = curAddr[activeType][activeBankIdx]; uint16_t &org = curAddr[activeType][activeBankIdx];
if (section->isAddressFixed && org != section->org) { 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, scriptError(
name.c_str(), org, section->org); 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) { } else if (section->isAlignFixed && (org & section->alignMask) != section->alignOfs) {
uint8_t alignment = std::countr_one(section->alignMask); 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 "]", scriptError(
name.c_str(), org, alignment, (uint16_t)(org & section->alignMask), alignment, section->alignOfs); 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->isAddressFixed = true;
section->isAlignFixed = false; // This can't be set when the above is. section->isAlignFixed = false; // This can't be set when the above is.
@@ -549,9 +628,12 @@ static void placeSection(std::string const &name, bool isOptional) {
uint16_t curOfs = org - typeInfo.startAddr; uint16_t curOfs = org - typeInfo.startAddr;
if (section->size > typeInfo.size - curOfs) { if (section->size > typeInfo.size - curOfs) {
uint16_t overflowSize = section->size - (typeInfo.size - curOfs); uint16_t overflowSize = 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 " byte%s", scriptError(
name.c_str(), org, typeInfo.name.c_str(), context,
overflowSize, overflowSize == 1 ? "" : "s"); "The linker script assigns section \"%s\" to address $%04" PRIx16
", but then it would overflow %s by %" PRIx16 " byte%s",
name.c_str(), org, typeInfo.name.c_str(), overflowSize, overflowSize == 1 ? "" : "s"
);
// Fill as much as possible without going out of bounds. // Fill as much as possible without going out of bounds.
org = typeInfo.startAddr + typeInfo.size; org = typeInfo.startAddr + typeInfo.size;
} else { } else {

View File

@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "link/sdas_obj.hpp"
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
@@ -12,20 +14,19 @@
#include <variant> #include <variant>
#include <vector> #include <vector>
#include "linkdefs.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "linkdefs.hpp"
#include "platform.hpp" #include "platform.hpp"
#include "link/assign.hpp" #include "link/assign.hpp"
#include "link/main.hpp" #include "link/main.hpp"
#include "link/sdas_obj.hpp"
#include "link/section.hpp" #include "link/section.hpp"
#include "link/symbol.hpp" #include "link/symbol.hpp"
enum NumberType { enum NumberType {
HEX = 16, // X HEX = 16, // X
DEC = 10, // D DEC = 10, // D
OCT = 8, // Q OCT = 8, // Q
}; };
static void consumeLF(FileStackNode const &where, uint32_t lineNo, FILE *file) { static void consumeLF(FileStackNode const &where, uint32_t lineNo, FILE *file) {
@@ -35,8 +36,8 @@ static void consumeLF(FileStackNode const &where, uint32_t lineNo, FILE *file) {
static char const *delim = " \f\n\r\t\v"; // Whitespace according to the C and POSIX locales static char const *delim = " \f\n\r\t\v"; // Whitespace according to the C and POSIX locales
static int nextLine(std::vector<char> &lineBuf, uint32_t &lineNo, static int
FileStackNode const &where, FILE *file) { nextLine(std::vector<char> &lineBuf, uint32_t &lineNo, FileStackNode const &where, FILE *file) {
retry: retry:
++lineNo; ++lineNo;
int firstChar = getc(file); int firstChar = getc(file);
@@ -91,7 +92,9 @@ static uint32_t readNumber(char const *str, char const *&endptr, enum NumberType
} }
} }
static uint32_t parseNumber(FileStackNode const &where, uint32_t lineNo, char const *str, enum NumberType base) { static uint32_t parseNumber(
FileStackNode const &where, uint32_t lineNo, char const *str, enum NumberType base
) {
if (str[0] == '\0') if (str[0] == '\0')
fatal(&where, lineNo, "Expected number, got empty string"); fatal(&where, lineNo, "Expected number, got empty string");
@@ -103,7 +106,8 @@ static uint32_t parseNumber(FileStackNode const &where, uint32_t lineNo, char co
return res; return res;
} }
static uint8_t parseByte(FileStackNode const &where, uint32_t lineNo, char const *str, enum NumberType base) { static uint8_t
parseByte(FileStackNode const &where, uint32_t lineNo, char const *str, enum NumberType base) {
uint32_t num = parseNumber(where, lineNo, str, base); uint32_t num = parseNumber(where, lineNo, str, base);
if (num > UINT8_MAX) if (num > UINT8_MAX)
@@ -113,49 +117,58 @@ static uint8_t parseByte(FileStackNode const &where, uint32_t lineNo, char const
enum AreaFlags { enum AreaFlags {
AREA_TYPE = 2, // 0: Concatenate, 1: overlay AREA_TYPE = 2, // 0: Concatenate, 1: overlay
AREA_ISABS, // 0: Relative (???) address, 1: absolute address AREA_ISABS, // 0: Relative (???) address, 1: absolute address
AREA_PAGING, // Unsupported AREA_PAGING, // Unsupported
AREA_ALL_FLAGS = 1 << AREA_TYPE | 1 << AREA_ISABS | 1 << AREA_PAGING, AREA_ALL_FLAGS = 1 << AREA_TYPE | 1 << AREA_ISABS | 1 << AREA_PAGING,
}; };
enum RelocFlags { enum RelocFlags {
RELOC_SIZE, // 0: 16-bit, 1: 8-bit RELOC_SIZE, // 0: 16-bit, 1: 8-bit
RELOC_ISSYM, // 0: Area, 1: Symbol RELOC_ISSYM, // 0: Area, 1: Symbol
RELOC_ISPCREL, // 0: Normal, 1: PC-relative RELOC_ISPCREL, // 0: Normal, 1: PC-relative
RELOC_EXPR16, // Only for 8-bit size; 0: 8-bit expr, 1: 16-bit expr RELOC_EXPR16, // Only for 8-bit size; 0: 8-bit expr, 1: 16-bit expr
RELOC_SIGNED, // 0: signed, 1: unsigned RELOC_SIGNED, // 0: signed, 1: unsigned
RELOC_ZPAGE, // Unsupported RELOC_ZPAGE, // Unsupported
RELOC_NPAGE, // Unsupported RELOC_NPAGE, // Unsupported
RELOC_WHICHBYTE, // 8-bit size with 16-bit expr only; 0: LOW(), 1: HIGH() RELOC_WHICHBYTE, // 8-bit size with 16-bit expr only; 0: LOW(), 1: HIGH()
RELOC_EXPR24, // Only for 8-bit size; 0: follow RELOC_EXPR16, 1: 24-bit expr RELOC_EXPR24, // Only for 8-bit size; 0: follow RELOC_EXPR16, 1: 24-bit expr
RELOC_BANKBYTE, // 8-bit size with 24-bit expr only; 0: follow RELOC_WHICHBYTE, 1: BANK() RELOC_BANKBYTE, // 8-bit size with 24-bit expr only; 0: follow RELOC_WHICHBYTE, 1: BANK()
RELOC_ALL_FLAGS = 1 << RELOC_SIZE | 1 << RELOC_ISSYM | 1 << RELOC_ISPCREL | 1 << RELOC_EXPR16 RELOC_ALL_FLAGS = 1 << RELOC_SIZE | 1 << RELOC_ISSYM | 1 << RELOC_ISPCREL | 1 << RELOC_EXPR16
| 1 << RELOC_SIGNED | 1 << RELOC_ZPAGE | 1 << RELOC_NPAGE | 1 << RELOC_WHICHBYTE | 1 << RELOC_SIGNED | 1 << RELOC_ZPAGE | 1 << RELOC_NPAGE
| 1 << RELOC_EXPR24 | 1 << RELOC_BANKBYTE, | 1 << RELOC_WHICHBYTE | 1 << RELOC_EXPR24 | 1 << RELOC_BANKBYTE,
}; };
void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol> &fileSymbols) { void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol> &fileSymbols) {
std::vector<char> line(256); std::vector<char> line(256);
char const *token; char const *token;
#define getToken(ptr, ...) do { \ #define getToken(ptr, ...) \
token = strtok((ptr), delim); \ do { \
if (!token) \ token = strtok((ptr), delim); \
fatal(&where, lineNo, __VA_ARGS__); \ if (!token) \
} while (0) fatal(&where, lineNo, __VA_ARGS__); \
#define expectEol(...) do { \ } while (0)
token = strtok(nullptr, delim); \ #define expectEol(...) \
if (token) \ do { \
fatal(&where, lineNo, __VA_ARGS__); \ token = strtok(nullptr, delim); \
} while (0) if (token) \
#define expectToken(expected, lineType) do { \ fatal(&where, lineNo, __VA_ARGS__); \
getToken(nullptr, "'%c' line is too short", (lineType)); \ } while (0)
if (strcasecmp(token, (expected)) != 0) \ #define expectToken(expected, lineType) \
fatal(&where, lineNo, "Malformed '%c' line: expected \"%s\", got \"%s\"", \ do { \
(lineType), (expected), token); \ getToken(nullptr, "'%c' line is too short", (lineType)); \
} while (0) if (strcasecmp(token, (expected)) != 0) \
fatal( \
&where, \
lineNo, \
"Malformed '%c' line: expected \"%s\", got \"%s\"", \
(lineType), \
(expected), \
token \
); \
} while (0)
uint32_t lineNo = 0; uint32_t lineNo = 0;
int lineType = nextLine(line, lineNo, where, file); int lineType = nextLine(line, lineNo, where, file);
@@ -175,8 +188,12 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
numberType = OCT; numberType = OCT;
break; break;
default: default:
fatal(&where, lineNo, "This does not look like a SDCC object file (unknown integer format '%c')", fatal(
lineType); &where,
lineNo,
"This does not look like a SDCC object file (unknown integer format '%c')",
lineType
);
} }
switch (line[0]) { switch (line[0]) {
@@ -237,9 +254,10 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
case 'A': { case 'A': {
if (fileSections.size() == expectedNbAreas) if (fileSections.size() == expectedNbAreas)
warning(&where, lineNo, "Got more 'A' lines than the expected %" PRIu32, warning(
expectedNbAreas); &where, lineNo, "Got more 'A' lines than the expected %" PRIu32, expectedNbAreas
Section *curSection = new(std::nothrow) Section(); );
Section *curSection = new (std::nothrow) Section();
if (!curSection) if (!curSection)
fatal(&where, lineNo, "Failed to alloc new area: %s", strerror(errno)); fatal(&where, lineNo, "Failed to alloc new area: %s", strerror(errno));
@@ -249,12 +267,11 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
// The following is required for fragment offsets to be reliably predicted // The following is required for fragment offsets to be reliably predicted
for (FileSection &entry : fileSections) { for (FileSection &entry : fileSections) {
if (!strcmp(token, entry.section->name.c_str())) if (!strcmp(token, entry.section->name.c_str()))
fatal(&where, lineNo, "Area \"%s\" already defined earlier", fatal(&where, lineNo, "Area \"%s\" already defined earlier", token);
token);
} }
char const *sectionName = token; // We'll deal with the section's name depending on type char const *sectionName = token; // We'll deal with the section's name depending on type
fileSections.push_back({ .section = curSection, .writeIndex = 0 }); fileSections.push_back({.section = curSection, .writeIndex = 0});
expectToken("size", 'A'); expectToken("size", 'A');
@@ -263,8 +280,12 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
uint32_t tmp = parseNumber(where, lineNo, token, numberType); uint32_t tmp = parseNumber(where, lineNo, token, numberType);
if (tmp > UINT16_MAX) if (tmp > UINT16_MAX)
fatal(&where, lineNo, "Area \"%s\" is larger than the GB address space!?", fatal(
curSection->name.c_str()); &where,
lineNo,
"Area \"%s\" is larger than the GB address space!?",
curSection->name.c_str()
);
curSection->size = tmp; curSection->size = tmp;
expectToken("flags", 'A'); expectToken("flags", 'A');
@@ -276,7 +297,8 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
curSection->isAddressFixed = tmp & (1 << AREA_ISABS); curSection->isAddressFixed = tmp & (1 << AREA_ISABS);
curSection->isBankFixed = curSection->isAddressFixed; curSection->isBankFixed = curSection->isAddressFixed;
curSection->modifier = curSection->isAddressFixed || (tmp & (1 << AREA_TYPE)) curSection->modifier = curSection->isAddressFixed || (tmp & (1 << AREA_TYPE))
? SECTION_NORMAL : SECTION_FRAGMENT; ? SECTION_NORMAL
: SECTION_FRAGMENT;
// If the section is absolute, its name might not be unique; thus, mangle the name // If the section is absolute, its name might not be unique; thus, mangle the name
if (curSection->modifier == SECTION_NORMAL) { if (curSection->modifier == SECTION_NORMAL) {
curSection->name.append(where.name()); curSection->name.append(where.name());
@@ -320,7 +342,7 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
} else { } else {
curSection->type = SECTTYPE_INVALID; // This means "indeterminate" curSection->type = SECTTYPE_INVALID; // This means "indeterminate"
} }
curSection->isAlignFixed = false; // No such concept! curSection->isAlignFixed = false; // No such concept!
curSection->fileSymbols = &fileSymbols; // IDs are instead per-section curSection->fileSymbols = &fileSymbols; // IDs are instead per-section
curSection->nextu = nullptr; curSection->nextu = nullptr;
break; break;
@@ -328,8 +350,12 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
case 'S': { case 'S': {
if (fileSymbols.size() == expectedNbSymbols) if (fileSymbols.size() == expectedNbSymbols)
warning(&where, lineNo, "Got more 'S' lines than the expected %" PRIu32, warning(
expectedNbSymbols); &where,
lineNo,
"Got more 'S' lines than the expected %" PRIu32,
expectedNbSymbols
);
Symbol &symbol = fileSymbols.emplace_back(); Symbol &symbol = fileSymbols.emplace_back();
// Init other members // Init other members
@@ -347,16 +373,11 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
// Symbols in sections are labels; their value is an offset // Symbols in sections are labels; their value is an offset
Section *section = fileSections.back().section; Section *section = fileSections.back().section;
if (section->isAddressFixed) { if (section->isAddressFixed) {
assert(value >= section->org && assert(value >= section->org && value <= section->org + section->size);
value <= section->org + section->size);
value -= section->org; value -= section->org;
} }
symbol.data = Label{ // No need to set the `sectionID`, since we set the pointer
// No need to set the `sectionID`, since we set the pointer symbol.data = Label{.sectionID = 0, .offset = value, .section = section};
.sectionID = 0,
.offset = value,
.section = section
};
} else { } else {
// Symbols without sections are just constants // Symbols without sections are just constants
symbol.data = value; symbol.data = value;
@@ -377,12 +398,12 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
// The same symbol can only be defined twice if neither // The same symbol can only be defined twice if neither
// definition is in a floating section // definition is in a floating section
auto visitor = Visitor{ auto visitor = Visitor{
[](int32_t value) -> std::tuple<Section *, int32_t> { [](int32_t value) -> std::tuple<Section *, int32_t> {
return {nullptr, value}; return {nullptr, value};
}, },
[](Label label) -> std::tuple<Section *, int32_t> { [](Label label) -> std::tuple<Section *, int32_t> {
return {label.section, label.offset}; return {label.section, label.offset};
} },
}; };
auto [symbolSection, symbolValue] = std::visit(visitor, symbol.data); auto [symbolSection, symbolValue] = std::visit(visitor, symbol.data);
auto [otherSection, otherValue] = std::visit(visitor, other->data); auto [otherSection, otherValue] = std::visit(visitor, other->data);
@@ -391,9 +412,16 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|| (symbolSection && !symbolSection->isAddressFixed)) { || (symbolSection && !symbolSection->isAddressFixed)) {
sym_AddSymbol(symbol); // This will error out sym_AddSymbol(symbol); // This will error out
} else if (otherValue != symbolValue) { } else if (otherValue != symbolValue) {
error(&where, lineNo, error(
"Definition of \"%s\" conflicts with definition in %s (%" PRId32 " != %" PRId32 ")", &where,
symbol.name.c_str(), other->objFileName, symbolValue, otherValue); lineNo,
"Definition of \"%s\" conflicts with definition in %s (%" PRId32
" != %" PRId32 ")",
symbol.name.c_str(),
other->objFileName,
symbolValue,
otherValue
);
} }
} else { } else {
// Add a new definition // Add a new definition
@@ -443,8 +471,13 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
getToken(nullptr, "'R' line is too short"); getToken(nullptr, "'R' line is too short");
areaIdx |= (uint16_t)parseByte(where, lineNo, token, numberType) << 8; areaIdx |= (uint16_t)parseByte(where, lineNo, token, numberType) << 8;
if (areaIdx >= fileSections.size()) if (areaIdx >= fileSections.size())
fatal(&where, lineNo, "'R' line references area #%" PRIu16 ", but there are only %zu (so far)", fatal(
areaIdx, fileSections.size()); &where,
lineNo,
"'R' line references area #%" PRIu16 ", but there are only %zu (so far)",
areaIdx,
fileSections.size()
);
assert(!fileSections.empty()); // There should be at least one, from the above check assert(!fileSections.empty()); // There should be at least one, from the above check
Section *section = fileSections[areaIdx].section; Section *section = fileSections[areaIdx].section;
uint16_t *writeIndex = &fileSections[areaIdx].writeIndex; uint16_t *writeIndex = &fileSections[areaIdx].writeIndex;
@@ -453,16 +486,29 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
if (section->isAddressFixed) { if (section->isAddressFixed) {
if (addr < section->org) if (addr < section->org)
fatal(&where, lineNo, "'T' line reports address $%04" PRIx16 " in \"%s\", which starts at $%04" PRIx16, fatal(
addr, section->name.c_str(), section->org); &where,
lineNo,
"'T' line reports address $%04" PRIx16
" in \"%s\", which starts at $%04" PRIx16,
addr,
section->name.c_str(),
section->org
);
addr -= section->org; addr -= section->org;
} }
// Lines are emitted that violate this check but contain no "payload"; // Lines are emitted that violate this check but contain no "payload";
// ignore those. "Empty" lines shouldn't trigger allocation, either. // ignore those. "Empty" lines shouldn't trigger allocation, either.
if (data.size() != ADDR_SIZE) { if (data.size() != ADDR_SIZE) {
if (addr != *writeIndex) if (addr != *writeIndex)
fatal(&where, lineNo, "'T' lines which don't append to their section are not supported (%" PRIu16 " != %" PRIu16 ")", fatal(
addr, *writeIndex); &where,
lineNo,
"'T' lines which don't append to their section are not supported (%" PRIu16
" != %" PRIu16 ")",
addr,
*writeIndex
);
if (section->data.empty()) { if (section->data.empty()) {
assert(section->size != 0); assert(section->size != 0);
section->data.resize(section->size); section->data.resize(section->size);
@@ -487,18 +533,29 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
if ((flags & 0xF0) == 0xF0) { if ((flags & 0xF0) == 0xF0) {
getToken(nullptr, "Incomplete relocation"); getToken(nullptr, "Incomplete relocation");
flags = (flags & 0x0F) | (uint16_t)parseByte(where, lineNo, token, numberType) << 4; flags =
(flags & 0x0F) | (uint16_t)parseByte(where, lineNo, token, numberType) << 4;
} }
getToken(nullptr, "Incomplete relocation"); getToken(nullptr, "Incomplete relocation");
uint8_t offset = parseByte(where, lineNo, token, numberType); uint8_t offset = parseByte(where, lineNo, token, numberType);
if (offset < ADDR_SIZE) if (offset < ADDR_SIZE)
fatal(&where, lineNo, "Relocation index cannot point to header (%" PRIu16 " < %u)", fatal(
offset, ADDR_SIZE); &where,
lineNo,
"Relocation index cannot point to header (%" PRIu16 " < %u)",
offset,
ADDR_SIZE
);
if (offset >= data.size()) if (offset >= data.size())
fatal(&where, lineNo, "Relocation index is out of bounds (%" PRIu16 " >= %zu)", fatal(
offset, data.size()); &where,
lineNo,
"Relocation index is out of bounds (%" PRIu16 " >= %zu)",
offset,
data.size()
);
getToken(nullptr, "Incomplete relocation"); getToken(nullptr, "Incomplete relocation");
uint16_t idx = parseByte(where, lineNo, token, numberType); uint16_t idx = parseByte(where, lineNo, token, numberType);
@@ -520,11 +577,17 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
patch.offset = offset - writtenOfs + *writeIndex; patch.offset = offset - writtenOfs + *writeIndex;
if (section->patches.size() > 1) { if (section->patches.size() > 1) {
uint32_t prevOffset = section->patches[section->patches.size() - 2].offset; uint32_t prevOffset = section->patches[section->patches.size() - 2].offset;
if (prevOffset>= patch.offset) if (prevOffset >= patch.offset)
fatal(&where, lineNo, "Relocs not sorted by offset are not supported (%" PRIu32 " >= %" PRIu32 ")", fatal(
prevOffset, patch.offset); &where,
lineNo,
"Relocs not sorted by offset are not supported (%" PRIu32 " >= %" PRIu32
")",
prevOffset,
patch.offset
);
} }
patch.pcSection = section; // No need to fill `pcSectionID`, then patch.pcSection = section; // No need to fill `pcSectionID`, then
patch.pcOffset = patch.offset - 1; // For `jr`s patch.pcOffset = patch.offset - 1; // For `jr`s
patch.type = (flags & 1 << RELOC_SIZE) ? PATCHTYPE_BYTE : PATCHTYPE_WORD; patch.type = (flags & 1 << RELOC_SIZE) ? PATCHTYPE_BYTE : PATCHTYPE_WORD;
@@ -533,8 +596,13 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
assert(offset < data.size()); assert(offset < data.size());
if (data.size() - offset < nbBaseBytes) if (data.size() - offset < nbBaseBytes)
fatal(&where, lineNo, "Reloc would patch out of bounds (%" PRIu8 " > %zu)", fatal(
nbBaseBytes, data.size() - offset); &where,
lineNo,
"Reloc would patch out of bounds (%" PRIu8 " > %zu)",
nbBaseBytes,
data.size() - offset
);
for (uint8_t i = 0; i < nbBaseBytes; ++i) for (uint8_t i = 0; i < nbBaseBytes; ++i)
baseValue = baseValue | data[offset + i] << (8 * i); baseValue = baseValue | data[offset + i] << (8 * i);
@@ -542,8 +610,13 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
// Generate a RPN expression from the info and flags // Generate a RPN expression from the info and flags
if (flags & 1 << RELOC_ISSYM) { if (flags & 1 << RELOC_ISSYM) {
if (idx >= fileSymbols.size()) if (idx >= fileSymbols.size())
fatal(&where, lineNo, "Reloc refers to symbol #%" PRIu16 " out of %zu", fatal(
idx, fileSymbols.size()); &where,
lineNo,
"Reloc refers to symbol #%" PRIu16 " out of %zu",
idx,
fileSymbols.size()
);
Symbol const &sym = fileSymbols[idx]; Symbol const &sym = fileSymbols[idx];
// SDCC has a bunch of "magic symbols" that start with a // SDCC has a bunch of "magic symbols" that start with a
@@ -552,13 +625,18 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
if (sym.name.starts_with("b_")) { if (sym.name.starts_with("b_")) {
// Look for the symbol being referenced, and use its index instead // Look for the symbol being referenced, and use its index instead
for (idx = 0; idx < fileSymbols.size(); ++idx) { for (idx = 0; idx < fileSymbols.size(); ++idx) {
if (sym.name.ends_with(fileSymbols[idx].name) && if (sym.name.ends_with(fileSymbols[idx].name)
1 + sym.name.length() == fileSymbols[idx].name.length()) && 1 + sym.name.length() == fileSymbols[idx].name.length())
break; break;
} }
if (idx == fileSymbols.size()) if (idx == fileSymbols.size())
fatal(&where, lineNo, "\"%s\" is missing a reference to \"%s\"", fatal(
sym.name.c_str(), &sym.name.c_str()[1]); &where,
lineNo,
"\"%s\" is missing a reference to \"%s\"",
sym.name.c_str(),
&sym.name.c_str()[1]
);
patch.rpnExpression.resize(5); patch.rpnExpression.resize(5);
patch.rpnExpression[0] = RPN_BANK_SYM; patch.rpnExpression[0] = RPN_BANK_SYM;
patch.rpnExpression[1] = idx; patch.rpnExpression[1] = idx;
@@ -568,11 +646,19 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
} else if (sym.name.starts_with("l_")) { } else if (sym.name.starts_with("l_")) {
patch.rpnExpression.resize(1 + sym.name.length() - 2 + 1); patch.rpnExpression.resize(1 + sym.name.length() - 2 + 1);
patch.rpnExpression[0] = RPN_SIZEOF_SECT; patch.rpnExpression[0] = RPN_SIZEOF_SECT;
memcpy((char *)&patch.rpnExpression[1], &sym.name.c_str()[2], sym.name.length() - 2 + 1); memcpy(
(char *)&patch.rpnExpression[1],
&sym.name.c_str()[2],
sym.name.length() - 2 + 1
);
} else if (sym.name.starts_with("s_")) { } else if (sym.name.starts_with("s_")) {
patch.rpnExpression.resize(1 + sym.name.length() - 2 + 1); patch.rpnExpression.resize(1 + sym.name.length() - 2 + 1);
patch.rpnExpression[0] = RPN_STARTOF_SECT; patch.rpnExpression[0] = RPN_STARTOF_SECT;
memcpy((char *)&patch.rpnExpression[1], &sym.name.c_str()[2], sym.name.length() - 2 + 1); memcpy(
(char *)&patch.rpnExpression[1],
&sym.name.c_str()[2],
sym.name.length() - 2 + 1
);
} else { } else {
patch.rpnExpression.resize(5); patch.rpnExpression.resize(5);
patch.rpnExpression[0] = RPN_SYM; patch.rpnExpression[0] = RPN_SYM;
@@ -583,8 +669,13 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
} }
} else { } else {
if (idx >= fileSections.size()) if (idx >= fileSections.size())
fatal(&where, lineNo, "Reloc refers to area #%" PRIu16 " out of %zu", fatal(
idx, fileSections.size()); &where,
lineNo,
"Reloc refers to area #%" PRIu16 " out of %zu",
idx,
fileSections.size()
);
// It gets funky. If the area is absolute, *actually*, we // It gets funky. If the area is absolute, *actually*, we
// must not add its base address, as the assembler will // must not add its base address, as the assembler will
// already have added it in `baseValue`. // already have added it in `baseValue`.
@@ -625,10 +716,18 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
// are present, so we must skip two of them // are present, so we must skip two of them
if (flags & 1 << RELOC_EXPR16) { if (flags & 1 << RELOC_EXPR16) {
if (*writeIndex + (offset - writtenOfs) > section->size) if (*writeIndex + (offset - writtenOfs) > section->size)
fatal(&where, lineNo, "'T' line writes past \"%s\"'s end (%u > %" PRIu16 ")", fatal(
section->name.c_str(), *writeIndex + (offset - writtenOfs), section->size); &where,
lineNo,
"'T' line writes past \"%s\"'s end (%u > %" PRIu16 ")",
section->name.c_str(),
*writeIndex + (offset - writtenOfs),
section->size
);
// Copy all bytes up to those (plus the byte that we'll overwrite) // Copy all bytes up to those (plus the byte that we'll overwrite)
memcpy(&section->data[*writeIndex], &data[writtenOfs], offset - writtenOfs + 1); memcpy(
&section->data[*writeIndex], &data[writtenOfs], offset - writtenOfs + 1
);
*writeIndex += offset - writtenOfs + 1; *writeIndex += offset - writtenOfs + 1;
writtenOfs = offset + 3; // Skip all three `baseValue` bytes, though writtenOfs = offset + 3; // Skip all three `baseValue` bytes, though
} }
@@ -644,7 +743,9 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
patch.rpnExpression.push_back(16 >> 8); patch.rpnExpression.push_back(16 >> 8);
patch.rpnExpression.push_back(16 >> 16); patch.rpnExpression.push_back(16 >> 16);
patch.rpnExpression.push_back(16 >> 24); patch.rpnExpression.push_back(16 >> 24);
patch.rpnExpression.push_back((flags & 1 << RELOC_SIGNED) ? RPN_SHR : RPN_USHR); patch.rpnExpression.push_back(
(flags & 1 << RELOC_SIGNED) ? RPN_SHR : RPN_USHR
);
} else { } else {
if (flags & 1 << RELOC_EXPR16 && flags & 1 << RELOC_WHICHBYTE) { if (flags & 1 << RELOC_EXPR16 && flags & 1 << RELOC_WHICHBYTE) {
patch.rpnExpression.push_back(RPN_CONST); patch.rpnExpression.push_back(RPN_CONST);
@@ -652,7 +753,9 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
patch.rpnExpression.push_back(8 >> 8); patch.rpnExpression.push_back(8 >> 8);
patch.rpnExpression.push_back(8 >> 16); patch.rpnExpression.push_back(8 >> 16);
patch.rpnExpression.push_back(8 >> 24); patch.rpnExpression.push_back(8 >> 24);
patch.rpnExpression.push_back((flags & 1 << RELOC_SIGNED) ? RPN_SHR : RPN_USHR); patch.rpnExpression.push_back(
(flags & 1 << RELOC_SIGNED) ? RPN_SHR : RPN_USHR
);
} }
patch.rpnExpression.push_back(RPN_CONST); patch.rpnExpression.push_back(RPN_CONST);
patch.rpnExpression.push_back(0xFF); patch.rpnExpression.push_back(0xFF);
@@ -665,8 +768,12 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
assert(patch.type == PATCHTYPE_WORD); assert(patch.type == PATCHTYPE_WORD);
fatal(&where, lineNo, "16-bit PC-relative relocations are not supported"); fatal(&where, lineNo, "16-bit PC-relative relocations are not supported");
} else if (flags & (1 << RELOC_EXPR16 | 1 << RELOC_EXPR24)) { } else if (flags & (1 << RELOC_EXPR16 | 1 << RELOC_EXPR24)) {
fatal(&where, lineNo, "Flags 0x%x are not supported for 16-bit relocs", fatal(
flags & (1 << RELOC_EXPR16 | 1 << RELOC_EXPR24)); &where,
lineNo,
"Flags 0x%x are not supported for 16-bit relocs",
flags & (1 << RELOC_EXPR16 | 1 << RELOC_EXPR24)
);
} }
} }
@@ -674,8 +781,14 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
if (writtenOfs != data.size()) { if (writtenOfs != data.size()) {
assert(data.size() > writtenOfs); assert(data.size() > writtenOfs);
if (*writeIndex + (data.size() - writtenOfs) > section->size) if (*writeIndex + (data.size() - writtenOfs) > section->size)
fatal(&where, lineNo, "'T' line writes past \"%s\"'s end (%zu > %" PRIu16 ")", fatal(
section->name.c_str(), *writeIndex + (data.size() - writtenOfs), section->size); &where,
lineNo,
"'T' line writes past \"%s\"'s end (%zu > %" PRIu16 ")",
section->name.c_str(),
*writeIndex + (data.size() - writtenOfs),
section->size
);
memcpy(&section->data[*writeIndex], &data[writtenOfs], data.size() - writtenOfs); memcpy(&section->data[*writeIndex], &data[writtenOfs], data.size() - writtenOfs);
*writeIndex += data.size() - writtenOfs; *writeIndex += data.size() - writtenOfs;
} }
@@ -694,11 +807,21 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
if (!data.empty()) if (!data.empty())
warning(&where, lineNo, "Last 'T' line had no 'R' line (ignored)"); warning(&where, lineNo, "Last 'T' line had no 'R' line (ignored)");
if (fileSections.size() < expectedNbAreas) if (fileSections.size() < expectedNbAreas)
warning(&where, lineNo, "Expected %" PRIu32 " 'A' lines, got only %zu", expectedNbAreas, warning(
fileSections.size()); &where,
lineNo,
"Expected %" PRIu32 " 'A' lines, got only %zu",
expectedNbAreas,
fileSections.size()
);
if (fileSymbols.size() < expectedNbSymbols) if (fileSymbols.size() < expectedNbSymbols)
warning(&where, lineNo, "Expected %" PRIu32 " 'S' lines, got only %zu", expectedNbSymbols, warning(
fileSymbols.size()); &where,
lineNo,
"Expected %" PRIu32 " 'S' lines, got only %zu",
expectedNbSymbols,
fileSymbols.size()
);
nbSectionsToAssign += fileSections.size(); nbSectionsToAssign += fileSections.size();
@@ -707,8 +830,14 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
// RAM sections can have a size, but don't get any data (they shouldn't have any) // RAM sections can have a size, but don't get any data (they shouldn't have any)
if (entry.writeIndex != section->size && entry.writeIndex != 0) if (entry.writeIndex != section->size && entry.writeIndex != 0)
fatal(&where, lineNo, "\"%s\" was not fully written (%" PRIu16 " < %" PRIu16 ")", fatal(
section->name.c_str(), entry.writeIndex, section->size); &where,
lineNo,
"\"%s\" was not fully written (%" PRIu16 " < %" PRIu16 ")",
section->name.c_str(),
entry.writeIndex,
section->size
);
sect_AddSection(*section); sect_AddSection(*section);

View File

@@ -1,39 +1,47 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "link/section.hpp"
#include <assert.h> #include <assert.h>
#include <inttypes.h> #include <inttypes.h>
#include <map> #include <map>
#include <stdlib.h> #include <stdlib.h>
#include <string>
#include <string.h> #include <string.h>
#include <string>
#include "link/main.hpp"
#include "link/section.hpp"
#include "error.hpp" #include "error.hpp"
#include "linkdefs.hpp" #include "linkdefs.hpp"
#include "link/main.hpp"
std::map<std::string, Section *> sections; std::map<std::string, Section *> sections;
void sect_ForEach(void (*callback)(Section &)) void sect_ForEach(void (*callback)(Section &)) {
{
for (auto &it : sections) for (auto &it : sections)
callback(*it.second); callback(*it.second);
} }
static void checkSectUnionCompat(Section &target, Section &other) static void checkSectUnionCompat(Section &target, Section &other) {
{
if (other.isAddressFixed) { if (other.isAddressFixed) {
if (target.isAddressFixed) { if (target.isAddressFixed) {
if (target.org != other.org) if (target.org != other.org)
errx("Section \"%s\" is defined with conflicting addresses $%04" errx(
PRIx16 " and $%04" PRIx16, other.name.c_str(), target.org, "Section \"%s\" is defined with conflicting addresses $%04" PRIx16
other.org); " and $%04" PRIx16,
other.name.c_str(),
target.org,
other.org
);
} else if (target.isAlignFixed) { } else if (target.isAlignFixed) {
if ((other.org - target.alignOfs) & target.alignMask) if ((other.org - target.alignOfs) & target.alignMask)
errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %" errx(
PRIu16 ") and address $%04" PRIx16, other.name.c_str(), "Section \"%s\" is defined with conflicting %d-byte alignment (offset %" PRIu16
target.alignMask + 1, target.alignOfs, other.org); ") and address $%04" PRIx16,
other.name.c_str(),
target.alignMask + 1,
target.alignOfs,
other.org
);
} }
target.isAddressFixed = true; target.isAddressFixed = true;
target.org = other.org; target.org = other.org;
@@ -41,17 +49,25 @@ static void checkSectUnionCompat(Section &target, Section &other)
} else if (other.isAlignFixed) { } else if (other.isAlignFixed) {
if (target.isAddressFixed) { if (target.isAddressFixed) {
if ((target.org - other.alignOfs) & other.alignMask) if ((target.org - other.alignOfs) & other.alignMask)
errx("Section \"%s\" is defined with conflicting address $%04" errx(
PRIx16 " and %d-byte alignment (offset %" PRIu16 ")", "Section \"%s\" is defined with conflicting address $%04" PRIx16
other.name.c_str(), target.org, other.alignMask + 1, " and %d-byte alignment (offset %" PRIu16 ")",
other.alignOfs); other.name.c_str(),
target.org,
other.alignMask + 1,
other.alignOfs
);
} else if (target.isAlignFixed } else if (target.isAlignFixed
&& (other.alignMask & target.alignOfs) && (other.alignMask & target.alignOfs) != (target.alignMask & other.alignOfs)) {
!= (target.alignMask & other.alignOfs)) { errx(
errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %" "Section \"%s\" is defined with conflicting %d-byte alignment (offset %" PRIu16
PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")", ") and %d-byte alignment (offset %" PRIu16 ")",
other.name.c_str(), target.alignMask + 1, target.alignOfs, other.name.c_str(),
other.alignMask + 1, other.alignOfs); target.alignMask + 1,
target.alignOfs,
other.alignMask + 1,
other.alignOfs
);
} else if (!target.isAlignFixed || (other.alignMask > target.alignMask)) { } else if (!target.isAlignFixed || (other.alignMask > target.alignMask)) {
target.isAlignFixed = true; target.isAlignFixed = true;
target.alignMask = other.alignMask; target.alignMask = other.alignMask;
@@ -59,22 +75,30 @@ static void checkSectUnionCompat(Section &target, Section &other)
} }
} }
static void checkFragmentCompat(Section &target, Section &other) static void checkFragmentCompat(Section &target, Section &other) {
{
if (other.isAddressFixed) { if (other.isAddressFixed) {
uint16_t org = other.org - target.size; uint16_t org = other.org - target.size;
if (target.isAddressFixed) { if (target.isAddressFixed) {
if (target.org != org) if (target.org != org)
errx("Section \"%s\" is defined with conflicting addresses $%04" errx(
PRIx16 " and $%04" PRIx16, other.name.c_str(), target.org, "Section \"%s\" is defined with conflicting addresses $%04" PRIx16
other.org); " and $%04" PRIx16,
other.name.c_str(),
target.org,
other.org
);
} else if (target.isAlignFixed) { } else if (target.isAlignFixed) {
if ((org - target.alignOfs) & target.alignMask) if ((org - target.alignOfs) & target.alignMask)
errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %" errx(
PRIu16 ") and address $%04" PRIx16, other.name.c_str(), "Section \"%s\" is defined with conflicting %d-byte alignment (offset %" PRIu16
target.alignMask + 1, target.alignOfs, other.org); ") and address $%04" PRIx16,
other.name.c_str(),
target.alignMask + 1,
target.alignOfs,
other.org
);
} }
target.isAddressFixed = true; target.isAddressFixed = true;
target.org = org; target.org = org;
@@ -87,17 +111,25 @@ static void checkFragmentCompat(Section &target, Section &other)
if (target.isAddressFixed) { if (target.isAddressFixed) {
if ((target.org - ofs) & other.alignMask) if ((target.org - ofs) & other.alignMask)
errx("Section \"%s\" is defined with conflicting address $%04" errx(
PRIx16 " and %d-byte alignment (offset %" PRIu16 ")", "Section \"%s\" is defined with conflicting address $%04" PRIx16
other.name.c_str(), target.org, other.alignMask + 1, " and %d-byte alignment (offset %" PRIu16 ")",
other.alignOfs); other.name.c_str(),
target.org,
other.alignMask + 1,
other.alignOfs
);
} else if (target.isAlignFixed } else if (target.isAlignFixed && (other.alignMask & target.alignOfs) != (target.alignMask & ofs)) {
&& (other.alignMask & target.alignOfs) != (target.alignMask & ofs)) { errx(
errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %" "Section \"%s\" is defined with conflicting %d-byte alignment (offset %" PRIu16
PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")", ") and %d-byte alignment (offset %" PRIu16 ")",
other.name.c_str(), target.alignMask + 1, target.alignOfs, other.name.c_str(),
other.alignMask + 1, other.alignOfs); target.alignMask + 1,
target.alignOfs,
other.alignMask + 1,
other.alignOfs
);
} else if (!target.isAlignFixed || (other.alignMask > target.alignMask)) { } else if (!target.isAlignFixed || (other.alignMask > target.alignMask)) {
target.isAlignFixed = true; target.isAlignFixed = true;
@@ -107,22 +139,28 @@ static void checkFragmentCompat(Section &target, Section &other)
} }
} }
static void mergeSections(Section &target, Section &other, enum SectionModifier mod) static void mergeSections(Section &target, Section &other, enum SectionModifier mod) {
{
// Common checks // Common checks
if (target.type != other.type) if (target.type != other.type)
errx("Section \"%s\" is defined with conflicting types %s and %s", errx(
other.name.c_str(), sectionTypeInfo[target.type].name.c_str(), "Section \"%s\" is defined with conflicting types %s and %s",
sectionTypeInfo[other.type].name.c_str()); other.name.c_str(),
sectionTypeInfo[target.type].name.c_str(),
sectionTypeInfo[other.type].name.c_str()
);
if (other.isBankFixed) { if (other.isBankFixed) {
if (!target.isBankFixed) { if (!target.isBankFixed) {
target.isBankFixed = true; target.isBankFixed = true;
target.bank = other.bank; target.bank = other.bank;
} else if (target.bank != other.bank) { } else if (target.bank != other.bank) {
errx("Section \"%s\" is defined with conflicting banks %" PRIu32 " and %" errx(
PRIu32, other.name.c_str(), target.bank, other.bank); "Section \"%s\" is defined with conflicting banks %" PRIu32 " and %" PRIu32,
other.name.c_str(),
target.bank,
other.bank
);
} }
} }
@@ -159,34 +197,38 @@ static void mergeSections(Section &target, Section &other, enum SectionModifier
target.nextu = &other; target.nextu = &other;
} }
void sect_AddSection(Section &section) void sect_AddSection(Section &section) {
{
// Check if the section already exists // Check if the section already exists
if (Section *other = sect_GetSection(section.name); other) { if (Section *other = sect_GetSection(section.name); other) {
if (section.modifier != other->modifier) if (section.modifier != other->modifier)
errx("Section \"%s\" defined as %s and %s", section.name.c_str(), errx(
sectionModNames[section.modifier], sectionModNames[other->modifier]); "Section \"%s\" defined as %s and %s",
section.name.c_str(),
sectionModNames[section.modifier],
sectionModNames[other->modifier]
);
else if (section.modifier == SECTION_NORMAL) else if (section.modifier == SECTION_NORMAL)
errx("Section name \"%s\" is already in use", section.name.c_str()); errx("Section name \"%s\" is already in use", section.name.c_str());
else else
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.name.c_str(), sectionTypeInfo[section.type].name.c_str()); "Section \"%s\" is of type %s, which cannot be unionized",
section.name.c_str(),
sectionTypeInfo[section.type].name.c_str()
);
} else { } else {
// If not, add it // If not, add it
sections[section.name] = &section; sections[section.name] = &section;
} }
} }
Section *sect_GetSection(std::string const &name) Section *sect_GetSection(std::string const &name) {
{
auto search = sections.find(name); auto search = sections.find(name);
return search != sections.end() ? search->second : nullptr; return search != sections.end() ? search->second : nullptr;
} }
static void doSanityChecks(Section &section) static void doSanityChecks(Section &section) {
{
// Sanity check the section's type // Sanity check the section's type
if (section.type < 0 || section.type >= SECTTYPE_INVALID) { if (section.type < 0 || section.type >= SECTTYPE_INVALID) {
error(nullptr, 0, "Section \"%s\" has an invalid type", section.name.c_str()); error(nullptr, 0, "Section \"%s\" has an invalid type", section.name.c_str());
@@ -195,21 +237,28 @@ static void doSanityChecks(Section &section)
if (is32kMode && section.type == SECTTYPE_ROMX) { if (is32kMode && section.type == SECTTYPE_ROMX) {
if (section.isBankFixed && section.bank != 1) if (section.isBankFixed && section.bank != 1)
error(nullptr, 0, "%s: ROMX sections must be in bank 1 (if any) with option -t", error(
section.name.c_str()); nullptr,
0,
"%s: ROMX sections must be in bank 1 (if any) with option -t",
section.name.c_str()
);
else else
section.type = SECTTYPE_ROM0; section.type = SECTTYPE_ROM0;
} }
if (isWRAM0Mode && section.type == SECTTYPE_WRAMX) { if (isWRAM0Mode && section.type == SECTTYPE_WRAMX) {
if (section.isBankFixed && section.bank != 1) if (section.isBankFixed && section.bank != 1)
error(nullptr, 0, "%s: WRAMX sections must be in bank 1 with options -w or -d", error(
section.name.c_str()); nullptr,
0,
"%s: WRAMX sections must be in bank 1 with options -w or -d",
section.name.c_str()
);
else else
section.type = SECTTYPE_WRAM0; section.type = SECTTYPE_WRAM0;
} }
if (isDmgMode && section.type == SECTTYPE_VRAM && section.bank == 1) if (isDmgMode && section.type == SECTTYPE_VRAM && section.bank == 1)
error(nullptr, 0, "%s: VRAM bank 1 can't be used with option -d", error(nullptr, 0, "%s: VRAM bank 1 can't be used with option -d", section.name.c_str());
section.name.c_str());
// Check if alignment is reasonable, this is important to avoid UB // Check if alignment is reasonable, this is important to avoid UB
// An alignment of zero is equivalent to no alignment, basically // An alignment of zero is equivalent to no alignment, basically
@@ -218,23 +267,42 @@ static void doSanityChecks(Section &section)
// 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(nullptr, 0, "%s: %s sections cannot be aligned to $%04x bytes", error(
section.name.c_str(), sectionTypeInfo[section.type].name.c_str(), nullptr,
section.alignMask + 1); 0,
"%s: %s sections cannot be aligned to $%04x bytes",
section.name.c_str(),
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;
if (section.isBankFixed && section.bank < minbank && section.bank > maxbank) if (section.isBankFixed && section.bank < minbank && section.bank > maxbank)
error(nullptr, 0, minbank == maxbank error(
? "Cannot place section \"%s\" in bank %" PRIu32 ", it must be %" PRIu32 nullptr,
: "Cannot place section \"%s\" in bank %" PRIu32 ", it must be between %" PRIu32 " and %" PRIu32, 0,
section.name.c_str(), section.bank, minbank, maxbank); minbank == maxbank
? "Cannot place section \"%s\" in bank %" PRIu32 ", it must be %" PRIu32
: "Cannot place section \"%s\" in bank %" PRIu32 ", it must be between %" PRIu32
" and %" PRIu32,
section.name.c_str(),
section.bank,
minbank,
maxbank
);
// Check if section has a chance to be placed // Check if section has a chance to be placed
if (section.size > sectionTypeInfo[section.type].size) if (section.size > sectionTypeInfo[section.type].size)
error(nullptr, 0, "Section \"%s\" is bigger than the max size for that type: $%" error(
PRIx16 " > $%" PRIx16, nullptr,
section.name.c_str(), section.size, sectionTypeInfo[section.type].size); 0,
"Section \"%s\" is bigger than the max size for that type: $%" PRIx16 " > $%" PRIx16,
section.name.c_str(),
section.size,
sectionTypeInfo[section.type].size
);
// Translate loose constraints to strong ones when they're equivalent // Translate loose constraints to strong ones when they're equivalent
@@ -247,26 +315,41 @@ static void doSanityChecks(Section &section)
// It doesn't make sense to have both org and alignment set // It doesn't make sense to have both org and alignment set
if (section.isAlignFixed) { if (section.isAlignFixed) {
if ((section.org & section.alignMask) != section.alignOfs) if ((section.org & section.alignMask) != section.alignOfs)
error(nullptr, 0, "Section \"%s\"'s fixed address doesn't match its alignment", error(
section.name.c_str()); nullptr,
0,
"Section \"%s\"'s fixed address doesn't match its alignment",
section.name.c_str()
);
section.isAlignFixed = false; section.isAlignFixed = false;
} }
// Ensure the target address is valid // Ensure the target address is valid
if (section.org < sectionTypeInfo[section.type].startAddr if (section.org < sectionTypeInfo[section.type].startAddr
|| section.org > endaddr(section.type)) || section.org > endaddr(section.type))
error(nullptr, 0, "Section \"%s\"'s fixed address $%04" PRIx16 " is outside of range [$%04" error(
PRIx16 "; $%04" PRIx16 "]", section.name.c_str(), section.org, nullptr,
sectionTypeInfo[section.type].startAddr, endaddr(section.type)); 0,
"Section \"%s\"'s fixed address $%04" PRIx16 " is outside of range [$%04" PRIx16
"; $%04" PRIx16 "]",
section.name.c_str(),
section.org,
sectionTypeInfo[section.type].startAddr,
endaddr(section.type)
);
if (section.org + section.size > endaddr(section.type) + 1) if (section.org + section.size > endaddr(section.type) + 1)
error(nullptr, 0, "Section \"%s\"'s end address $%04x is greater than last address $%04x", error(
section.name.c_str(), section.org + section.size, nullptr,
endaddr(section.type) + 1); 0,
"Section \"%s\"'s end address $%04x is greater than last address $%04x",
section.name.c_str(),
section.org + section.size,
endaddr(section.type) + 1
);
} }
} }
void sect_DoSanityChecks() void sect_DoSanityChecks() {
{
sect_ForEach(doSanityChecks); sect_ForEach(doSanityChecks);
} }

View File

@@ -1,35 +1,33 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "link/symbol.hpp"
#include <inttypes.h> #include <inttypes.h>
#include <map> #include <map>
#include <stdlib.h> #include <stdlib.h>
#include <string> #include <string>
#include <variant> #include <variant>
#include "link/object.hpp"
#include "link/section.hpp"
#include "link/symbol.hpp"
#include "link/main.hpp"
#include "error.hpp" #include "error.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "link/main.hpp"
#include "link/object.hpp"
#include "link/section.hpp"
std::map<std::string, Symbol *> symbols; std::map<std::string, Symbol *> symbols;
Label &Symbol::label() Label &Symbol::label() {
{
assert(std::holds_alternative<Label>(data)); assert(std::holds_alternative<Label>(data));
return std::get<Label>(data); return std::get<Label>(data);
} }
Label const &Symbol::label() const Label const &Symbol::label() const {
{
assert(std::holds_alternative<Label>(data)); assert(std::holds_alternative<Label>(data));
return std::get<Label>(data); return std::get<Label>(data);
} }
void sym_AddSymbol(Symbol &symbol) void sym_AddSymbol(Symbol &symbol) {
{
// Check if the symbol already exists // Check if the symbol already exists
if (Symbol *other = sym_GetSymbol(symbol.name); other) { if (Symbol *other = sym_GetSymbol(symbol.name); other) {
fprintf(stderr, "error: \"%s\" both in %s from ", symbol.name.c_str(), symbol.objFileName); fprintf(stderr, "error: \"%s\" both in %s from ", symbol.name.c_str(), symbol.objFileName);
@@ -44,8 +42,7 @@ void sym_AddSymbol(Symbol &symbol)
symbols[symbol.name] = &symbol; symbols[symbol.name] = &symbol;
} }
Symbol *sym_GetSymbol(std::string const &name) Symbol *sym_GetSymbol(std::string const &name) {
{
auto search = symbols.find(name); auto search = symbols.find(name);
return search != symbols.end() ? search->second : nullptr; return search != symbols.end() ? search->second : nullptr;
} }

View File

@@ -1,73 +1,74 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "linkdefs.hpp" #include "linkdefs.hpp"
#include "platform.hpp" #include "platform.hpp"
using namespace std::literals; 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
// so RGBASM's job is only to catch unconditional errors earlier. // info, so RGBASM's job is only to catch unconditional errors earlier.
SectionTypeInfo sectionTypeInfo[SECTTYPE_INVALID] = { SectionTypeInfo sectionTypeInfo[SECTTYPE_INVALID] = {
{ // SECTTYPE_WRAM0 {
.name = "WRAM0"s, .name = "WRAM0"s,
.startAddr = 0xC000, .startAddr = 0xC000,
.size = 0x2000, // Patched to 0x1000 if !isWRAM0Mode .size = 0x2000 /* Patched to 0x1000 if !isWRAM0Mode */,
.firstBank = 0, .firstBank = 0,
.lastBank = 0, .lastBank = 0,
}, },
{ // SECTTYPE_VRAM {
.name = "VRAM"s, .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 */,
}, },
{ // SECTTYPE_ROMX {
.name = "ROMX"s, .name = "ROMX"s,
.startAddr = 0x4000, .startAddr = 0x4000,
.size = 0x4000, .size = 0x4000,
.firstBank = 1, .firstBank = 1,
.lastBank = 65535, .lastBank = 65535,
}, },
{ // SECTTYPE_ROM0 {
.name = "ROM0"s, .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,
}, },
{ // SECTTYPE_HRAM {
.name = "HRAM"s, .name = "HRAM"s,
.startAddr = 0xFF80, .startAddr = 0xFF80,
.size = 0x007F, .size = 0x007F,
.firstBank = 0, .firstBank = 0,
.lastBank = 0, .lastBank = 0,
}, },
{ // SECTTYPE_WRAMX {
.name = "WRAMX"s, .name = "WRAMX"s,
.startAddr = 0xD000, .startAddr = 0xD000,
.size = 0x1000, .size = 0x1000,
.firstBank = 1, .firstBank = 1,
.lastBank = 7, .lastBank = 7,
}, },
{ // SECTTYPE_SRAM {
.name = "SRAM"s, .name = "SRAM"s,
.startAddr = 0xA000, .startAddr = 0xA000,
.size = 0x2000, .size = 0x2000,
.firstBank = 0, .firstBank = 0,
.lastBank = 255, .lastBank = 255,
}, },
{ // SECTTYPE_OAM {
.name = "OAM"s, .name = "OAM"s,
.startAddr = 0xFE00, .startAddr = 0xFE00,
.size = 0x00A0, .size = 0x00A0,
.firstBank = 0, .firstBank = 0,
.lastBank = 0, .lastBank = 0,
}, },
}; };
char const * const sectionModNames[] = { char const * const sectionModNames[] = {
"regular", // SECTION_NORMAL "regular", // SECTION_NORMAL
"union", // SECTION_UNION "union", // SECTION_UNION
"fragment", // SECTION_FRAGMENT "fragment", // SECTION_FRAGMENT
}; };

View File

@@ -2,19 +2,17 @@
// Mathematical operators that don't reuse C++'s behavior // Mathematical operators that don't reuse C++'s behavior
#include <stdint.h>
#include "opmath.hpp" #include "opmath.hpp"
int32_t op_divide(int32_t dividend, int32_t divisor) #include <stdint.h>
{
int32_t op_divide(int32_t dividend, int32_t divisor) {
// Adjust division to floor toward negative infinity, // Adjust division to floor toward negative infinity,
// not truncate toward zero // not truncate toward zero
return dividend / divisor - ((dividend % divisor < 0) != (divisor < 0)); return dividend / divisor - ((dividend % divisor < 0) != (divisor < 0));
} }
int32_t op_modulo(int32_t dividend, int32_t divisor) int32_t op_modulo(int32_t dividend, int32_t divisor) {
{
int32_t remainder = dividend % divisor; int32_t remainder = dividend % divisor;
// Adjust modulo to have the sign of the divisor, // Adjust modulo to have the sign of the divisor,
@@ -22,8 +20,7 @@ int32_t op_modulo(int32_t dividend, int32_t divisor)
return remainder + divisor * ((remainder < 0) != (divisor < 0)); return remainder + divisor * ((remainder < 0) != (divisor < 0));
} }
int32_t op_exponent(int32_t base, uint32_t power) int32_t op_exponent(int32_t base, uint32_t power) {
{
int32_t result = 1; int32_t result = 1;
for (;;) { for (;;) {
@@ -38,8 +35,7 @@ int32_t op_exponent(int32_t base, uint32_t power)
return result; return result;
} }
int32_t op_shift_left(int32_t value, int32_t amount) int32_t op_shift_left(int32_t value, int32_t amount) {
{
// Get the easy cases out of the way // Get the easy cases out of the way
if (amount == 0) if (amount == 0)
return value; return value;
@@ -55,8 +51,7 @@ int32_t op_shift_left(int32_t value, int32_t amount)
return (uint32_t)value << amount; return (uint32_t)value << amount;
} }
int32_t op_shift_right(int32_t value, int32_t amount) int32_t op_shift_right(int32_t value, int32_t amount) {
{
// Repeat the easy cases here to avoid INT_MIN funny business // Repeat the easy cases here to avoid INT_MIN funny business
if (amount == 0) if (amount == 0)
return value; return value;
@@ -79,8 +74,7 @@ int32_t op_shift_right(int32_t value, int32_t amount)
return ((uint32_t)value >> amount) | amount_high_bits; return ((uint32_t)value >> amount) | amount_high_bits;
} }
int32_t op_shift_right_unsigned(int32_t value, int32_t amount) int32_t op_shift_right_unsigned(int32_t value, int32_t amount) {
{
// Repeat the easy cases here to avoid INT_MIN funny business // Repeat the easy cases here to avoid INT_MIN funny business
if (amount == 0) if (amount == 0)
return value; return value;

View File

@@ -1,16 +1,15 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "util.hpp"
#include <ctype.h> #include <ctype.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <vector> #include <vector>
#include "util.hpp"
#include "extern/utf8decoder.hpp" #include "extern/utf8decoder.hpp"
char const *printChar(int c) char const *printChar(int c) {
{
// "'A'" + '\0': 4 bytes // "'A'" + '\0': 4 bytes
// "'\\n'" + '\0': 5 bytes // "'\\n'" + '\0': 5 bytes
// "0xFF" + '\0': 5 bytes // "0xFF" + '\0': 5 bytes
@@ -51,8 +50,7 @@ char const *printChar(int c)
return buf; return buf;
} }
size_t readUTF8Char(std::vector<uint8_t> *dest, char const *src) size_t readUTF8Char(std::vector<uint8_t> *dest, char const *src) {
{
uint32_t state = 0; uint32_t state = 0;
uint32_t codep; uint32_t codep;
size_t i = 0; size_t i = 0;

View File

@@ -1,32 +1,29 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "version.hpp"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "helpers.hpp" #include "helpers.hpp"
#include "version.hpp"
// This variable is passed via `-D` from the Makefile, but not from CMake // This variable is passed via `-D` from the Makefile, but not from CMake
// (in which `configure_file()` is used on this file to replace some syntax) // (in which `configure_file()` is used on this file to replace some syntax)
#ifndef BUILD_VERSION_STRING #ifndef BUILD_VERSION_STRING
// CMake-specific syntax here // CMake-specific syntax here
#define BUILD_VERSION_STRING "@GIT_REV@" #define BUILD_VERSION_STRING "@GIT_REV@"
#endif #endif
char const *get_package_version_string() char const *get_package_version_string() {
{
// The following conditional should be simplified by the compiler. // The following conditional should be simplified by the compiler.
if (strlen(BUILD_VERSION_STRING) == 0) { if (strlen(BUILD_VERSION_STRING) == 0) {
// Fallback if version string can't be obtained from Git // Fallback if version string can't be obtained from Git
#ifndef PACKAGE_VERSION_RC #ifndef PACKAGE_VERSION_RC
return "v" EXPAND_AND_STR(PACKAGE_VERSION_MAJOR) return "v" EXPAND_AND_STR(PACKAGE_VERSION_MAJOR) "." EXPAND_AND_STR(PACKAGE_VERSION_MINOR
"." EXPAND_AND_STR(PACKAGE_VERSION_MINOR) ) "." EXPAND_AND_STR(PACKAGE_VERSION_PATCH);
"." EXPAND_AND_STR(PACKAGE_VERSION_PATCH);
#else #else
return "v" EXPAND_AND_STR(PACKAGE_VERSION_MAJOR) return "v" EXPAND_AND_STR(PACKAGE_VERSION_MAJOR) "." EXPAND_AND_STR(PACKAGE_VERSION_MINOR
"." EXPAND_AND_STR(PACKAGE_VERSION_MINOR) ) "." EXPAND_AND_STR(PACKAGE_VERSION_PATCH) "-rc" EXPAND_AND_STR(PACKAGE_VERSION_RC);
"." EXPAND_AND_STR(PACKAGE_VERSION_PATCH)
"-rc" EXPAND_AND_STR(PACKAGE_VERSION_RC);
#endif #endif
} else { } else {
return BUILD_VERSION_STRING; return BUILD_VERSION_STRING;

View File

@@ -15,8 +15,8 @@
#include <png.h> #include <png.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <string>
#include <string.h> #include <string.h>
#include <string>
#include <vector> #include <vector>
struct Attributes { struct Attributes {
@@ -70,8 +70,9 @@ static void generate_tile_attributes(Attributes *attributes) {
// Use an array to look up the number of colors in the palette; this is faster (and simpler) // Use an array to look up the number of colors in the palette; this is faster (and simpler)
// than doing a population count over the bits // than doing a population count over the bits
static char const popcount[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, static char const popcount[] = {
4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4}; 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4,
};
attributes->nbColors = popcount[pal]; attributes->nbColors = popcount[pal];
} }
@@ -104,8 +105,7 @@ static void generate_tile_data(unsigned char tiledata[8][8], unsigned colorcount
} }
} }
static void static void copy_tile_data(unsigned char destination[8][8], unsigned char const source[8][8]) {
copy_tile_data(unsigned char destination[8][8], unsigned char const source[8][8]) {
// Apply a random rotation to the copy // Apply a random rotation to the copy
// coord ^ 7 = inverted coordinate; coord ^ 0 = regular coordinate // coord ^ 7 = inverted coordinate; coord ^ 0 = regular coordinate
unsigned xmask = getRandomBits(1) * 7; unsigned xmask = getRandomBits(1) * 7;
@@ -148,9 +148,14 @@ static uint8_t _5to8(uint8_t five) {
} }
// Can't mark as `const`, as the array type is otherwise not compatible (augh) // Can't mark as `const`, as the array type is otherwise not compatible (augh)
static void write_image(char const *filename, uint16_t /* const */ palettes[/* 60 */][4], static void write_image(
unsigned char /* const */ (*tileData)[8][8], Attributes const *attributes, char const *filename,
uint8_t width, uint8_t height) { uint16_t /* const */ palettes[/* 60 */][4],
unsigned char /* const */ (*tileData)[8][8],
Attributes const *attributes,
uint8_t width,
uint8_t height
) {
uint8_t const nbTiles = width * height; uint8_t const nbTiles = width * height;
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
png_infop pngInfo = png_create_info_struct(png); png_infop pngInfo = png_create_info_struct(png);
@@ -167,9 +172,17 @@ static void write_image(char const *filename, uint16_t /* const */ palettes[/* 6
} }
png_init_io(png, file); png_init_io(png, file);
png_set_IHDR(png, pngInfo, width * 8, height * 8, 8, PNG_COLOR_TYPE_RGB_ALPHA, png_set_IHDR(
getRandomBits(1) ? PNG_INTERLACE_NONE : PNG_INTERLACE_ADAM7, png,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); pngInfo,
width * 8,
height * 8,
8,
PNG_COLOR_TYPE_RGB_ALPHA,
getRandomBits(1) ? PNG_INTERLACE_NONE : PNG_INTERLACE_ADAM7,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT
);
// While it would be nice to write the image little by little, I really don't want to handle // While it would be nice to write the image little by little, I really don't want to handle
// interlacing myself. (We're doing interlacing to test that RGBGFX correctly handles it.) // interlacing myself. (We're doing interlacing to test that RGBGFX correctly handles it.)

View File

@@ -111,10 +111,13 @@ class Png {
std::streamsize nbBytesRead = self->file.sgetn(reinterpret_cast<char *>(data), expectedLen); std::streamsize nbBytesRead = self->file.sgetn(reinterpret_cast<char *>(data), expectedLen);
if (nbBytesRead != expectedLen) { if (nbBytesRead != expectedLen) {
fatal("Error reading input image (\"%s\"): file too short (expected at least %zd more " fatal(
"bytes after reading %zu)", "Error reading input image (\"%s\"): file too short (expected at least %zd more "
self->path.c_str(), length - nbBytesRead, "bytes after reading %zu)",
(size_t)self->file.pubseekoff(0, std::ios_base::cur)); self->path.c_str(),
length - nbBytesRead,
(size_t)self->file.pubseekoff(0, std::ios_base::cur)
);
} }
} }
@@ -149,8 +152,9 @@ public:
fatal("Input file (\"%s\") is not a PNG image!", path.c_str()); fatal("Input file (\"%s\") is not a PNG image!", path.c_str());
} }
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError, png = png_create_read_struct(
handleWarning); PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError, handleWarning
);
if (!png) { if (!png) {
fatal("Failed to allocate PNG structure: %s", strerror(errno)); fatal("Failed to allocate PNG structure: %s", strerror(errno));
} }
@@ -174,8 +178,9 @@ public:
int bitDepth, interlaceType; //, compressionType, filterMethod; int bitDepth, interlaceType; //, compressionType, filterMethod;
png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr, png_get_IHDR(
nullptr); png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr, nullptr
);
if (width % 8 != 0) { if (width % 8 != 0) {
fatal("Image width (%" PRIu32 " pixels) is not a multiple of 8!", width); fatal("Image width (%" PRIu32 " pixels) is not a multiple of 8!", width);
@@ -308,19 +313,36 @@ static char *execProg(char const *name, char * const *argv) {
fatal("Error waiting for %s: %s", name, strerror(errno)); fatal("Error waiting for %s: %s", name, strerror(errno));
} else if (info.si_code != CLD_EXITED) { } else if (info.si_code != CLD_EXITED) {
assert(info.si_code == CLD_KILLED || info.si_code == CLD_DUMPED); assert(info.si_code == CLD_KILLED || info.si_code == CLD_DUMPED);
fatal("%s was terminated by signal %s%s\n\tThe command was: [%s]", name, strsignal(info.si_status), fatal(
info.si_code == CLD_DUMPED ? " (core dumped)" : "", formatArgv()); "%s was terminated by signal %s%s\n\tThe command was: [%s]",
name,
strsignal(info.si_status),
info.si_code == CLD_DUMPED ? " (core dumped)" : "",
formatArgv()
);
} else if (info.si_status != 0) { } else if (info.si_status != 0) {
fatal("%s returned with status %d\n\tThe command was: [%s]", name, info.si_status, formatArgv()); fatal(
"%s returned with status %d\n\tThe command was: [%s]",
name,
info.si_status,
formatArgv()
);
} }
#else // defined(_MSC_VER) || defined(__MINGW32__) #else // defined(_MSC_VER) || defined(__MINGW32__)
auto winStrerror = [](DWORD errnum) { auto winStrerror = [](DWORD errnum) {
LPTSTR buf; LPTSTR buf;
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM if (FormatMessage(
| FORMAT_MESSAGE_MAX_WIDTH_MASK, FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
nullptr, errnum, 0, (LPTSTR)&buf, 0, nullptr) | FORMAT_MESSAGE_MAX_WIDTH_MASK,
nullptr,
errnum,
0,
(LPTSTR)&buf,
0,
nullptr
)
== 0) { == 0) {
fatal("Failed to get error message for error 0x%x", errnum); fatal("Failed to get error message for error 0x%x", errnum);
} }
@@ -341,28 +363,31 @@ static char *execProg(char const *name, char * const *argv) {
STARTUPINFOA startupInfo; STARTUPINFOA startupInfo;
GetStartupInfoA(&startupInfo); GetStartupInfoA(&startupInfo);
STARTUPINFOA childStartupInfo{sizeof(startupInfo), STARTUPINFOA childStartupInfo{
nullptr, sizeof(startupInfo),
nullptr, nullptr,
nullptr, nullptr,
0, nullptr,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
nullptr, 0,
0, nullptr,
0, 0,
0}; 0,
0,
};
PROCESS_INFORMATION child; PROCESS_INFORMATION child;
if (CreateProcessA(nullptr, cmdLine, nullptr, nullptr, true, 0, nullptr, nullptr, if (CreateProcessA(
&childStartupInfo, &child) nullptr, cmdLine, nullptr, nullptr, true, 0, nullptr, nullptr, &childStartupInfo, &child
)
== 0) { == 0) {
return winStrerror(GetLastError()); return winStrerror(GetLastError());
} }
@@ -395,8 +420,9 @@ int main(int argc, char *argv[]) {
char *args[] = {path, argv[1], file, nullptr}; char *args[] = {path, argv[1], file, nullptr};
if (auto ret = execProg("randtilegen", args); ret != nullptr) { if (auto ret = execProg("randtilegen", args); ret != nullptr) {
fatal("Failed to execute ./randtilegen (%s). Is it in the current working directory?", fatal(
ret); "Failed to execute ./randtilegen (%s). Is it in the current working directory?", ret
);
} }
} }
@@ -405,7 +431,8 @@ int main(int argc, char *argv[]) {
pal_opt[] = "-p", pal_file[] = "result.pal", attr_opt[] = "-a", pal_opt[] = "-p", pal_file[] = "result.pal", attr_opt[] = "-a",
attr_file[] = "result.attrmap", in_file[] = "out0.png"; attr_file[] = "result.attrmap", in_file[] = "out0.png";
std::vector<char *> args( std::vector<char *> args(
{path, out_opt, out_file, pal_opt, pal_file, attr_opt, attr_file, in_file}); {path, out_opt, out_file, pal_opt, pal_file, attr_opt, attr_file, in_file}
);
// Also copy the trailing `nullptr` // Also copy the trailing `nullptr`
std::copy_n(&argv[2], argc - 1, std::back_inserter(args)); std::copy_n(&argv[2], argc - 1, std::back_inserter(args));
@@ -422,8 +449,17 @@ int main(int argc, char *argv[]) {
attr_opt[] = "-a", attr_file[] = "result.attrmap", in_file[] = "result.png"; attr_opt[] = "-a", attr_file[] = "result.attrmap", in_file[] = "result.png";
auto width_string = std::to_string(image0.getWidth() / 8); auto width_string = std::to_string(image0.getWidth() / 8);
std::vector<char *> args = { std::vector<char *> args = {
path, reverse_opt, width_string.data(), out_opt, out_file, pal_opt, path,
pal_file, attr_opt, attr_file, in_file}; reverse_opt,
width_string.data(),
out_opt,
out_file,
pal_opt,
pal_file,
attr_opt,
attr_file,
in_file,
};
// Also copy the trailing `nullptr` // Also copy the trailing `nullptr`
std::copy_n(&argv[2], argc - 1, std::back_inserter(args)); std::copy_n(&argv[2], argc - 1, std::back_inserter(args));
@@ -456,10 +492,20 @@ int main(int argc, char *argv[]) {
}; };
if (cgbColor(px0) != cgbColor(px1)) { if (cgbColor(px0) != cgbColor(px1)) {
error("Color mismatch at (%" PRIu32 ", %" PRIu32 error(
"): (%u,%u,%u,%u) became (%u,%u,%u,%u) after round-tripping", "Color mismatch at (%" PRIu32 ", %" PRIu32
x, y, px0.red, px0.green, px0.blue, px0.alpha, px1.red, px1.green, px1.blue, "): (%u,%u,%u,%u) became (%u,%u,%u,%u) after round-tripping",
px1.alpha); x,
y,
px0.red,
px0.green,
px0.blue,
px0.alpha,
px1.red,
px1.green,
px1.blue,
px1.alpha
);
} }
} }
} }