mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Add -B/--backtrace option to RGBASM and RGBLINK (#1787)
This commit is contained in:
@@ -60,6 +60,7 @@ set(rgbasm_src
|
||||
set(rgblink_src
|
||||
"${BISON_LINKER_SCRIPT_PARSER_OUTPUT_SOURCE}"
|
||||
"link/assign.cpp"
|
||||
"link/fstack.cpp"
|
||||
"link/lexer.cpp"
|
||||
"link/layout.cpp"
|
||||
"link/main.cpp"
|
||||
|
||||
@@ -60,42 +60,79 @@ std::string FileStackNode::reptChain() const {
|
||||
return chain;
|
||||
}
|
||||
|
||||
std::string const &FileStackNode::dump(uint32_t curLineNo) const {
|
||||
std::vector<std::pair<std::string, uint32_t>> FileStackNode::backtrace(uint32_t curLineNo) const {
|
||||
if (std::holds_alternative<std::vector<uint32_t>>(data)) {
|
||||
assume(parent); // REPT nodes use their parent's name
|
||||
std::string const &lastName = parent->dump(lineNo);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fputs(" -> ", stderr);
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(lastName.c_str(), stderr);
|
||||
fputs(reptChain().c_str(), stderr);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||
style_Reset(stderr);
|
||||
return lastName;
|
||||
std::vector<std::pair<std::string, uint32_t>> nodes = parent->backtrace(lineNo);
|
||||
assume(!nodes.empty());
|
||||
nodes.emplace_back(nodes.back().first + reptChain(), curLineNo);
|
||||
return nodes;
|
||||
} else if (parent) {
|
||||
std::vector<std::pair<std::string, uint32_t>> nodes = parent->backtrace(lineNo);
|
||||
nodes.emplace_back(name(), curLineNo);
|
||||
return nodes;
|
||||
} else {
|
||||
if (parent) {
|
||||
parent->dump(lineNo);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fputs(" -> ", stderr);
|
||||
}
|
||||
std::string const &nodeName = name();
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(nodeName.c_str(), stderr);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||
style_Reset(stderr);
|
||||
return nodeName;
|
||||
return {
|
||||
{name(), curLineNo}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
bool fstk_DumpCurrent() {
|
||||
if (lexer_AtTopLevel()) {
|
||||
return false;
|
||||
static void printNode(std::pair<std::string, uint32_t> const &node) {
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(node.first.c_str(), stderr);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fprintf(stderr, "(%" PRIu32 ")", node.second);
|
||||
}
|
||||
|
||||
void FileStackNode::printBacktrace(uint32_t curLineNo) const {
|
||||
std::vector<std::pair<std::string, uint32_t>> nodes = backtrace(curLineNo);
|
||||
size_t n = nodes.size();
|
||||
|
||||
if (warnings.traceDepth == TRACE_COLLAPSE) {
|
||||
fputs(" ", stderr); // Just three spaces; the fourth will be handled by the loop
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
style_Reset(stderr);
|
||||
fprintf(stderr, " %s ", i == 0 ? "at" : "<-");
|
||||
printNode(nodes[n - i - 1]);
|
||||
}
|
||||
putc('\n', stderr);
|
||||
} else if (warnings.traceDepth == 0 || static_cast<size_t>(warnings.traceDepth) >= n) {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
style_Reset(stderr);
|
||||
fprintf(stderr, " %s ", i == 0 ? "at" : "<-");
|
||||
printNode(nodes[n - i - 1]);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
} else {
|
||||
size_t last = warnings.traceDepth / 2;
|
||||
size_t first = warnings.traceDepth - last;
|
||||
size_t skipped = n - warnings.traceDepth;
|
||||
for (size_t i = 0; i < first; ++i) {
|
||||
style_Reset(stderr);
|
||||
fprintf(stderr, " %s ", i == 0 ? "at" : "<-");
|
||||
printNode(nodes[n - i - 1]);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
style_Reset(stderr);
|
||||
fprintf(stderr, " ...%zu more%s\n", skipped, last ? "..." : "");
|
||||
for (size_t i = n - last; i < n; ++i) {
|
||||
style_Reset(stderr);
|
||||
fputs(" <- ", stderr);
|
||||
printNode(nodes[n - i - 1]);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
}
|
||||
assume(!contextStack.empty());
|
||||
contextStack.top().fileInfo->dump(lexer_GetLineNo());
|
||||
return true;
|
||||
|
||||
style_Reset(stderr);
|
||||
}
|
||||
|
||||
void fstk_TraceCurrent() {
|
||||
if (!lexer_AtTopLevel()) {
|
||||
assume(!contextStack.empty());
|
||||
contextStack.top().fileInfo->printBacktrace(lexer_GetLineNo());
|
||||
}
|
||||
lexer_TraceStringExpansions();
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
|
||||
@@ -838,16 +838,16 @@ uint32_t lexer_GetLineNo() {
|
||||
return lexerState->lineNo;
|
||||
}
|
||||
|
||||
void lexer_DumpStringExpansions() {
|
||||
void lexer_TraceStringExpansions() {
|
||||
if (!lexerState) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Expansion &exp : lexerState->expansions) {
|
||||
// Only register EQUS expansions, not string args
|
||||
// Only print EQUS expansions, not string args
|
||||
if (exp.name) {
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fputs("while expanding symbol \"", stderr);
|
||||
fputs(" while expanding symbol \"", stderr);
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(exp.name->c_str(), stderr);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
|
||||
@@ -34,7 +34,7 @@ static char const *dependFileName = nullptr;
|
||||
static std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs; // -s
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
static char const *optstring = "B:b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
|
||||
// Variables for the long-only options
|
||||
static int longOpt; // `--color` and variants of `-M`
|
||||
@@ -47,6 +47,7 @@ static int longOpt; // `--color` and variants of `-M`
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"backtrace", required_argument, nullptr, 'B'},
|
||||
{"binary-digits", required_argument, nullptr, 'b'},
|
||||
{"define", required_argument, nullptr, 'D'},
|
||||
{"export-all", no_argument, nullptr, 'E'},
|
||||
@@ -77,7 +78,7 @@ static option const longopts[] = {
|
||||
static Usage usage = {
|
||||
.name = "rgbasm",
|
||||
.flags = {
|
||||
"[-EhVvw]", "[-b chars]", "[-D name[=value]]", "[-g chars]", "[-I path]",
|
||||
"[-EhVvw]", "[-B depth]", "[-b chars]", "[-D name[=value]]", "[-g chars]", "[-I path]",
|
||||
"[-M depend_file]", "[-MC]", "[-MG]", "[-MP]", "[-MT target_file]", "[-MQ target_file]",
|
||||
"[-o out_file]", "[-P include_file]", "[-p pad_value]", "[-Q precision]", "[-r depth]",
|
||||
"[-s features:state_file]", "[-W warning]", "[-X max_errors]", "<file>",
|
||||
@@ -215,7 +216,6 @@ static void verboseOutputConfig(int argc, char *argv[]) {
|
||||
if (options.generatePhonyDeps) {
|
||||
fputs("\tGenerate phony dependencies\n", stderr);
|
||||
}
|
||||
// [-MG] [-MC]
|
||||
}
|
||||
fputs("Ready.\n", stderr);
|
||||
|
||||
@@ -303,6 +303,24 @@ int main(int argc, char *argv[]) {
|
||||
switch (ch) {
|
||||
char *endptr;
|
||||
|
||||
case 'B': {
|
||||
if (!strcasecmp(musl_optarg, "collapse")) {
|
||||
warnings.traceDepth = TRACE_COLLAPSE;
|
||||
break;
|
||||
}
|
||||
|
||||
warnings.traceDepth = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
fatal("Invalid argument for option 'B'");
|
||||
}
|
||||
|
||||
if (warnings.traceDepth >= UINT64_MAX) {
|
||||
fatal("Argument for option 'B' is too large");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'b':
|
||||
if (strlen(musl_optarg) == 2) {
|
||||
opt_B(musl_optarg);
|
||||
|
||||
@@ -281,9 +281,12 @@ static void mergeSections(
|
||||
break;
|
||||
|
||||
case SECTION_NORMAL:
|
||||
sectError([&]() {
|
||||
fputs("Section already defined previously at ", stderr);
|
||||
sect.src->dump(sect.fileLine);
|
||||
errorNoTrace([&]() {
|
||||
fputs("Section already defined\n", stderr);
|
||||
fstk_TraceCurrent();
|
||||
fputs(" and also:\n", stderr);
|
||||
sect.src->printBacktrace(sect.fileLine);
|
||||
++nbSectErrors;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -113,14 +113,15 @@ std::shared_ptr<std::string> Symbol::getEqus() const {
|
||||
return std::get<std::shared_ptr<std::string>>(data);
|
||||
}
|
||||
|
||||
static void dumpFilename(Symbol const &sym) {
|
||||
fputs(" at ", stderr);
|
||||
// Meant to be called last in an `errorNoTrace` callback
|
||||
static void printBacktraces(Symbol const &sym) {
|
||||
putc('\n', stderr);
|
||||
fstk_TraceCurrent();
|
||||
fputs(" and also:\n", stderr);
|
||||
if (sym.src) {
|
||||
sym.src->dump(sym.fileLine);
|
||||
} else if (sym.isBuiltin) {
|
||||
fputs("<builtin>", stderr);
|
||||
sym.src->printBacktrace(sym.fileLine);
|
||||
} else {
|
||||
fputs("<command-line>", stderr);
|
||||
fprintf(stderr, " at <%s>\n", sym.isBuiltin ? "builtin" : "command-line");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,37 +142,52 @@ static bool isValidIdentifier(std::string const &s) {
|
||||
}
|
||||
|
||||
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
||||
if (sym.isBuiltin && !sym_FindScopedValidSymbol(sym.name)) {
|
||||
// `DEF()` would return false, so we should not claim the symbol is already defined
|
||||
error("'%s' is reserved for a built-in symbol", sym.name.c_str());
|
||||
auto suggestion = [&]() {
|
||||
std::string s;
|
||||
if (auto const &contents = sym.type == SYM_EQUS ? sym.getEqus() : nullptr;
|
||||
contents && isValidIdentifier(*contents)) {
|
||||
s.append(" (should it be {interpolated} to define its contents \"");
|
||||
s.append(*contents);
|
||||
s.append("\"?)");
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
if (sym.isBuiltin) {
|
||||
if (sym_FindScopedValidSymbol(sym.name)) {
|
||||
if (std::string s = suggestion(); asType) {
|
||||
error("'%s' already defined as built-in %s%s", sym.name.c_str(), asType, s.c_str());
|
||||
} else {
|
||||
error("'%s' already defined as built-in%s", sym.name.c_str(), s.c_str());
|
||||
}
|
||||
} else {
|
||||
// `DEF()` would return false, so we should not claim the symbol is already defined,
|
||||
// nor suggest to interpolate it
|
||||
if (asType) {
|
||||
error("'%s' is reserved for a built-in %s symbol", sym.name.c_str(), asType);
|
||||
} else {
|
||||
error("'%s' is reserved for a built-in symbol", sym.name.c_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error([&]() {
|
||||
errorNoTrace([&]() {
|
||||
fprintf(stderr, "'%s' already defined", sym.name.c_str());
|
||||
if (asType) {
|
||||
fprintf(stderr, " as %s", asType);
|
||||
}
|
||||
dumpFilename(sym);
|
||||
if (sym.type != SYM_EQUS) {
|
||||
return;
|
||||
}
|
||||
if (std::string const &contents = *sym.getEqus(); isValidIdentifier(contents)) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"\n (should it be {interpolated} to define its contents \"%s\"?)",
|
||||
contents.c_str()
|
||||
);
|
||||
}
|
||||
fputs(suggestion().c_str(), stderr);
|
||||
printBacktraces(sym);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void redefinedError(Symbol const &sym) {
|
||||
assume(sym.isBuiltin);
|
||||
if (!sym_FindScopedValidSymbol(sym.name)) {
|
||||
if (sym_FindScopedValidSymbol(sym.name)) {
|
||||
error("Built-in symbol '%s' cannot be redefined", sym.name.c_str());
|
||||
} else {
|
||||
// `DEF()` would return false, so we should not imply the symbol is already defined
|
||||
error("'%s' is reserved for a built-in symbol", sym.name.c_str());
|
||||
} else {
|
||||
error("Built-in symbol '%s' cannot be redefined", sym.name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,9 +392,9 @@ static Symbol *createNonrelocSymbol(std::string const &symName, bool numeric) {
|
||||
return nullptr; // Don't allow overriding the symbol, that'd be bad!
|
||||
} else if (!numeric) {
|
||||
// The symbol has already been referenced, but it's not allowed
|
||||
error([&]() {
|
||||
errorNoTrace([&]() {
|
||||
fprintf(stderr, "'%s' already referenced", symName.c_str());
|
||||
dumpFilename(*sym);
|
||||
printBacktraces(*sym);
|
||||
});
|
||||
return nullptr; // Don't allow overriding the symbol, that'd be bad!
|
||||
}
|
||||
@@ -444,9 +460,9 @@ Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string>
|
||||
if (sym->isDefined()) {
|
||||
alreadyDefinedError(*sym, "non-EQUS");
|
||||
} else {
|
||||
error([&]() {
|
||||
errorNoTrace([&]() {
|
||||
fprintf(stderr, "'%s' already referenced", symName.c_str());
|
||||
dumpFilename(*sym);
|
||||
printBacktraces(*sym);
|
||||
});
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
@@ -60,6 +60,7 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
|
||||
},
|
||||
.state = DiagnosticsState<WarningID>(),
|
||||
.traceDepth = 0,
|
||||
.nbErrors = 0,
|
||||
};
|
||||
// clang-format on
|
||||
@@ -74,19 +75,16 @@ static void printDiag(
|
||||
) {
|
||||
style_Set(stderr, color, true);
|
||||
fprintf(stderr, "%s: ", type);
|
||||
if (fstk_DumpCurrent()) {
|
||||
putc(':', stderr);
|
||||
if (flagfmt) {
|
||||
style_Set(stderr, color, true);
|
||||
fprintf(stderr, flagfmt, flag);
|
||||
}
|
||||
fputs("\n ", stderr);
|
||||
}
|
||||
style_Reset(stderr);
|
||||
vfprintf(stderr, fmt, args);
|
||||
if (flagfmt) {
|
||||
style_Set(stderr, color, true);
|
||||
putc(' ', stderr);
|
||||
fprintf(stderr, flagfmt, flag);
|
||||
}
|
||||
putc('\n', stderr);
|
||||
|
||||
lexer_DumpStringExpansions();
|
||||
fstk_TraceCurrent();
|
||||
}
|
||||
|
||||
static void incrementErrors() {
|
||||
@@ -117,16 +115,11 @@ void error(char const *fmt, ...) {
|
||||
incrementErrors();
|
||||
}
|
||||
|
||||
void error(std::function<void()> callback) {
|
||||
void errorNoTrace(std::function<void()> callback) {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("error: ", stderr);
|
||||
if (fstk_DumpCurrent()) {
|
||||
fputs(":\n ", stderr);
|
||||
}
|
||||
style_Reset(stderr);
|
||||
callback();
|
||||
putc('\n', stderr);
|
||||
lexer_DumpStringExpansions();
|
||||
|
||||
incrementErrors();
|
||||
}
|
||||
@@ -167,11 +160,11 @@ void warning(WarningID id, char const *fmt, ...) {
|
||||
break;
|
||||
|
||||
case WarningBehavior::ENABLED:
|
||||
printDiag(fmt, args, "warning", STYLE_YELLOW, " [-W%s]", flag);
|
||||
printDiag(fmt, args, "warning", STYLE_YELLOW, "[-W%s]", flag);
|
||||
break;
|
||||
|
||||
case WarningBehavior::ERROR:
|
||||
printDiag(fmt, args, "error", STYLE_RED, " [-Werror=%s]", flag);
|
||||
printDiag(fmt, args, "error", STYLE_RED, "[-Werror=%s]", flag);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
},
|
||||
.paramWarnings = {},
|
||||
.state = DiagnosticsState<WarningID>(),
|
||||
.traceDepth = 0,
|
||||
.nbErrors = 0,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -22,6 +22,7 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
},
|
||||
.paramWarnings = {},
|
||||
.state = DiagnosticsState<WarningID>(),
|
||||
.traceDepth = 0,
|
||||
.nbErrors = 0,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
80
src/link/fstack.cpp
Normal file
80
src/link/fstack.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "link/fstack.hpp"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <utility>
|
||||
|
||||
#include "style.hpp"
|
||||
|
||||
#include "link/warning.hpp"
|
||||
|
||||
std::vector<std::pair<std::string, uint32_t>> FileStackNode::backtrace(uint32_t curLineNo) const {
|
||||
if (std::holds_alternative<std::vector<uint32_t>>(data)) {
|
||||
assume(parent); // REPT nodes use their parent's name
|
||||
std::vector<std::pair<std::string, uint32_t>> nodes = parent->backtrace(lineNo);
|
||||
assume(!nodes.empty());
|
||||
std::string reptChain = nodes.back().first;
|
||||
for (uint32_t iter : iters()) {
|
||||
reptChain.append("::REPT~");
|
||||
reptChain.append(std::to_string(iter));
|
||||
}
|
||||
nodes.emplace_back(reptChain, curLineNo);
|
||||
return nodes;
|
||||
} else if (parent) {
|
||||
std::vector<std::pair<std::string, uint32_t>> nodes = parent->backtrace(lineNo);
|
||||
nodes.emplace_back(name(), curLineNo);
|
||||
return nodes;
|
||||
} else {
|
||||
return {
|
||||
{name(), curLineNo}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static void printNode(std::pair<std::string, uint32_t> const &node) {
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(node.first.c_str(), stderr);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fprintf(stderr, "(%" PRIu32 ")", node.second);
|
||||
}
|
||||
|
||||
void FileStackNode::printBacktrace(uint32_t curLineNo) const {
|
||||
std::vector<std::pair<std::string, uint32_t>> nodes = backtrace(curLineNo);
|
||||
size_t n = nodes.size();
|
||||
|
||||
if (warnings.traceDepth == TRACE_COLLAPSE) {
|
||||
fputs(" ", stderr); // Just three spaces; the fourth will be handled by the loop
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
style_Reset(stderr);
|
||||
fprintf(stderr, " %s ", i == 0 ? "at" : "<-");
|
||||
printNode(nodes[n - i - 1]);
|
||||
}
|
||||
putc('\n', stderr);
|
||||
} else if (warnings.traceDepth == 0 || static_cast<size_t>(warnings.traceDepth) >= n) {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
style_Reset(stderr);
|
||||
fprintf(stderr, " %s ", i == 0 ? "at" : "<-");
|
||||
printNode(nodes[n - i - 1]);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
} else {
|
||||
size_t last = warnings.traceDepth / 2;
|
||||
size_t first = warnings.traceDepth - last;
|
||||
size_t skipped = n - warnings.traceDepth;
|
||||
for (size_t i = 0; i < first; ++i) {
|
||||
style_Reset(stderr);
|
||||
fprintf(stderr, " %s ", i == 0 ? "at" : "<-");
|
||||
printNode(nodes[n - i - 1]);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
style_Reset(stderr);
|
||||
fprintf(stderr, " ...%zu more%s\n", skipped, last ? "..." : "");
|
||||
for (size_t i = n - last; i < n; ++i) {
|
||||
style_Reset(stderr);
|
||||
fputs(" <- ", stderr);
|
||||
printNode(nodes[n - i - 1]);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
}
|
||||
|
||||
style_Reset(stderr);
|
||||
}
|
||||
@@ -36,7 +36,7 @@ Options options;
|
||||
static char const *linkerScriptName = nullptr; // -l
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "dhl:m:Mn:O:o:p:S:tVvW:wx";
|
||||
static char const *optstring = "B:dhl:m:Mn:O:o:p:S:tVvW:wx";
|
||||
|
||||
// Variables for the long-only options
|
||||
static int longOpt; // `--color`
|
||||
@@ -49,6 +49,7 @@ static int longOpt; // `--color`
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"backtrace", required_argument, nullptr, 'B'},
|
||||
{"dmg", no_argument, nullptr, 'd'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"linkerscript", required_argument, nullptr, 'l'},
|
||||
@@ -73,8 +74,8 @@ static option const longopts[] = {
|
||||
static Usage usage = {
|
||||
.name = "rgblink",
|
||||
.flags = {
|
||||
"[-dhMtVvwx]", "[-l script]", "[-m map_file]", "[-n sym_file]", "[-O overlay_file]",
|
||||
"[-o out_file]", "[-p pad_value]", "[-S spec]", "<file> ...",
|
||||
"[-dhMtVvwx]", "[-B depth]", "[-l script]", "[-m map_file]", "[-n sym_file]",
|
||||
"[-O overlay_file]", "[-o out_file]", "[-p pad_value]", "[-S spec]", "<file> ...",
|
||||
},
|
||||
.options = {
|
||||
{{"-l", "--linkerscript <path>"}, {"set the input linker script"}},
|
||||
@@ -180,37 +181,6 @@ static void verboseOutputConfig(int argc, char *argv[]) {
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
std::string const &FileStackNode::dump(uint32_t curLineNo) const {
|
||||
if (std::holds_alternative<std::vector<uint32_t>>(data)) {
|
||||
assume(parent); // REPT nodes use their parent's name
|
||||
std::string const &lastName = parent->dump(lineNo);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fputs(" -> ", stderr);
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(lastName.c_str(), stderr);
|
||||
for (uint32_t iter : iters()) {
|
||||
fprintf(stderr, "::REPT~%" PRIu32, iter);
|
||||
}
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||
style_Reset(stderr);
|
||||
return lastName;
|
||||
} else {
|
||||
if (parent) {
|
||||
parent->dump(lineNo);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fputs(" -> ", stderr);
|
||||
}
|
||||
std::string const &nodeName = name();
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(nodeName.c_str(), stderr);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||
style_Reset(stderr);
|
||||
return nodeName;
|
||||
}
|
||||
}
|
||||
|
||||
static void parseScrambleSpec(char *spec) {
|
||||
// clang-format off: vertically align nested initializers
|
||||
static UpperMap<std::pair<uint16_t *, uint16_t>> scrambleSpecs{
|
||||
@@ -325,6 +295,21 @@ int main(int argc, char *argv[]) {
|
||||
// Parse options
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
switch (ch) {
|
||||
case 'B': {
|
||||
if (!strcasecmp(musl_optarg, "collapse")) {
|
||||
warnings.traceDepth = TRACE_COLLAPSE;
|
||||
break;
|
||||
}
|
||||
char *endptr;
|
||||
warnings.traceDepth = strtoul(musl_optarg, &endptr, 0);
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
fatal("Invalid argument for option 'B'");
|
||||
}
|
||||
if (warnings.traceDepth >= UINT64_MAX) {
|
||||
fatal("Argument for option 'B' is too large");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'd':
|
||||
options.isDmgMode = true;
|
||||
options.isWRAM0Mode = true;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "version.hpp"
|
||||
|
||||
#include "link/assign.hpp"
|
||||
#include "link/fstack.hpp"
|
||||
#include "link/main.hpp"
|
||||
#include "link/patch.hpp"
|
||||
#include "link/sdas_obj.hpp"
|
||||
|
||||
@@ -428,7 +428,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
}
|
||||
} else if (Symbol const *symbol = getSymbol(fileSymbols, value); !symbol) {
|
||||
errorAt(patch, "Undefined symbol \"%s\"", fileSymbols[value].name.c_str());
|
||||
sym_DumpLocalAliasedSymbols(fileSymbols[value].name);
|
||||
sym_TraceLocalAliasedSymbols(fileSymbols[value].name);
|
||||
isError = true;
|
||||
} else if (std::holds_alternative<Label>(symbol->data)) {
|
||||
Label const &label = std::get<Label>(symbol->data);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "platform.hpp"
|
||||
|
||||
#include "link/assign.hpp"
|
||||
#include "link/fstack.hpp"
|
||||
#include "link/main.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include "link/symbol.hpp"
|
||||
@@ -280,7 +281,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
||||
// The following is required for fragment offsets to be reliably predicted
|
||||
for (FileSection &entry : fileSections) {
|
||||
if (!strcmp(token, entry.section->name.c_str())) {
|
||||
fatalAt(where, "Area \"%s\" already defined earlier", token);
|
||||
fatalAt(where, "Area \"%s\" already defined", token);
|
||||
}
|
||||
}
|
||||
char const *sectName = token; // We'll deal with the section's name depending on type
|
||||
@@ -422,14 +423,14 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
||||
|| (symbolSection && !symbolSection->isAddressFixed)) {
|
||||
sym_AddSymbol(symbol); // This will error out
|
||||
} else if (otherValue != symbolValue) {
|
||||
errorNoDump(
|
||||
"\"%s\" is defined as %" PRId32 " at ", symbol.name.c_str(), symbolValue
|
||||
fatalTwoAt(
|
||||
symbol,
|
||||
*other,
|
||||
"\"%s\" is defined as %" PRId32 ", but was already defined as %" PRId32,
|
||||
symbol.name.c_str(),
|
||||
symbolValue,
|
||||
otherValue
|
||||
);
|
||||
symbol.src->dump(symbol.lineNo);
|
||||
fprintf(stderr, ", but as %" PRId32 " at ", otherValue);
|
||||
other->src->dump(other->lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
// Add a new definition
|
||||
|
||||
@@ -24,30 +24,28 @@ void sect_ForEach(void (*callback)(Section &)) {
|
||||
static void checkAgainstFixedAddress(Section const &target, Section const &other, uint16_t org) {
|
||||
if (target.isAddressFixed) {
|
||||
if (target.org != org) {
|
||||
errorNoDump(
|
||||
"Section \"%s\" is defined with address $%04" PRIx16 " at ",
|
||||
fatalTwoAt(
|
||||
target,
|
||||
other,
|
||||
"Section \"%s\" is defined with address $%04" PRIx16
|
||||
", but also with address $%04" PRIx16,
|
||||
target.name.c_str(),
|
||||
target.org
|
||||
target.org,
|
||||
other.org
|
||||
);
|
||||
target.src->dump(target.lineNo);
|
||||
fprintf(stderr, ", but with address $%04" PRIx16 " at ", other.org);
|
||||
other.src->dump(other.lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
} else if (target.isAlignFixed) {
|
||||
if ((org - target.alignOfs) & target.alignMask) {
|
||||
errorNoDump(
|
||||
"Section \"%s\" is defined with %d-byte alignment (offset %" PRIu16 ") at ",
|
||||
fatalTwoAt(
|
||||
target,
|
||||
other,
|
||||
"Section \"%s\" is defined with %d-byte alignment (offset %" PRIu16
|
||||
"), but also with address $%04" PRIx16,
|
||||
target.name.c_str(),
|
||||
target.alignMask + 1,
|
||||
target.alignOfs
|
||||
target.alignOfs,
|
||||
other.org
|
||||
);
|
||||
target.src->dump(target.lineNo);
|
||||
fprintf(stderr, ", but with address $%04" PRIx16 " at ", other.org);
|
||||
other.src->dump(other.lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,41 +53,31 @@ static void checkAgainstFixedAddress(Section const &target, Section const &other
|
||||
static bool checkAgainstFixedAlign(Section const &target, Section const &other, int32_t ofs) {
|
||||
if (target.isAddressFixed) {
|
||||
if ((target.org - ofs) & other.alignMask) {
|
||||
errorNoDump(
|
||||
"Section \"%s\" is defined with address $%04" PRIx16 " at ",
|
||||
fatalTwoAt(
|
||||
target,
|
||||
other,
|
||||
"Section \"%s\" is defined with address $%04" PRIx16
|
||||
", but also with %d-byte alignment (offset %" PRIu16 ")",
|
||||
target.name.c_str(),
|
||||
target.org
|
||||
);
|
||||
target.src->dump(target.lineNo);
|
||||
fprintf(
|
||||
stderr,
|
||||
", but with %d-byte alignment (offset %" PRIu16 ") at ",
|
||||
target.org,
|
||||
other.alignMask + 1,
|
||||
other.alignOfs
|
||||
);
|
||||
other.src->dump(other.lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
return false;
|
||||
} else if (target.isAlignFixed
|
||||
&& (other.alignMask & target.alignOfs) != (target.alignMask & ofs)) {
|
||||
errorNoDump(
|
||||
"Section \"%s\" is defined with %d-byte alignment (offset %" PRIu16 ") at ",
|
||||
fatalTwoAt(
|
||||
target,
|
||||
other,
|
||||
"Section \"%s\" is defined with %d-byte alignment (offset %" PRIu16
|
||||
"), but also with %d-byte alignment (offset %" PRIu16 ")",
|
||||
target.name.c_str(),
|
||||
target.alignMask + 1,
|
||||
target.alignOfs
|
||||
);
|
||||
target.src->dump(target.lineNo);
|
||||
fprintf(
|
||||
stderr,
|
||||
", but with %d-byte alignment (offset %" PRIu16 ") at ",
|
||||
target.alignOfs,
|
||||
other.alignMask + 1,
|
||||
other.alignOfs
|
||||
);
|
||||
other.src->dump(other.lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
} else {
|
||||
return !target.isAlignFixed || (other.alignMask > target.alignMask);
|
||||
}
|
||||
@@ -129,34 +117,25 @@ static void checkFragmentCompat(Section &target, Section &other) {
|
||||
|
||||
static void mergeSections(Section &target, std::unique_ptr<Section> &&other) {
|
||||
if (target.modifier != other->modifier) {
|
||||
errorNoDump(
|
||||
"Section \"%s\" is defined as SECTION %s at ",
|
||||
fatalTwoAt(
|
||||
target,
|
||||
*other,
|
||||
"Section \"%s\" is defined as SECTION %s, but also as SECTION %s",
|
||||
target.name.c_str(),
|
||||
sectionModNames[target.modifier]
|
||||
sectionModNames[target.modifier],
|
||||
sectionModNames[other->modifier]
|
||||
);
|
||||
target.src->dump(target.lineNo);
|
||||
fprintf(stderr, ", but as SECTION %s at ", sectionModNames[other->modifier]);
|
||||
other->src->dump(other->lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
} else if (other->modifier == SECTION_NORMAL) {
|
||||
errorNoDump("Section \"%s\" is defined at ", target.name.c_str());
|
||||
target.src->dump(target.lineNo);
|
||||
fputs(", but also at ", stderr);
|
||||
other->src->dump(other->lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
fatalTwoAt(target, *other, "Section \"%s\" is already defined", target.name.c_str());
|
||||
} else if (target.type != other->type) {
|
||||
errorNoDump(
|
||||
"Section \"%s\" is defined with type %s at ",
|
||||
fatalTwoAt(
|
||||
target,
|
||||
*other,
|
||||
"Section \"%s\" is defined with type %s, but also with type %s",
|
||||
target.name.c_str(),
|
||||
sectionTypeInfo[target.type].name.c_str()
|
||||
sectionTypeInfo[target.type].name.c_str(),
|
||||
sectionTypeInfo[other->type].name.c_str()
|
||||
);
|
||||
target.src->dump(target.lineNo);
|
||||
fprintf(stderr, ", but with type %s at ", sectionTypeInfo[other->type].name.c_str());
|
||||
other->src->dump(other->lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (other->isBankFixed) {
|
||||
@@ -164,16 +143,14 @@ static void mergeSections(Section &target, std::unique_ptr<Section> &&other) {
|
||||
target.isBankFixed = true;
|
||||
target.bank = other->bank;
|
||||
} else if (target.bank != other->bank) {
|
||||
errorNoDump(
|
||||
"Section \"%s\" is defined with bank %" PRIu32 " at ",
|
||||
fatalTwoAt(
|
||||
target,
|
||||
*other,
|
||||
"Section \"%s\" is defined with bank %" PRIu32 ", but also with bank %" PRIu32,
|
||||
target.name.c_str(),
|
||||
target.bank
|
||||
target.bank,
|
||||
other->bank
|
||||
);
|
||||
target.src->dump(target.lineNo);
|
||||
fprintf(stderr, ", but with bank %" PRIu32 " at ", other->bank);
|
||||
other->src->dump(other->lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "helpers.hpp" // assume
|
||||
|
||||
#include "link/main.hpp"
|
||||
#include "link/fstack.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include "link/warning.hpp"
|
||||
|
||||
@@ -39,24 +39,16 @@ void sym_AddSymbol(Symbol &symbol) {
|
||||
|
||||
// Check if the symbol already exists with a different value
|
||||
if (other && !(symValue && otherValue && *symValue == *otherValue)) {
|
||||
errorNoDump("\"%s\" is defined as ", symbol.name.c_str());
|
||||
if (symValue) {
|
||||
fprintf(stderr, "%" PRId32, *symValue);
|
||||
} else {
|
||||
fputs("a label", stderr);
|
||||
}
|
||||
fputs(" at ", stderr);
|
||||
symbol.src->dump(symbol.lineNo);
|
||||
fputs(", but as ", stderr);
|
||||
if (otherValue) {
|
||||
fprintf(stderr, "%" PRId32, *otherValue);
|
||||
} else {
|
||||
fputs("another label", stderr);
|
||||
}
|
||||
fputs(" at ", stderr);
|
||||
other->src->dump(other->lineNo);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
std::string symDef = symValue ? std::to_string(*symValue) : "a label";
|
||||
std::string otherDef = otherValue ? std::to_string(*otherValue) : "another label";
|
||||
fatalTwoAt(
|
||||
symbol,
|
||||
*other,
|
||||
"\"%s\" is defined as %s, but also as %s",
|
||||
symbol.name.c_str(),
|
||||
symDef.c_str(),
|
||||
otherDef.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
// If not, add it (potentially replacing the previous same-value symbol)
|
||||
@@ -68,29 +60,28 @@ Symbol *sym_GetSymbol(std::string const &name) {
|
||||
return search != symbols.end() ? search->second : nullptr;
|
||||
}
|
||||
|
||||
void sym_DumpLocalAliasedSymbols(std::string const &name) {
|
||||
void sym_TraceLocalAliasedSymbols(std::string const &name) {
|
||||
std::vector<Symbol *> const &locals = localSymbols[name];
|
||||
if (locals.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool plural = locals.size() != 1;
|
||||
fprintf(
|
||||
stderr,
|
||||
" %zu symbol%s with that name %s defined but not exported:\n",
|
||||
locals.size(),
|
||||
plural ? "s" : "",
|
||||
plural ? "are" : "is"
|
||||
);
|
||||
|
||||
int count = 0;
|
||||
for (Symbol *local : locals) {
|
||||
if (count++ == 3) {
|
||||
size_t remaining = locals.size() - 3;
|
||||
bool plural = remaining != 1;
|
||||
fprintf(
|
||||
stderr,
|
||||
" ...and %zu more symbol%s with that name %s defined but not exported\n",
|
||||
remaining,
|
||||
plural ? "s" : "",
|
||||
plural ? "are" : "is"
|
||||
);
|
||||
assume(local->src);
|
||||
local->src->printBacktrace(local->lineNo);
|
||||
if (++count == 3 && locals.size() > 3) {
|
||||
fprintf(stderr, " ...and %zu more\n", locals.size() - 3);
|
||||
break;
|
||||
}
|
||||
fprintf(
|
||||
stderr,
|
||||
" A %s with that name is defined but not exported at ",
|
||||
std::holds_alternative<Label>(local->data) ? "label" : "constant"
|
||||
);
|
||||
assume(local->src);
|
||||
local->src->dump(local->lineNo);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include "style.hpp"
|
||||
|
||||
#include "link/main.hpp"
|
||||
#include "link/fstack.hpp"
|
||||
|
||||
// clang-format off: nested initializers
|
||||
Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
@@ -25,6 +25,7 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
},
|
||||
.paramWarnings = {},
|
||||
.state = DiagnosticsState<WarningID>(),
|
||||
.traceDepth = 0,
|
||||
.nbErrors = 0,
|
||||
};
|
||||
// clang-format on
|
||||
@@ -41,18 +42,18 @@ static void printDiag(
|
||||
) {
|
||||
style_Set(stderr, color, true);
|
||||
fprintf(stderr, "%s: ", type);
|
||||
if (src) {
|
||||
src->dump(lineNo);
|
||||
fputs(": ", stderr);
|
||||
}
|
||||
if (flagfmt) {
|
||||
style_Set(stderr, color, true);
|
||||
fprintf(stderr, flagfmt, flag);
|
||||
fputs("\n ", stderr);
|
||||
}
|
||||
style_Reset(stderr);
|
||||
vfprintf(stderr, fmt, args);
|
||||
if (flagfmt) {
|
||||
style_Set(stderr, color, true);
|
||||
putc(' ', stderr);
|
||||
fprintf(stderr, flagfmt, flag);
|
||||
}
|
||||
putc('\n', stderr);
|
||||
|
||||
if (src) {
|
||||
src->printBacktrace(lineNo);
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
@@ -101,26 +102,15 @@ void error(char const *fmt, ...) {
|
||||
warnings.incrementErrors();
|
||||
}
|
||||
|
||||
void errorNoDump(char const *fmt, ...) {
|
||||
va_list args;
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("error: ", stderr);
|
||||
style_Reset(stderr);
|
||||
va_start(args, fmt);
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
warnings.incrementErrors();
|
||||
}
|
||||
|
||||
void scriptError(char const *name, uint32_t lineNo, char const *fmt, va_list args) {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("error: ", stderr);
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(name, stderr);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fprintf(stderr, "(%" PRIu32 "): ", lineNo);
|
||||
fprintf(stderr, "(%" PRIu32 ")", lineNo);
|
||||
style_Reset(stderr);
|
||||
fputs(": ", stderr);
|
||||
vfprintf(stderr, fmt, args);
|
||||
putc('\n', stderr);
|
||||
|
||||
@@ -149,6 +139,32 @@ void fatal(char const *fmt, ...) {
|
||||
abortLinking(nullptr);
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
void fatalTwo(
|
||||
FileStackNode const &src1,
|
||||
uint32_t lineNo1,
|
||||
FileStackNode const &src2,
|
||||
uint32_t lineNo2,
|
||||
char const *fmt,
|
||||
...
|
||||
) {
|
||||
va_list args;
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("FATAL: ", stderr);
|
||||
style_Reset(stderr);
|
||||
va_start(args, fmt);
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
putc('\n', stderr);
|
||||
|
||||
src1.printBacktrace(lineNo1);
|
||||
fputs(" and also:\n", stderr);
|
||||
src2.printBacktrace(lineNo2);
|
||||
|
||||
warnings.incrementErrors();
|
||||
abortLinking(nullptr);
|
||||
}
|
||||
|
||||
void requireZeroErrors() {
|
||||
if (warnings.nbErrors != 0) {
|
||||
abortLinking("failed");
|
||||
|
||||
Reference in New Issue
Block a user