mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Multiple fixes and enhancements to RPN behavior: (#1448)
- FIX: `Label & const` was not actually doing the `& const` masking (fixes #1446) - ADD: `LOW(Label)` can be constant if `Label` is aligned to 8 or more bits (resolves #1444) - ADD: `!expr` can be constant 0 if `expr` has any non-zero bits (resolves #1447) - `LOW()` and `HIGH()` have their own RPN operator values (resolves #1445) The change to RPN values means that the object file version was incremented. This also refactors unary operators and functions, combining their evaluation similarly to binary ones.
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
|
||||
170
src/asm/rpn.cpp
170
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);
|
||||
} else {
|
||||
uint8_t bytes[] = {RPN_CONST, 8, 0, 0, 0, RPN_SHR, RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
|
||||
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();
|
||||
|
||||
memcpy(reserveSpace(sizeof(bytes)), bytes, sizeof(bytes));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeLow() {
|
||||
isSymbol = false;
|
||||
if (isKnown()) {
|
||||
data = value() & 0xFF;
|
||||
} 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, 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);
|
||||
|
||||
@@ -139,6 +139,13 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> 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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
$0
|
||||
$A
|
||||
$A
|
||||
$2
|
||||
$0
|
||||
$A
|
||||
$0
|
||||
|
||||
Reference in New Issue
Block a user