]> git.xonotic.org Git - xonotic/gmqcc.git/commitdiff
Merge branch 'arithmetic_exceptions' into cooking
authorDale Weiler <weilercdale@gmail.com>
Sun, 25 May 2014 07:01:47 +0000 (03:01 -0400)
committerDale Weiler <weilercdale@gmail.com>
Sun, 25 May 2014 07:01:47 +0000 (03:01 -0400)
Conflicts:
doc/gmqcc.1
gmqcc.ini.example
opts.def
parser.c

23 files changed:
BSDmakefile
Makefile
ast.c
ast.h
doc/gmqcc.1
exec.c
fold.c
gmqcc.ini.example
include.mk
intrin.c
opts.c
opts.def
parser.c
parser.h
test.c
tests/arithexcept.qc [new file with mode: 0644]
tests/arithexcept.tmpl [new file with mode: 0644]
tests/arithexcept_of.tmpl [new file with mode: 0644]
tests/arithexcept_uf.tmpl [new file with mode: 0644]
tests/inexact-local.qc [new file with mode: 0644]
tests/inexact-local.tmpl [new file with mode: 0644]
tests/inexact.qc [new file with mode: 0644]
tests/inexact.tmpl [new file with mode: 0644]

index 49385413b3e126023284533bb66adc0940c8e71a..b34cb1e7610c11a4215f29cbce9ff119d5a19e8e 100644 (file)
@@ -11,8 +11,6 @@ GITINFO  :=
     GITINFO != git describe --always
 .endif
 
-CFLAGS   +=  -Wall -Wextra -Werror -Wstrict-aliasing -Wno-attributes
-
 .if $(CC) == clang
     CFLAGS +=   -Weverything\
                 -Wno-padded\
@@ -22,6 +20,7 @@ CFLAGS   +=  -Wall -Wextra -Werror -Wstrict-aliasing -Wno-attributes
                 -Wno-float-equal\
                 -Wno-unknown-warning-option\
                 -Wno-cast-align\
+                -Wno-assign-enum\
                 -pedantic-errors
 .else
 .    if $(CC) != g++
index 3f286e27f6117db926fbdd051dad85abf3433644..f6a21ffe301305671fd17ea32fc945fa0f5f41c9 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -4,8 +4,7 @@ UNAME  ?= $(shell uname)
 CYGWIN  = $(findstring CYGWIN, $(UNAME))
 MINGW   = $(findstring MINGW,  $(UNAME))
 
-CFLAGS += -Wall -Wextra -Werror -Wstrict-aliasing -Wno-attributes
-#turn on tons of warnings if clang is present
+# turn on tons of warnings if clang is present
 # but also turn off the STUPID ONES
 ifeq ($(CC), clang)
        CFLAGS +=                              \
@@ -17,6 +16,7 @@ ifeq ($(CC), clang)
            -Wno-float-equal                   \
            -Wno-unknown-warning-option        \
            -Wno-cast-align                    \
+           -Wno-assign-enum                   \
            -pedantic-errors
 else
        ifneq ($(CC), g++)
diff --git a/ast.c b/ast.c
index 8b3f110f3fd18e2ff1a14222ca6bbbe749fef8fa..24d0479a71f0b6ae956cce8aa3e264ebdff5cc1c 100644 (file)
--- a/ast.c
+++ b/ast.c
@@ -360,6 +360,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 dd7c5c1eadcfa12b3640a880de3a89f65245a30f..d1b250d8abb5cc4a5e2ffc1ebc945e8a13a7d09f 100644 (file)
--- a/ast.h
+++ b/ast.h
@@ -216,6 +216,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
index 66d2e4e4e14e3b50779e9f7f6bdb641c74ca1281..50bce0ecf7cd8adae09f787055a909bf71c8137d 100644 (file)
@@ -349,6 +349,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
@@ -587,6 +591,11 @@ Emulate OP_STATE operations in code rather than using the instruction.
 The desired fps can be set via -state-fps=NUM, defaults to 10.
 Specifying \-state-fps implicitly sets this flag. Defaults to off in all
 standards.
