From 53e06dbf202d4bd15a5d8da071a8478bd9b45b93 Mon Sep 17 00:00:00 2001 From: Rangi42 Date: Tue, 6 Aug 2024 13:12:42 -0400 Subject: [PATCH 1/6] Implement `BITWIDTH` and `TZCOUNT` functions --- include/linkdefs.hpp | 2 ++ man/rgbasm.5 | 60 +++++++++++++++++++++++++++--------- man/rgbds.5 | 8 +++-- src/asm/lexer.cpp | 3 ++ src/asm/parser.y | 8 +++++ src/asm/rpn.cpp | 10 +++++- src/link/patch.cpp | 11 ++++++- test/asm/bit-functions.asm | 11 +++++++ test/link/operators.asm | 3 ++ test/link/operators.out.bin | Bin 41 -> 43 bytes 10 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 test/asm/bit-functions.asm 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..b152046bd 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -324,9 +324,32 @@ 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. +.EQ +delim $$ +.EN +.Bl -column "BITWIDTH(n)" +.It Sy Name Ta Sy Operation +.It Fn BITWIDTH n Ta Returns the number of bits necessary to represent +.Ar n . +Some useful formulas: +.Ic BITWIDTH Ns Po Ar n Ns Pc - 1 +equals $\[lf] roman log2 ( n ) \[rf]$, +.Ic BITWIDTH Ns Pq Ar n No - 1 +equals $\[lc] roman log2 ( n ) \[rc]$, +and +.Ic No 32 - 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 +368,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 equals +.Ic TZCOUNT Ns Pq 1.0 . .Pp The following functions are designed to operate with fixed-point numbers: .EQ @@ -354,9 +380,9 @@ delim $$ .EN .Bl -column -offset indent "ATAN2(y, x)" .It Sy Name Ta Sy Operation -.It Fn DIV x y Ta Fixed-point division $( x \[di] y ) \[mu] ( 2 ^ precision )$ -.It Fn MUL x y Ta Fixed-point multiplication $( x \[mu] y ) \[di] ( 2 ^ precision )$ -.It Fn FMOD x y Ta Fixed-point modulo $( x % y ) \[di] ( 2 ^ precision )$ +.It Fn DIV x y Ta Fixed-point division $( x ^\[di]^ y ) ^\[mu]^ ( 2 ^\[ha]^ precision )$ +.It Fn MUL x y Ta Fixed-point multiplication $( x ^\[mu]^ y ) ^\[di]^ ( 2 ^\[ha]^ precision )$ +.It Fn FMOD x y Ta Fixed-point modulo $( x ^%^ y ) ^\[di]^ ( 2 ^\[ha]^ precision )$ .It Fn POW x y Ta $x$ to the $y$ power .It Fn LOG x y Ta Logarithm of $x$ to the base $y$ .It Fn ROUND x Ta Round $x$ to the nearest integer @@ -383,11 +409,15 @@ no matter what value is set as the current .Cm Q option. .Pp -The trigonometry functions ( -.Ic SIN , -.Ic COS , -.Ic TAN , -etc) are defined in terms of a circle divided into 1.0 "turns" (equal to 2pi radians or 360 degrees). +.EQ +delim $$ +.EN +The trigonometry functions +.Pq Ic SIN , Ic COS , Ic TAN , No etc +are defined in terms of a circle divided into 1.0 "turns" (equal to $2 pi$ radians or 360 degrees). +.EQ +delim off +.EN .Pp These functions are useful for automatic generation of various tables. For example: @@ -513,8 +543,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,9 +582,9 @@ 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 . 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 37fa3ace5..ed8e21e9a 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 0dfc0727b..c21be6ce6 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 4fbae8755..873d76495 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" @@ -315,6 +315,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: @@ -494,6 +500,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 e5e08472a5257071de5ec2d3d1b6d37187e8bc97..0a88108878ae7d3fef2235954f736807472ee99b 100644 GIT binary patch delta 7 OcmdPYo}kIZ#s~lfuK^bT delta 4 LcmdPaoS+E+0(1cm From 5d4053a39d9b17f1bfd9fb71418e4364db8e7ed1 Mon Sep 17 00:00:00 2001 From: Rangi42 Date: Wed, 7 Aug 2024 08:29:21 -0400 Subject: [PATCH 2/6] Review comments --- man/rgbasm.5 | 49 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/man/rgbasm.5 b/man/rgbasm.5 index b152046bd..e7c091a51 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. @@ -46,6 +46,25 @@ They must come first in the line. 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 when some instructions require an 8-bit register +.Ar r8 , +.Nm +will 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. @@ -334,18 +353,27 @@ delim $$ .EN .Bl -column "BITWIDTH(n)" .It Sy Name Ta Sy Operation +.It Fn HIGH n Ta Returns the top 8 bits of +.Ar n . +.It Fn LOW n Ta Returns the bottom 8 bits of +.Ar n . +The result may be constant if +.Nm +is able to compute it. .It Fn BITWIDTH n Ta Returns the number of bits necessary to represent .Ar n . Some useful formulas: -.Ic BITWIDTH Ns Po Ar n Ns Pc - 1 -equals $\[lf] roman log2 ( n ) \[rf]$, -.Ic BITWIDTH Ns Pq Ar n No - 1 -equals $\[lc] roman log2 ( n ) \[rc]$, -and -.Ic No 32 - Ic BITWIDTH Ns Pq Ar n +.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 . +The result may be constant if +.Nm +is able to compute it. .El .EQ delim off @@ -586,11 +614,6 @@ The result is not constant, since only RGBLINK can compute its value. .Ar symbol 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. From d8b00ba531c0045e8a11ecb29a183e9637d9de0c Mon Sep 17 00:00:00 2001 From: Rangi42 Date: Wed, 7 Aug 2024 09:07:00 -0400 Subject: [PATCH 3/6] Review part 2 --- man/rgbasm.5 | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/man/rgbasm.5 b/man/rgbasm.5 index e7c091a51..9fb2c1915 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -46,15 +46,13 @@ They must come first in the line. 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 when some instructions require an 8-bit register +Note that where an instruction requires an 8-bit register .Ar r8 , .Nm -will interpret +can interpret .Ic HIGH Ns Pq Ar r16 as the top 8-bit register of the given .Ar r16 , @@ -408,9 +406,9 @@ delim $$ .EN .Bl -column -offset indent "ATAN2(y, x)" .It Sy Name Ta Sy Operation -.It Fn DIV x y Ta Fixed-point division $( x ^\[di]^ y ) ^\[mu]^ ( 2 ^\[ha]^ precision )$ -.It Fn MUL x y Ta Fixed-point multiplication $( x ^\[mu]^ y ) ^\[di]^ ( 2 ^\[ha]^ precision )$ -.It Fn FMOD x y Ta Fixed-point modulo $( x ^%^ y ) ^\[di]^ ( 2 ^\[ha]^ precision )$ +.It Fn DIV x y Ta Fixed-point division $( x \[di] y ) \[mu] ( 2 ^ precision )$ +.It Fn MUL x y Ta Fixed-point multiplication $( x \[mu] y ) \[di] ( 2 ^ precision )$ +.It Fn FMOD x y Ta Fixed-point modulo $( x % y ) \[di] ( 2 ^ precision )$ .It Fn POW x y Ta $x$ to the $y$ power .It Fn LOG x y Ta Logarithm of $x$ to the base $y$ .It Fn ROUND x Ta Round $x$ to the nearest integer @@ -440,9 +438,11 @@ option. .EQ delim $$ .EN -The trigonometry functions -.Pq Ic SIN , Ic COS , Ic TAN , No etc -are defined in terms of a circle divided into 1.0 "turns" (equal to $2 pi$ radians or 360 degrees). +The trigonometry functions ( +.Ic SIN , +.Ic COS , +.Ic TAN , +etc) are defined in terms of a circle divided into 1.0 "turns" (equal to 2pi radians or 360 degrees). .EQ delim off .EN From 888d2cdc2432772a3c129a6462db48d011675f7f Mon Sep 17 00:00:00 2001 From: Rangi42 Date: Wed, 7 Aug 2024 09:51:59 -0400 Subject: [PATCH 4/6] Even more --- man/rgbasm.5 | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/man/rgbasm.5 b/man/rgbasm.5 index 9fb2c1915..a2ca82546 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -346,18 +346,13 @@ 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 -.Bl -column "BITWIDTH(n)" -.It Sy Name Ta Sy Operation -.It Fn HIGH n Ta Returns the top 8 bits of -.Ar n . -.It Fn LOW n Ta Returns the bottom 8 bits of -.Ar n . -The result may be constant if -.Nm -is able to compute it. .It Fn BITWIDTH n Ta Returns the number of bits necessary to represent .Ar n . Some useful formulas: @@ -369,9 +364,6 @@ equals $\[lc] log sub 2 ( n ) \[rc]$, and 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 . -The result may be constant if -.Nm -is able to compute it. .El .EQ delim off From 667ec86b1aaa7b6d48038da353e22999708983c8 Mon Sep 17 00:00:00 2001 From: Rangi42 Date: Wed, 7 Aug 2024 09:53:20 -0400 Subject: [PATCH 5/6] Oops --- man/rgbasm.5 | 6 ------ 1 file changed, 6 deletions(-) diff --git a/man/rgbasm.5 b/man/rgbasm.5 index a2ca82546..579958c5b 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -427,17 +427,11 @@ no matter what value is set as the current .Cm Q option. .Pp -.EQ -delim $$ -.EN The trigonometry functions ( .Ic SIN , .Ic COS , .Ic TAN , etc) are defined in terms of a circle divided into 1.0 "turns" (equal to 2pi radians or 360 degrees). -.EQ -delim off -.EN .Pp These functions are useful for automatic generation of various tables. For example: From 9da249cfe88d14f7ec074b031622a5f8e0e698b7 Mon Sep 17 00:00:00 2001 From: Rangi42 Date: Wed, 7 Aug 2024 10:25:24 -0400 Subject: [PATCH 6/6] Computed --- man/rgbasm.5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/rgbasm.5 b/man/rgbasm.5 index 579958c5b..a7cc5f8a1 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -389,7 +389,7 @@ 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 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 equals +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: