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:
Sylvie
2024-08-06 07:54:55 -04:00
committed by GitHub
parent 2f8f99bd94
commit 2706f94788
8 changed files with 150 additions and 74 deletions

View File

@@ -45,11 +45,7 @@ struct Expression {
void makeStartOfSection(std::string const &sectName);
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();

View File

@@ -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
};

View File

@@ -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.

View File

@@ -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.

View File

@@ -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());

View File

@@ -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 &sect = *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 &sect = *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 &sect = *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 &sect = *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);

View File

@@ -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;

View File

@@ -1,6 +1,6 @@
$0
$A
$A
$2
$0
$A
$0