+.It Fl f Ns Cm arithmetic-exceptions
+Turn on arithmetic exception tests in the compiler. In constant expressions
+which trigger exceptions like division by zero, overflow, underflow, etc,
+the following flag will produce diagnostics for what triggered that
+exception.
 .El
 .Sh OPTIMIZATIONS
 .Bl -tag -width Ds
diff --git a/exec.c b/exec.c
index fde8196b2e4833ffebbd0f452b0c5517cf276916..9b6677f38305469bf8a9866ae04b6cebc779765b 100644 (file)
--- a/exec.c
+++ b/exec.c
@@ -950,7 +950,7 @@ static void prog_main_setparams(qc_program_t *prog) {
     }
 }
 
-void prog_disasm_function(qc_program_t *prog, size_t id);
+static void prog_disasm_function(qc_program_t *prog, size_t id);
 
 int main(int argc, char **argv) {
     size_t      i;
@@ -1265,7 +1265,7 @@ int main(int argc, char **argv) {
     return 0;
 }
 
-void prog_disasm_function(qc_program_t *prog, size_t id) {
+static void prog_disasm_function(qc_program_t *prog, size_t id) {
     prog_section_function_t *fdef = prog->functions + id;
     prog_section_statement_t *st;
 
diff --git a/fold.c b/fold.c
index af37c4c76f652c7e083f02ffa55e7188de0bc5f8..38190749e39c4db29ef4c0eb13eafa7418f718ad 100644 (file)
--- a/fold.c
+++ b/fold.c
 #define FOLD_STRING_UNTRANSLATE_HTSIZE 1024
 #define FOLD_STRING_DOTRANSLATE_HTSIZE 1024
 
+/* The options to use for inexact and arithmetic exceptions */
+#define FOLD_ROUNDING SFLOAT_ROUND_NEAREST_EVEN
+#define FOLD_TINYNESS SFLOAT_TBEFORE
+
+/*
+ * 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;
+
+/* Count of leading zero bits before the most-significand 1 bit. */
+#ifdef _MSC_VER
+/* MSVC has an intrinsic for this */
+    static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) {
+        int r = 0;
+        _BitScanForward(&r, x);
+        return r;
+    }
+#   define SFLOAT_CLZ(X, SUB) \
+        (sfloat_clz((X)) - (SUB))
+#elif defined(__GNUC__) || defined(__CLANG__)
+/* Clang and GCC have a builtin for this */
+#   define SFLOAT_CLZ(X, SUB) \
+        (__builtin_clz((X)) - (SUB))
+#else
+/* Native fallback */
+    static GMQCC_INLINE uint32_t sfloat_popcnt(uint32_t x) {
+        x -= ((x >> 1) & 0x55555555);
+        x  = (((x >> 2) & 0x33333333) + (x & 0x33333333));
+        x  = (((x >> 4) + x) & 0x0F0F0F0F);
+        x += x >> 8;
+        x += x >> 16;
+        return x & 0x0000003F;
+    }
+    static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) {
+        x |= (x >> 1);
+        x |= (x >> 2);
+        x |= (x >> 4);
+        x |= (x >> 8);
+        x |= (x >> 16);
+        return 32 - sfloat_popcnt(x);
+    }
+#   define SFLOAT_CLZ(X, SUB) \
+        (sfloat_clz((X) - (SUB)))
+#endif
+
+/* The value of a NaN */
+#define SFLOAT_NAN 0xFFC00000
+/* 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);
+}
+
+static GMQCC_INLINE void sfloat_check(lex_ctx_t ctx, sfloat_state_t *state, const char *vec) {
+    /* Exception comes from vector component */
+    if (vec) {
+        if (state->exceptionflags & SFLOAT_DIVBYZERO)
+            compile_error(ctx, "division by zero in `%s' component", vec);
+        if (state->exceptionflags & SFLOAT_INVALID)
+            compile_error(ctx, "undefined (inf) in `%s' component", vec);
+        if (state->exceptionflags & SFLOAT_OVERFLOW)
+            compile_error(ctx, "arithmetic overflow in `%s' component", vec);
+        if (state->exceptionflags & SFLOAT_UNDERFLOW)
+            compile_error(ctx, "arithmetic underflow in `%s' component", vec);
+            return;
+    }
+    if (state->exceptionflags & SFLOAT_DIVBYZERO)
+        compile_error(ctx, "division by zero");
+    if (state->exceptionflags & SFLOAT_INVALID)
+        compile_error(ctx, "undefined (inf)");
+    if (state->exceptionflags & SFLOAT_OVERFLOW)
+        compile_error(ctx, "arithmetic overflow");
+    if (state->exceptionflags & SFLOAT_UNDERFLOW)
+        compile_error(ctx, "arithmetic underflow");
+}
+
+static GMQCC_INLINE void sfloat_init(sfloat_state_t *state) {
+    state->exceptionflags = 0;
+    state->roundingmode   = FOLD_ROUNDING;
+    state->tiny           = FOLD_TINYNESS;
+}
+
 /*
  * There is two stages to constant folding in GMQCC: there is the parse
  * stage constant folding, where, witht he help of the AST, operator
  *
  * TODO: gcc/clang hinting for autovectorization
  */
