mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
We were not initializing some expressions, and they were using the values of the previous expressions instead. This just so happened to not crash the tests, and to sometimes even give valid results (although `BANK()` of a non-label symbol being $4B4E4142, the ASCII balue of "BANK", was something we missed).
630 lines
16 KiB
C++
630 lines
16 KiB
C++
/* SPDX-License-Identifier: MIT */
|
|
|
|
// Controls RPN expressions for objectfiles
|
|
|
|
#include "asm/rpn.hpp"
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <vector>
|
|
|
|
#include "opmath.hpp"
|
|
|
|
#include "asm/main.hpp"
|
|
#include "asm/output.hpp"
|
|
#include "asm/section.hpp"
|
|
#include "asm/symbol.hpp"
|
|
#include "asm/warning.hpp"
|
|
|
|
// Init a RPN expression
|
|
static void initExpression(Expression &expr) {
|
|
expr.val = 0;
|
|
expr.reason = nullptr;
|
|
expr.isKnown = true;
|
|
expr.isSymbol = false;
|
|
expr.rpn = nullptr;
|
|
expr.rpnPatchSize = 0;
|
|
}
|
|
|
|
// Makes an expression "not known", also setting its error message
|
|
template<typename... Ts>
|
|
static void makeUnknown(Expression &expr, Ts... parts) {
|
|
expr.isKnown = false;
|
|
expr.reason = new std::string();
|
|
if (!expr.reason)
|
|
fatalerror("Failed to allocate RPN error string: %s\n", strerror(errno));
|
|
(expr.reason->append(parts), ...);
|
|
}
|
|
|
|
static uint8_t *reserveSpace(Expression &expr, uint32_t size) {
|
|
if (!expr.rpn) {
|
|
expr.rpn = new (std::nothrow) std::vector<uint8_t>();
|
|
if (!expr.rpn)
|
|
fatalerror("Failed to allocate RPN expression: %s\n", strerror(errno));
|
|
}
|
|
|
|
size_t curSize = expr.rpn->size();
|
|
|
|
expr.rpn->resize(curSize + size);
|
|
return &(*expr.rpn)[curSize];
|
|
}
|
|
|
|
// Free the RPN expression
|
|
void rpn_Free(Expression &expr) {
|
|
delete expr.rpn;
|
|
delete expr.reason;
|
|
initExpression(expr);
|
|
}
|
|
|
|
// Add symbols, constants and operators to expression
|
|
void rpn_Number(Expression &expr, uint32_t val) {
|
|
initExpression(expr);
|
|
expr.val = val;
|
|
}
|
|
|
|
void rpn_Symbol(Expression &expr, char const *symName) {
|
|
initExpression(expr);
|
|
|
|
Symbol *sym = sym_FindScopedSymbol(symName);
|
|
|
|
if (sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
|
error("PC has no value outside a section\n");
|
|
expr.val = 0;
|
|
} else if (!sym || !sym->isConstant()) {
|
|
expr.isSymbol = true;
|
|
|
|
if (sym_IsPC(sym))
|
|
makeUnknown(expr, "PC is not constant at assembly time");
|
|
else
|
|
makeUnknown(expr, "'", symName, "' is not constant at assembly time");
|
|
sym = sym_Ref(symName);
|
|
expr.rpnPatchSize += 5; // 1-byte opcode + 4-byte symbol ID
|
|
|
|
size_t nameLen = strlen(sym->name) + 1; // Don't forget NUL!
|
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
|
*ptr++ = RPN_SYM;
|
|
memcpy(ptr, sym->name, nameLen);
|
|
} else {
|
|
expr.val = sym_GetConstantValue(symName);
|
|
}
|
|
}
|
|
|
|
static void bankSelf(Expression &expr) {
|
|
initExpression(expr);
|
|
|
|
if (!currentSection) {
|
|
error("PC has no bank outside a section\n");
|
|
expr.val = 1;
|
|
} else if (currentSection->bank == (uint32_t)-1) {
|
|
makeUnknown(expr, "Current section's bank is not known");
|
|
expr.rpnPatchSize++;
|
|
*reserveSpace(expr, 1) = RPN_BANK_SELF;
|
|
} else {
|
|
expr.val = currentSection->bank;
|
|
}
|
|
}
|
|
|
|
void rpn_BankSymbol(Expression &expr, char const *symName) {
|
|
Symbol const *sym = sym_FindScopedSymbol(symName);
|
|
|
|
// The @ symbol is treated differently.
|
|
if (sym_IsPC(sym)) {
|
|
bankSelf(expr);
|
|
return;
|
|
}
|
|
|
|
initExpression(expr);
|
|
if (sym && !sym->isLabel()) {
|
|
error("BANK argument must be a label\n");
|
|
expr.val = 1;
|
|
} else {
|
|
sym = sym_Ref(symName);
|
|
assert(sym); // If the symbol didn't exist, it should have been created
|
|
|
|
if (sym->getSection() && sym->getSection()->bank != (uint32_t)-1) {
|
|
// Symbol's section is known and bank is fixed
|
|
expr.val = sym->getSection()->bank;
|
|
} else {
|
|
makeUnknown(expr, "\"", symName, "\"'s bank is not known");
|
|
expr.rpnPatchSize += 5; // opcode + 4-byte sect ID
|
|
|
|
size_t nameLen = strlen(sym->name) + 1; // Room for NUL!
|
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
|
|
|
*ptr++ = RPN_BANK_SYM;
|
|
memcpy(ptr, sym->name, nameLen);
|
|
}
|
|
}
|
|
}
|
|
|
|
void rpn_BankSection(Expression &expr, char const *sectionName) {
|
|
initExpression(expr);
|
|
|
|
Section *section = sect_FindSectionByName(sectionName);
|
|
|
|
if (section && section->bank != (uint32_t)-1) {
|
|
expr.val = section->bank;
|
|
} else {
|
|
makeUnknown(expr, "Section \"", sectionName, "\"'s bank is not known");
|
|
|
|
size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
|
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
|
|
|
expr.rpnPatchSize += nameLen + 1;
|
|
*ptr++ = RPN_BANK_SECT;
|
|
memcpy(ptr, sectionName, nameLen);
|
|
}
|
|
}
|
|
|
|
void rpn_SizeOfSection(Expression &expr, char const *sectionName) {
|
|
initExpression(expr);
|
|
|
|
Section *section = sect_FindSectionByName(sectionName);
|
|
|
|
if (section && section->isSizeKnown()) {
|
|
expr.val = section->size;
|
|
} else {
|
|
makeUnknown(expr, "Section \"", sectionName, "\"'s size is not known");
|
|
|
|
size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
|
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
|
|
|
expr.rpnPatchSize += nameLen + 1;
|
|
*ptr++ = RPN_SIZEOF_SECT;
|
|
memcpy(ptr, sectionName, nameLen);
|
|
}
|
|
}
|
|
|
|
void rpn_StartOfSection(Expression &expr, char const *sectionName) {
|
|
initExpression(expr);
|
|
|
|
Section *section = sect_FindSectionByName(sectionName);
|
|
|
|
if (section && section->org != (uint32_t)-1) {
|
|
expr.val = section->org;
|
|
} else {
|
|
makeUnknown(expr, "Section \"", sectionName, "\"'s start is not known");
|
|
|
|
size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
|
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
|
|
|
expr.rpnPatchSize += nameLen + 1;
|
|
*ptr++ = RPN_STARTOF_SECT;
|
|
memcpy(ptr, sectionName, nameLen);
|
|
}
|
|
}
|
|
|
|
void rpn_SizeOfSectionType(Expression &expr, enum SectionType type) {
|
|
initExpression(expr);
|
|
makeUnknown(expr, "Section type's size is not known");
|
|
|
|
uint8_t *ptr = reserveSpace(expr, 2);
|
|
|
|
expr.rpnPatchSize += 2;
|
|
*ptr++ = RPN_SIZEOF_SECTTYPE;
|
|
*ptr++ = type;
|
|
}
|
|
|
|
void rpn_StartOfSectionType(Expression &expr, enum SectionType type) {
|
|
initExpression(expr);
|
|
makeUnknown(expr, "Section type's start is not known");
|
|
|
|
uint8_t *ptr = reserveSpace(expr, 2);
|
|
|
|
expr.rpnPatchSize += 2;
|
|
*ptr++ = RPN_STARTOF_SECTTYPE;
|
|
*ptr++ = type;
|
|
}
|
|
|
|
void rpn_CheckHRAM(Expression &expr, const Expression &src) {
|
|
expr = src;
|
|
expr.isSymbol = false;
|
|
|
|
if (!expr.isKnown) {
|
|
expr.rpnPatchSize++;
|
|
*reserveSpace(expr, 1) = RPN_HRAM;
|
|
} else if (expr.val >= 0xFF00 && expr.val <= 0xFFFF) {
|
|
// That range is valid, but only keep the lower byte
|
|
expr.val &= 0xFF;
|
|
} else if (expr.val < 0 || expr.val > 0xFF) {
|
|
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", expr.val);
|
|
}
|
|
}
|
|
|
|
void rpn_CheckRST(Expression &expr, const Expression &src) {
|
|
expr = src;
|
|
|
|
if (expr.isKnown) {
|
|
// A valid RST address must be masked with 0x38
|
|
if (expr.val & ~0x38)
|
|
error("Invalid address $%" PRIx32 " for RST\n", expr.val);
|
|
// The target is in the "0x38" bits, all other bits are set
|
|
expr.val |= 0xC7;
|
|
} else {
|
|
expr.rpnPatchSize++;
|
|
*reserveSpace(expr, 1) = RPN_RST;
|
|
}
|
|
}
|
|
|
|
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
|
void rpn_CheckNBit(Expression const &expr, uint8_t n) {
|
|
assert(n != 0); // That doesn't make sense
|
|
assert(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
|
|
|
|
if (expr.isKnown) {
|
|
int32_t val = expr.val;
|
|
|
|
if (val < -(1 << n) || val >= 1 << n)
|
|
warning(WARNING_TRUNCATION_1, "Expression must be %u-bit\n", n);
|
|
else if (val < -(1 << (n - 1)))
|
|
warning(WARNING_TRUNCATION_2, "Expression must be %u-bit\n", n);
|
|
}
|
|
}
|
|
|
|
int32_t Expression::getConstVal() const {
|
|
if (!isKnown) {
|
|
error("Expected constant expression: %s\n", reason->c_str());
|
|
return 0;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
void rpn_LOGNOT(Expression &expr, const Expression &src) {
|
|
expr = src;
|
|
expr.isSymbol = false;
|
|
|
|
if (expr.isKnown) {
|
|
expr.val = !expr.val;
|
|
} else {
|
|
expr.rpnPatchSize++;
|
|
*reserveSpace(expr, 1) = RPN_LOGNOT;
|
|
}
|
|
}
|
|
|
|
Symbol const *Expression::symbolOf() const {
|
|
if (!isSymbol)
|
|
return nullptr;
|
|
return sym_FindScopedSymbol((char const *)&(*rpn)[1]);
|
|
}
|
|
|
|
bool Expression::isDiffConstant(Symbol const *sym) const {
|
|
// Check if both expressions only refer to a single symbol
|
|
Symbol const *sym1 = symbolOf();
|
|
|
|
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL)
|
|
return false;
|
|
|
|
Section const *section1 = sym1->getSection();
|
|
Section const *section2 = sym->getSection();
|
|
return section1 && (section1 == section2);
|
|
}
|
|
|
|
/*
|
|
* Attempts to compute a constant binary AND from 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.
|
|
*/
|
|
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
|
Symbol const *lhsSymbol = lhs.symbolOf();
|
|
Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf();
|
|
bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection();
|
|
bool rhsIsSymbol = rhsSymbol && rhsSymbol->getSection();
|
|
|
|
if (!lhsIsSymbol && !rhsIsSymbol)
|
|
return -1;
|
|
|
|
// If the lhs isn't a symbol, try again the other way around
|
|
Symbol const &sym = lhsIsSymbol ? *lhsSymbol : *rhsSymbol;
|
|
Expression const &expr = lhsIsSymbol ? rhs : lhs; // Opposite side of `sym`
|
|
|
|
assert(sym.isNumeric());
|
|
|
|
if (!expr.isKnown)
|
|
return -1;
|
|
// We can now safely use `expr.val`
|
|
Section const § = *sym.getSection();
|
|
int32_t unknownBits = (1 << 16) - (1 << sect.align); // The max alignment is 16
|
|
|
|
// The mask must ignore all unknown bits
|
|
if ((expr.val & unknownBits) != 0)
|
|
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)
|
|
assert(sect.org == (uint32_t)-1);
|
|
int32_t symbolOfs = sym.getValue() + 1;
|
|
|
|
return (symbolOfs + sect.alignOfs) & ~unknownBits;
|
|
}
|
|
|
|
void rpn_BinaryOp(
|
|
enum RPNCommand op, Expression &expr, const Expression &src1, const Expression &src2
|
|
) {
|
|
initExpression(expr);
|
|
expr.isSymbol = false;
|
|
int32_t constMaskVal;
|
|
|
|
// First, check if the expression is known
|
|
expr.isKnown = src1.isKnown && src2.isKnown;
|
|
if (expr.isKnown) {
|
|
|
|
// If both expressions are known, just compute the value
|
|
uint32_t uleft = src1.val, uright = src2.val;
|
|
|
|
switch (op) {
|
|
case RPN_LOGOR:
|
|
expr.val = src1.val || src2.val;
|
|
break;
|
|
case RPN_LOGAND:
|
|
expr.val = src1.val && src2.val;
|
|
break;
|
|
case RPN_LOGEQ:
|
|
expr.val = src1.val == src2.val;
|
|
break;
|
|
case RPN_LOGGT:
|
|
expr.val = src1.val > src2.val;
|
|
break;
|
|
case RPN_LOGLT:
|
|
expr.val = src1.val < src2.val;
|
|
break;
|
|
case RPN_LOGGE:
|
|
expr.val = src1.val >= src2.val;
|
|
break;
|
|
case RPN_LOGLE:
|
|
expr.val = src1.val <= src2.val;
|
|
break;
|
|
case RPN_LOGNE:
|
|
expr.val = src1.val != src2.val;
|
|
break;
|
|
case RPN_ADD:
|
|
expr.val = uleft + uright;
|
|
break;
|
|
case RPN_SUB:
|
|
expr.val = uleft - uright;
|
|
break;
|
|
case RPN_XOR:
|
|
expr.val = src1.val ^ src2.val;
|
|
break;
|
|
case RPN_OR:
|
|
expr.val = src1.val | src2.val;
|
|
break;
|
|
case RPN_AND:
|
|
expr.val = src1.val & src2.val;
|
|
break;
|
|
case RPN_SHL:
|
|
if (src2.val < 0)
|
|
warning(
|
|
WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", src2.val
|
|
);
|
|
|
|
if (src2.val >= 32)
|
|
warning(
|
|
WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", src2.val
|
|
);
|
|
|
|
expr.val = op_shift_left(src1.val, src2.val);
|
|
break;
|
|
case RPN_SHR:
|
|
if (src1.val < 0)
|
|
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", src1.val);
|
|
|
|
if (src2.val < 0)
|
|
warning(
|
|
WARNING_SHIFT_AMOUNT,
|
|
"Shifting right by negative amount %" PRId32 "\n",
|
|
src2.val
|
|
);
|
|
|
|
if (src2.val >= 32)
|
|
warning(
|
|
WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", src2.val
|
|
);
|
|
|
|
expr.val = op_shift_right(src1.val, src2.val);
|
|
break;
|
|
case RPN_USHR:
|
|
if (src2.val < 0)
|
|
warning(
|
|
WARNING_SHIFT_AMOUNT,
|
|
"Shifting right by negative amount %" PRId32 "\n",
|
|
src2.val
|
|
);
|
|
|
|
if (src2.val >= 32)
|
|
warning(
|
|
WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", src2.val
|
|
);
|
|
|
|
expr.val = op_shift_right_unsigned(src1.val, src2.val);
|
|
break;
|
|
case RPN_MUL:
|
|
expr.val = uleft * uright;
|
|
break;
|
|
case RPN_DIV:
|
|
if (src2.val == 0)
|
|
fatalerror("Division by zero\n");
|
|
|
|
if (src1.val == INT32_MIN && src2.val == -1) {
|
|
warning(
|
|
WARNING_DIV,
|
|
"Division of %" PRId32 " by -1 yields %" PRId32 "\n",
|
|
INT32_MIN,
|
|
INT32_MIN
|
|
);
|
|
expr.val = INT32_MIN;
|
|
} else {
|
|
expr.val = op_divide(src1.val, src2.val);
|
|
}
|
|
break;
|
|
case RPN_MOD:
|
|
if (src2.val == 0)
|
|
fatalerror("Modulo by zero\n");
|
|
|
|
if (src1.val == INT32_MIN && src2.val == -1)
|
|
expr.val = 0;
|
|
else
|
|
expr.val = op_modulo(src1.val, src2.val);
|
|
break;
|
|
case RPN_EXP:
|
|
if (src2.val < 0)
|
|
fatalerror("Exponentiation by negative power\n");
|
|
|
|
expr.val = op_exponent(src1.val, src2.val);
|
|
break;
|
|
|
|
case RPN_NEG:
|
|
case RPN_NOT:
|
|
case RPN_LOGNOT:
|
|
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 a binary operator\n", op);
|
|
}
|
|
|
|
} else if (op == RPN_SUB && src1.isDiffConstant(src2.symbolOf())) {
|
|
Symbol const &symbol1 = *src1.symbolOf();
|
|
Symbol const &symbol2 = *src2.symbolOf();
|
|
|
|
expr.val = symbol1.getValue() - symbol2.getValue();
|
|
expr.isKnown = true;
|
|
} else if (op == RPN_AND && (constMaskVal = tryConstMask(src1, src2)) != -1) {
|
|
expr.val = constMaskVal;
|
|
expr.isKnown = true;
|
|
} else {
|
|
// If it's not known, start computing the RPN expression
|
|
|
|
// Convert the left-hand expression if it's constant
|
|
if (src1.isKnown) {
|
|
uint32_t lval = src1.val;
|
|
uint8_t bytes[] = {
|
|
RPN_CONST,
|
|
(uint8_t)lval,
|
|
(uint8_t)(lval >> 8),
|
|
(uint8_t)(lval >> 16),
|
|
(uint8_t)(lval >> 24),
|
|
};
|
|
expr.rpnPatchSize = sizeof(bytes);
|
|
expr.rpn = nullptr;
|
|
memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes));
|
|
|
|
// Use the other expression's un-const reason
|
|
expr.reason = src2.reason;
|
|
delete src1.reason;
|
|
} else {
|
|
// Otherwise just reuse its RPN buffer
|
|
expr.rpnPatchSize = src1.rpnPatchSize;
|
|
expr.rpn = src1.rpn;
|
|
expr.reason = src1.reason;
|
|
delete src2.reason;
|
|
}
|
|
|
|
// Now, merge the right expression into the left one
|
|
uint8_t const *ptr = nullptr;
|
|
uint32_t len = 0;
|
|
uint32_t patchSize = 0;
|
|
|
|
// If the right expression is constant, merge a shim instead
|
|
uint32_t rval = src2.val;
|
|
uint8_t bytes[] = {
|
|
RPN_CONST,
|
|
(uint8_t)rval,
|
|
(uint8_t)(rval >> 8),
|
|
(uint8_t)(rval >> 16),
|
|
(uint8_t)(rval >> 24),
|
|
};
|
|
if (src2.isKnown) {
|
|
ptr = bytes;
|
|
len = sizeof(bytes);
|
|
patchSize = sizeof(bytes);
|
|
} else {
|
|
ptr = src2.rpn->data(); // Pointer to the right RPN
|
|
len = src2.rpn->size(); // Size of the right RPN
|
|
patchSize = src2.rpnPatchSize;
|
|
}
|
|
// Copy the right RPN and append the operator
|
|
uint8_t *buf = reserveSpace(expr, len + 1);
|
|
|
|
if (ptr)
|
|
// If there was none, `memcpy(buf, nullptr, 0)` would be UB
|
|
memcpy(buf, ptr, len);
|
|
buf[len] = op;
|
|
|
|
delete src2.rpn; // If there was none, this is `delete nullptr`
|
|
expr.rpnPatchSize += patchSize + 1;
|
|
}
|
|
}
|
|
|
|
void rpn_HIGH(Expression &expr, const Expression &src) {
|
|
expr = src;
|
|
expr.isSymbol = false;
|
|
|
|
if (expr.isKnown) {
|
|
expr.val = (uint32_t)expr.val >> 8 & 0xFF;
|
|
} else {
|
|
uint8_t bytes[] = {RPN_CONST, 8, 0, 0, 0, RPN_SHR, RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
|
|
expr.rpnPatchSize += sizeof(bytes);
|
|
memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes));
|
|
}
|
|
}
|
|
|
|
void rpn_LOW(Expression &expr, const Expression &src) {
|
|
expr = src;
|
|
expr.isSymbol = false;
|
|
|
|
if (expr.isKnown) {
|
|
expr.val = expr.val & 0xFF;
|
|
} else {
|
|
uint8_t bytes[] = {RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
|
|
|
|
expr.rpnPatchSize += sizeof(bytes);
|
|
memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes));
|
|
}
|
|
}
|
|
|
|
void rpn_ISCONST(Expression &expr, const Expression &src) {
|
|
initExpression(expr);
|
|
expr.val = src.isKnown;
|
|
expr.isKnown = true;
|
|
expr.isSymbol = false;
|
|
}
|
|
|
|
void rpn_NEG(Expression &expr, const Expression &src) {
|
|
expr = src;
|
|
expr.isSymbol = false;
|
|
|
|
if (expr.isKnown) {
|
|
expr.val = -(uint32_t)expr.val;
|
|
} else {
|
|
expr.rpnPatchSize++;
|
|
*reserveSpace(expr, 1) = RPN_NEG;
|
|
}
|
|
}
|
|
|
|
void rpn_NOT(Expression &expr, const Expression &src) {
|
|
expr = src;
|
|
expr.isSymbol = false;
|
|
|
|
if (expr.isKnown) {
|
|
expr.val = ~expr.val;
|
|
} else {
|
|
expr.rpnPatchSize++;
|
|
*reserveSpace(expr, 1) = RPN_NOT;
|
|
}
|
|
}
|