mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-21 18:52:07 +00:00
Implement opt Q for fixed-point precision, and q literals (e.g. 12.34q8) (#958)
Fixes #957 Co-authored-by: ISSOtm <eldredhabert0@gmail.com>
This commit is contained in:
@@ -17,17 +17,24 @@
|
||||
#include "asm/symbol.h"
|
||||
#include "asm/warning.h"
|
||||
|
||||
#define fix2double(i) ((double)((i) / 65536.0))
|
||||
#define double2fix(d) ((int32_t)round((d) * 65536.0))
|
||||
|
||||
// pi radians == 32768 fixed-point "degrees"
|
||||
#define fdeg2rad(f) ((f) * (M_PI / 32768.0))
|
||||
#define rad2fdeg(r) ((r) * (32768.0 / M_PI))
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#define fix2double(i) ((double)((i) / fix_PrecisionFactor()))
|
||||
#define double2fix(d) ((int32_t)round((d) * fix_PrecisionFactor()))
|
||||
|
||||
// pi*2 radians == 2**fixPrecision fixed-point "degrees"
|
||||
#define fdeg2rad(f) ((f) * (M_PI * 2) / fix_PrecisionFactor())
|
||||
#define rad2fdeg(r) ((r) * fix_PrecisionFactor() / (M_PI * 2))
|
||||
|
||||
uint8_t fixPrecision;
|
||||
|
||||
double fix_PrecisionFactor(void)
|
||||
{
|
||||
return pow(2.0, fixPrecision);
|
||||
}
|
||||
|
||||
void fix_Print(int32_t i)
|
||||
{
|
||||
uint32_t u = i;
|
||||
@@ -38,7 +45,7 @@ void fix_Print(int32_t i)
|
||||
sign = "-";
|
||||
}
|
||||
|
||||
printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> 16,
|
||||
printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> fixPrecision,
|
||||
((uint32_t)(fix2double(u) * 100000 + 0.5)) % 100000);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "asm/fixpoint.h"
|
||||
#include "asm/format.h"
|
||||
#include "asm/warning.h"
|
||||
|
||||
@@ -225,7 +226,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
||||
} else if (fmt->type == 'f') {
|
||||
// Special case for fixed-point
|
||||
|
||||
// Default fractional width (C's is 6 for "%f"; here 5 is enough)
|
||||
// Default fractional width (C's is 6 for "%f"; here 5 is enough for Q16.16)
|
||||
size_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
|
||||
|
||||
if (fracWidth > 255) {
|
||||
@@ -234,7 +235,8 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
||||
fracWidth = 255;
|
||||
}
|
||||
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth, value / 65536.0);
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth,
|
||||
value / fix_PrecisionFactor());
|
||||
} else {
|
||||
char const *spec = fmt->type == 'd' ? "%" PRId32
|
||||
: fmt->type == 'u' ? "%" PRIu32
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "platform.h" // For `ssize_t`
|
||||
|
||||
#include "asm/lexer.h"
|
||||
#include "asm/fixpoint.h"
|
||||
#include "asm/format.h"
|
||||
#include "asm/fstack.h"
|
||||
#include "asm/macro.h"
|
||||
@@ -1125,39 +1126,66 @@ static uint32_t readNumber(int radix, uint32_t baseValue)
|
||||
return value;
|
||||
}
|
||||
|
||||
static uint32_t readFractionalPart(int32_t integer)
|
||||
static uint32_t readFractionalPart(uint32_t integer)
|
||||
{
|
||||
uint32_t value = 0, divisor = 1;
|
||||
uint8_t precision = 0;
|
||||
enum {
|
||||
READFRACTIONALPART_DIGITS,
|
||||
READFRACTIONALPART_PRECISION,
|
||||
READFRACTIONALPART_PRECISION_DIGITS,
|
||||
} state = READFRACTIONALPART_DIGITS;
|
||||
|
||||
for (;; shiftChar()) {
|
||||
int c = peek();
|
||||
|
||||
if (c == '_')
|
||||
continue;
|
||||
else if (c < '0' || c > '9')
|
||||
break;
|
||||
if (divisor > (UINT32_MAX - (c - '0')) / 10) {
|
||||
warning(WARNING_LARGE_CONSTANT,
|
||||
"Precision of fixed-point constant is too large\n");
|
||||
// Discard any additional digits
|
||||
shiftChar();
|
||||
while (c = peek(), (c >= '0' && c <= '9') || c == '_')
|
||||
if (state == READFRACTIONALPART_DIGITS) {
|
||||
if (c == '_') {
|
||||
continue;
|
||||
} else if (c == 'q' || c == 'Q') {
|
||||
state = READFRACTIONALPART_PRECISION;
|
||||
continue;
|
||||
} else if (c < '0' || c > '9') {
|
||||
break;
|
||||
}
|
||||
if (divisor > (UINT32_MAX - (c - '0')) / 10) {
|
||||
warning(WARNING_LARGE_CONSTANT,
|
||||
"Precision of fixed-point constant is too large\n");
|
||||
// Discard any additional digits
|
||||
shiftChar();
|
||||
break;
|
||||
while (c = peek(), (c >= '0' && c <= '9') || c == '_')
|
||||
shiftChar();
|
||||
break;
|
||||
}
|
||||
value = value * 10 + (c - '0');
|
||||
divisor *= 10;
|
||||
} else {
|
||||
if (c == '.' && state == READFRACTIONALPART_PRECISION) {
|
||||
state = READFRACTIONALPART_PRECISION_DIGITS;
|
||||
continue;
|
||||
} else if (c < '0' || c > '9') {
|
||||
break;
|
||||
}
|
||||
precision = precision * 10 + (c - '0');
|
||||
}
|
||||
value = value * 10 + (c - '0');
|
||||
divisor *= 10;
|
||||
}
|
||||
|
||||
if (integer > INT16_MAX || integer < INT16_MIN)
|
||||
if (precision == 0) {
|
||||
if (state >= READFRACTIONALPART_PRECISION)
|
||||
error("Invalid fixed-point constant, no significant digits after 'q'\n");
|
||||
precision = fixPrecision;
|
||||
} else if (precision > 31) {
|
||||
error("Fixed-point constant precision must be between 1 and 31\n");
|
||||
precision = fixPrecision;
|
||||
}
|
||||
|
||||
if (integer >= ((uint32_t)1 << (precision - 1)))
|
||||
warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n");
|
||||
|
||||
// Cast to unsigned avoids UB if shifting discards bits
|
||||
integer = (uint32_t)integer << 16;
|
||||
// Cast to unsigned avoids undefined overflow behavior
|
||||
uint16_t fractional = (uint16_t)round(value * 65536.0 / divisor);
|
||||
uint32_t fractional = (uint32_t)round((double)value / divisor * pow(2.0, precision));
|
||||
|
||||
return (uint32_t)integer | (fractional * (integer >= 0 ? 1 : -1));
|
||||
return (integer << precision) | fractional;
|
||||
}
|
||||
|
||||
char binDigits[2];
|
||||
@@ -1741,6 +1769,8 @@ static int yylex_SKIP_TO_ENDC(void); // forward declaration for yylex_NORMAL
|
||||
|
||||
static int yylex_NORMAL(void)
|
||||
{
|
||||
uint32_t num = 0;
|
||||
|
||||
for (;;) {
|
||||
int c = nextChar();
|
||||
char secondChar;
|
||||
@@ -1912,10 +1942,12 @@ static int yylex_NORMAL(void)
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
yylval.constValue = readNumber(10, c - '0');
|
||||
num = readNumber(10, c - '0');
|
||||
if (peek() == '.') {
|
||||
shiftChar();
|
||||
yylval.constValue = readFractionalPart(yylval.constValue);
|
||||
yylval.constValue = readFractionalPart(num);
|
||||
} else {
|
||||
yylval.constValue = num;
|
||||
}
|
||||
return T_NUMBER;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <time.h>
|
||||
|
||||
#include "asm/charmap.h"
|
||||
#include "asm/fixpoint.h"
|
||||
#include "asm/format.h"
|
||||
#include "asm/fstack.h"
|
||||
#include "asm/lexer.h"
|
||||
@@ -86,7 +87,7 @@ static char *make_escape(char const *str)
|
||||
}
|
||||
|
||||
// Short options
|
||||
static const char *optstring = "b:D:Eg:Hhi:LlM:o:p:r:VvW:w";
|
||||
static const char *optstring = "b:D:Eg:Hhi:LlM:o:p:Q:r:VvW:w";
|
||||
|
||||
// Variables for the long-only options
|
||||
static int depType; // Variants of `-M`
|
||||
@@ -116,6 +117,7 @@ static struct option const longopts[] = {
|
||||
{ "MQ", required_argument, &depType, 'Q' },
|
||||
{ "output", required_argument, NULL, 'o' },
|
||||
{ "pad-value", required_argument, NULL, 'p' },
|
||||
{ "q-precision", required_argument, NULL, 'Q' },
|
||||
{ "recursion-depth", required_argument, NULL, 'r' },
|
||||
{ "version", no_argument, NULL, 'V' },
|
||||
{ "verbose", no_argument, NULL, 'v' },
|
||||
@@ -128,7 +130,8 @@ static void print_usage(void)
|
||||
fputs(
|
||||
"Usage: rgbasm [-EHhLlVvw] [-b chars] [-D name[=value]] [-g chars] [-i path]\n"
|
||||
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||
" [-o out_file] [-p pad_value] [-r depth] [-W warning] <file>\n"
|
||||
" [-o out_file] [-p pad_value] [-Q precision] [-r depth]\n"
|
||||
" [-W warning] <file>\n"
|
||||
"Useful options:\n"
|
||||
" -E, --export-all export all labels\n"
|
||||
" -M, --dependfile <path> set the output dependency file\n"
|
||||
@@ -170,6 +173,7 @@ int main(int argc, char *argv[])
|
||||
opt_B("01");
|
||||
opt_G("0123");
|
||||
opt_P(0);
|
||||
opt_Q(16);
|
||||
haltnop = true;
|
||||
warnOnHaltNop = true;
|
||||
optimizeLoads = true;
|
||||
@@ -250,17 +254,34 @@ int main(int argc, char *argv[])
|
||||
out_SetFileName(musl_optarg);
|
||||
break;
|
||||
|
||||
unsigned long fill;
|
||||
unsigned long padByte;
|
||||
case 'p':
|
||||
fill = strtoul(musl_optarg, &ep, 0);
|
||||
padByte = strtoul(musl_optarg, &ep, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *ep != '\0')
|
||||
errx("Invalid argument for option 'p'");
|
||||
|
||||
if (fill > 0xFF)
|
||||
if (padByte > 0xFF)
|
||||
errx("Argument for option 'p' must be between 0 and 0xFF");
|
||||
|
||||
opt_P(fill);
|
||||
opt_P(padByte);
|
||||
break;
|
||||
|
||||
unsigned long precision;
|
||||
const char *precisionArg;
|
||||
case 'Q':
|
||||
precisionArg = musl_optarg;
|
||||
if (precisionArg[0] == '.')
|
||||
precisionArg++;
|
||||
precision = strtoul(precisionArg, &ep, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *ep != '\0')
|
||||
errx("Invalid argument for option 'Q'");
|
||||
|
||||
if (precision < 1 || precision > 31)
|
||||
errx("Argument for option 'Q' must be between 1 and 31");
|
||||
|
||||
opt_Q(precision);
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "asm/fixpoint.h"
|
||||
#include "asm/fstack.h"
|
||||
#include "asm/lexer.h"
|
||||
#include "asm/main.h"
|
||||
@@ -23,7 +24,8 @@
|
||||
struct OptStackEntry {
|
||||
char binary[2];
|
||||
char gbgfx[4];
|
||||
int32_t fillByte;
|
||||
uint8_t fixPrecision;
|
||||
uint8_t fillByte;
|
||||
bool haltnop;
|
||||
bool warnOnHaltNop;
|
||||
bool optimizeLoads;
|
||||
@@ -47,9 +49,14 @@ void opt_G(char const chars[4])
|
||||
lexer_SetGfxDigits(chars);
|
||||
}
|
||||
|
||||
void opt_P(uint8_t fill)
|
||||
void opt_P(uint8_t padByte)
|
||||
{
|
||||
fillByte = fill;
|
||||
fillByte = padByte;
|
||||
}
|
||||
|
||||
void opt_Q(uint8_t precision)
|
||||
{
|
||||
fixPrecision = precision;
|
||||
}
|
||||
|
||||
void opt_R(size_t newDepth)
|
||||
@@ -103,18 +110,41 @@ void opt_Parse(char *s)
|
||||
case 'p':
|
||||
if (strlen(&s[1]) <= 2) {
|
||||
int result;
|
||||
unsigned int fillchar;
|
||||
unsigned int padByte;
|
||||
|
||||
result = sscanf(&s[1], "%x", &fillchar);
|
||||
if (result != EOF && result != 1)
|
||||
result = sscanf(&s[1], "%x", &padByte);
|
||||
if (result != 1)
|
||||
error("Invalid argument for option 'p'\n");
|
||||
else if (padByte > 0xFF)
|
||||
error("Argument for option 'p' must be between 0 and 0xFF\n");
|
||||
else
|
||||
opt_P(fillchar);
|
||||
opt_P(padByte);
|
||||
} else {
|
||||
error("Invalid argument for option 'p'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
const char *precisionArg;
|
||||
case 'Q':
|
||||
precisionArg = &s[1];
|
||||
if (precisionArg[0] == '.')
|
||||
precisionArg++;
|
||||
if (strlen(precisionArg) <= 2) {
|
||||
int result;
|
||||
unsigned int precision;
|
||||
|
||||
result = sscanf(precisionArg, "%u", &precision);
|
||||
if (result != 1)
|
||||
error("Invalid argument for option 'Q'\n");
|
||||
else if (precision < 1 || precision > 31)
|
||||
error("Argument for option 'Q' must be between 1 and 31\n");
|
||||
else
|
||||
opt_Q(precision);
|
||||
} else {
|
||||
error("Invalid argument for option 'Q'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'r': {
|
||||
++s; // Skip 'r'
|
||||
while (isblank(*s))
|
||||
@@ -231,6 +261,8 @@ void opt_Push(void)
|
||||
entry->gbgfx[2] = gfxDigits[2];
|
||||
entry->gbgfx[3] = gfxDigits[3];
|
||||
|
||||
entry->fixPrecision = fixPrecision; // Pulled from fixpoint.h
|
||||
|
||||
entry->fillByte = fillByte; // Pulled from section.h
|
||||
|
||||
entry->haltnop = haltnop; // Pulled from main.h
|
||||
@@ -259,6 +291,7 @@ void opt_Pop(void)
|
||||
opt_B(entry->binary);
|
||||
opt_G(entry->gbgfx);
|
||||
opt_P(entry->fillByte);
|
||||
opt_Q(entry->fixPrecision);
|
||||
opt_H(entry->warnOnHaltNop);
|
||||
opt_h(entry->haltnop);
|
||||
opt_L(entry->optimizeLoads);
|
||||
|
||||
Reference in New Issue
Block a user