-static GMQCC_INLINE vec3_t vec3_add(vec3_t a, vec3_t b) {
+typedef enum {
+    VEC_COMP_X = 1 << 0,
+    VEC_COMP_Y = 1 << 1,
+    VEC_COMP_Z = 1 << 2
+} vec3_comp_t;
+
+typedef struct {
+    sfloat_cast_t x;
+    sfloat_cast_t y;
+    sfloat_cast_t z;
+} vec3_soft_t;
+
+typedef struct {
+    vec3_comp_t    faults;
+    sfloat_state_t state[3];
+} vec3_soft_state_t;
+
+static GMQCC_INLINE vec3_soft_t vec3_soft_convert(vec3_t vec) {
+    vec3_soft_t soft;
+    soft.x.f = vec.x;
+    soft.y.f = vec.y;
+    soft.z.f = vec.z;
+    return soft;
+}
+
+static GMQCC_INLINE bool vec3_soft_exception(vec3_soft_state_t *vstate, size_t index) {
+    sfloat_exceptionflags_t flags = vstate->state[index].exceptionflags;
+    if (flags & SFLOAT_DIVBYZERO) return true;
+    if (flags & SFLOAT_INVALID)   return true;
+    if (flags & SFLOAT_OVERFLOW)  return true;
+    if (flags & SFLOAT_UNDERFLOW) return true;
+    return false;
+}
+
+static GMQCC_INLINE void vec3_soft_eval(vec3_soft_state_t *state,
+                                        sfloat_t         (*callback)(sfloat_state_t *, sfloat_t, sfloat_t),
+                                        vec3_t             a,
+                                        vec3_t             b)
+{
+    vec3_soft_t sa = vec3_soft_convert(a);
+    vec3_soft_t sb = vec3_soft_convert(b);
+    callback(&state->state[0], sa.x.s, sb.x.s);
+    if (vec3_soft_exception(state, 0)) state->faults |= VEC_COMP_X;
+    callback(&state->state[1], sa.y.s, sb.y.s);
+    if (vec3_soft_exception(state, 1)) state->faults |= VEC_COMP_Y;
+    callback(&state->state[2], sa.z.s, sb.z.s);
+    if (vec3_soft_exception(state, 2)) state->faults |= VEC_COMP_Z;
+}
+
+static GMQCC_INLINE void vec3_check_except(vec3_t     a,
+                                           vec3_t     b,
+                                           lex_ctx_t  ctx,
+                                           sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t))
+{
+    vec3_soft_state_t state;
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        return;
+
+    sfloat_init(&state.state[0]);
+    sfloat_init(&state.state[1]);
+    sfloat_init(&state.state[2]);
+
+    vec3_soft_eval(&state, callback, a, b);
+    if (state.faults & VEC_COMP_X) sfloat_check(ctx, &state.state[0], "x");
+    if (state.faults & VEC_COMP_Y) sfloat_check(ctx, &state.state[1], "y");
+    if (state.faults & VEC_COMP_Z) sfloat_check(ctx, &state.state[2], "z");
+}
+
+static GMQCC_INLINE vec3_t vec3_add(lex_ctx_t ctx, vec3_t a, vec3_t b) {
     vec3_t out;
+    vec3_check_except(a, b, ctx, &sfloat_add);
     out.x = a.x + b.x;
     out.y = a.y + b.y;
     out.z = a.z + b.z;
     return out;
 }
 
