From 6424ebaf98262bfd65a48b644b57fdf99b7866a9 Mon Sep 17 00:00:00 2001 From: Dale Weiler Date: Sat, 24 May 2014 09:53:38 -0400 Subject: [PATCH] Perliminary work on arithmetic exception handling in the constant evaluator. We can safely check for arithmetic underflow, overflow, divison by zero and inexactness now. Inexactness of expressions are propagated through the AST such that using an inexact value in a comparison will trigger a warning with -Winexact-compares. --- ast.c | 1 + ast.h | 1 + doc/gmqcc.1 | 4 + fold.c | 558 ++++++++++++++++++++++++++++++++++++++++++---- gmqcc.ini.example | 6 + intrin.c | 8 +- opts.c | 1 + opts.def | 1 + parser.c | 18 +- parser.h | 2 +- 10 files changed, 542 insertions(+), 58 deletions(-) diff --git a/ast.c b/ast.c index d7e3d7a..5c38fa9 100644 --- a/ast.c +++ b/ast.c @@ -359,6 +359,7 @@ ast_value* ast_value_new(lex_ctx_t ctx, const char *name, int t) self->cvq = CV_NONE; self->hasvalue = false; self->isimm = false; + self->inexact = false; self->uses = 0; memset(&self->constval, 0, sizeof(self->constval)); self->initlist = NULL; diff --git a/ast.h b/ast.h index 7cc7a6c..4720b40 100644 --- a/ast.h +++ b/ast.h @@ -214,6 +214,7 @@ struct ast_value_s bool isfield; /* this declares a field */ bool isimm; /* an immediate, not just const */ bool hasvalue; + bool inexact; /* inexact coming from folded expression */ basic_value_t constval; /* for TYPE_ARRAY we have an optional vector * of constants when an initializer list diff --git a/doc/gmqcc.1 b/doc/gmqcc.1 index b5d39ea..e88acd9 100644 --- a/doc/gmqcc.1 +++ b/doc/gmqcc.1 @@ -344,6 +344,10 @@ will search its intrinsics table for something that matches that function name by appending "__builtin_" to it. This behaviour may be unexpected, so enabling this will produce a diagnostic when such a function is resolved to a builtin. +.It Fl W Ns Cm inexact-compares +When comparing an inexact value such as `1.0/3.0' the result is +pathologically wrong. Enabling this will trigger a compiler warning +on such expressions. .El .Sh COMPILE FLAGS .Bl -tag -width Ds diff --git a/fold.c b/fold.c index af37c4c..1cd8dfa 100644 --- a/fold.c +++ b/fold.c @@ -29,6 +29,409 @@ #define FOLD_STRING_UNTRANSLATE_HTSIZE 1024 #define FOLD_STRING_DOTRANSLATE_HTSIZE 1024 +/* + * The constant folder is also responsible for validating if the constant + * expressions produce valid results. We cannot trust the FPU control + * unit for these exceptions because setting FPU control words might not + * work. Systems can set and enforce FPU modes of operation. It's also valid + * for libc's to simply ignore FPU exceptions. For instance ARM CPUs in + * glibc. We implement some trivial and IEE 754 conformant functions which + * emulate those operations. This is an entierly optional compiler feature + * which shouldn't be enabled for anything other than performing strict + * passes on constant expressions since it's quite slow. + */ +typedef uint32_t sfloat_t; + +typedef union { + qcfloat_t f; + sfloat_t s; +} sfloat_cast_t; + +typedef enum { + SFLOAT_INVALID = 1, + SFLOAT_DIVBYZERO = 4, + SFLOAT_OVERFLOW = 8, + SFLOAT_UNDERFLOW = 16, + SFLOAT_INEXACT = 32 +} sfloat_exceptionflags_t; + +typedef enum { + SFLOAT_ROUND_NEAREST_EVEN, + SFLOAT_ROUND_DOWN, + SFLOAT_ROUND_UP, + SFLOAT_ROUND_TO_ZERO +} sfloat_roundingmode_t; + +typedef enum { + SFLOAT_TAFTER, + SFLOAT_TBEFORE +} sfloat_tdetect_t; + +typedef struct { + sfloat_roundingmode_t roundingmode; + sfloat_exceptionflags_t exceptionflags; + sfloat_tdetect_t tiny; +} sfloat_state_t; + +/* The value of a NaN */ +#define SFLOAT_NAN 0xFFC00000 +/* Count of leading zero bits before the most-significand 1 bit. */ +#define SFLOAT_CLZ(X, SUB) \ + (__builtin_clz((X)) - (SUB)) +/* Test if NaN */ +#define SFLOAT_ISNAN(A) \ + (0xFF000000 < (uint32_t)((A) << 1)) +/* Test if signaling NaN */ +#define SFLOAT_ISSNAN(A) \ + (((((A) >> 22) & 0x1FF) == 0x1FE) && ((A) & 0x003FFFFF)) +/* Raise exception */ +#define SFLOAT_RAISE(STATE, FLAGS) \ + ((STATE)->exceptionflags |= (FLAGS)) +/* + * Shifts `A' right `COUNT' bits. Non-zero bits are stored in LSB. Size + * sets the arbitrarly-large limit. + */ +#define SFLOAT_SHIFT(SIZE, A, COUNT, Z) \ + *(Z) = ((COUNT) == 0) \ + ? 1 \ + : (((COUNT) < (SIZE)) \ + ? ((A) >> (COUNT)) | (((A) << ((-(COUNT)) & ((SIZE) - 1))) != 0) \ + : ((A) != 0)) +/* Extract fractional component */ +#define SFLOAT_EXTRACT_FRAC(X) \ + ((uint32_t)((X) & 0x007FFFFF)) +/* Extract exponent component */ +#define SFLOAT_EXTRACT_EXP(X) \ + ((int16_t)((X) >> 23) & 0xFF) +/* Extract sign bit */ +#define SFLOAT_EXTRACT_SIGN(X) \ + ((X) >> 31) +/* Normalize a subnormal */ +#define SFLOAT_SUBNORMALIZE(SA, Z, SZ) \ + (void)(*(SZ) = (SA) << SFLOAT_CLZ((SA), 8), *(SZ) = 1 - SFLOAT_CLZ((SA), 8)) +/* + * Pack sign, exponent and significand and produce a float. + * + * Integer portions of the significand are added to the exponent. The + * exponent input should be one less than the result exponent whenever + * the significand is normalized since normalized significand will + * always have an integer portion of value one. + */ +#define SFLOAT_PACK(SIGN, EXP, SIG) \ + (sfloat_t)((((uint32_t)(SIGN)) << 31) + (((uint32_t)(EXP)) << 23) + (SIG)) + +/* Calculate NaN. If either operands are signaling then raise invalid */ +static sfloat_t sfloat_propagate_nan(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + bool isnan_a = SFLOAT_ISNAN(a); + bool issnan_a = SFLOAT_ISSNAN(a); + bool isnan_b = SFLOAT_ISNAN(b); + bool issnan_b = SFLOAT_ISSNAN(b); + + a |= 0x00400000; + b |= 0x00400000; + + if (issnan_a | issnan_b) + SFLOAT_RAISE(state, SFLOAT_INEXACT); + if (issnan_a) { + if (issnan_b) + goto larger; + return isnan_b ? b : a; + } else if (isnan_a) { + if (issnan_b | !isnan_b) + return a; +larger: + if ((uint32_t)(a << 1) < (uint32_t)(b << 1)) return b; + if ((uint32_t)(b << 1) < (uint32_t)(a << 1)) return a; + return (a < b) ? a : b; + } + return b; +} + +/* Round and pack */ +static sfloat_t SFLOAT_PACK_round(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) { + sfloat_roundingmode_t mode = state->roundingmode; + bool even = !!(mode == SFLOAT_ROUND_NEAREST_EVEN); + unsigned char increment = 0x40; + unsigned char bits = sig_z & 0x7F; + + if (!even) { + if (mode == SFLOAT_ROUND_TO_ZERO) + increment = 0; + else { + increment = 0x7F; + if (sign_z) { + if (mode == SFLOAT_ROUND_UP) + increment = 0; + } else { + if (mode == SFLOAT_ROUND_DOWN) + increment = 0; + } + } + } + + if (0xFD <= (uint16_t)exp_z) { + if ((0xFD < exp_z) || ((exp_z == 0xFD) && ((int32_t)(sig_z + increment) < 0))) { + SFLOAT_RAISE(state, SFLOAT_OVERFLOW | SFLOAT_INEXACT); + return SFLOAT_PACK(sign_z, 0xFF, 0) - (increment == 0); + } + if (exp_z < 0) { + /* Check for underflow */ + bool tiny = (state->tiny == SFLOAT_TBEFORE) || (exp_z < -1) || (sig_z + increment < 0x80000000); + SFLOAT_SHIFT(32, sig_z, -exp_z, &sig_z); + exp_z = 0; + bits = sig_z & 0x7F; + if (tiny && bits) + SFLOAT_RAISE(state, SFLOAT_UNDERFLOW); + } + } + + /* + * Significand has point between bits 30 and 29, 7 bits to the left of + * the usual place. This shifted significand has to be normalized + * or smaller, if it isn't the exponent must be zero, in which case + * no rounding occurs since the result will be a subnormal. + */ + if (bits) + SFLOAT_RAISE(state, SFLOAT_INEXACT); + sig_z = (sig_z + increment) >> 7; + sig_z &= ~(((bits ^ 0x40) == 0) & even); + if (sig_z == 0) + exp_z = 0; + return SFLOAT_PACK(sign_z, exp_z, sig_z); +} + +/* Normalized round and pack */ +static sfloat_t SFLOAT_PACK_normal(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) { + unsigned char c = SFLOAT_CLZ(sig_z, 1); + return SFLOAT_PACK_round(state, sign_z, exp_z - c, sig_z << c); +} + +static sfloat_t sfloat_add_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) { + int16_t exp_a = SFLOAT_EXTRACT_EXP(a); + int16_t exp_b = SFLOAT_EXTRACT_EXP(b); + int16_t exp_z = 0; + int16_t exp_d = exp_a - exp_b; + uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 6; + uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 6; + uint32_t sig_z = 0; + + if (0 < exp_d) { + if (exp_a == 0xFF) + return sig_a ? sfloat_propagate_nan(state, a, b) : a; + if (exp_b == 0) + --exp_d; + else + sig_b |= 0x20000000; + SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b); + exp_z = exp_a; + } else if (exp_d < 0) { + if (exp_b == 0xFF) + return sig_b ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0xFF, 0); + if (exp_a == 0) + ++exp_d; + else + sig_a |= 0x20000000; + SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a); + exp_z = exp_b; + } else { + if (exp_a == 0xFF) + return (sig_a | sig_b) ? sfloat_propagate_nan(state, a, b) : a; + if (exp_a == 0) + return SFLOAT_PACK(sign_z, 0, (sig_a + sig_b) >> 6); + sig_z = 0x40000000 + sig_a + sig_b; + exp_z = exp_a; + goto end; + } + sig_a |= 0x20000000; + sig_z = (sig_a + sig_b) << 1; + --exp_z; + if ((int32_t)sig_z < 0) { + sig_z = sig_a + sig_b; + ++exp_z; + } +end: + return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); +} + +static sfloat_t sfloat_sub_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) { + int16_t exp_a = SFLOAT_EXTRACT_EXP(a); + int16_t exp_b = SFLOAT_EXTRACT_EXP(b); + int16_t exp_z = 0; + int16_t exp_d = exp_a - exp_b; + uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 7; + uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 7; + uint32_t sig_z = 0; + + if (0 < exp_d) goto exp_greater_a; + if (exp_d < 0) goto exp_greater_b; + + if (exp_a == 0xFF) { + if (sig_a | sig_b) + return sfloat_propagate_nan(state, a, b); + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + + if (exp_a == 0) + exp_a = exp_b = 1; + + if (sig_b < sig_a) goto greater_a; + if (sig_a < sig_b) goto greater_b; + + return SFLOAT_PACK(state->roundingmode == SFLOAT_ROUND_DOWN, 0, 0); + +exp_greater_b: + if (exp_b == 0xFF) + return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z ^ 1, 0xFF, 0); + if (exp_a == 0) + ++exp_d; + else + sig_a |= 0x40000000; + SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a); + sig_b |= 0x40000000; +greater_b: + sig_z = sig_b - sig_a; + exp_z = exp_b; + sign_z ^= 1; + goto end; + +exp_greater_a: + if (exp_a == 0xFF) + return (sig_a) ? sfloat_propagate_nan(state, a, b) : a; + if (exp_b == 0) + --exp_d; + else + sig_b |= 0x40000000; + SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b); + sig_a |= 0x40000000; +greater_a: + sig_z = sig_a - sig_b; + exp_z = exp_a; + +end: + --exp_z; + return SFLOAT_PACK_normal(state, sign_z, exp_z, sig_z); +} + +static GMQCC_INLINE sfloat_t sfloat_add(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + bool sign_a = SFLOAT_EXTRACT_SIGN(a); + bool sign_b = SFLOAT_EXTRACT_SIGN(b); + return (sign_a == sign_b) ? sfloat_add_impl(state, a, b, sign_a) + : sfloat_sub_impl(state, a, b, sign_a); +} + +static GMQCC_INLINE sfloat_t sfloat_sub(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + bool sign_a = SFLOAT_EXTRACT_SIGN(a); + bool sign_b = SFLOAT_EXTRACT_SIGN(b); + return (sign_a == sign_b) ? sfloat_sub_impl(state, a, b, sign_a) + : sfloat_add_impl(state, a, b, sign_a); +} + +static sfloat_t sfloat_mul(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + int16_t exp_a = SFLOAT_EXTRACT_EXP(a); + int16_t exp_b = SFLOAT_EXTRACT_EXP(b); + int16_t exp_z = 0; + uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a); + uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b); + uint32_t sig_z = 0; + uint64_t sig_z64 = 0; + bool sign_a = SFLOAT_EXTRACT_SIGN(a); + bool sign_b = SFLOAT_EXTRACT_SIGN(b); + bool sign_z = sign_a ^ sign_b; + + if (exp_a == 0xFF) { + if (sig_a || ((exp_b == 0xFF) && sig_b)) + return sfloat_propagate_nan(state, a, b); + if ((exp_b | sig_b) == 0) { + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + return SFLOAT_PACK(sign_z, 0xFF, 0); + } + if (exp_b == 0xFF) { + if (sig_b) + return sfloat_propagate_nan(state, a, b); + if ((exp_a | sig_a) == 0) { + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + return SFLOAT_PACK(sign_z, 0xFF, 0); + } + if (exp_a == 0) { + if (sig_a == 0) + return SFLOAT_PACK(sign_z, 0, 0); + SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a); + } + if (exp_b == 0) { + if (sig_b == 0) + return SFLOAT_PACK(sign_z, 0, 0); + SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b); + } + exp_z = exp_a + exp_b - 0x7F; + sig_a = (sig_a | 0x00800000) << 7; + sig_b = (sig_b | 0x00800000) << 8; + SFLOAT_SHIFT(64, ((uint64_t)sig_a) * sig_b, 32, &sig_z64); + sig_z = sig_z64; + if (0 <= (int32_t)(sig_z << 1)) { + sig_z <<= 1; + --exp_z; + } + return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); +} + +static sfloat_t sfloat_div(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + int16_t exp_a = SFLOAT_EXTRACT_EXP(a); + int16_t exp_b = SFLOAT_EXTRACT_EXP(b); + int16_t exp_z = 0; + uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a); + uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b); + uint32_t sig_z = 0; + bool sign_a = SFLOAT_EXTRACT_SIGN(a); + bool sign_b = SFLOAT_EXTRACT_SIGN(b); + bool sign_z = sign_a ^ sign_b; + + if (exp_a == 0xFF) { + if (sig_a) + return sfloat_propagate_nan(state, a, b); + if (exp_b == 0xFF) { + if (sig_b) + return sfloat_propagate_nan(state, a, b); + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + return SFLOAT_PACK(sign_z, 0xFF, 0); + } + if (exp_b == 0xFF) + return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0, 0); + if (exp_b == 0) { + if (sig_b == 0) { + if ((exp_a | sig_a) == 0) { + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + SFLOAT_RAISE(state, SFLOAT_DIVBYZERO); + return SFLOAT_PACK(sign_z, 0xFF, 0); + } + SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b); + } + if (exp_a == 0) { + if (sig_a == 0) + return SFLOAT_PACK(sign_z, 0, 0); + SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a); + } + exp_z = exp_a - exp_b + 0x7D; + sig_a = (sig_a | 0x00800000) << 7; + sig_b = (sig_b | 0x00800000) << 8; + if (sig_b <= (sig_a + sig_a)) { + sig_a >>= 1; + ++exp_z; + } + sig_z = (((uint64_t)sig_a) << 32) / sig_b; + if ((sig_z & 0x3F) == 0) + sig_z |= ((uint64_t)sig_b * sig_z != ((uint64_t)sig_a) << 32); + return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); +} + /* * There is two stages to constant folding in GMQCC: there is the parse * stage constant folding, where, witht he help of the AST, operator @@ -227,10 +630,10 @@ fold_t *fold_init(parser_t *parser) { * prime the tables with common constant values at constant * locations. */ - (void)fold_constgen_float (fold, 0.0f); - (void)fold_constgen_float (fold, 1.0f); - (void)fold_constgen_float (fold, -1.0f); - (void)fold_constgen_float (fold, 2.0f); + (void)fold_constgen_float (fold, 0.0f, false); + (void)fold_constgen_float (fold, 1.0f, false); + (void)fold_constgen_float (fold, -1.0f, false); + (void)fold_constgen_float (fold, 2.0f, false); (void)fold_constgen_vector(fold, vec3_create(0.0f, 0.0f, 0.0f)); (void)fold_constgen_vector(fold, vec3_create(-1.0f, -1.0f, -1.0f)); @@ -275,7 +678,7 @@ void fold_cleanup(fold_t *fold) { mem_d(fold); } -ast_expression *fold_constgen_float(fold_t *fold, qcfloat_t value) { +ast_expression *fold_constgen_float(fold_t *fold, qcfloat_t value, bool inexact) { ast_value *out = NULL; size_t i; @@ -287,6 +690,7 @@ ast_expression *fold_constgen_float(fold_t *fold, qcfloat_t value) { out = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_FLOAT); out->cvq = CV_CONST; out->hasvalue = true; + out->inexact = inexact; out->constval.vfloat = value; vec_push(fold->imm_float, out); @@ -372,7 +776,7 @@ static GMQCC_INLINE ast_expression *fold_op_mul_vec(fold_t *fold, vec3_t vec, as out->node.keep = false; ((ast_member*)out)->rvalue = true; if (x != -1.0f) - return (ast_expression*)ast_binary_new(fold_ctx(fold), INSTR_MUL_F, fold_constgen_float(fold, x), out); + return (ast_expression*)ast_binary_new(fold_ctx(fold), INSTR_MUL_F, fold_constgen_float(fold, x, false), out); } return NULL; } @@ -381,7 +785,7 @@ static GMQCC_INLINE ast_expression *fold_op_mul_vec(fold_t *fold, vec3_t vec, as static GMQCC_INLINE ast_expression *fold_op_neg(fold_t *fold, ast_value *a) { if (isfloat(a)) { if (fold_can_1(a)) - return fold_constgen_float(fold, -fold_immvalue_float(a)); + return fold_constgen_float(fold, -fold_immvalue_float(a), false); } else if (isvector(a)) { if (fold_can_1(a)) return fold_constgen_vector(fold, vec3_neg(fold_immvalue_vector(a))); @@ -392,25 +796,72 @@ static GMQCC_INLINE ast_expression *fold_op_neg(fold_t *fold, ast_value *a) { static GMQCC_INLINE ast_expression *fold_op_not(fold_t *fold, ast_value *a) { if (isfloat(a)) { if (fold_can_1(a)) - return fold_constgen_float(fold, !fold_immvalue_float(a)); + return fold_constgen_float(fold, !fold_immvalue_float(a), false); } else if (isvector(a)) { if (fold_can_1(a)) - return fold_constgen_float(fold, vec3_notf(fold_immvalue_vector(a))); + return fold_constgen_float(fold, vec3_notf(fold_immvalue_vector(a)), false); } else if (isstring(a)) { if (fold_can_1(a)) { if (OPTS_FLAG(TRUE_EMPTY_STRINGS)) - return fold_constgen_float(fold, !fold_immvalue_string(a)); + return fold_constgen_float(fold, !fold_immvalue_string(a), false); else - return fold_constgen_float(fold, !fold_immvalue_string(a) || !*fold_immvalue_string(a)); + return fold_constgen_float(fold, !fold_immvalue_string(a) || !*fold_immvalue_string(a), false); } } return NULL; } +static bool fold_check_except_float(sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t), + fold_t *fold, + ast_value *a, + ast_value *b) +{ + sfloat_state_t s; + sfloat_cast_t ca; + sfloat_cast_t cb; + + s.roundingmode = SFLOAT_ROUND_NEAREST_EVEN; + s.tiny = SFLOAT_TBEFORE; + s.exceptionflags = 0; + ca.f = fold_immvalue_float(a); + cb.f = fold_immvalue_float(b); + + callback(&s, ca.s, cb.s); + if (s.exceptionflags == 0) + return false; + + if (s.exceptionflags & SFLOAT_DIVBYZERO) + compile_error(fold_ctx(fold), "division by zero"); +#if 0 + /* + * To be enabled once softfloat implementations for stuff like sqrt() + * exist + */ + if (s.exceptionflags & SFLOAT_INVALID) + compile_error(fold_ctx(fold), "invalid argument"); +#endif + + if (s.exceptionflags & SFLOAT_OVERFLOW) + compile_error(fold_ctx(fold), "arithmetic overflow"); + if (s.exceptionflags & SFLOAT_UNDERFLOW) + compile_error(fold_ctx(fold), "arithmetic underflow"); + + return s.exceptionflags == SFLOAT_INEXACT; +} + +static bool fold_check_inexact_float(fold_t *fold, ast_value *a, ast_value *b) { + lex_ctx_t ctx = fold_ctx(fold); + if (!a->inexact && !b->inexact) + return false; + return compile_warning(ctx, WARN_INEXACT_COMPARES, "inexact value in comparison"); +} + static GMQCC_INLINE ast_expression *fold_op_add(fold_t *fold, ast_value *a, ast_value *b) { if (isfloat(a)) { - if (fold_can_2(a, b)) - return fold_constgen_float(fold, fold_immvalue_float(a) + fold_immvalue_float(b)); + if (fold_can_2(a, b)) { + bool inexact = fold_check_except_float(&sfloat_add, fold, a, b); + return fold_constgen_float(fold, fold_immvalue_float(a) + fold_immvalue_float(b), inexact); + } } else if (isvector(a)) { if (fold_can_2(a, b)) return fold_constgen_vector(fold, vec3_add(fold_immvalue_vector(a), fold_immvalue_vector(b))); @@ -420,8 +871,10 @@ static GMQCC_INLINE ast_expression *fold_op_add(fold_t *fold, ast_value *a, ast_ static GMQCC_INLINE ast_expression *fold_op_sub(fold_t *fold, ast_value *a, ast_value *b) { if (isfloat(a)) { - if (fold_can_2(a, b)) - return fold_constgen_float(fold, fold_immvalue_float(a) - fold_immvalue_float(b)); + if (fold_can_2(a, b)) { + bool inexact = fold_check_except_float(&sfloat_sub, fold, a, b); + return fold_constgen_float(fold, fold_immvalue_float(a) - fold_immvalue_float(b), inexact); + } } else if (isvector(a)) { if (fold_can_2(a, b)) return fold_constgen_vector(fold, vec3_sub(fold_immvalue_vector(a), fold_immvalue_vector(b))); @@ -435,8 +888,10 @@ static GMQCC_INLINE ast_expression *fold_op_mul(fold_t *fold, ast_value *a, ast_ if (fold_can_2(a, b)) return fold_constgen_vector(fold, vec3_mulvf(fold_immvalue_vector(b), fold_immvalue_float(a))); } else { - if (fold_can_2(a, b)) - return fold_constgen_float(fold, fold_immvalue_float(a) * fold_immvalue_float(b)); + if (fold_can_2(a, b)) { + bool inexact = fold_check_except_float(&sfloat_mul, fold, a, b); + return fold_constgen_float(fold, fold_immvalue_float(a) * fold_immvalue_float(b), inexact); + } } } else if (isvector(a)) { if (isfloat(b)) { @@ -444,7 +899,7 @@ static GMQCC_INLINE ast_expression *fold_op_mul(fold_t *fold, ast_value *a, ast_ return fold_constgen_vector(fold, vec3_mulvf(fold_immvalue_vector(a), fold_immvalue_float(b))); } else { if (fold_can_2(a, b)) { - return fold_constgen_float(fold, vec3_mulvv(fold_immvalue_vector(a), fold_immvalue_vector(b))); + return fold_constgen_float(fold, vec3_mulvv(fold_immvalue_vector(a), fold_immvalue_vector(b)), false); } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(a)) { ast_expression *out; if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "xyz"))) return out; @@ -464,13 +919,14 @@ static GMQCC_INLINE ast_expression *fold_op_mul(fold_t *fold, ast_value *a, ast_ static GMQCC_INLINE ast_expression *fold_op_div(fold_t *fold, ast_value *a, ast_value *b) { if (isfloat(a)) { if (fold_can_2(a, b)) { - return fold_constgen_float(fold, fold_immvalue_float(a) / fold_immvalue_float(b)); + bool inexact = fold_check_except_float(&sfloat_div, fold, a, b); + return fold_constgen_float(fold, fold_immvalue_float(a) / fold_immvalue_float(b), inexact); } else if (fold_can_1(b)) { return (ast_expression*)ast_binary_new( fold_ctx(fold), INSTR_MUL_F, (ast_expression*)a, - fold_constgen_float(fold, 1.0f / fold_immvalue_float(b)) + fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false) ); } } else if (isvector(a)) { @@ -482,7 +938,7 @@ static GMQCC_INLINE ast_expression *fold_op_div(fold_t *fold, ast_value *a, ast_ INSTR_MUL_VF, (ast_expression*)a, (fold_can_1(b)) - ? (ast_expression*)fold_constgen_float(fold, 1.0f / fold_immvalue_float(b)) + ? (ast_expression*)fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false) : (ast_expression*)ast_binary_new( fold_ctx(fold), INSTR_DIV_F, @@ -497,14 +953,14 @@ static GMQCC_INLINE ast_expression *fold_op_div(fold_t *fold, ast_value *a, ast_ static GMQCC_INLINE ast_expression *fold_op_mod(fold_t *fold, ast_value *a, ast_value *b) { return (fold_can_2(a, b)) - ? fold_constgen_float(fold, fmod(fold_immvalue_float(a), fold_immvalue_float(b))) + ? fold_constgen_float(fold, fmod(fold_immvalue_float(a), fold_immvalue_float(b)), false) : NULL; } static GMQCC_INLINE ast_expression *fold_op_bor(fold_t *fold, ast_value *a, ast_value *b) { if (isfloat(a)) { if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) | ((qcint_t)fold_immvalue_float(b)))); + return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) | ((qcint_t)fold_immvalue_float(b))), false); } else { if (isvector(b)) { if (fold_can_2(a, b)) @@ -520,7 +976,7 @@ static GMQCC_INLINE ast_expression *fold_op_bor(fold_t *fold, ast_value *a, ast_ static GMQCC_INLINE ast_expression *fold_op_band(fold_t *fold, ast_value *a, ast_value *b) { if (isfloat(a)) { if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) & ((qcint_t)fold_immvalue_float(b)))); + return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) & ((qcint_t)fold_immvalue_float(b))), false); } else { if (isvector(b)) { if (fold_can_2(a, b)) @@ -536,7 +992,7 @@ static GMQCC_INLINE ast_expression *fold_op_band(fold_t *fold, ast_value *a, ast static GMQCC_INLINE ast_expression *fold_op_xor(fold_t *fold, ast_value *a, ast_value *b) { if (isfloat(a)) { if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) ^ ((qcint_t)fold_immvalue_float(b)))); + return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) ^ ((qcint_t)fold_immvalue_float(b))), false); } else { if (fold_can_2(a, b)) { if (isvector(b)) @@ -550,13 +1006,13 @@ static GMQCC_INLINE ast_expression *fold_op_xor(fold_t *fold, ast_value *a, ast_ static GMQCC_INLINE ast_expression *fold_op_lshift(fold_t *fold, ast_value *a, ast_value *b) { if (fold_can_2(a, b) && isfloats(a, b)) - return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) * powf(2.0f, fold_immvalue_float(b)))); + return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) * powf(2.0f, fold_immvalue_float(b))), false); return NULL; } static GMQCC_INLINE ast_expression *fold_op_rshift(fold_t *fold, ast_value *a, ast_value *b) { if (fold_can_2(a, b) && isfloats(a, b)) - return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) / powf(2.0f, fold_immvalue_float(b)))); + return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) / powf(2.0f, fold_immvalue_float(b))), false); return NULL; } @@ -573,7 +1029,8 @@ static GMQCC_INLINE ast_expression *fold_op_andor(fold_t *fold, ast_value *a, as ((expr) ? (fold_immediate_true(fold, a) || fold_immediate_true(fold, b)) : (fold_immediate_true(fold, a) && fold_immediate_true(fold, b))) ? 1 - : 0 + : 0, + false ); } } @@ -591,12 +1048,13 @@ static GMQCC_INLINE ast_expression *fold_op_tern(fold_t *fold, ast_value *a, ast static GMQCC_INLINE ast_expression *fold_op_exp(fold_t *fold, ast_value *a, ast_value *b) { if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)powf(fold_immvalue_float(a), fold_immvalue_float(b))); + return fold_constgen_float(fold, (qcfloat_t)powf(fold_immvalue_float(a), fold_immvalue_float(b)), false); return NULL; } static GMQCC_INLINE ast_expression *fold_op_lteqgt(fold_t *fold, ast_value *a, ast_value *b) { if (fold_can_2(a,b)) { + fold_check_inexact_float(fold, a, b); if (fold_immvalue_float(a) < fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[2]; if (fold_immvalue_float(a) == fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[0]; if (fold_immvalue_float(a) > fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[1]; @@ -604,11 +1062,21 @@ static GMQCC_INLINE ast_expression *fold_op_lteqgt(fold_t *fold, ast_value *a, a return NULL; } +static GMQCC_INLINE ast_expression *fold_op_ltgt(fold_t *fold, ast_value *a, ast_value *b, bool lt) { + if (fold_can_2(a, b)) { + fold_check_inexact_float(fold, a, b); + return (lt) ? (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) < fold_immvalue_float(b))] + : (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) > fold_immvalue_float(b))]; + } + return NULL; +} + static GMQCC_INLINE ast_expression *fold_op_cmp(fold_t *fold, ast_value *a, ast_value *b, bool ne) { if (fold_can_2(a, b)) { if (isfloat(a) && isfloat(b)) { float la = fold_immvalue_float(a); float lb = fold_immvalue_float(b); + fold_check_inexact_float(fold, a, b); return (ast_expression*)fold->imm_float[!(ne ? la == lb : la != lb)]; } if (isvector(a) && isvector(b)) { vec3_t la = fold_immvalue_vector(a); @@ -622,7 +1090,7 @@ static GMQCC_INLINE ast_expression *fold_op_cmp(fold_t *fold, ast_value *a, ast_ static GMQCC_INLINE ast_expression *fold_op_bnot(fold_t *fold, ast_value *a) { if (isfloat(a)) { if (fold_can_1(a)) - return fold_constgen_float(fold, -1-fold_immvalue_float(a)); + return fold_constgen_float(fold, -1-fold_immvalue_float(a), false); } else { if (isvector(a)) { if (fold_can_1(a)) @@ -685,6 +1153,8 @@ ast_expression *fold_op(fold_t *fold, const oper_info *info, ast_expression **op fold_op_case(1, ('|'), bor, (fold, a, b)); fold_op_case(1, ('&'), band, (fold, a, b)); fold_op_case(1, ('^'), xor, (fold, a, b)); + fold_op_case(1, ('<'), ltgt, (fold, a, b, true)); + fold_op_case(1, ('>'), ltgt, (fold, a, b, false)); fold_op_case(2, ('<', '<'), lshift, (fold, a, b)); fold_op_case(2, ('>', '>'), rshift, (fold, a, b)); fold_op_case(2, ('|', '|'), andor, (fold, a, b, true)); @@ -708,46 +1178,46 @@ ast_expression *fold_op(fold_t *fold, const oper_info *info, ast_expression **op * and a generic selection function. */ static GMQCC_INLINE ast_expression *fold_intrin_isfinite(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, isfinite(fold_immvalue_float(a))); + return fold_constgen_float(fold, isfinite(fold_immvalue_float(a)), false); } static GMQCC_INLINE ast_expression *fold_intrin_isinf(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, isinf(fold_immvalue_float(a))); + return fold_constgen_float(fold, isinf(fold_immvalue_float(a)), false); } static GMQCC_INLINE ast_expression *fold_intrin_isnan(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, isnan(fold_immvalue_float(a))); + return fold_constgen_float(fold, isnan(fold_immvalue_float(a)), false); } static GMQCC_INLINE ast_expression *fold_intrin_isnormal(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, isnormal(fold_immvalue_float(a))); + return fold_constgen_float(fold, isnormal(fold_immvalue_float(a)), false); } static GMQCC_INLINE ast_expression *fold_intrin_signbit(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, signbit(fold_immvalue_float(a))); + return fold_constgen_float(fold, signbit(fold_immvalue_float(a)), false); } static GMQCC_INLINE ast_expression *fold_intirn_acosh(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, acoshf(fold_immvalue_float(a))); + return fold_constgen_float(fold, acoshf(fold_immvalue_float(a)), false); } static GMQCC_INLINE ast_expression *fold_intrin_asinh(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, asinhf(fold_immvalue_float(a))); + return fold_constgen_float(fold, asinhf(fold_immvalue_float(a)), false); } static GMQCC_INLINE ast_expression *fold_intrin_atanh(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, (float)atanh(fold_immvalue_float(a))); + return fold_constgen_float(fold, (float)atanh(fold_immvalue_float(a)), false); } static GMQCC_INLINE ast_expression *fold_intrin_exp(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, expf(fold_immvalue_float(a))); + return fold_constgen_float(fold, expf(fold_immvalue_float(a)), false); } static GMQCC_INLINE ast_expression *fold_intrin_exp2(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, exp2f(fold_immvalue_float(a))); + return fold_constgen_float(fold, exp2f(fold_immvalue_float(a)), false); } static GMQCC_INLINE ast_expression *fold_intrin_expm1(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, expm1f(fold_immvalue_float(a))); + return fold_constgen_float(fold, expm1f(fold_immvalue_float(a)), false); } static GMQCC_INLINE ast_expression *fold_intrin_mod(fold_t *fold, ast_value *lhs, ast_value *rhs) { - return fold_constgen_float(fold, fmodf(fold_immvalue_float(lhs), fold_immvalue_float(rhs))); + return fold_constgen_float(fold, fmodf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false); } static GMQCC_INLINE ast_expression *fold_intrin_pow(fold_t *fold, ast_value *lhs, ast_value *rhs) { - return fold_constgen_float(fold, powf(fold_immvalue_float(lhs), fold_immvalue_float(rhs))); + return fold_constgen_float(fold, powf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false); } static GMQCC_INLINE ast_expression *fold_intrin_fabs(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, fabsf(fold_immvalue_float(a))); + return fold_constgen_float(fold, fabsf(fold_immvalue_float(a)), false); } diff --git a/gmqcc.ini.example b/gmqcc.ini.example index 20680d3..a12628e 100644 --- a/gmqcc.ini.example +++ b/gmqcc.ini.example @@ -568,6 +568,12 @@ BUILTINS = true + #When comparing an inexact value such as `1.0/3.0' the result is + #pathologically wrong. Enabling this will trigger a compiler warning + #on such expressions. + INEXACT_COMPARES = true + + [optimizations] #Some general peephole optimizations. For instance the code `a = b #+ c` typically generates 2 instructions, an ADD and a STORE. This diff --git a/intrin.c b/intrin.c index 8c78ba7..c1b84ed 100644 --- a/intrin.c +++ b/intrin.c @@ -422,7 +422,7 @@ static ast_expression *intrin_atanh(intrin_t *intrin) { (ast_expression*)ast_binary_new( intrin_ctx(intrin), INSTR_MUL_F, - (ast_expression*)fold_constgen_float(intrin->fold, 0.5), + (ast_expression*)fold_constgen_float(intrin->fold, 0.5, false), (ast_expression*)calllog ) ); @@ -496,7 +496,7 @@ static ast_expression *intrin_exp(intrin_t *intrin) { intrin_ctx(intrin), INSTR_LT, (ast_expression*)i, - (ast_expression*)fold_constgen_float(intrin->fold, 200.0f) + (ast_expression*)fold_constgen_float(intrin->fold, 200.0f, false) ), false, NULL, @@ -1027,7 +1027,7 @@ static ast_expression *intrin_pow(intrin_t *intrin) { intrin_ctx(intrin), INSTR_GT, (ast_expression*)callfabs, - (ast_expression*)fold_constgen_float(intrin->fold, QC_POW_EPSILON) + (ast_expression*)fold_constgen_float(intrin->fold, QC_POW_EPSILON, false) ), /* pre not */ false, @@ -1911,7 +1911,7 @@ static ast_expression *intrin_log_variant(intrin_t *intrin, const char *name, fl vec_push(value->expression.params, arg1); vec_push(callln->params, (ast_expression*)arg1); - vec_push(callln->params, (ast_expression*)fold_constgen_float(intrin->fold, base)); + vec_push(callln->params, (ast_expression*)fold_constgen_float(intrin->fold, base, false)); vec_push(body->exprs, (ast_expression*)ast_return_new( diff --git a/opts.c b/opts.c index 7998024..ddb48f7 100644 --- a/opts.c +++ b/opts.c @@ -93,6 +93,7 @@ static void opts_setdefault(void) { opts_set(opts.warn, WARN_CONST_OVERWRITE, true); opts_set(opts.warn, WARN_DIRECTIVE_INMACRO, true); opts_set(opts.warn, WARN_BUILTINS, true); + opts_set(opts.warn, WARN_INEXACT_COMPARES, true); /* flags */ opts_set(opts.flags, ADJUST_VECTOR_FIELDS, true); diff --git a/opts.def b/opts.def index b229245..5aa52ab 100644 --- a/opts.def +++ b/opts.def @@ -98,6 +98,7 @@ GMQCC_DEFINE_FLAG(CONST_OVERWRITE) GMQCC_DEFINE_FLAG(DIRECTIVE_INMACRO) GMQCC_DEFINE_FLAG(BUILTINS) + GMQCC_DEFINE_FLAG(INEXACT_COMPARES) #endif #ifdef GMQCC_TYPE_OPTIMIZATIONS diff --git a/parser.c b/parser.c index eaa1490..354ad03 100644 --- a/parser.c +++ b/parser.c @@ -1293,7 +1293,7 @@ static bool parser_close_call(parser_t *parser, shunt *sy) if ((fun->flags & AST_FLAG_VARIADIC) && !(/*funval->cvq == CV_CONST && */ funval->hasvalue && funval->constval.vfunc->builtin)) { - call->va_count = (ast_expression*)fold_constgen_float(parser->fold, (qcfloat_t)paramcount); + call->va_count = (ast_expression*)fold_constgen_float(parser->fold, (qcfloat_t)paramcount, false); } } @@ -1548,14 +1548,14 @@ static bool parse_sya_operand(parser_t *parser, shunt *sy, bool with_labels) return true; } else if (parser->tok == TOKEN_FLOATCONST) { - ast_expression *val = fold_constgen_float(parser->fold, (parser_token(parser)->constval.f)); + ast_expression *val = fold_constgen_float(parser->fold, (parser_token(parser)->constval.f), false); if (!val) return false; vec_push(sy->out, syexp(parser_ctx(parser), val)); return true; } else if (parser->tok == TOKEN_INTCONST || parser->tok == TOKEN_CHARCONST) { - ast_expression *val = fold_constgen_float(parser->fold, (qcfloat_t)(parser_token(parser)->constval.i)); + ast_expression *val = fold_constgen_float(parser->fold, (qcfloat_t)(parser_token(parser)->constval.i), false); if (!val) return false; vec_push(sy->out, syexp(parser_ctx(parser), val)); @@ -4030,7 +4030,7 @@ static bool parse_function_body(parser_t *parser, ast_value *var) self_think = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_think); time_plus_1 = (ast_expression*)ast_binary_new(ctx, INSTR_ADD_F, - gbl_time, (ast_expression*)fold_constgen_float(parser->fold, 0.1f)); + gbl_time, (ast_expression*)fold_constgen_float(parser->fold, 0.1f, false)); if (!self_frame || !self_nextthink || !self_think || !time_plus_1) { if (self_frame) ast_delete(self_frame); @@ -4155,7 +4155,7 @@ static bool parse_function_body(parser_t *parser, ast_value *var) goto enderrfn; } func->varargs = varargs; - func->fixedparams = (ast_value*)fold_constgen_float(parser->fold, vec_size(var->expression.params)); + func->fixedparams = (ast_value*)fold_constgen_float(parser->fold, vec_size(var->expression.params), false); } parser->function = func; @@ -4213,7 +4213,7 @@ static ast_expression *array_accessor_split( cmp = ast_binary_new(ctx, INSTR_LT, (ast_expression*)index, - (ast_expression*)fold_constgen_float(parser->fold, middle)); + (ast_expression*)fold_constgen_float(parser->fold, middle, false)); if (!cmp) { ast_delete(left); ast_delete(right); @@ -4246,7 +4246,7 @@ static ast_expression *array_setter_node(parser_t *parser, ast_value *array, ast if (value->expression.vtype == TYPE_FIELD && value->expression.next->vtype == TYPE_VECTOR) assignop = INSTR_STORE_V; - subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from)); + subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false)); if (!subscript) return NULL; @@ -4312,7 +4312,7 @@ static ast_expression *array_field_setter_node( if (value->expression.vtype == TYPE_FIELD && value->expression.next->vtype == TYPE_VECTOR) assignop = INSTR_STOREP_V; - subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from)); + subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false)); if (!subscript) return NULL; @@ -4375,7 +4375,7 @@ static ast_expression *array_getter_node(parser_t *parser, ast_value *array, ast ast_return *ret; ast_array_index *subscript; - subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from)); + subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false)); if (!subscript) return NULL; diff --git a/parser.h b/parser.h index 51140be..eb77eff 100644 --- a/parser.h +++ b/parser.h @@ -127,7 +127,7 @@ ast_expression *parser_find_global(parser_t *parser, const char *name); /* fold.c */ fold_t *fold_init (parser_t *); void fold_cleanup (fold_t *); -ast_expression *fold_constgen_float (fold_t *, qcfloat_t); +ast_expression *fold_constgen_float (fold_t *, qcfloat_t, bool); ast_expression *fold_constgen_vector(fold_t *, vec3_t); ast_expression *fold_constgen_string(fold_t *, const char *, bool); bool fold_generate (fold_t *, ir_builder *);