diff --git a/include/asm/rpn.hpp b/include/asm/rpn.hpp index 2955c2bc..7d641dd1 100644 --- a/include/asm/rpn.hpp +++ b/include/asm/rpn.hpp @@ -45,11 +45,7 @@ struct Expression { void makeStartOfSection(std::string const §Name); void makeSizeOfSectionType(SectionType type); void makeStartOfSectionType(SectionType type); - void makeHigh(); - void makeLow(); - void makeNeg(); - void makeNot(); - void makeLogicNot(); + void makeUnaryOp(RPNCommand op, Expression &&src); void makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2); void makeCheckHRAM(); diff --git a/include/linkdefs.hpp b/include/linkdefs.hpp index 443f8bb0..9216a222 100644 --- a/include/linkdefs.hpp +++ b/include/linkdefs.hpp @@ -9,7 +9,7 @@ #include "helpers.hpp" // assume #define RGBDS_OBJECT_VERSION_STRING "RGB9" -#define RGBDS_OBJECT_REV 10U +#define RGBDS_OBJECT_REV 11U enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL }; @@ -53,6 +53,9 @@ enum RPNCommand { RPN_HRAM = 0x60, RPN_RST = 0x61, + RPN_HIGH = 0x70, + RPN_LOW = 0x71, + RPN_CONST = 0x80, RPN_SYM = 0x81 }; diff --git a/man/rgbasm.5 b/man/rgbasm.5 index 754ffa8c..a5715937 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -2,7 +2,7 @@ .\" .\" SPDX-License-Identifier: MIT .\" -.Dd December 22, 2023 +.Dd August 6, 2024 .Dt RGBASM 5 .Os .Sh NAME @@ -326,6 +326,7 @@ with a non-zero constant as either operand will be constant 1, even if the other .Pp .Ic \&! returns 1 if the operand was 0, and 0 otherwise. +Even a non-constant operand with any non-zero bits will return 0. .Ss Fixed-point expressions Fixed-point numbers are basically normal (32-bit) integers, which count fractions instead of whole numbers. They offer better precision than integers but limit the range of values. @@ -557,6 +558,9 @@ has been defined, FALSE (0) otherwise. String constants are not expanded within the parentheses. .It Fn HIGH arg Ta Returns the top 8 bits of the operand if Ar arg No is a label or constant, or the top 8-bit register if it is a 16-bit register . .It Fn LOW arg Ta Returns the bottom 8 bits of the operand if Ar arg No is a label or constant, or the bottom 8-bit register if it is a 16-bit register Pq Cm AF No isn't a valid register for this function . +The result may be constant if +.Nm +is able to compute it. .It Fn ISCONST arg Ta Returns 1 if Ar arg Ap s value is known by RGBASM (e.g. if it can be an argument to .Ic IF ) , or 0 if only RGBLINK can compute its value. diff --git a/man/rgbds.5 b/man/rgbds.5 index ed3858cb..f76456ee 100644 --- a/man/rgbds.5 +++ b/man/rgbds.5 @@ -1,6 +1,6 @@ .\" SPDX-License-Identifier: MIT .\" -.Dd December 22, 2023 +.Dd August 6, 2024 .Dt RGBDS 5 .Os .Sh NAME @@ -391,6 +391,10 @@ The value is then ORed with $C7 .It Li $80 Ta Integer literal; followed by the .Cm LONG integer. +.It Li $70 Ta Ql HIGH +byte. +.It Li $71 Ta Ql LOW +byte. .It Li $81 Ta A symbol's value; followed by the symbol's .Cm LONG ID. diff --git a/src/asm/parser.y b/src/asm/parser.y index 09ad943a..0dfc0727 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -1235,8 +1235,7 @@ reloc_8bit_offset: $$.checkNBit(8); } | OP_SUB relocexpr { - $$ = std::move($2); - $$.makeNeg(); + $$.makeUnaryOp(RPN_NEG, std::move($2)); $$.checkNBit(8); } ; @@ -1273,8 +1272,7 @@ relocexpr_no_str: $$.makeNumber($1); } | OP_LOGICNOT relocexpr %prec NEG { - $$ = std::move($2); - $$.makeLogicNot(); + $$.makeUnaryOp(RPN_LOGNOT, std::move($2)); } | relocexpr OP_LOGICOR relocexpr { $$.makeBinaryOp(RPN_LOGOR, std::move($1), $3); @@ -1340,20 +1338,16 @@ relocexpr_no_str: $$ = std::move($2); } | OP_SUB relocexpr %prec NEG { - $$ = std::move($2); - $$.makeNeg(); + $$.makeUnaryOp(RPN_NEG, std::move($2)); } | OP_NOT relocexpr %prec NEG { - $$ = std::move($2); - $$.makeNot(); + $$.makeUnaryOp(RPN_NOT, std::move($2)); } | OP_HIGH LPAREN relocexpr RPAREN { - $$ = std::move($3); - $$.makeHigh(); + $$.makeUnaryOp(RPN_HIGH, std::move($3)); } | OP_LOW LPAREN relocexpr RPAREN { - $$ = std::move($3); - $$.makeLow(); + $$.makeUnaryOp(RPN_LOW, std::move($3)); } | OP_ISCONST LPAREN relocexpr RPAREN { $$.makeNumber($3.isKnown()); diff --git a/src/asm/rpn.cpp b/src/asm/rpn.cpp index 1242498e..4fbae875 100644 --- a/src/asm/rpn.cpp +++ b/src/asm/rpn.cpp @@ -207,12 +207,57 @@ static bool tryConstNonzero(Expression const &lhs, Expression const &rhs) { return expr.isKnown() && expr.value() != 0; } +static bool tryConstLogNot(Expression const &expr) { + Symbol const *sym = expr.symbolOf(); + if (!sym || !sym->getSection()) + return -1; + + assume(sym->isNumeric()); + + Section const § = *sym->getSection(); + int32_t unknownBits = (1 << 16) - (1 << sect.align); + + // `sym->getValue()` attempts to add the section's address, but that's "-1" + // because the section is floating (otherwise we wouldn't be here) + assume(sect.org == (uint32_t)-1); + int32_t symbolOfs = sym->getValue() + 1; + + int32_t knownBits = (symbolOfs + sect.alignOfs) & ~unknownBits; + return knownBits != 0; +} + /* - * Attempts to compute a constant binary AND from non-constant operands + * Attempts to compute a constant LOW() from non-constant argument + * This is possible if the argument is a symbol belonging to an `ALIGN[8]` section. + * + * @return The constant `LOW(expr)` result if it can be computed, or -1 otherwise. + */ +static int32_t tryConstLow(Expression const &expr) { + Symbol const *sym = expr.symbolOf(); + if (!sym || !sym->getSection()) + return -1; + + assume(sym->isNumeric()); + + // The low byte must not cover any unknown bits + Section const § = *sym->getSection(); + if (sect.align < 8) + return -1; + + // `sym->getValue()` attempts to add the section's address, but that's "-1" + // because the section is floating (otherwise we wouldn't be here) + assume(sect.org == (uint32_t)-1); + int32_t symbolOfs = sym->getValue() + 1; + + return (symbolOfs + sect.alignOfs) & 0xFF; +} + +/* + * Attempts to compute a constant binary AND with one non-constant operands * This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is * a constant that only keeps (some of) the lower N bits. * - * @return The constant result if it can be computed, or -1 otherwise. + * @return The constant `lhs & rhs` result if it can be computed, or -1 otherwise. */ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) { Symbol const *lhsSymbol = lhs.symbolOf(); @@ -232,11 +277,11 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) { if (!expr.isKnown()) return -1; // We can now safely use `expr.value()` - Section const § = *sym.getSection(); - int32_t unknownBits = (1 << 16) - (1 << sect.align); // The max alignment is 16 + int32_t mask = expr.value(); - // The mask must ignore all unknown bits - if ((expr.value() & unknownBits) != 0) + // The mask must not cover any unknown bits + Section const § = *sym.getSection(); + if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0) return -1; // `sym.getValue()` attempts to add the section's address, but that's "-1" @@ -244,61 +289,82 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) { assume(sect.org == (uint32_t)-1); int32_t symbolOfs = sym.getValue() + 1; - return (symbolOfs + sect.alignOfs) & ~unknownBits; + return (symbolOfs + sect.alignOfs) & mask; } -void Expression::makeHigh() { - isSymbol = false; - if (isKnown()) { - data = (int32_t)((uint32_t)value() >> 8 & 0xFF); +void Expression::makeUnaryOp(RPNCommand op, Expression &&src) { + clear(); + // First, check if the expression is known + if (src.isKnown()) { + // If the expressions is known, just compute the value + int32_t val = src.value(); + + switch (op) { + case RPN_NEG: + data = (int32_t) - (uint32_t)val; + break; + case RPN_NOT: + data = ~val; + break; + case RPN_LOGNOT: + data = !val; + break; + case RPN_HIGH: + data = (int32_t)((uint32_t)val >> 8 & 0xFF); + break; + case RPN_LOW: + data = val & 0xFF; + break; + + case RPN_LOGOR: + case RPN_LOGAND: + case RPN_LOGEQ: + case RPN_LOGGT: + case RPN_LOGLT: + case RPN_LOGGE: + case RPN_LOGLE: + case RPN_LOGNE: + case RPN_ADD: + case RPN_SUB: + case RPN_XOR: + case RPN_OR: + case RPN_AND: + case RPN_SHL: + case RPN_SHR: + case RPN_USHR: + case RPN_MUL: + case RPN_DIV: + case RPN_MOD: + case RPN_EXP: + case RPN_BANK_SYM: + case RPN_BANK_SECT: + case RPN_BANK_SELF: + case RPN_SIZEOF_SECT: + case RPN_STARTOF_SECT: + case RPN_SIZEOF_SECTTYPE: + case RPN_STARTOF_SECTTYPE: + case RPN_HRAM: + case RPN_RST: + case RPN_CONST: + case RPN_SYM: + fatalerror("%d is not an unary operator\n", op); + } + } else if (op == RPN_LOGNOT && tryConstLogNot(src)) { + data = 0; + } else if (int32_t constVal; op == RPN_LOW && (constVal = tryConstLow(src)) != -1) { + data = constVal; } else { - uint8_t bytes[] = {RPN_CONST, 8, 0, 0, 0, RPN_SHR, RPN_CONST, 0xFF, 0, 0, 0, RPN_AND}; - - memcpy(reserveSpace(sizeof(bytes)), bytes, sizeof(bytes)); - } -} - -void Expression::makeLow() { - isSymbol = false; - if (isKnown()) { - data = value() & 0xFF; - } else { - uint8_t bytes[] = {RPN_CONST, 0xFF, 0, 0, 0, RPN_AND}; - - memcpy(reserveSpace(sizeof(bytes)), bytes, sizeof(bytes)); - } -} - -void Expression::makeNeg() { - isSymbol = false; - if (isKnown()) { - data = (int32_t) - (uint32_t)value(); - } else { - *reserveSpace(1) = RPN_NEG; - } -} - -void Expression::makeNot() { - isSymbol = false; - if (isKnown()) { - data = ~value(); - } else { - *reserveSpace(1) = RPN_NOT; - } -} - -void Expression::makeLogicNot() { - isSymbol = false; - if (isKnown()) { - data = !value(); - } else { - *reserveSpace(1) = RPN_LOGNOT; + // If it's not known, just reuse its RPN buffer and append the operator + rpnPatchSize = src.rpnPatchSize; + std::swap(rpn, src.rpn); + data = std::move(src.data); + *reserveSpace(1) = op; } } void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2) { clear(); - // First, check if the expression is known + // First, check if the expressions are known if (src1.isKnown() && src2.isKnown()) { // If both expressions are known, just compute the value int32_t lval = src1.value(), rval = src2.value(); @@ -426,6 +492,8 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const case RPN_STARTOF_SECTTYPE: case RPN_HRAM: case RPN_RST: + case RPN_HIGH: + case RPN_LOW: case RPN_CONST: case RPN_SYM: fatalerror("%d is not a binary operator\n", op); diff --git a/src/link/patch.cpp b/src/link/patch.cpp index 2558cac0..68a5d512 100644 --- a/src/link/patch.cpp +++ b/src/link/patch.cpp @@ -139,6 +139,13 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector const &fil } break; + case RPN_HIGH: + value = (popRPN(patch) >> 8) & 0xFF; + break; + case RPN_LOW: + value = popRPN(patch) & 0xFF; + break; + case RPN_OR: value = popRPN(patch) | popRPN(patch); break; diff --git a/test/asm/const-and.out b/test/asm/const-and.out index 8359c714..ab62ab83 100644 --- a/test/asm/const-and.out +++ b/test/asm/const-and.out @@ -1,6 +1,6 @@ $0 $A -$A +$2 $0 $A $0