Skip to content

Commit

Permalink
Implement opt Q for fixed-point precision, and q literals (e.g. `…
Browse files Browse the repository at this point in the history
…12.34q8`) (#958)

Fixes #957

Co-authored-by: ISSOtm <[email protected]>
  • Loading branch information
Rangi42 and ISSOtm authored Sep 4, 2022
1 parent 889302a commit 98a6dff
Show file tree
Hide file tree
Showing 20 changed files with 253 additions and 56 deletions.
1 change: 1 addition & 0 deletions contrib/bash_compl/_rgbasm.bash
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ _rgbasm_completions() {
[M]="dependfile:glob-*.mk *.d"
[o]="output:glob-*.o"
[p]="pad-value:unk"
[Q]="q-precision:unk"
[r]="recursion-depth:unk"
[W]="warning:warning"
)
Expand Down
1 change: 1 addition & 0 deletions contrib/zsh_compl/_rgbasm
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ local args=(
'*'-MQ"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
'(-o --output)'{-o,--output}'+[Output file]:output file:_files'
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
'(-Q --q-precision)'{-Q,--q-precision}'+[Set fixed-point precision]:precision:'
'(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:'
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings'

Expand Down
3 changes: 3 additions & 0 deletions include/asm/fixpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

#include <stdint.h>

extern uint8_t fixPrecision;

double fix_PrecisionFactor(void);
void fix_Print(int32_t i);
int32_t fix_Sin(int32_t i);
int32_t fix_Cos(int32_t i);
Expand Down
3 changes: 2 additions & 1 deletion include/asm/opt.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

void opt_B(char const chars[2]);
void opt_G(char const chars[4]);
void opt_P(uint8_t fill);
void opt_P(uint8_t padByte);
void opt_Q(uint8_t precision);
void opt_L(bool optimize);
void opt_W(char const *flag);
void opt_Parse(char const *option);
Expand Down
8 changes: 8 additions & 0 deletions man/rgbasm.1
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
.Op Fl MQ Ar target_file
.Op Fl o Ar out_file
.Op Fl p Ar pad_value
.Op Fl Q Ar fix_precision
.Op Fl r Ar recursion_depth
.Op Fl W Ar warning
.Ar
Expand Down Expand Up @@ -148,6 +149,13 @@ Write an object file to the given filename.
.It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
When padding an image, pad with this value.
The default is 0x00.
.It Fl Q Ar fix_precision , Fl Fl q-precision Ar fix_precision
Use this as the precision of fixed-point numbers after the decimal point, unless they specify their own precision.
The default is 16, so fixed-point numbers are Q16.16 (since they are 32-bit integers).
The argument may start with a
.Ql \&.
to match the Q notation, for example,
.Ql Fl Q Ar .16 .
.It Fl r Ar recursion_depth , Fl Fl recursion-depth Ar recursion_depth
Specifies the recursion depth past which RGBASM will assume being in an infinite loop.
The default is 64.
Expand Down
29 changes: 20 additions & 9 deletions man/rgbasm.5
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,14 @@ section.
The instructions in the macro-language generally require constant expressions.
.Ss Numeric formats
There are a number of numeric formats.
.Bl -column -offset indent "Fixed point (Q16.16)" "Prefix"
.Bl -column -offset indent "Precise fixed-point" "Prefix"
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
.It Hexadecimal Ta $ Ta 0123456789ABCDEF
.It Decimal Ta none Ta 0123456789
.It Octal Ta & Ta 01234567
.It Binary Ta % Ta 01
.It Fixed point (Q16.16) Ta none Ta 01234.56789
.It Fixed-point Ta none Ta 01234.56789
.It Precise fixed-point Ta none Ta 12.34q8
.It Character constant Ta none Ta \(dqABYZ\(dq
.It Gameboy graphics Ta \` Ta 0123
.El
Expand Down Expand Up @@ -301,9 +302,19 @@ and
.Ic \&!
returns 1 if the operand was 0, and 0 otherwise.
.Ss Fixed-point expressions
Fixed-point numbers are basically normal (32-bit) integers, which count 65536ths instead of entire units, offering better precision than integers but limiting the range of values.
The upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
Since they are still akin to integers, you can use them in normal integer expressions, and some integer operators like
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.
By default, the upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
The default number of fractional bits can be changed with the
.Fl Q
command-line option.
You can also specify a precise fixed-point value by appending a
.Dq q
to it followed by the number of fractional bits, such as
.Ql 12.34q8 .
.Pp
Since fixed-point values are still just integers, you can use them in normal integer expressions.
Some integer operators like
.Sq +
and
.Sq -
Expand All @@ -317,9 +328,9 @@ delim $$
.EN
.Bl -column -offset indent "ATAN2(x, y)"
.It Sy Name Ta Sy Operation
.It Fn DIV x y Ta $x \[di] y$
.It Fn MUL x y Ta $x \[mu] y$
.It Fn FMOD x y Ta $x % y$
.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
Expand Down Expand Up @@ -2034,7 +2045,7 @@ POPO
The options that
.Ic OPT
can modify are currently:
.Cm b , g , p , r , h , L ,
.Cm b , g , p , Q , r , h , L ,
and
.Cm W .
The Boolean flag options
Expand Down
23 changes: 15 additions & 8 deletions src/asm/fixpoint.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}

Expand Down
6 changes: 4 additions & 2 deletions src/asm/format.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <stdlib.h>
#include <string.h>

#include "asm/fixpoint.h"
#include "asm/format.h"
#include "asm/warning.h"

Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand Down
74 changes: 53 additions & 21 deletions src/asm/lexer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
33 changes: 27 additions & 6 deletions src/asm/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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' },
Expand All @@ -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"
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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':
Expand Down
Loading

0 comments on commit 98a6dff

Please sign in to comment.