-static GMQCC_INLINE vec3_t vec3_sub(vec3_t a, vec3_t b) {
+static GMQCC_INLINE vec3_t vec3_sub(lex_ctx_t ctx, vec3_t a, vec3_t b) {
     vec3_t out;
+    vec3_check_except(a, b, ctx, &sfloat_sub);
     out.x = a.x - b.x;
     out.y = a.y - b.y;
     out.z = a.z - b.z;
@@ -129,12 +669,64 @@ static GMQCC_INLINE vec3_t vec3_not(vec3_t a) {
     return out;
 }
 
-static GMQCC_INLINE qcfloat_t vec3_mulvv(vec3_t a, vec3_t b) {
+static GMQCC_INLINE qcfloat_t vec3_mulvv(lex_ctx_t ctx, vec3_t a, vec3_t b) {
+    vec3_soft_t    sa;
+    vec3_soft_t    sb;
+    sfloat_state_t s[5];
+    sfloat_t       r[5];
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto end;
+
+    sa = vec3_soft_convert(a);
+    sb = vec3_soft_convert(b);
+
+    sfloat_init(&s[0]);
+    sfloat_init(&s[1]);
+    sfloat_init(&s[2]);
+    sfloat_init(&s[3]);
+    sfloat_init(&s[4]);
+
+    r[0] = sfloat_mul(&s[0], sa.x.s, sb.x.s);
+    r[1] = sfloat_mul(&s[1], sa.y.s, sb.y.s);
+    r[2] = sfloat_mul(&s[2], sa.z.s, sb.z.s);
+    r[3] = sfloat_add(&s[3], r[0],   r[1]);
+    r[4] = sfloat_add(&s[4], r[3],   r[2]);
+
+    sfloat_check(ctx, &s[0], NULL);
+    sfloat_check(ctx, &s[1], NULL);
+    sfloat_check(ctx, &s[2], NULL);
+    sfloat_check(ctx, &s[3], NULL);
+    sfloat_check(ctx, &s[4], NULL);
+
+end:
     return (a.x * b.x + a.y * b.y + a.z * b.z);
 }
 
-static GMQCC_INLINE vec3_t vec3_mulvf(vec3_t a, qcfloat_t b) {
-    vec3_t out;
+static GMQCC_INLINE vec3_t vec3_mulvf(lex_ctx_t ctx, vec3_t a, qcfloat_t b) {
+    vec3_t         out;
+    vec3_soft_t    sa;
+    sfloat_cast_t  sb;
+    sfloat_state_t s[3];
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto end;
+
+    sa   = vec3_soft_convert(a);
+    sb.f = b;
+    sfloat_init(&s[0]);
+    sfloat_init(&s[1]);
+    sfloat_init(&s[2]);
+
+    sfloat_mul(&s[0], sa.x.s, sb.s);
+    sfloat_mul(&s[1], sa.y.s, sb.s);
+    sfloat_mul(&s[2], sa.z.s, sb.s);
+
+    sfloat_check(ctx, &s[0], "x");
+    sfloat_check(ctx, &s[1], "y");
+    sfloat_check(ctx, &s[2], "z");
+
+end:
     out.x = a.x * b;
     out.y = a.y * b;
     out.z = a.z * b;
@@ -163,8 +755,50 @@ static GMQCC_INLINE bool vec3_pbool(vec3_t a) {
     return (a.x || a.y || a.z);
 }
 
-static GMQCC_INLINE vec3_t vec3_cross(vec3_t a, vec3_t b) {
-    vec3_t out;
+static GMQCC_INLINE vec3_t vec3_cross(lex_ctx_t ctx, vec3_t a, vec3_t b) {
+    vec3_t         out;
+    vec3_soft_t    sa;
+    vec3_soft_t    sb;
+    sfloat_t       r[9];
+    sfloat_state_t s[9];
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto end;
+
+    sa = vec3_soft_convert(a);
+    sb = vec3_soft_convert(b);
+
+    sfloat_init(&s[0]);
+    sfloat_init(&s[1]);
+    sfloat_init(&s[2]);
+    sfloat_init(&s[3]);
+    sfloat_init(&s[4]);
+    sfloat_init(&s[5]);
+    sfloat_init(&s[6]);
+    sfloat_init(&s[7]);
+    sfloat_init(&s[8]);
+
+    r[0] = sfloat_mul(&s[0], sa.y.s, sb.z.s);
+    r[1] = sfloat_mul(&s[1], sa.z.s, sb.y.s);
+    r[2] = sfloat_mul(&s[2], sa.z.s, sb.x.s);
+    r[3] = sfloat_mul(&s[3], sa.x.s, sb.z.s);
+    r[4] = sfloat_mul(&s[4], sa.x.s, sb.y.s);
+    r[5] = sfloat_mul(&s[5], sa.y.s, sb.x.s);
+    r[6] = sfloat_sub(&s[6], r[0],   r[1]);
+    r[7] = sfloat_sub(&s[7], r[2],   r[3]);
+    r[8] = sfloat_sub(&s[8], r[4],   r[5]);
+
+    sfloat_check(ctx, &s[0], NULL);
+    sfloat_check(ctx, &s[1], NULL);
+    sfloat_check(ctx, &s[2], NULL);
+    sfloat_check(ctx, &s[3], NULL);
+    sfloat_check(ctx, &s[4], NULL);
+    sfloat_check(ctx, &s[5], NULL);
+    sfloat_check(ctx, &s[6], "x");
+    sfloat_check(ctx, &s[7], "y");
+    sfloat_check(ctx, &s[8], "z");
+
+end:
     out.x = a.y * b.z - a.z * b.y;
     out.y = a.z * b.x - a.x * b.z;
     out.z = a.x * b.y - a.y * b.x;
@@ -227,10 +861,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 +909,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 +921,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 +1007,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 +1016,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,39 +1027,85 @@ 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;
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS) && !OPTS_WARN(WARN_INEXACT_COMPARES))
+        return false;
+
+    sfloat_init(&s);
+    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 (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto inexact_possible;
+
+    sfloat_check(fold_ctx(fold), &s, NULL);
+
+inexact_possible:
+    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 (!OPTS_WARN(WARN_INEXACT_COMPARES))
+        return false;
+    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)));
+            return fold_constgen_vector(fold, vec3_add(fold_ctx(fold),
+                                                       fold_immvalue_vector(a),
+                                                       fold_immvalue_vector(b)));
     }
     return NULL;
 }
 
 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)));
