diff --git a/include/helpers.hpp b/include/helpers.hpp index e12d350e..426182b5 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -42,12 +42,13 @@ static inline void unreachable_() { // Ideally we'd use `std::bit_width`, but it has insufficient compiler support #ifdef __GNUC__ // GCC or compatible - #define ctz __builtin_ctz - #define clz __builtin_clz + #define ctz __builtin_ctz + #define clz __builtin_clz + #define popcount __builtin_popcount #elif defined(_MSC_VER) #include - #pragma intrinsic(_BitScanReverse, _BitScanForward) + #pragma intrinsic(_BitScanReverse, _BitScanForward, __popcnt) static inline int ctz(unsigned int x) { assume(x != 0); @@ -63,6 +64,10 @@ static inline int clz(unsigned int x) { return 31 - count; } +static inline int popcount(unsigned int x) { + return __popcnt(x); +} + #else #include @@ -85,6 +90,15 @@ static inline int clz(unsigned int x) { } return count; } + +static inline int popcount(unsigned int x) { + int count = 0; + while (x) { + x &= x - 1; + ++count; + } + return count; +} #endif // Macros for stringification diff --git a/include/linkdefs.hpp b/include/linkdefs.hpp index f88cdf36..f8dbcdae 100644 --- a/include/linkdefs.hpp +++ b/include/linkdefs.hpp @@ -57,6 +57,7 @@ enum RPNCommand { RPN_LOW = 0x71, RPN_BITWIDTH = 0x72, RPN_TZCOUNT = 0x73, + RPN_POPCOUNT = 0x74, RPN_CONST = 0x80, RPN_SYM = 0x81 diff --git a/include/opmath.hpp b/include/opmath.hpp index 682769a5..ab926909 100644 --- a/include/opmath.hpp +++ b/include/opmath.hpp @@ -23,5 +23,6 @@ int32_t op_low(int32_t value); int32_t op_bitwidth(int32_t value); int32_t op_tzcount(int32_t value); +int32_t op_popcount(int32_t value); #endif // RGBDS_OP_MATH_HPP diff --git a/man/rgbasm.5 b/man/rgbasm.5 index d4531a38..58568cc2 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -436,6 +436,8 @@ equals $roman clz ( n )$, the count of leading zero bits in the binary represent .Ar n . .It Fn TZCOUNT n Ta Returns $roman ctz ( n )$, the count of trailing zero bits in the binary representation of .Ar n . +.It Fn POPCOUNT n Ta Returns how many one bits are in the binary representation of +.Ar n . .El .EQ delim off diff --git a/man/rgbds.5 b/man/rgbds.5 index e3aa6be6..fb06b506 100644 --- a/man/rgbds.5 +++ b/man/rgbds.5 @@ -411,6 +411,8 @@ byte. value. .It Li $73 Ta Cm TZCOUNT value. +.It Li $74 Ta Cm POPCOUNT +value. .It Li $80 Ta Integer literal; followed by the .Cm LONG integer. diff --git a/src/asm/lexer.cpp b/src/asm/lexer.cpp index 35d6ce44..286e4790 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -173,6 +173,7 @@ static UpperMap const keywords{ {"ISCONST", T_(OP_ISCONST) }, {"BITWIDTH", T_(OP_BITWIDTH) }, + {"POPCOUNT", T_(OP_POPCOUNT) }, {"TZCOUNT", T_(OP_TZCOUNT) }, {"BYTELEN", T_(OP_BYTELEN) }, diff --git a/src/asm/parser.y b/src/asm/parser.y index 16fbe9c2..aa808647 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -286,6 +286,7 @@ %token OP_ISCONST "ISCONST" %token OP_LOG "LOG" %token OP_LOW "LOW" +%token OP_POPCOUNT "POPCOUNT" %token OP_POW "POW" %token OP_READFILE "READFILE" %token OP_REVCHAR "REVCHAR" @@ -1396,6 +1397,9 @@ relocexpr_no_str: | OP_BITWIDTH LPAREN relocexpr RPAREN { $$.makeUnaryOp(RPN_BITWIDTH, std::move($3)); } + | OP_POPCOUNT LPAREN relocexpr RPAREN { + $$.makeUnaryOp(RPN_POPCOUNT, std::move($3)); + } | OP_TZCOUNT LPAREN relocexpr RPAREN { $$.makeUnaryOp(RPN_TZCOUNT, std::move($3)); } diff --git a/src/asm/rpn.cpp b/src/asm/rpn.cpp index 9a4aba9a..5dd7f29f 100644 --- a/src/asm/rpn.cpp +++ b/src/asm/rpn.cpp @@ -279,6 +279,9 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) { case RPN_TZCOUNT: data = op_tzcount(val); break; + case RPN_POPCOUNT: + data = op_popcount(val); + break; // LCOV_EXCL_START default: // `makeUnaryOp` should never be called with a non-unary operator! diff --git a/src/link/patch.cpp b/src/link/patch.cpp index 75729c9d..272d071e 100644 --- a/src/link/patch.cpp +++ b/src/link/patch.cpp @@ -182,6 +182,9 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector const &fil case RPN_TZCOUNT: value = op_tzcount(popRPN(patch)); break; + case RPN_POPCOUNT: + value = op_popcount(popRPN(patch)); + break; case RPN_OR: value = popRPN(patch) | popRPN(patch); diff --git a/src/opmath.cpp b/src/opmath.cpp index b3cfa8e2..8f6ef06e 100644 --- a/src/opmath.cpp +++ b/src/opmath.cpp @@ -141,3 +141,7 @@ int32_t op_bitwidth(int32_t value) { int32_t op_tzcount(int32_t value) { return value != 0 ? ctz(static_cast(value)) : 32; } + +int32_t op_popcount(int32_t value) { + return popcount(static_cast(value)); +} diff --git a/test/asm/bit-functions.asm b/test/asm/bit-functions.asm index 5d8b2fe6..9eeea47c 100644 --- a/test/asm/bit-functions.asm +++ b/test/asm/bit-functions.asm @@ -9,3 +9,9 @@ assert TZCOUNT(-1) == 0 assert TZCOUNT($80000000) == 31 assert TZCOUNT(1.0) == 16 + +assert POPCOUNT(0) == 0 +assert POPCOUNT(1) == 1 +assert POPCOUNT(-1) == 32 +assert POPCOUNT(%10101100) == 4 +assert POPCOUNT(-%10101100) == 27 diff --git a/test/link/operators.asm b/test/link/operators.asm index db50e9ab..4a0430dc 100644 --- a/test/link/operators.asm +++ b/test/link/operators.asm @@ -30,3 +30,4 @@ dw @ >>> 1 db BITWIDTH(@) db TZCOUNT(@) +db POPCOUNT(@) diff --git a/test/link/operators.out.bin b/test/link/operators.out.bin index 0a881088..d20bb246 100644 Binary files a/test/link/operators.out.bin and b/test/link/operators.out.bin differ