diff --git a/include/linkdefs.hpp b/include/linkdefs.hpp index 9216a222f..2a18c6f43 100644 --- a/include/linkdefs.hpp +++ b/include/linkdefs.hpp @@ -55,6 +55,8 @@ enum RPNCommand { RPN_HIGH = 0x70, RPN_LOW = 0x71, + RPN_BITWIDTH = 0x72, + RPN_TZCOUNT = 0x73, RPN_CONST = 0x80, RPN_SYM = 0x81 diff --git a/man/rgbasm.5 b/man/rgbasm.5 index a57159377..a7cc5f8a1 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -9,9 +9,9 @@ .Nm rgbasm .Nd language documentation .Sh DESCRIPTION -This is the full description of the language used by +This is the full description of the assembly language used by .Xr rgbasm 1 . -The description of the instructions supported by the Game Boy CPU is in +For the full description of instructions in the machine language supported by the Game Boy CPU, see .Xr gbz80 7 . .Pp It is advisable to have some familiarity with the Game Boy hardware before reading this document. @@ -47,6 +47,23 @@ Instructions are assembled into Game Boy opcodes. Multiple instructions on one line can be separated by double colons .Ql :: . .Pp +The available instructions are documented in +.Xr gbz80 7 . +Note that where an instruction requires an 8-bit register +.Ar r8 , +.Nm +can interpret +.Ic HIGH Ns Pq Ar r16 +as the top 8-bit register of the given +.Ar r16 , +and +.Ic LOW Ns Pq Ar r16 +as the bottom one (except for +.Ic LOW Ns Pq Ic AF , +since +.Ic F +is not a valid register). +.Pp All reserved keywords (directives, register names, etc.) are case-insensitive; all identifiers (labels and other symbol names) are case-sensitive. .Pp @@ -324,9 +341,33 @@ with a zero constant as either operand will be constant 0, and .Sq || with a non-zero constant as either operand will be constant 1, even if the other operand is non-constant. .Pp -.Ic \&! +.Sq \&! returns 1 if the operand was 0, and 0 otherwise. Even a non-constant operand with any non-zero bits will return 0. +.Ss Integer functions +Besides operators, there are also some functions which have more specialized uses. +.Bl -column "BITWIDTH(n)" +.It Sy Name Ta Sy Operation +.It Fn HIGH n Ta Equivalent to Ql Ar n No & $FF . +.It Fn LOW n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 . +.EQ +delim $$ +.EN +.It Fn BITWIDTH n Ta Returns the number of bits necessary to represent +.Ar n . +Some useful formulas: +.Ic BITWIDTH Ns ( Ar n Ns )\ \-\ 1 +equals $\[lf] log sub 2 ( n ) \[rf]$, +.Ic BITWIDTH Ns Pq Ar n Ns \ \-\ 1 +equals $\[lc] log sub 2 ( n ) \[rc]$, and +.No 32\ \-\ Ns Ic BITWIDTH Ns Pq Ar n +equals $roman clz ( n )$. +.It Fn TZCOUNT n Ta Returns $roman ctz ( n )$, the count of trailing zero bits at the end of the binary representation of +.Ar n . +.El +.EQ +delim off +.EN .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. @@ -345,8 +386,11 @@ Some integer operators like and .Sq - don't care whether the operands are integers or fixed-point. -You can easily truncate a fixed-point number into an integer by shifting it right by 16 bits. -It follows that you can convert an integer to a fixed-point number by shifting it left. +You can easily truncate a fixed-point number into an integer by shifting it right by the number of fractional bits. +It follows that you can convert an integer to a fixed-point number by shifting it left that same amount. +.Pp +Note that the current number of fractional bits can be computed as +.Ic TZCOUNT Ns Pq 1.0 . .Pp The following functions are designed to operate with fixed-point numbers: .EQ @@ -513,8 +557,8 @@ There is also a character map stack that can be used to save and restore which c .Sy Note : Modifications to a character map take effect immediately from that point onward. .Ss Other functions -There are a few other functions that do various useful things: -.Bl -column "DEF(symbol)" +There are a few other functions that do things beyond numeric or string operations: +.Bl -column "SECTION(symbol)" .It Sy Name Ta Sy Operation .It Fn BANK arg Ta Returns a bank number. If @@ -552,15 +596,10 @@ If .Ar arg is a section type keyword, it returns the starting address of that section type. The result is not constant, since only RGBLINK can compute its value. -.It Fn DEF symbol Ta Returns TRUE (1) if +.It Fn DEF symbol Ta Returns 1 if .Ar symbol -has been defined, FALSE (0) otherwise. +has been defined, 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 f76456ee1..17cd4251c 100644 --- a/man/rgbds.5 +++ b/man/rgbds.5 @@ -391,10 +391,14 @@ 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 +.It Li $70 Ta Cm HIGH byte. -.It Li $71 Ta Ql LOW +.It Li $71 Ta Cm LOW byte. +.It Li $72 Ta Cm BITWIDTH +value. +.It Li $73 Ta Cm TZCOUNT +value. .It Li $81 Ta A symbol's value; followed by the symbol's .Cm LONG ID. diff --git a/src/asm/lexer.cpp b/src/asm/lexer.cpp index 8768d973b..06d309748 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -237,6 +237,9 @@ static std::unordered_map ke {"LOW", T_(OP_LOW) }, {"ISCONST", T_(OP_ISCONST) }, + {"BITWIDTH", T_(OP_BITWIDTH) }, + {"TZCOUNT", T_(OP_TZCOUNT) }, + {"STRCMP", T_(OP_STRCMP) }, {"STRIN", T_(OP_STRIN) }, {"STRRIN", T_(OP_STRRIN) }, diff --git a/src/asm/parser.y b/src/asm/parser.y index 4cb64dff7..7b0a3fd45 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -174,6 +174,8 @@ %type opt_q_arg %token OP_HIGH "HIGH" OP_LOW "LOW" +%token OP_BITWIDTH "BITWIDTH" +%token OP_TZCOUNT "TZCOUNT" %token OP_ISCONST "ISCONST" %token OP_STRCMP "STRCMP" @@ -1349,6 +1351,12 @@ relocexpr_no_str: | OP_LOW LPAREN relocexpr RPAREN { $$.makeUnaryOp(RPN_LOW, std::move($3)); } + | OP_BITWIDTH LPAREN relocexpr RPAREN { + $$.makeUnaryOp(RPN_BITWIDTH, std::move($3)); + } + | OP_TZCOUNT LPAREN relocexpr RPAREN { + $$.makeUnaryOp(RPN_TZCOUNT, std::move($3)); + } | OP_ISCONST LPAREN relocexpr RPAREN { $$.makeNumber($3.isKnown()); } diff --git a/src/asm/rpn.cpp b/src/asm/rpn.cpp index 54ce92e28..8515f675e 100644 --- a/src/asm/rpn.cpp +++ b/src/asm/rpn.cpp @@ -9,7 +9,7 @@ #include #include -#include "helpers.hpp" // assume +#include "helpers.hpp" // assume, clz, ctz #include "opmath.hpp" #include "asm/output.hpp" @@ -319,6 +319,12 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) { case RPN_LOW: data = val & 0xFF; break; + case RPN_BITWIDTH: + data = val != 0 ? 32 - clz((uint32_t)val) : 0; + break; + case RPN_TZCOUNT: + data = val != 0 ? ctz((uint32_t)val) : 32; + break; case RPN_LOGOR: case RPN_LOGAND: @@ -498,6 +504,8 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const case RPN_RST: case RPN_HIGH: case RPN_LOW: + case RPN_BITWIDTH: + case RPN_TZCOUNT: 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 68a5d5129..24da60191 100644 --- a/src/link/patch.cpp +++ b/src/link/patch.cpp @@ -8,7 +8,7 @@ #include #include -#include "helpers.hpp" // assume +#include "helpers.hpp" // assume, clz, ctz #include "linkdefs.hpp" #include "opmath.hpp" @@ -146,6 +146,15 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector const &fil value = popRPN(patch) & 0xFF; break; + case RPN_BITWIDTH: + value = popRPN(patch); + value = value != 0 ? 32 - clz((uint32_t)value) : 0; + break; + case RPN_TZCOUNT: + value = popRPN(patch); + value = value != 0 ? ctz((uint32_t)value) : 32; + break; + case RPN_OR: value = popRPN(patch) | popRPN(patch); break; diff --git a/test/asm/bit-functions.asm b/test/asm/bit-functions.asm new file mode 100644 index 000000000..5d8b2fe6f --- /dev/null +++ b/test/asm/bit-functions.asm @@ -0,0 +1,11 @@ +assert BITWIDTH(0) == 0 +assert BITWIDTH(42) == 6 +assert BITWIDTH(-1) == 32 +assert BITWIDTH($80000000) == 32 + +assert TZCOUNT(0) == 32 +assert TZCOUNT(42) == 1 +assert TZCOUNT(-1) == 0 +assert TZCOUNT($80000000) == 31 + +assert TZCOUNT(1.0) == 16 diff --git a/test/link/operators.asm b/test/link/operators.asm index 941b036f3..db50e9ab3 100644 --- a/test/link/operators.asm +++ b/test/link/operators.asm @@ -27,3 +27,6 @@ db @ <= 7 dw @ << 1 dw @ >> 1 dw @ >>> 1 + +db BITWIDTH(@) +db TZCOUNT(@) diff --git a/test/link/operators.out.bin b/test/link/operators.out.bin index e5e08472a..0a8810887 100644 Binary files a/test/link/operators.out.bin and b/test/link/operators.out.bin differ