+            return fold_constgen_vector(fold, vec3_sub(fold_ctx(fold),
+                                                       fold_immvalue_vector(a),
+                                                       fold_immvalue_vector(b)));
     }
     return NULL;
 }
@@ -433,18 +1114,20 @@ static GMQCC_INLINE ast_expression *fold_op_mul(fold_t *fold, ast_value *a, ast_
     if (isfloat(a)) {
         if (isvector(b)) {
             if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_mulvf(fold_immvalue_vector(b), fold_immvalue_float(a)));
+                return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), 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)) {
             if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_mulvf(fold_immvalue_vector(a), fold_immvalue_float(b)));
+                return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), 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_ctx(fold), 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,25 +1147,26 @@ 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)) {
         if (fold_can_2(a, b)) {
-            return fold_constgen_vector(fold, vec3_mulvf(fold_immvalue_vector(a), 1.0f / fold_immvalue_float(b)));
+            return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(a), 1.0f / fold_immvalue_float(b)));
         } else {
             return (ast_expression*)ast_binary_new(
                 fold_ctx(fold),
                 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 +1181,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 +1204,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 +1220,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 +1234,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 +1257,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 +1276,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 +1290,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 +1318,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))
@@ -634,7 +1330,9 @@ static GMQCC_INLINE ast_expression *fold_op_bnot(fold_t *fold, ast_value *a) {
 
 static GMQCC_INLINE ast_expression *fold_op_cross(fold_t *fold, ast_value *a, ast_value *b) {
     if (fold_can_2(a, b))
-        return fold_constgen_vector(fold, vec3_cross(fold_immvalue_vector(a), fold_immvalue_vector(b)));
+        return fold_constgen_vector(fold, vec3_cross(fold_ctx(fold),
+                                                     fold_immvalue_vector(a),
+                                                     fold_immvalue_vector(b)));
     return NULL;
 }
 
@@ -685,6 +1383,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 +1408,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);
 }
 
 
