Add support for local labels
MODIFY
src/assembler/assembler.h
src/assembler/assembler.h
@@ -248,7 +248,10 @@ protected:
bool uniqueSymbols = true;
for (const auto& s : symbolsAndRest.first) {
- if (symbols.count(s) != 0) {
+ if (!s.isLegal())
+ errors.push_back(Error(tsl.sourceLine, "Illegal symbol '" + s.v + "'"));
+
+ if (!s.isLocal() && symbols.count(s) != 0) {
errors.push_back(Error(tsl.sourceLine, "Multiple definitions of symbol '" + s.v + "'"));
uniqueSymbols = false;
break;
@@ -356,7 +359,11 @@ protected:
VInt addr_offset = currentSection->data.size();
for (const auto& s : line.symbols) {
// Record symbol position as its absolute address in memory
- runOperationNoRes(addSymbol, line, s, addr_offset + program.sections.at(m_currentSection).address);
+ auto res = m_symbolMap.addSymbol(line, s, addr_offset + program.sections.at(m_currentSection).address);
+ if (res) {
+ errors.push_back(res.value());
+ continue;
+ }
}
runOperation(directiveBytes, std::optional<QByteArray>, assembleDirective,
@@ -405,8 +412,9 @@ protected:
return {errors};
}
- // Register address symbols in program struct
- for (const auto& iter : m_symbolMap) {
+ // Register address symbols in program struct.
+ /// @todo: also consider relative symbols here.
+ for (const auto& iter : m_symbolMap.abs) {
if (iter.first.is(Symbol::Type::Address)) {
program.symbols[iter.second] = iter.first;
}
@@ -424,10 +432,10 @@ protected:
// Add the special __address__ symbol indicating the address of the instruction itself. Not done through
// addSymbol given that we redefine this symbol on each line.
const Reg_T linkRequestAddress = linkReqAddress(linkRequest);
- m_symbolMap["__address__"] = linkRequestAddress;
+ m_symbolMap.abs["__address__"] = linkRequestAddress;
// Expression evaluation also performs symbol evaluation
- auto exprRes = evalExpr(symbol);
+ auto exprRes = evalExpr(symbol, linkRequest.sourceLine);
if (auto* err = std::get_if<Error>(&exprRes)) {
err->first = linkRequest.sourceLine;
errors.push_back(*err);

MODIFY
src/assembler/assembler_defines.h
src/assembler/assembler_defines.h
@@ -33,7 +33,7 @@ using LineTokens = QVector<Token>;
using LineTokensVec = std::vector<LineTokens>;
using Symbols = std::set<Symbol>;
using DirectiveLinePair = std::pair<QString, LineTokens>;
-using SymbolMap = std::map<Symbol, VIntS>;
+
using HandleDirectiveRes = std::variant<Error, std::optional<QByteArray>>;
struct TokenizedSrcLine {
@@ -47,7 +47,6 @@ struct TokenizedSrcLine {
using SymbolLinePair = std::pair<Symbols, LineTokens>;
using SourceProgram = std::vector<TokenizedSrcLine>;
using NoPassResult = std::monostate;
-
using Section = QString;
/**

MODIFY
src/assembler/assemblerbase.cpp
src/assembler/assemblerbase.cpp
@@ -31,27 +31,15 @@ AssembleResult AssemblerBase::assembleRaw(const QString& program, const SymbolMa
return assemble(programLines, symbols, Program::calculateHash(program.toUtf8()));
}
-/// Adds a symbol to the current symbol mapping of this assembler defined at the 'line' in the input program.
-std::optional<Error> AssemblerBase::addSymbol(const TokenizedSrcLine& line, const Symbol& s, VInt v) const {
- return addSymbol(line.sourceLine, s, v);
-}
-
-/// Adds a symbol to the current symbol mapping of this assembler.
-std::optional<Error> AssemblerBase::addSymbol(const unsigned& line, const Symbol& s, VInt v) const {
- if (m_symbolMap.count(s)) {
- return {Error(line, "Multiple definitions of symbol '" + s.v + "'")};
- }
- m_symbolMap[s] = v;
- return {};
-}
-
/// Resolves an expression through either the built-in symbol map, or through the expression evaluator.
-ExprEvalRes AssemblerBase::evalExpr(const QString& expr) const {
- auto symbolValue = m_symbolMap.find(expr);
- if (symbolValue != m_symbolMap.end()) {
+ExprEvalRes AssemblerBase::evalExpr(const QString& expr, unsigned sourceLine) const {
+ auto relativeMap = m_symbolMap.copyRelativeTo(sourceLine);
+
+ auto symbolValue = relativeMap.find(expr);
+ if (symbolValue != relativeMap.end()) {
return symbolValue->second;
} else {
- return evaluate(expr, &m_symbolMap);
+ return evaluate(expr, &relativeMap);
}
}

MODIFY
src/assembler/assemblerbase.h
src/assembler/assemblerbase.h
#include "assembler_defines.h"
#include "directive.h"
#include "expreval.h"
+#include "symbolmap.h"
namespace Ripes {
namespace Assembler {
@@ -48,18 +49,20 @@ public:
/// Returns the set of opcodes (as strings) which are supported by this assembler.
virtual std::set<QString> getOpcodes() const = 0;
- /// Adds a symbol to the current symbol mapping of this assembler defined at the 'line' in the input program.
- std::optional<Error> addSymbol(const TokenizedSrcLine& line, const Symbol& s, VInt v) const;
-
- /// Adds a symbol to the current symbol mapping of this assembler.
- std::optional<Error> addSymbol(const unsigned& line, const Symbol& s, VInt v) const;
-
/// Resolves an expression through either the built-in symbol map, or through the expression evaluator.
- ExprEvalRes evalExpr(const QString& expr) const;
+ ExprEvalRes evalExpr(const QString& expr, unsigned sourceLine) const;
/// Set the supported directives for this assembler.
void setDirectives(const DirectiveVec& directives);
+ /**
+ * @brief symbolMap maintains the symbols recorded during assembling. Marked mutable to allow for assembler
+ * directives to add symbols during assembling.
+ * publically exposed to allow for Directives (such as equDirective) to be able to insert symbols into the
+ * assembler... this might be smelly code (!!!).
+ */
+ mutable SymbolMap m_symbolMap;
+
protected:
/// Creates a set of LineTokens by tokenizing a line of source code.
QRegularExpression m_splitterRegex;
@@ -93,12 +96,6 @@ protected:
*/
mutable Section m_currentSection;
- /**
- * @brief symbolMap maintains the symbols recorded during assembling. Marked mutable to allow for assembler
- * directives to add symbols during assembling.
- */
- mutable SymbolMap m_symbolMap;
-
/**
* The set of supported assembler directives. A assembler can add directives through
* AssemblerBase::setDirectives.

MODIFY
src/assembler/expreval.cpp
src/assembler/expreval.cpp
@@ -214,7 +214,7 @@ ExprRes parseLeft(const QString& s, int& pos, int& depth) {
}
}
-VIntS evaluate(const std::shared_ptr<Expr>& expr, const SymbolMap* variables) {
+VIntS evaluate(const std::shared_ptr<Expr>& expr, const AbsoluteSymbolMap* variables) {
// There is a bug in GCC for variant visitors on incomplete variant types (recursive), So instead we'll macro
// our way towards something that looks like a pattern match for the variant type.
IfExpr(Add, v) { return evaluate(v->lhs, variables) + evaluate(v->rhs, variables); }
@@ -262,7 +262,7 @@ VIntS evaluate(const std::shared_ptr<Expr>& expr, const SymbolMap* variables) {
Q_UNREACHABLE();
}
-ExprEvalRes evaluate(const QString& s, const SymbolMap* variables) {
+ExprEvalRes evaluate(const QString& s, const AbsoluteSymbolMap* variables) {
QString sNoWhitespace = s;
sNoWhitespace.replace(" ", "");
int pos = 0;

MODIFY
src/assembler/expreval.h
src/assembler/expreval.h
#include <variant>
#include "assembler_defines.h"
#include "assemblererror.h"
+#include "symbolmap.h"
namespace Ripes {
namespace Assembler {
@@ -21,7 +22,7 @@ using ExprEvalRes = std::variant<Error, ExprEvalVT>;
* functionality is mainly intended to be used by the assembler to expand complex pseudoinstructions and as such not
* by the user.
*/
-ExprEvalRes evaluate(const QString&, const SymbolMap* variables = nullptr);
+ExprEvalRes evaluate(const QString&, const AbsoluteSymbolMap* variables = nullptr);
/**
* @brief couldBeExpression

MODIFY
src/assembler/gnudirectives.cpp
src/assembler/gnudirectives.cpp
@@ -46,7 +46,7 @@ DirectiveVec gnuDirectives() {
}
#define getImmediateErroring(token, res, srcline) \
- auto exprRes##res = assembler->evalExpr(token); \
+ auto exprRes##res = assembler->evalExpr(token, srcline); \
if (auto* err = std::get_if<Error>(&exprRes##res)) { \
err->first = srcline; \
return {*err}; \
@@ -195,7 +195,7 @@ Directive equDirective() {
int64_t value;
getImmediateErroring(arg.line.tokens.at(1), value, arg.line.sourceLine);
- auto err = assembler->addSymbol(arg.line, arg.line.tokens.at(0), value);
+ auto err = assembler->m_symbolMap.addSymbol(arg.line, arg.line.tokens.at(0), value);
if (err) {
return err.value();
}

MODIFY
src/assembler/program.h
src/assembler/program.h
@@ -42,6 +42,23 @@ public:
bool is(const Type t) const { return type & t; }
bool is(const unsigned t) const { return type & t; }
+ // A local symbol is a numerical label.
+ bool isLocal() const {
+ bool ok;
+ (void)v.toUInt(&ok);
+ return ok;
+ }
+
+ /// Returns true if this symbol is legal. An illegal symbal is any symbol that starts with a numeric value which is
+ /// not a number.
+ bool isLegal() const {
+ bool startsWithNum;
+ QString(v.front()).toInt(&startsWithNum);
+ if (startsWithNum && !isLocal())
+ return false;
+ return true;
+ }
+
operator const QString&() const { return v; }
QString v;

MODIFY
src/assembler/pseudoinstruction.h
src/assembler/pseudoinstruction.h
#include "assembler_defines.h"
#include "instruction.h"
+#include "symbolmap.h"
namespace Ripes {
namespace Assembler {

MODIFY
src/assembler/rv_i_ext.h
src/assembler/rv_i_ext.h
@@ -178,9 +178,10 @@ struct RV_I {
int64_t immediate = getImmediateSext32(line.tokens.at(2), canConvert);
if (!canConvert) {
+ auto absSymbols = symbols.copyRelativeTo(line.sourceLine);
// Check if the immediate has been made available in the symbol set at this point...
- auto it = symbols.find(line.tokens.at(2));
- if (it != symbols.end()) {
+ auto it = absSymbols.find(line.tokens.at(2));
+ if (it != absSymbols.end()) {
immediate = it->second;
} else {
if (unsignedFitErr) {

ADD
src/assembler/symbolmap.cpp
+#include "symbolmap.h"
+
+namespace Ripes {
+namespace Assembler {
+
+/// Adds a symbol to the current symbol mapping of this assembler.
+std::optional<Error> SymbolMap::addAbsSymbol(const unsigned& line, const Symbol& s, VInt v) {
+ if (abs.count(s)) {
+ return {Error(line, "Multiple definitions of symbol '" + s.v + "'")};
+ }
+ abs[s] = v;
+ return {};
+}
+
+std::optional<Error> SymbolMap::addRelSymbol(const unsigned& line, const Symbol& s, VInt v) {
+ assert(s.isLocal());
+ auto& it = rel[s.v.toInt()];
+ if (it.count(line))
+ return {Error(line, QString::fromStdString("Multiple definitions of relative symbol '" + std::to_string(v) +
+ "' on line '" + std::to_string(line)))};
+ it[line] = v;
+ return {};
+}
+
+AbsoluteSymbolMap SymbolMap::copyRelativeTo(unsigned line, const QString& beforeSuffix,
+ const QString& afterSuffix) const {
+ AbsoluteSymbolMap res = abs;
+
+ for (auto& relSymbols : rel) {
+ auto ub = relSymbols.second.upper_bound(line);
+ if (ub != relSymbols.second.end())
+ res[QString::number(relSymbols.first) + afterSuffix] = ub->second;
+
+ if (ub != relSymbols.second.begin()) {
+ auto lb = ub;
+ std::advance(lb, -1);
+ res[QString::number(relSymbols.first) + beforeSuffix] = lb->second;
+ }
+ }
+
+ return res;
+}
+
+} // namespace Assembler
+} // namespace Ripes

ADD
src/assembler/symbolmap.h
+#pragma once
+
+#include <optional>
+#include "assembler_defines.h"
+
+namespace Ripes {
+namespace Assembler {
+
+using AbsoluteSymbolMap = std::map<Symbol, VIntS>;
+struct SymbolMap {
+ AbsoluteSymbolMap abs;
+ using RelativeSymbol = int;
+ using SourceLine = unsigned;
+ std::map<RelativeSymbol, std::map<SourceLine, VIntS>> rel;
+
+ void clear() {
+ abs.clear();
+ rel.clear();
+ }
+
+ std::optional<Error> addSymbol(const TokenizedSrcLine& line, const Symbol& s, VInt v) {
+ return s.isLocal() ? addRelSymbol(line.sourceLine, s, v) : addAbsSymbol(line.sourceLine, s, v);
+ }
+
+ /// Adds a symbol to the current symbol mapping of this assembler defined at the 'line' in the input program.
+ std::optional<Error> addAbsSymbol(const TokenizedSrcLine& line, const Symbol& s, VInt v) {
+ return addAbsSymbol(line.sourceLine, s, v);
+ }
+
+ /// Adds a symbol to the current symbol mapping of this assembler.
+ std::optional<Error> addAbsSymbol(const unsigned& line, const Symbol& s, VInt v);
+
+ /// Adds a relative symbol to this symbol map. A relative symbol is unqiued based on the tuple <symbol ID, source
+ /// line>.
+ std::optional<Error> addRelSymbol(const TokenizedSrcLine& line, const Symbol& s, VInt v) {
+ return addRelSymbol(line.sourceLine, s, v);
+ }
+
+ std::optional<Error> addRelSymbol(const unsigned& line, const Symbol& s, VInt v);
+
+ /// Returns a copy of this symbol map with relative symbols copied relative to 'line'.
+ /// Relative symbols may be suffixed with additional strings in cases of being before/after the provided source
+ /// line.
+ AbsoluteSymbolMap copyRelativeTo(unsigned line, const QString& beforeSuffix = "b",
+ const QString& afterSuffix = "f") const;
+};
+
+} // namespace Assembler
+} // namespace Ripes

MODIFY
src/io/iomanager.cpp
src/io/iomanager.cpp
@@ -181,7 +181,7 @@ void IOManager::updateSymbols() {
headerfile << "// *****************************************************************************";
auto symbols = assemblerSymbolsForPeriph(p.first);
- m_assemblerSymbols.insert(symbols.begin(), symbols.end());
+ m_assemblerSymbols.abs.insert(symbols.begin(), symbols.end());
for (const auto& symbol : assemblerSymbolsForPeriph(p.first)) {
headerfile << "#define " + symbol.first.v + "\t" + "(0x" + QString::number(symbol.second, 16) + ")";

MODIFY
src/io/iomanager.h
src/io/iomanager.h
#pragma once
#include "assembler/assembler_defines.h"
+#include "assembler/symbolmap.h"
#include "iobase.h"
#include "ioregistry.h"

MODIFY
test/tst_assembler.cpp
test/tst_assembler.cpp
@@ -45,6 +45,7 @@ private slots:
void tst_directives();
void tst_stringDirectives();
void tst_riscv();
+ void tst_relativeLabels();
private:
QString createProgram(int entries) {
@@ -348,6 +349,23 @@ void tst_Assembler::tst_stringDirectives() {
testAssemble(QStringList() << R"(A: .string "")", Expect::Success);
}
+void tst_Assembler::tst_relativeLabels() {
+ testAssemble(QStringList() << "1f: bne x0 a0 1f", Expect::Fail);
+ testAssemble(QStringList() << "1: bne x0 a0 1f"
+ << "1: bne x0 a0 1b",
+ Expect::Success);
+
+ testAssemble(QStringList() << "bne x0 a0 2f"
+ << "2:"
+ << "1: bne x0 a0 1f"
+ << "1: bne x0 a0 1b"
+ << "bne x0 a0 2b",
+ Expect::Success);
+ testAssemble(QStringList() << "bne x0 a0 2f"
+ << "2:",
+ Expect::Fail);
+}
+
void tst_Assembler::tst_matcher() {
auto isa = std::make_unique<ISAInfo<ISA::RV32I>>(QStringList());
auto assembler = RV32I_Assembler(isa.get());

MODIFY
test/tst_expreval.cpp
test/tst_expreval.cpp
@@ -23,8 +23,8 @@ void tst_ExprEval::tst_binops() {
expect(evaluate("(0x2*(3+4))+4"), 18);
expect(evaluate("2+3*7*5"), 107);
SymbolMap symbols;
- symbols["B"] = 2;
- expect(evaluate("(B *(3+ 4))+4", &symbols), 18);
+ symbols.abs["B"] = 2;
+ expect(evaluate("(B *(3+ 4))+4", &symbols.abs), 18);
}
QTEST_APPLESS_MAIN(tst_ExprEval)

Add support for local labels  #199 (#201)