From 2aef09c8d9c6dd8c248e49058238bc9e7342db2a Mon Sep 17 00:00:00 2001 From: Rangi <35663410+Rangi42@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:14:10 +0100 Subject: [PATCH] Allow the `bit`/`res`/`set` bit index to be determined at link time (#1654) This increments the object file revision number from 11 to 12 since it adds a new `RPN_BIT_INDEX` command. --- include/asm/rpn.hpp | 1 + include/linkdefs.hpp | 3 ++- man/rgbds.5 | 13 ++++++++-- src/asm/parser.y | 43 +++++++++++++++++++++---------- src/asm/rpn.cpp | 16 ++++++++++++ src/link/patch.cpp | 16 +++++++++++- test/asm/invalid-instructions.err | 4 ++- test/link/bit-res-set-bad.asm | 5 ++++ test/link/bit-res-set-bad.out | 4 +++ test/link/bit-res-set.asm | 10 +++++++ test/link/bit-res-set.out | 0 test/link/bit-res-set.out.bin | 1 + 12 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 test/link/bit-res-set-bad.asm create mode 100644 test/link/bit-res-set-bad.out create mode 100644 test/link/bit-res-set.asm create mode 100644 test/link/bit-res-set.out create mode 100644 test/link/bit-res-set.out.bin diff --git a/include/asm/rpn.hpp b/include/asm/rpn.hpp index 28f2fb6c4..4381ad9ab 100644 --- a/include/asm/rpn.hpp +++ b/include/asm/rpn.hpp @@ -51,6 +51,7 @@ struct Expression { bool makeCheckHRAM(); void makeCheckRST(); + void makeCheckBitIndex(uint8_t mask); void checkNBit(uint8_t n) const; diff --git a/include/linkdefs.hpp b/include/linkdefs.hpp index 67b8d08e0..816393c4d 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 11U +#define RGBDS_OBJECT_REV 12U enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL }; @@ -52,6 +52,7 @@ enum RPNCommand { RPN_HRAM = 0x60, RPN_RST = 0x61, + RPN_BIT_INDEX = 0x62, RPN_HIGH = 0x70, RPN_LOW = 0x71, diff --git a/man/rgbds.5 b/man/rgbds.5 index cd11f7adb..3ae90dcd7 100644 --- a/man/rgbds.5 +++ b/man/rgbds.5 @@ -388,10 +388,19 @@ The value is then ANDed with $00FF check. Checks if the value is a valid .Ql rst -.Pq see Do RST vec Dc in Xr gbz80 7 -vector, that is one of $00, $08, $10, $18, $20, $28, $30, or $38. +vector +.Pq see Do RST vec Dc in Xr gbz80 7 , +that is, one of $00, $08, $10, $18, $20, $28, $30, or $38. The value is then ORed with $C7 .Pq Ql \&| $C7 . +.It Li $62 Ta Ql bit/res/set +check; followed by the instruction's +.Cm BYTE +mask. +Checks if the value is a valid bit index +.Pq see e.g. Do BIT u3, r8 Dc in Xr gbz80 7 , +that is, from 0 to 7. +The value is then ORed with the instruction's mask. .It Li $80 Ta Integer literal; followed by the .Cm LONG integer. diff --git a/src/asm/parser.y b/src/asm/parser.y index 789701a5d..3f7ca62f7 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -325,6 +325,7 @@ // `relocexpr_no_str` exists because strings usually count as numeric expressions, but some // contexts treat numbers and strings differently, e.g. `db "string"` or `print "string"`. %type relocexpr_no_str +%type reloc_3bit %type reloc_8bit %type reloc_8bit_offset %type reloc_16bit @@ -333,7 +334,6 @@ %type iconst %type uconst // Constant numbers used only in specific contexts -%type bit_const %type precision_arg %type rs_uconst %type sect_org @@ -1214,13 +1214,10 @@ print_expr: } ; -bit_const: - iconst { - $$ = $1; - if ($$ < 0 || $$ > 7) { - ::error("Bit number must be between 0 and 7, not %" PRId32 "\n", $$); - $$ = 0; - } +reloc_3bit: + relocexpr { + $$ = std::move($1); + $$.checkNBit(3); } ; @@ -1789,9 +1786,15 @@ sm83_and: ; sm83_bit: - SM83_BIT bit_const COMMA reg_r { + SM83_BIT reloc_3bit COMMA reg_r { + uint8_t mask = static_cast(0x40 | $4); + $2.makeCheckBitIndex(mask); sect_ConstByte(0xCB); - sect_ConstByte(0x40 | ($2 << 3) | $4); + if (!$2.isKnown()) { + sect_RelByte($2, 0); + } else { + sect_ConstByte(mask | ($2.value() << 3)); + } } ; @@ -2103,9 +2106,15 @@ sm83_push: ; sm83_res: - SM83_RES bit_const COMMA reg_r { + SM83_RES reloc_3bit COMMA reg_r { + uint8_t mask = static_cast(0x80 | $4); + $2.makeCheckBitIndex(mask); sect_ConstByte(0xCB); - sect_ConstByte(0x80 | ($2 << 3) | $4); + if (!$2.isKnown()) { + sect_RelByte($2, 0); + } else { + sect_ConstByte(mask | ($2.value() << 3)); + } } ; @@ -2204,9 +2213,15 @@ sm83_scf: ; sm83_set: - SM83_SET bit_const COMMA reg_r { + SM83_SET reloc_3bit COMMA reg_r { + uint8_t mask = static_cast(0xC0 | $4); + $2.makeCheckBitIndex(mask); sect_ConstByte(0xCB); - sect_ConstByte(0xC0 | ($2 << 3) | $4); + if (!$2.isKnown()) { + sect_RelByte($2, 0); + } else { + sect_ConstByte(mask | ($2.value() << 3)); + } } ; diff --git a/src/asm/rpn.cpp b/src/asm/rpn.cpp index cb56765da..da61a246c 100644 --- a/src/asm/rpn.cpp +++ b/src/asm/rpn.cpp @@ -355,6 +355,7 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) { case RPN_STARTOF_SECTTYPE: case RPN_HRAM: case RPN_RST: + case RPN_BIT_INDEX: case RPN_CONST: case RPN_SYM: fatalerror("%d is not an unary operator\n", op); @@ -514,6 +515,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const case RPN_STARTOF_SECTTYPE: case RPN_HRAM: case RPN_RST: + case RPN_BIT_INDEX: case RPN_HIGH: case RPN_LOW: case RPN_BITWIDTH: @@ -608,6 +610,20 @@ void Expression::makeCheckRST() { } } +void Expression::makeCheckBitIndex(uint8_t mask) { + assume((mask & 0xC0) != 0x00); // The high two bits must correspond to BIT, RES, or SET + + if (!isKnown()) { + uint8_t *ptr = reserveSpace(2); + *ptr++ = RPN_BIT_INDEX; + *ptr = mask; + } else if (int32_t val = value(); val & ~0x07) { + // A valid bit index must be masked with 0x07 + static char const *instructions[4] = {"instruction", "BIT", "RES", "SET"}; + error("Invalid bit index %" PRId32 " for %s\n", val, instructions[mask >> 6]); + } +} + // Checks that an RPN expression's value fits within N bits (signed or unsigned) void Expression::checkNBit(uint8_t n) const { if (isKnown()) { diff --git a/src/link/patch.cpp b/src/link/patch.cpp index 839e2edf7..334878a70 100644 --- a/src/link/patch.cpp +++ b/src/link/patch.cpp @@ -363,7 +363,6 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector const &fil case RPN_RST: value = popRPN(patch); // Acceptable values are 0x00, 0x08, 0x10, ..., 0x38 - // They can be easily checked with a bitmask if (value & ~0x38) { if (!isError) { error(patch.src, patch.lineNo, "Value $%" PRIx32 " is not a RST vector", value); @@ -374,6 +373,21 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector const &fil value |= 0xC7; break; + case RPN_BIT_INDEX: { + value = popRPN(patch); + int32_t mask = getRPNByte(expression, size, patch); + // Acceptable values are 0 to 7 + if (value & ~0x07) { + if (!isError) { + error(patch.src, patch.lineNo, "Value $%" PRIx32 " is not a bit index", value); + isError = true; + } + value = 0; + } + value = mask | (value << 3); + break; + } + case RPN_CONST: value = 0; for (uint8_t shift = 0; shift < 32; shift += 8) { diff --git a/test/asm/invalid-instructions.err b/test/asm/invalid-instructions.err index 082df27ce..7a903a7df 100644 --- a/test/asm/invalid-instructions.err +++ b/test/asm/invalid-instructions.err @@ -10,8 +10,10 @@ error: invalid-instructions.asm(5): syntax error, unexpected bc, expecting hl error: invalid-instructions.asm(6): syntax error, unexpected number, expecting hl +warning: invalid-instructions.asm(7): [-Wtruncation] + Expression must be 3-bit error: invalid-instructions.asm(7): - Bit number must be between 0 and 7, not 8 + Invalid bit index 8 for BIT error: invalid-instructions.asm(8): Invalid address $40 for RST error: Assembly aborted (8 errors)! diff --git a/test/link/bit-res-set-bad.asm b/test/link/bit-res-set-bad.asm new file mode 100644 index 000000000..47531b1bd --- /dev/null +++ b/test/link/bit-res-set-bad.asm @@ -0,0 +1,5 @@ +SECTION "test", ROM0 +Label: +bit Label - 1, a +res Label + 8, [hl] +set Label | $ff00, b diff --git a/test/link/bit-res-set-bad.out b/test/link/bit-res-set-bad.out new file mode 100644 index 000000000..6f44233bd --- /dev/null +++ b/test/link/bit-res-set-bad.out @@ -0,0 +1,4 @@ +error: bit-res-set-bad.asm(5): Value $ff00 is not a bit index +error: bit-res-set-bad.asm(4): Value $8 is not a bit index +error: bit-res-set-bad.asm(3): Value $ffffffff is not a bit index +Linking failed with 3 errors diff --git a/test/link/bit-res-set.asm b/test/link/bit-res-set.asm new file mode 100644 index 000000000..733367304 --- /dev/null +++ b/test/link/bit-res-set.asm @@ -0,0 +1,10 @@ +SECTION "test", ROM0 +Label: +bit Label + 0, b +bit Label + 1, c +res Label + 2, d +res Label + 3, e +set Label + 4, h +set Label + 5, l +bit Label + 6, a +set Label + 7, [hl] diff --git a/test/link/bit-res-set.out b/test/link/bit-res-set.out new file mode 100644 index 000000000..e69de29bb diff --git a/test/link/bit-res-set.out.bin b/test/link/bit-res-set.out.bin new file mode 100644 index 000000000..287c83566 --- /dev/null +++ b/test/link/bit-res-set.out.bin @@ -0,0 +1 @@ +Ë@ËIË’Ë›ËäËíËwËþ \ No newline at end of file