index 290e1bd5d3ea56bd8ba278dd9d30ea5ae688d737..ceacf289c3fd606f43c9532171c9161fa8d464b4 100644 (file)
     EMULATE_STATE = false
 
 
+    #Turn on arithmetic exception tests in the compiler. In constant expressions
+    #which trigger exceptions like division by zero, overflow, underflow, etc,
+    #the following flag will produce diagnostics for what triggered that
+    #exception.
+    ARITHMETIC_EXCEPTIONS = false
+
 [warnings]
     #Generate a warning about variables which are declared but never
     #used.  This can be avoided by adding the â€˜noref’ keyword in front
     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
index 23c3fb94c45d7562a8deb1670db3e8e19c0e81be..081ce3b6c03f7760af4c41675461b7c4a3a3642a 100644 (file)
@@ -5,6 +5,9 @@ BINDIR  := $(PREFIX)/bin
 DATADIR := $(PREFIX)/share
 MANDIR  := $(DATADIR)/man
 
+# default flags
+CFLAGS  += -Wall -Wextra -Werror -Wstrict-aliasing -Wno-attributes -O2
+
 # compiler
 CC      ?= clang
 
@@ -101,7 +104,12 @@ SPLINTFLAGS =                 \
     -observertrans            \
     -abstract                 \
     -statictrans              \
-    -castfcnptr
+    -castfcnptr               \
+    -shiftimplementation      \
+    -shiftnegative            \
+    -boolcompare              \
+    -infloops                 \
+    -sysunrecog
 
 #always the right rule
 default: all
@@ -118,13 +126,16 @@ uninstall:
 #style rule
 STYLE_MATCH = \( -name '*.[ch]' -or -name '*.def' -or -name '*.qc' \)
 
+# splint cannot parse the MSVC source
+SPLINT_MATCH = \( -name '*.[ch]' -and ! -name 'msvc.c' -and ! -path './doc/*' \)
+
 style:
        find . -type f $(STYLE_MATCH) -exec sed -i 's/ *$$//' '{}' ';'
        find . -type f $(STYLE_MATCH) -exec sed -i -e '$$a\' '{}' ';'
        find . -type f $(STYLE_MATCH) -exec sed -i 's/\t/    /g' '{}' ';'
 
 splint:
-       @splint $(SPLINTFLAGS) *.c *.h
+       @splint $(SPLINTFLAGS) `find . -type f $(SPLINT_MATCH)`
 
 gource:
        @gource $(GOURCEFLAGS)
index 8c78ba744a17dde3e6b213d9badf5c899e23954c..c1b84ed230c9165619bdcca9f8461b5589125271 100644 (file)
--- 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 34d76bbcc72fedfa1ee57544052730ca568e2c86..12429a8ac3e637d7b499ed1c358af9aab917b9c1 100644 (file)
--- 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);
index d311f87ac913905893efb064fe1a296269ea8e03..11571947dbbf96d41cefabbc3f41ed19b12a438d 100644 (file)
--- a/opts.def
+++ b/opts.def
@@ -57,6 +57,7 @@
     GMQCC_DEFINE_FLAG(TYPELESS_STORES)
     GMQCC_DEFINE_FLAG(SORT_OPERANDS)
     GMQCC_DEFINE_FLAG(EMULATE_STATE)
+    GMQCC_DEFINE_FLAG(ARITHMETIC_EXCEPTIONS)
 #endif
 
 /* warning flags */
     GMQCC_DEFINE_FLAG(CONST_OVERWRITE)
     GMQCC_DEFINE_FLAG(DIRECTIVE_INMACRO)
     GMQCC_DEFINE_FLAG(BUILTINS)
+    GMQCC_DEFINE_FLAG(INEXACT_COMPARES)
 #endif
 
 #ifdef GMQCC_TYPE_OPTIMIZATIONS
index 06437436ef9c721dc4d8e4bfeafb2c5cd0e3a857..b7d4bf56ae560296063261be7609ce112d43cc04 100644 (file)
--- 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));
@@ -4043,7 +4043,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, frame_delta));
+                             gbl_time, (ast_expression*)fold_constgen_float(parser->fold, frame_delta, false));
 
             if (!self_frame || !self_nextthink || !self_think || !time_plus_1) {
                 if (self_frame)     ast_delete(self_frame);
@@ -4169,7 +4169,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;
@@ -4227,7 +4227,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);
@@ -4260,7 +4260,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;
 
@@ -4326,7 +4326,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;
 
@@ -4389,7 +4389,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;
 
@@ -5160,6 +5160,7 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
     bool      wasarray  = false;
 
     ast_member *me[3] = { NULL, NULL, NULL };
+    ast_member *last_me[3] = { NULL, NULL, NULL };
 
     if (!localblock && is_static)
         parseerror(parser, "`static` qualifier is not supported in global scope");
@@ -5623,6 +5624,7 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
                 }
             }
         }
+        memcpy(last_me, me, sizeof(me));
         me[0] = me[1] = me[2] = NULL;
         cleanvar = false;
         /* Part 2.2
@@ -5830,15 +5832,44 @@ skipvar:
         } else {
             ast_expression *cexp;
             ast_value      *cval;
+            bool            folded_const = false;
 
             cexp = parse_expression_leave(parser, true, false, false);
             if (!cexp)
                 break;
+            cval = ast_istype(cexp, ast_value) ? (ast_value*)cexp : NULL;
 
-            if (!localblock || is_static) {
-                cval = (ast_value*)cexp;
+            /* deal with foldable constants: */
+            if (localblock &&
+                var->cvq == CV_CONST && cval && cval->hasvalue && cval->cvq == CV_CONST && !cval->isfield)
+            {
+                /* remove it from the current locals */
+                if (isvector) {
+                    for (i = 0; i < 3; ++i) {
+                        vec_pop(parser->_locals);
+                        vec_pop(localblock->collect);
+                    }
+                }
+                /* do sanity checking, this function really needs refactoring */
+                if (vec_last(parser->_locals) != (ast_expression*)var)
+                    parseerror(parser, "internal error: unexpected change in local variable handling");
+                else
+                    vec_pop(parser->_locals);
+                if (vec_last(localblock->locals) != var)
+                    parseerror(parser, "internal error: unexpected change in local variable handling (2)");
+                else
+                    vec_pop(localblock->locals);
+                /* push it to the to-be-generated globals */
+                vec_push(parser->globals, (ast_expression*)var);
+                if (isvector)
+                    for (i = 0; i < 3; ++i)
+                        vec_push(parser->globals, (ast_expression*)last_me[i]);
+                folded_const = true;
+            }
+
+            if (folded_const || !localblock || is_static) {
                 if (cval != parser->nil &&
-                    (!ast_istype(cval, ast_value) || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield))
+                    (!cval || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield))
                    )
                 {
                     parseerror(parser, "initializer is non constant");
@@ -5886,6 +5917,13 @@ skipvar:
                 vec_free(sy.argc);
                 var->cvq = cvq;
             }
+            /* a constant initialized to an inexact value should be marked inexact:
+             * const float x = <inexact>; should propagate the inexact flag
+             */
+            if (var->cvq == CV_CONST && var->expression.vtype == TYPE_FLOAT) {
+                if (cval && cval->hasvalue && cval->cvq == CV_CONST)
+                    var->inexact = cval->inexact;
+            }
         }
 
 another:
index 51140be9781325ed369c37acfadad595575d7498..eb77effe8e5e046c4f248cc96da39d6be9b054b3 100644 (file)
--- 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 *);
diff --git a/test.c b/test.c
index f1f714fab34a6722f27674a100684fc458a5f37f..23fbfb6b4ef8ed0d5f8c9e37446ddd9b1c9b775e 100644 (file)
--- a/test.c
+++ b/test.c
@@ -1100,6 +1100,7 @@ static size_t task_schedualize(size_t *pad) {
     size_t i        = 0;
     size_t j        = 0;
     size_t failed   = 0;
+    int    status   = 0;
 
     util_snprintf(space[0], sizeof(space[0]), "%d", (int)vec_size(task_tasks));
 
@@ -1167,7 +1168,9 @@ static size_t task_schedualize(size_t *pad) {
             continue;
         }
 
-        if (task_pclose(task_tasks[i].runhandles) != EXIT_SUCCESS && strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) {
+        status = task_pclose(task_tasks[i].runhandles);
+        if ((!strcmp(task_tasks[i].tmpl->proceduretype, "-fail") && status == EXIT_SUCCESS)
+        ||  ( strcmp(task_tasks[i].tmpl->proceduretype, "-fail") && status == EXIT_FAILURE)) {
             con_out("failure:   `%s` %*s %*s\n",
                 task_tasks[i].tmpl->description,
                 (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
diff --git a/tests/arithexcept.qc b/tests/arithexcept.qc
new file mode 100644 (file)
index 0000000..6441f78
--- /dev/null
@@ -0,0 +1,13 @@
+const float huge = 340282346638528859811704183484516925440.000000; // FLT_MAX
+
+#ifdef DIVBYZERO
+const float a = 1.0 / 0.0;
+#endif
+
+#ifdef OVERFLOW
+const float a = huge * huge;
+#endif
+
+#ifdef UNDERFLOW
+const float a = 1 / huge;
+#endif
diff --git a/tests/arithexcept.tmpl b/tests/arithexcept.tmpl
new file mode 100644 (file)
index 0000000..3055e98
--- /dev/null
@@ -0,0 +1,4 @@
+I: arithexcept.qc
+D: arithmetic exceptions (divide by zero)
+T: -fail
+C: -std=fteqcc -farithmetic-exceptions -DDIVBYZERO
diff --git a/tests/arithexcept_of.tmpl b/tests/arithexcept_of.tmpl
new file mode 100644 (file)
index 0000000..94b7d7d
--- /dev/null
@@ -0,0 +1,4 @@
+I: arithexcept.qc
+D: arithmetic exceptions (overflow)
+T: -fail
+C: -std=ftqcc -farithmetic-exceptions -DOVERFLOW
diff --git a/tests/arithexcept_uf.tmpl b/tests/arithexcept_uf.tmpl
new file mode 100644 (file)
index 0000000..ed0a817
--- /dev/null
@@ -0,0 +1,4 @@
+I: arithexcept.qc
+D: arithmetic exceptions (underflow)
+T: -fail
+C: -std=ftqcc -farithmetic-exceptions -DUNDERFLOW
diff --git a/tests/inexact-local.qc b/tests/inexact-local.qc
new file mode 100644 (file)
index 0000000..a17cccd
--- /dev/null
@@ -0,0 +1,7 @@
+void main() {
+    const float a = 1.0 / 3.0;
+    const float b = 0.33333333333;
+    if (a == b) {
+        // Should trigger warning
+    }
+}
diff --git a/tests/inexact-local.tmpl b/tests/inexact-local.tmpl
new file mode 100644 (file)
index 0000000..3917a90
--- /dev/null
@@ -0,0 +1,4 @@
+I: inexact-local.qc
+D: inexact comparisons
+T: -fail
+C: -std=gmqcc -Winexact-compares -Wall -Werror
diff --git a/tests/inexact.qc b/tests/inexact.qc
new file mode 100644 (file)
index 0000000..11dc2c6
--- /dev/null
@@ -0,0 +1,8 @@
+const float a = 1.0 / 3.0;
+const float b = 0.33333333333;
+
+void main() {
+    if (a == b) {
+        // Should trigger warning
+    }
+}
diff --git a/tests/inexact.tmpl b/tests/inexact.tmpl
new file mode 100644 (file)
index 0000000..9808f7d
--- /dev/null
@@ -0,0 +1,4 @@
+I: inexact.qc
+D: inexact comparisons
+T: -fail
+C: -std=gmqcc -Winexact-compares -Wall -Werror