]> git.xonotic.org Git - xonotic/gmqcc.git/commitdiff
Merge branch 'cooking'
authorDale Weiler <weilercdale@gmail.com>
Wed, 10 Dec 2014 07:43:42 +0000 (02:43 -0500)
committerDale Weiler <weilercdale@gmail.com>
Wed, 10 Dec 2014 07:43:42 +0000 (02:43 -0500)
Conflicts:
test.c

48 files changed:
BSDmakefile
Makefile
ansi.c
ast.c
ast.h
doc/gmqcc.1
exec.c
fold.c
ftepp.c
gmqcc.h
gmqcc.ini.example
hash.c
include.mk
intrin.c
ir.c
ir.h
lexer.c
lexer.h
main.c
opts.c
opts.def
parser.c
parser.h
stat.c
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/defs.qh
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]
tests/length.qc [new file with mode: 0644]
tests/length.tmpl [new file with mode: 0644]
tests/noreturn.qc
tests/paramomit.qc [new file with mode: 0644]
tests/paramomit.tmpl [new file with mode: 0644]
tests/pointlife.qc
tests/ppindirectexpand.qc [new file with mode: 0644]
tests/ppindirectexpand.tmpl [new file with mode: 0644]
tests/split-vectors.qc [new file with mode: 0644]
tests/split-vectors.tmpl [new file with mode: 0644]
tests/state-emu.tmpl [new file with mode: 0644]
tests/state.qc [new file with mode: 0644]
tests/state.tmpl [new file with mode: 0644]
tests/typedefs.qc

index 49385413b3e126023284533bb66adc0940c8e71a..44a742a138d7d9f524d718e53c5a7590fdf11b35 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,9 @@ CFLAGS   +=  -Wall -Wextra -Werror -Wstrict-aliasing -Wno-attributes
                 -Wno-float-equal\
                 -Wno-unknown-warning-option\
                 -Wno-cast-align\
+                -Wno-assign-enum\
+                -Wno-empty-body\
+                -Wno-date-time\
                 -pedantic-errors
 .else
 .    if $(CC) != g++
index 3f286e27f6117db926fbdd051dad85abf3433644..530bd4cd4ea42249b3315b14951ec65724b3e56a 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,9 @@ ifeq ($(CC), clang)
            -Wno-float-equal                   \
            -Wno-unknown-warning-option        \
            -Wno-cast-align                    \
+           -Wno-assign-enum                   \
+           -Wno-empty-body                    \
+           -Wno-date-time                     \
            -pedantic-errors
 else
        ifneq ($(CC), g++)
diff --git a/ansi.c b/ansi.c
index 029508a95ca2e84254412e4a3046a02829910878..e377424e035014a4560660609138485e1fc5f475 100644 (file)
--- a/ansi.c
+++ b/ansi.c
@@ -62,6 +62,9 @@ int platform_vasprintf(char **dat, const char *fmt, va_list args) {
     len = vsnprintf(buf, sizeof(buf), fmt, cpy);
     va_end (cpy);
 
+    if (len < 0)
+        return len;
+
     if (len < (int)sizeof(buf)) {
         *dat = util_strdup(buf);
         return len;
diff --git a/ast.c b/ast.c
index d7e3d7a198cae3df58a8e2c0e0d87372e534e074..3af6ffcfd5c1b69acddbf0babf077888a57d80ce 100644 (file)
--- a/ast.c
+++ b/ast.c
@@ -75,6 +75,7 @@ static void ast_binstore_delete(ast_binstore*);
 static bool ast_binstore_codegen(ast_binstore*, ast_function*, bool lvalue, ir_value**);
 static void ast_binary_delete(ast_binary*);
 static bool ast_binary_codegen(ast_binary*, ast_function*, bool lvalue, ir_value**);
+static bool ast_state_codegen(ast_state*, ast_function*, bool lvalue, ir_value**);
 
 /* It must not be possible to get here. */
 static GMQCC_NORETURN void _ast_node_destroy(ast_node *self)
@@ -359,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;
@@ -972,6 +974,26 @@ void ast_goto_set_label(ast_goto *self, ast_label *label)
     self->target = label;
 }
 
+ast_state* ast_state_new(lex_ctx_t ctx, ast_expression *frame, ast_expression *think)
+{
+    ast_instantiate(ast_state, ctx, ast_state_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_state_codegen);
+    self->framenum  = frame;
+    self->nextthink = think;
+    return self;
+}
+
+void ast_state_delete(ast_state *self)
+{
+    if (self->framenum)
+        ast_unref(self->framenum);
+    if (self->nextthink)
+        ast_unref(self->nextthink);
+
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
 ast_call* ast_call_new(lex_ctx_t ctx,
                        ast_expression *funcexpr)
 {
@@ -1431,7 +1453,7 @@ bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
         if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
             self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
         if (self->expression.flags & AST_FLAG_ERASEABLE)
-            self->ir_v->flags |= IR_FLAG_ERASEABLE;
+            self->ir_v->flags |= IR_FLAG_ERASABLE;
         if (self->expression.flags & AST_FLAG_BLOCK_COVERAGE)
             func->flags |= IR_FLAG_BLOCK_COVERAGE;
         /* The function is filled later on ast_function_codegen... */
@@ -1479,7 +1501,7 @@ bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
             if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
                 self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
             if (self->expression.flags & AST_FLAG_ERASEABLE)
-                self->ir_v->flags |= IR_FLAG_ERASEABLE;
+                self->ir_v->flags |= IR_FLAG_ERASABLE;
 
             namelen = strlen(self->name);
             name    = (char*)mem_a(namelen + 16);
@@ -1514,7 +1536,7 @@ bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
                 self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
 
             if (self->expression.flags & AST_FLAG_ERASEABLE)
-                self->ir_v->flags |= IR_FLAG_ERASEABLE;
+                self->ir_v->flags |= IR_FLAG_ERASABLE;
         }
         return true;
     }
@@ -1548,7 +1570,7 @@ bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
         if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
             v->flags |= IR_FLAG_INCLUDE_DEF;
         if (self->expression.flags & AST_FLAG_ERASEABLE)
-            self->ir_v->flags |= IR_FLAG_ERASEABLE;
+            self->ir_v->flags |= IR_FLAG_ERASABLE;
 
         namelen = strlen(self->name);
         name    = (char*)mem_a(namelen + 16);
@@ -1593,7 +1615,7 @@ bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
     if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
         self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
     if (self->expression.flags & AST_FLAG_ERASEABLE)
-        self->ir_v->flags |= IR_FLAG_ERASEABLE;
+        self->ir_v->flags |= IR_FLAG_ERASABLE;
 
     /* initialize */
     if (self->hasvalue) {
@@ -3347,6 +3369,44 @@ bool ast_goto_codegen(ast_goto *self, ast_function *func, bool lvalue, ir_value
     return true;
 }
 
+#include <stdio.h>
+bool ast_state_codegen(ast_state *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+
+    ir_value *frameval, *thinkval;
+
+    if (lvalue) {
+        compile_error(ast_ctx(self), "not an l-value (state operation)");
+        return false;
+    }
+    if (self->expression.outr) {
+        compile_error(ast_ctx(self), "internal error: ast_state cannot be reused!");
+        return false;
+    }
+    *out = NULL;
+
+    cgen = self->framenum->codegen;
+    if (!(*cgen)((ast_expression*)(self->framenum), func, false, &frameval))
+        return false;
+    if (!frameval)
+        return false;
+
+    cgen = self->nextthink->codegen;
+    if (!(*cgen)((ast_expression*)(self->nextthink), func, false, &thinkval))
+        return false;
+    if (!frameval)
+        return false;
+
+    if (!ir_block_create_state_op(func->curblock, ast_ctx(self), frameval, thinkval)) {
+        compile_error(ast_ctx(self), "failed to create STATE instruction");
+        return false;
+    }
+
+    self->expression.outr = (ir_value*)1;
+    return true;
+}
+
 bool ast_call_codegen(ast_call *self, ast_function *func, bool lvalue, ir_value **out)
 {
     ast_expression_codegen *cgen;
diff --git a/ast.h b/ast.h
index 7cc7a6c89b13975549307d2a269c3fde4bb4ec85..d1b250d8abb5cc4a5e2ffc1ebc945e8a13a7d09f 100644 (file)
--- a/ast.h
+++ b/ast.h
@@ -54,6 +54,7 @@ typedef struct ast_switch_s      ast_switch;
 typedef struct ast_label_s       ast_label;
 typedef struct ast_goto_s        ast_goto;
 typedef struct ast_argpipe_s     ast_argpipe;
+typedef struct ast_state_s       ast_state;
 
 enum {
     AST_FLAG_VARIADIC       = 1 << 0,
@@ -111,7 +112,8 @@ enum {
     TYPE_ast_switch,      /* 18 */
     TYPE_ast_label,       /* 19 */
     TYPE_ast_goto,        /* 20 */
-    TYPE_ast_argpipe      /* 21 */
+    TYPE_ast_argpipe,     /* 21 */
+    TYPE_ast_state        /* 22 */
 };
 
 #define ast_istype(x, t) ( ((ast_node*)x)->nodetype == (TYPE_##t) )
@@ -214,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
@@ -579,6 +582,19 @@ struct ast_goto_s
 ast_goto* ast_goto_new(lex_ctx_t ctx, const char *name);
 void ast_goto_set_label(ast_goto*, ast_label*);
 
+/* STATE node
+ *
+ * For frame/think state updates: void foo() [framenum, nextthink] {}
+ */
+struct ast_state_s
+{
+    ast_expression  expression;
+    ast_expression *framenum;
+    ast_expression *nextthink;
+};
+ast_state* ast_state_new(lex_ctx_t ctx, ast_expression *frame, ast_expression *think);
+void ast_state_delete(ast_state*);
+
 /* CALL node
  *
  * Contains an ast_expression as target, rather than an ast_function/value.
index b5d39eab4578f22ed95fade87d0162def05e98e1..78d767e26f384ae38e8e7394a0a0d607c9f90a50 100644 (file)
@@ -168,6 +168,11 @@ DEBUG OPTION. Print the code's intermediate representation after the
 optimization and finalization passes to stdout before generating the
 binary. The instructions will be enumerated, and values will contain a
 list of liferanges.
+.It Fl force-crc= Ns Ar CRC
+Force the produced progs file to use the specified CRC.
+.It Fl state-fps= Ns Ar NUM
+Activate \-femulate-state and set the emulated FPS to
+.Ar NUM Ns .
 .El
 .Sh COMPILE WARNINGS
 .Bl -tag -width Ds
@@ -344,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
@@ -417,6 +426,25 @@ M_SQRT2
 M_SQRT1_2
 M_TAU
 .Ed
+.It Fl f Ns Cm ftepp-indirect-expansion
+Enable indirect macro expansion. This only works in combination
+with '-fftepp' and is currently not included by '-std=fteqcc'.
+Enabling this behavior will allow the preprocessor to operate more
+like the standard C preprocessor in that it will allow arguments
+of macros which are macro-expanded to be substituted into the
+definition of the macro.
+.Pp
+As an example:
+.Bd -literal -offset indent
+#define STR1(x) #x
+#define STR2(x) STR1(x)
+#define THE_ANSWER 42
+#define THE_ANSWER_STR STR2(THE_ANSWER) /* "42" */
+
+.Ed
+With this enabled, an expansion of THE_ANSWER_STR will yield
+the string "42". With this disabled an expansion of THE_ANSWER_STR
+will yield "THE_ANSWER"
 .It Fl f Ns Cm relaxed-switch
 Allow switch cases to use non constant variables.
 .It Fl f Ns Cm short-logic
@@ -577,6 +605,26 @@ breaks decompilers, but causes the output file to be better compressible.
 In commutative instructions, always put the lower-numbered operand first.
 This shaves off 1 byte of entropy from all these instructions, reducing
 compressed size of the output file.
+.It Fl f Ns Cm emulate-state
+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.
+.It Fl f Ns Cm split-vector-parameters
+With this flag immediate vector literals which only ever appear as function
+parameters won't be stored as vector immediates. Instead, the 3 floats making
+up the vector will be copied separately. Essentially this turns a vector-store
+instruction into 3 float-store instructions for such cases. This increases
+code size but can dramatically reduce the amount of vector globals, which is
+after all limited to 64k. There's at least one known codebase where this
+lowers the number of globals from over 80k down to around 3k. In other code
+bases it doesn't reduce the globals at all but only increases code size.
+Just try it and see whether it helps you.
 .El
 .Sh OPTIMIZATIONS
 .Bl -tag -width Ds
diff --git a/exec.c b/exec.c
index 29a04a3fcc689f8faeabd40264e4876a9ce2acce..9b6677f38305469bf8a9866ae04b6cebc779765b 100644 (file)
--- a/exec.c
+++ b/exec.c
@@ -55,8 +55,16 @@ qc_program_t* prog_load(const char *filename, bool skipversion)
 {
     prog_header_t   header;
     qc_program_t   *prog;
+    size_t          i;
     fs_file_t      *file  = fs_file_open(filename, "rb");
 
+    /* we need all those in order to support INSTR_STATE: */
+    bool            has_self      = false,
+                    has_time      = false,
+                    has_think     = false,
+                    has_nextthink = false,
+                    has_frame     = false;
+
     if (!file)
         return NULL;
 
@@ -137,6 +145,36 @@ qc_program_t* prog_load(const char *filename, bool skipversion)
     memset(vec_add(prog->entitydata, prog->entityfields), 0, prog->entityfields * sizeof(prog->entitydata[0]));
     prog->entities = 1;
 
+    /* cache some globals and fields from names */
+    for (i = 0; i < vec_size(prog->defs); ++i) {
+        const char *name = prog_getstring(prog, prog->defs[i].name);
+        if      (!strcmp(name, "self")) {
+            prog->cached_globals.self = prog->defs[i].offset;
+            has_self = true;
+        }
+        else if (!strcmp(name, "time")) {
+            prog->cached_globals.time = prog->defs[i].offset;
+            has_time = true;
+        }
+    }
+    for (i = 0; i < vec_size(prog->fields); ++i) {
+        const char *name = prog_getstring(prog, prog->fields[i].name);
+        if      (!strcmp(name, "think")) {
+            prog->cached_fields.think     = prog->fields[i].offset;
+            has_think = true;
+        }
+        else if (!strcmp(name, "nextthink")) {
+            prog->cached_fields.nextthink = prog->fields[i].offset;
+            has_nextthink = true;
+        }
+        else if (!strcmp(name, "frame")) {
+            prog->cached_fields.frame     = prog->fields[i].offset;
+            has_frame = true;
+        }
+    }
+    if (has_self && has_time && has_think && has_nextthink && has_frame)
+        prog->supports_state = true;
+
     return prog;
 
 error:
@@ -912,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;
@@ -1227,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;
 
@@ -1574,8 +1612,24 @@ while (prog->vmerror == 0) {
             break;
 
         case INSTR_STATE:
-            qcvmerror(prog, "`%s` tried to execute a STATE operation", prog->filename);
+        {
+            qcfloat_t *nextthink;
+            qcfloat_t *time;
+            qcfloat_t *frame;
+            if (!prog->supports_state) {
+                qcvmerror(prog, "`%s` tried to execute a STATE operation but misses its defs!", prog->filename);
+                goto cleanup;
+            }
+            ed = prog_getedict(prog, prog->globals[prog->cached_globals.self]);
+            ((qcint_t*)ed)[prog->cached_fields.think] = OPB->function;
+
+            frame     = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.frame];
+            *frame    = OPA->_float;
+            nextthink = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.nextthink];
+            time      = (qcfloat_t*)(prog->globals + prog->cached_globals.time);
+            *nextthink = *time + 0.1;
             break;
+        }
 
         case INSTR_GOTO:
             st += st->o1.s1 - 1;    /* offset the s++ */
diff --git a/fold.c b/fold.c
index af37c4c76f652c7e083f02ffa55e7188de0bc5f8..b519831c0d1fe24d1b14fb1985c2874d15261f5a 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_NOEXCEPT  = 0,
+    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 = (sfloat_exceptionflags_t)((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 sfloat_t sfloat_neg(sfloat_state_t *state, sfloat_t a) {
+    sfloat_cast_t neg;
+    neg.f = -1;
+    return sfloat_mul(state, a, neg.s);
+}
+
+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 = SFLOAT_NOEXCEPT;
+    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
 #define isfloat(X)      (((ast_expression*)(X))->vtype == TYPE_FLOAT)
 #define isvector(X)     (((ast_expression*)(X))->vtype == TYPE_VECTOR)
 #define isstring(X)     (((ast_expression*)(X))->vtype == TYPE_STRING)
+#define isarray(X)      (((ast_expression*)(X))->vtype == TYPE_ARRAY)
 #define isfloats(X,Y)   (isfloat  (X) && isfloat (Y))
 
 /*
  *
  * 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 = (vec3_comp_t)(state->faults | VEC_COMP_X);
+    callback(&state->state[1], sa.y.s, sb.y.s);
+    if (vec3_soft_exception(state, 1)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Y);
+    callback(&state->state[2], sa.z.s, sb.z.s);
+    if (vec3_soft_exception(state, 2)) state->faults = (vec3_comp_t)(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;
     return out;
 }
 
-static GMQCC_INLINE vec3_t vec3_neg(vec3_t a) {
-    vec3_t out;
+static GMQCC_INLINE vec3_t vec3_neg(lex_ctx_t ctx, vec3_t a) {
+    vec3_t         out;
+    sfloat_cast_t  v[3];
+    sfloat_state_t s[3];
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto end;
+
+    v[0].f = a.x;
+    v[1].f = a.y;
+    v[2].f = a.z;
+
+    sfloat_init(&s[0]);
+    sfloat_init(&s[1]);
+    sfloat_init(&s[2]);
+
+    sfloat_neg(&s[0], v[0].s);
+    sfloat_neg(&s[1], v[1].s);
+    sfloat_neg(&s[2], v[2].s);
+
+    sfloat_check(ctx, &s[0], NULL);
+    sfloat_check(ctx, &s[1], NULL);
+    sfloat_check(ctx, &s[2], NULL);
+
+end:
     out.x = -a.x;
     out.y = -a.y;
     out.z = -a.z;
@@ -129,12 +700,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 +786,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 +892,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 +940,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 +952,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);
@@ -340,6 +1006,58 @@ ast_expression *fold_constgen_string(fold_t *fold, const char *str, bool transla
     return (ast_expression*)out;
 }
 
+typedef union {
+    void     (*callback)(void);
+    sfloat_t (*binary)(sfloat_state_t *, sfloat_t, sfloat_t);
+    sfloat_t (*unary)(sfloat_state_t *, sfloat_t);
+} float_check_callback_t;
+
+static bool fold_check_except_float_impl(void     (*callback)(void),
+                                         fold_t    *fold,
+                                         ast_value *a,
+                                         ast_value *b)
+{
+    float_check_callback_t call;
+    sfloat_state_t s;
+    sfloat_cast_t ca;
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS) && !OPTS_WARN(WARN_INEXACT_COMPARES))
+        return false;
+
+    call.callback = callback;
+    sfloat_init(&s);
+    ca.f = fold_immvalue_float(a);
+    if (b) {
+        sfloat_cast_t cb;
+        cb.f = fold_immvalue_float(b);
+        call.binary(&s, ca.s, cb.s);
+    } else {
+        call.unary(&s, ca.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;
+}
+
+#define fold_check_except_float(CALLBACK, FOLD, A, B) \
+    fold_check_except_float_impl(((void (*)(void))(CALLBACK)), (FOLD), (A), (B))
+
+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_mul_vec(fold_t *fold, vec3_t vec, ast_value *sel, const char *set) {
     /*
@@ -372,7 +1090,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;
 }
@@ -380,11 +1098,14 @@ 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));
+        if (fold_can_1(a)) {
+            /* Negation can produce inexact as well */
+            bool inexact = fold_check_except_float(&sfloat_neg, fold, a, NULL);
+            return fold_constgen_float(fold, -fold_immvalue_float(a), inexact);
+        }
     } else if (isvector(a)) {
         if (fold_can_1(a))
-            return fold_constgen_vector(fold, vec3_neg(fold_immvalue_vector(a)));
+            return fold_constgen_vector(fold, vec3_neg(fold_ctx(fold), fold_immvalue_vector(a)));
     }
     return NULL;
 }
@@ -392,16 +1113,16 @@ 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;
@@ -409,22 +1130,30 @@ static GMQCC_INLINE ast_expression *fold_op_not(fold_t *fold, ast_value *a) {
 
 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 +1162,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 +1195,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 +1229,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 +1252,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 +1268,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 +1282,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 +1305,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 +1324,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 +1338,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 +1366,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 +1378,17 @@ 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;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_length(fold_t *fold, ast_value *a) {
+    if (fold_can_1(a) && isstring(a))
+        return fold_constgen_float(fold, strlen(fold_immvalue_string(a)), false);
+    if (isarray(a))
+        return fold_constgen_float(fold, vec_size(a->initlist), false);
     return NULL;
 }
 
@@ -675,27 +1429,30 @@ ast_expression *fold_op(fold_t *fold, const oper_info *info, ast_expression **op
             return e
 
     switch(info->id) {
-        fold_op_case(2, ('-', 'P'),    neg,    (fold, a));
-        fold_op_case(2, ('!', 'P'),    not,    (fold, a));
-        fold_op_case(1, ('+'),         add,    (fold, a, b));
-        fold_op_case(1, ('-'),         sub,    (fold, a, b));
-        fold_op_case(1, ('*'),         mul,    (fold, a, b));
-        fold_op_case(1, ('/'),         div,    (fold, a, b));
-        fold_op_case(1, ('%'),         mod,    (fold, a, b));
-        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(2, ('<', '<'),    lshift, (fold, a, b));
-        fold_op_case(2, ('>', '>'),    rshift, (fold, a, b));
-        fold_op_case(2, ('|', '|'),    andor,  (fold, a, b, true));
-        fold_op_case(2, ('&', '&'),    andor,  (fold, a, b, false));
-        fold_op_case(2, ('?', ':'),    tern,   (fold, a, b, c));
-        fold_op_case(2, ('*', '*'),    exp,    (fold, a, b));
-        fold_op_case(3, ('<','=','>'), lteqgt, (fold, a, b));
-        fold_op_case(2, ('!', '='),    cmp,    (fold, a, b, true));
-        fold_op_case(2, ('=', '='),    cmp,    (fold, a, b, false));
-        fold_op_case(2, ('~', 'P'),    bnot,   (fold, a));
-        fold_op_case(2, ('>', '<'),    cross,  (fold, a, b));
+        fold_op_case(2, ('-', 'P'),      neg,    (fold, a));
+        fold_op_case(2, ('!', 'P'),      not,    (fold, a));
+        fold_op_case(1, ('+'),           add,    (fold, a, b));
+        fold_op_case(1, ('-'),           sub,    (fold, a, b));
+        fold_op_case(1, ('*'),           mul,    (fold, a, b));
+        fold_op_case(1, ('/'),           div,    (fold, a, b));
+        fold_op_case(1, ('%'),           mod,    (fold, a, b));
+        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));
+        fold_op_case(2, ('&', '&'),      andor,  (fold, a, b, false));
+        fold_op_case(2, ('?', ':'),      tern,   (fold, a, b, c));
+        fold_op_case(2, ('*', '*'),      exp,    (fold, a, b));
+        fold_op_case(3, ('<','=','>'),   lteqgt, (fold, a, b));
+        fold_op_case(2, ('!', '='),      cmp,    (fold, a, b, true));
+        fold_op_case(2, ('=', '='),      cmp,    (fold, a, b, false));
+        fold_op_case(2, ('~', 'P'),      bnot,   (fold, a));
+        fold_op_case(2, ('>', '<'),      cross,  (fold, a, b));
+        fold_op_case(3, ('l', 'e', 'n'), length, (fold, a));
     }
     #undef fold_op_case
     compile_error(fold_ctx(fold), "internal error: attempted to constant-fold for unsupported operator");
@@ -708,46 +1465,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/ftepp.c b/ftepp.c
index b974f4fa83a81338b4b080b8e35eb92da8a57afe..d53fd9e8876338ec8880694301130aeb558aeac0 100644 (file)
--- a/ftepp.c
+++ b/ftepp.c
@@ -703,9 +703,9 @@ static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token)
                 ++ch;
             }
             break;
-        case TOKEN_WHITE:
+        /*case TOKEN_WHITE:
             ftepp_out(ftepp, " ", false);
-            break;
+            break;*/
         case TOKEN_EOL:
             ftepp_out(ftepp, "\\n", false);
             break;
@@ -734,6 +734,7 @@ static void ftepp_recursion_footer(ftepp_t *ftepp)
     ftepp_out(ftepp, "\n#pragma pop(line)\n", false);
 }
 
+static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline);
 static void ftepp_param_out(ftepp_t *ftepp, macroparam *param)
 {
     size_t   i;
@@ -742,8 +743,13 @@ static void ftepp_param_out(ftepp_t *ftepp, macroparam *param)
         out = param->tokens[i];
         if (out->token == TOKEN_EOL)
             ftepp_out(ftepp, "\n", false);
-        else
-            ftepp_out(ftepp, out->value, false);
+        else {
+            ppmacro *find = ftepp_macro_find(ftepp, out->value);
+            if (OPTS_FLAG(FTEPP_INDIRECT_EXPANSION) && find && !find->has_params)
+                ftepp_macro_expand(ftepp, find, NULL, false);
+            else
+                ftepp_out(ftepp, out->value, false);
+        }
     }
 }
 
@@ -837,7 +843,7 @@ static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *param
                         macro_params_find(macro, macro->output[o+1]->value, &pi))
                     {
                         ++o;
-                        
+
                         ftepp_stringify(ftepp, &params[pi]);
                         break;
                     }
@@ -1455,6 +1461,7 @@ static bool ftepp_include(ftepp_t *ftepp)
     lex_ctx_t ctx;
     char     lineno[128];
     char     *filename;
+    char     *parsename = NULL;
     char     *old_includename;
 
     (void)ftepp_next(ftepp);
@@ -1462,28 +1469,56 @@ static bool ftepp_include(ftepp_t *ftepp)
         return false;
 
     if (ftepp->token != TOKEN_STRINGCONST) {
-        ftepp_error(ftepp, "expected filename to include");
-        return false;
+        ppmacro *macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+        if (macro) {
+            char *backup = ftepp->output_string;
+            ftepp->output_string = NULL;
+            if (ftepp_macro_expand(ftepp, macro, NULL, true)) {
+                parsename = util_strdup(ftepp->output_string);
+                vec_free(ftepp->output_string);
+                ftepp->output_string = backup;
+            } else {
+                ftepp->output_string = backup;
+                ftepp_error(ftepp, "expected filename to include");
+                return false;
+            }
+        } else if (OPTS_FLAG(FTEPP_PREDEFS)) {
+            /* Well it could be a predefine like __LINE__ */
+            char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp));
+            if (predef) {
+                parsename = predef(ftepp);
+            } else {
+                ftepp_error(ftepp, "expected filename to include");
+                return false;
+            }
+        }
     }
 
     if (!ftepp->output_on) {
-        ftepp_next(ftepp);
+        (void)ftepp_next(ftepp);
         return true;
     }
 
-    ctx = ftepp_ctx(ftepp);
-
-    unescape(ftepp_tokval(ftepp), ftepp_tokval(ftepp));
+    if (parsename)
+        unescape(parsename, parsename);
+    else {
+        char *tokval = ftepp_tokval(ftepp);
+        unescape(tokval, tokval);
+        parsename = util_strdup(tokval);
+    }
 
+    ctx = ftepp_ctx(ftepp);
     ftepp_out(ftepp, "\n#pragma file(", false);
-    ftepp_out(ftepp, ftepp_tokval(ftepp), false);
+    ftepp_out(ftepp, parsename, false);
     ftepp_out(ftepp, ")\n#pragma line(1)\n", false);
 
-    filename = ftepp_include_find(ftepp, ftepp_tokval(ftepp));
+    filename = ftepp_include_find(ftepp, parsename);
     if (!filename) {
-        ftepp_error(ftepp, "failed to open include file `%s`", ftepp_tokval(ftepp));
+        ftepp_error(ftepp, "failed to open include file `%s`", parsename);
+        mem_d(parsename);
         return false;
     }
+    mem_d(parsename);
     inlex = lex_open(filename);
     if (!inlex) {
         ftepp_error(ftepp, "open failed on include file `%s`", filename);
diff --git a/gmqcc.h b/gmqcc.h
index 835931df990a5431399f463728053f652321ce72..5f1bdb3cfc00ca21fa9b5b814bc969caebd66a7b 100644 (file)
--- a/gmqcc.h
+++ b/gmqcc.h
@@ -789,17 +789,17 @@ typedef struct {
 } qc_exec_stack_t;
 
 typedef struct qc_program_s {
-    char                    *filename;
+    char                     *filename;
     prog_section_statement_t *code;
     prog_section_def_t       *defs;
     prog_section_def_t       *fields;
     prog_section_function_t  *functions;
-    char                    *strings;
-    qcint_t                   *globals;
-    qcint_t                   *entitydata;
-    bool                    *entitypool;
+    char                     *strings;
+    qcint_t                  *globals;
+    qcint_t                  *entitydata;
+    bool                     *entitypool;
 
-    const char*             *function_stack;
+    const char*              *function_stack;
 
     uint16_t crc16;
 
@@ -825,6 +825,20 @@ typedef struct qc_program_s {
     size_t xflags;
 
     int    argc; /* current arg count for debugging */
+
+    /* cached fields */
+    struct {
+        qcint_t frame;
+        qcint_t nextthink;
+        qcint_t think;
+    } cached_fields;
+
+    struct {
+        qcint_t self;
+        qcint_t time;
+    } cached_globals;
+
+    bool supports_state; /* is INSTR_STATE supported? */
 } qc_program_t;
 
 qc_program_t*       prog_load      (const char *filename, bool ignoreversion);
@@ -973,7 +987,7 @@ typedef struct {
 
 extern opts_cmd_t opts;
 
-#define OPTS_GENERIC(f,i)    (!! (((f)[(i)/32]) & (1<< (unsigned)((i)%32))))
+#define OPTS_GENERIC(f,i)    (!! (((f)[(i)/32]) & (1<< (unsigned)((i)%32))))
 
 #define OPTS_FLAG(i)         OPTS_GENERIC(opts.flags,        (i))
 #define OPTS_WARN(i)         OPTS_GENERIC(opts.warn,         (i))
index 20680d372771cc2d0972e8508fa3632ff2cc5849..59a27f9a2091476bd7882b41e9bb0984774d7d4f 100644 (file)
     FTEPP_MATHDEFS = false
 
 
+    #Enable indirect macro expansion. This only works in combination
+    #with '-fftepp' and is currently not included by '-std=fteqcc'.
+    #Enabling this behavior will allow the preprocessor to operate more
+    #like the standard C preprocessor in that it will allow arguments
+    #of macros which are macro-expanded to be substituted into the
+    #definition of the macro. As an example:
+    #
+    #   #define STR1(x) #x
+    #   #define STR2(x) STR1(x)
+    #   #define THE_ANSWER 42
+    #   #define THE_ANSWER_STR STR2(THE_ANSWER) /* "42" */
+    #
+    #With this enabled, an expansion of THE_ANSWER_STR will yield
+    #the string "42". With this disabled an expansion of THE_ANSWER_STR
+    #will yield "THE_ANSWER"
+
+    FTEPP_INDIRECT_EXPANSION = false
+
+
     #Allow switch cases to use non constant variables.
 
     RELAXED_SWITCH = true
     SORT_OPERANDS = false
 
 
+    #Emulate OP_STATE operations in code rather than using the instruction.
+    #The desired fps can be set via -state-fps=NUM, defaults to 10.
+
+    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
+
+    #Split vector-literals which are only used dirctly as function parameters
+    #into 3 floats stored separately to reduce the number of globals at the
+    #expense of additional instructions.
+    SPLIT_VECTOR_PARAMETERS = false
 
 [warnings]
     #Generate a warning about variables which are declared but never
     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/hash.c b/hash.c
index bc65b7f6821b2e33f0cd6434d2cef69dbb4e1da9..01a0d924867e17a3f1a64631891e2980c39f1c78 100644 (file)
--- a/hash.c
+++ b/hash.c
@@ -223,26 +223,114 @@ static GMQCC_FORCEINLINE uint32_t hash_murmur(const void *GMQCC_RESTRICT key, si
     return hash_murmur_result(hash, carry, length);
 }
 
-size_t hash(const char *key) {
-    const char   *s = key;
-    const char   *a = s;
+/*
+ * The following hash function implements it's own strlen to avoid using libc's
+ * which isn't always slow but isn't always fastest either.
+ *
+ * Some things to note about this strlen that are otherwise confusing to grasp
+ * at first is that it does intentionally depend on undefined behavior.
+ *
+ * The first step to the strlen is to ensure alignment before checking words,
+ * without this step we risk crossing a page boundry with the word check and
+ * that would cause a crash.
+ *
+ * The second step to the strlen contains intentional undefined behavior. When
+ * accessing a word of any size, the first byte of that word is accessible if
+ * and only if the whole word is accessible because words are aligned. This is
+ * indicated by the fact that size / alignment always divides the page size.
+ * One could argue that an architecture exists where size_t and alignment are
+ * different, if that were the case, the alignment will always assume to be the
+ * size of the type (size_t). So it's always safe in that regard.
+ *
+ * In other words, an aligned 2^n load cannot cross a page boundry unless
+ * n > log2(PAGE_SIZE). There are no known architectures which support such
+ * a wide load larger than PAGE_SIZE.
+ *
+ * Valgrind and address sanatizer may choke on this because they're strictly
+ * trying to find bugs, it's a false positive to assume this is a bug when it's
+ * intentional. To prevent these false positives, both things need to be taught
+ * about the intentional behavior; for address sanatizer this can be done with
+ * a compiler attribute, effectively preventing the function from being
+ * instrumented. Valgrind requires a little more work as there is no way to
+ * downright prevent a function from being instrumented, instead we can mark
+ * + sizeof(size_t) bytes ahead of each byte we're reading as we calculate
+ * the length of the string, then we can make that additional + sizeof(size_t)
+ * on the end undefined after the length has been calculated.
+ *
+ * If the compiler doesn't have the attribute to disable address sanatizer
+ * instrumentation we fall back to using libc's strlen instead. This isn't the
+ * best solution. On windows we can assume this method always because neither
+ * address sanatizer or valgrind exist.
+ */
+
+/* Some compilers expose this */
+#if defined(__has_feature)
+#   if __has_feature(address_sanitizer)
+#       define ASAN_DISABLE __attribute__((no_sanitize_address))
+#       define HAS_ASAN_DISABLE
+#   endif
+#endif
+
+/* If they don't try to find by version the attriubte was introduces */
+#if defined(__GNUC__) && __GNUC__ >= 4
+#   define ASAN_DISABLE __attribute__((no_sanitize_address))
+#   define HAS_ASAN_DISABLE
+#elif defined(__clang__) && __clang_major__ >= 3
+#   define ASAN_DISABLE __attribute__((no_sanatize_address))
+#   define HAS_ASAN_DISABLE
+/* On windows asan doesn't exist */
+#elif defined(_WIN32)
+#   define ASAN_DISABLE /* nothing */
+#   define HAS_ASAN_DISABLE
+#endif
+
+#ifndef HAS_ASAN_DISABLE
+#   include <string.h>
+#endif
+
+#ifndef NVALGRIND
+#   include <valgrind/valgrind.h>
+#   include <valgrind/memcheck.h>
+#else
+#   define VALGRIND_MAKE_MEM_DEFINED(PTR, REDZONE_SIZE)
+#   define VALGRIND_MAKE_MEM_NOACCESS(PTR, REDZONE_SIZE)
+#endif
+
+#ifdef HAS_ASAN_DISABLE
+#define STRLEN_ALIGN (sizeof(size_t))
+#define STRLEN_ONES ((size_t)-1/UCHAR_MAX)
+#define STRLEN_HIGHS (STRLEN_ONES * (UCHAR_MAX/2+1))
+#define STRLEN_HASZERO(X) (((X)-STRLEN_ONES) & ~(X) & STRLEN_HIGHS)
+
+static ASAN_DISABLE size_t hash_strlen(const char *key) {
+    const char *s = key;
+    const char *a = s;
     const size_t *w;
 
-    for (; (uintptr_t)s % sizeof(size_t); s++)
-        if (!*s)
-            return hash_murmur((const void *)key, s-a);
+    for (; (uintptr_t)s % STRLEN_ALIGN; s++) {
+        if (*s)
+            continue;
+        return s-a;
+    }
+
+    VALGRIND_MAKE_MEM_DEFINED(s, STRLEN_ALIGN);
+    for (w = (const size_t *)s; !STRLEN_HASZERO(*w); w++) {
+        /* Make the next word legal to access */
+        VALGRIND_MAKE_MEM_DEFINED(w + STRLEN_ALIGN, STRLEN_ALIGN);
+    }
 
-    for (w = (const size_t*)s; !((*w-(size_t)-1/UCHAR_MAX) & ~*w & ((size_t)-1/UCHAR_MAX) * (UCHAR_MAX / 2 + 1)); w++);
     for (s = (const char *)w; *s; s++);
 
-    return hash_murmur((const void *)key, s-a);
+    /* It's not legal to access this area anymore */
+    VALGRIND_MAKE_MEM_NOACCESS(s + 1, STRLEN_ALIGN);
+    return s-a;
+}
+#else
+static GMQCC_INLINE size_t hash_strlen(const char *key) {
+    return strlen(key);
 }
+#endif
 
-#undef HASH_ROTL32
-#undef HASH_MURMUR_MASK1
-#undef HASH_MURMUR_MASK2
-#undef HASH_MURMUR_SEED
-#undef HASH_MURMUR_SAFEREAD
-#undef HASH_MURMUR_BLOCK
-#undef HASH_MURMUR_BYTES
-#undef HASH_MURMUR_TAIL
+size_t hash(const char *key) {
+    return hash_murmur((const void *)key, hash_strlen(key));
+}
index 23c3fb94c45d7562a8deb1670db3e8e19c0e81be..1f90111aa332e63344ad8f30b20f51f2cf291e9a 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
@@ -111,20 +119,23 @@ uninstall:
        rm -f $(DESTDIR)$(BINDIR)/gmqcc
        rm -f $(DESTDIR)$(BINDIR)/qcvm
        rm -f $(DESTDIR)$(BINDIR)/gmqpak
-       rm -f $(DESTDIR)$(MANDIR)/man1/doc/gmqcc.1
-       rm -f $(DESTDIR)$(MANDIR)/man1/doc/qcvm.1
-       rm -f $(DESTDIR)$(MANDIR)/man1/doc/gmqpak.1
+       rm -f $(DESTDIR)$(MANDIR)/man1/gmqcc.1
+       rm -f $(DESTDIR)$(MANDIR)/man1/qcvm.1
+       rm -f $(DESTDIR)$(MANDIR)/man1/gmqpak.1
 
 #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/ir.c b/ir.c
index 7df8bdbff6d7a51b33561a80edb4c96097dc167c..c99f591e884ced27c055be2633adf18007826076 100644 (file)
--- a/ir.c
+++ b/ir.c
@@ -1121,6 +1121,20 @@ ir_value* ir_value_var(const char *name, int storetype, int vtype)
     return self;
 }
 
+/*  helper function */
+static ir_value* ir_builder_imm_float(ir_builder *self, float value, bool add_to_list) {
+    ir_value *v = ir_value_var("#IMMEDIATE", store_global, TYPE_FLOAT);
+    v->flags |= IR_FLAG_ERASABLE;
+    v->hasvalue = true;
+    v->cvq = CV_CONST;
+    v->constval.vfloat = value;
+
+    vec_push(self->globals, v);
+    if (add_to_list)
+        vec_push(self->const_floats, v);
+    return v;
+}
+
 ir_value* ir_value_vector_member(ir_value *self, unsigned int member)
 {
     char     *name;
@@ -1206,9 +1220,11 @@ void ir_value_delete(ir_value* self)
         if (self->vtype == TYPE_STRING)
             mem_d((void*)self->constval.vstring);
     }
-    for (i = 0; i < 3; ++i) {
-        if (self->members[i])
-            ir_value_delete(self->members[i]);
+    if (!(self->flags & IR_FLAG_SPLIT_VECTOR)) {
+        for (i = 0; i < 3; ++i) {
+            if (self->members[i])
+                ir_value_delete(self->members[i]);
+        }
     }
     vec_free(self->reads);
     vec_free(self->writes);
@@ -1537,6 +1553,26 @@ bool ir_block_create_store_op(ir_block *self, lex_ctx_t ctx, int op, ir_value *t
     return true;
 }
 
+bool ir_block_create_state_op(ir_block *self, lex_ctx_t ctx, ir_value *frame, ir_value *think)
+{
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return false;
+
+    in = ir_instr_new(ctx, self, INSTR_STATE);
+    if (!in)
+        return false;
+
+    if (!ir_instr_op(in, 0, frame, false) ||
+        !ir_instr_op(in, 1, think, false))
+    {
+        ir_instr_delete(in);
+        return false;
+    }
+    vec_push(self->instr, in);
+    return true;
+}
+
 static bool ir_block_create_store(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what)
 {
     int op = 0;
@@ -3109,7 +3145,21 @@ static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *bloc
                     stmt.opcode = type_store_instr[param->vtype];
                 stmt.o1.u1 = ir_value_code_addr(param);
                 stmt.o2.u1 = OFS_PARM0 + 3 * p;
-                code_push_statement(code, &stmt, instr->context);
+
+                if (param->vtype == TYPE_VECTOR && (param->flags & IR_FLAG_SPLIT_VECTOR)) {
+                    /* fetch 3 separate floats */
+                    stmt.opcode = INSTR_STORE_F;
+                    stmt.o1.u1 = ir_value_code_addr(param->members[0]);
+                    code_push_statement(code, &stmt, instr->context);
+                    stmt.o2.u1++;
+                    stmt.o1.u1 = ir_value_code_addr(param->members[1]);
+                    code_push_statement(code, &stmt, instr->context);
+                    stmt.o2.u1++;
+                    stmt.o1.u1 = ir_value_code_addr(param->members[2]);
+                    code_push_statement(code, &stmt, instr->context);
+                }
+                else
+                    code_push_statement(code, &stmt, instr->context);
             }
             /* Now handle extparams */
             first = vec_size(instr->params);
@@ -3138,7 +3188,20 @@ static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *bloc
                     stmt.opcode = type_store_instr[param->vtype];
                 stmt.o1.u1 = ir_value_code_addr(param);
                 stmt.o2.u1 = ir_value_code_addr(targetparam);
-                code_push_statement(code, &stmt, instr->context);
+                if (param->vtype == TYPE_VECTOR && (param->flags & IR_FLAG_SPLIT_VECTOR)) {
+                    /* fetch 3 separate floats */
+                    stmt.opcode = INSTR_STORE_F;
+                    stmt.o1.u1 = ir_value_code_addr(param->members[0]);
+                    code_push_statement(code, &stmt, instr->context);
+                    stmt.o2.u1++;
+                    stmt.o1.u1 = ir_value_code_addr(param->members[1]);
+                    code_push_statement(code, &stmt, instr->context);
+                    stmt.o2.u1++;
+                    stmt.o1.u1 = ir_value_code_addr(param->members[2]);
+                    code_push_statement(code, &stmt, instr->context);
+                }
+                else
+                    code_push_statement(code, &stmt, instr->context);
             }
 
             stmt.opcode = INSTR_CALL0 + vec_size(instr->params);
@@ -3167,8 +3230,14 @@ static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *bloc
         }
 
         if (instr->opcode == INSTR_STATE) {
-            irerror(block->context, "TODO: state instruction");
-            return false;
+            stmt.opcode = instr->opcode;
+            if (instr->_ops[0])
+                stmt.o1.u1 = ir_value_code_addr(instr->_ops[0]);
+            if (instr->_ops[1])
+                stmt.o2.u1 = ir_value_code_addr(instr->_ops[1]);
+            stmt.o3.u1 = 0;
+            code_push_statement(code, &stmt, instr->context);
+            continue;
         }
 
         stmt.opcode = instr->opcode;
@@ -3512,7 +3581,7 @@ static bool gen_global_function_code(ir_builder *ir, ir_value *global)
      * If there is no definition and the thing is eraseable, we can ignore
      * outputting the function to begin with.
      */
-    if (global->flags & IR_FLAG_ERASEABLE && irfun->code_function_def < 0) {
+    if (global->flags & IR_FLAG_ERASABLE && irfun->code_function_def < 0) {
         return true;
     }
 
@@ -3609,6 +3678,10 @@ static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool isloc
     prog_section_def_t def;
     bool               pushdef = opts.optimizeoff;
 
+    /* we don't generate split-vectors */
+    if (global->vtype == TYPE_VECTOR && (global->flags & IR_FLAG_SPLIT_VECTOR))
+        return true;
+
     def.type   = global->vtype;
     def.offset = vec_size(self->code->globals);
     def.name   = 0;
@@ -3620,7 +3693,7 @@ static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool isloc
          * if we're eraseable and the function isn't referenced ignore outputting
          * the function.
          */
-        if (global->flags & IR_FLAG_ERASEABLE && vec_size(global->reads) == 0) {
+        if (global->flags & IR_FLAG_ERASABLE && vec_size(global->reads) == 0) {
             return true;
         }
 
@@ -3859,12 +3932,113 @@ static bool ir_builder_gen_field(ir_builder *self, ir_value *field)
     return field->code.globaladdr >= 0;
 }
 
+static void ir_builder_collect_reusables(ir_builder *builder) {
+    size_t i;
+    ir_value **reusables = NULL;
+    for (i = 0; i < vec_size(builder->globals); ++i) {
+        ir_value *value = builder->globals[i];
+        if (value->vtype != TYPE_FLOAT || !value->hasvalue)
+            continue;
+        if (value->cvq == CV_CONST || (value->name && value->name[0] == '#')) {
+            vec_push(reusables, value);
+        }
+    }
+    builder->const_floats = reusables;
+}
+
+static void ir_builder_split_vector(ir_builder *self, ir_value *vec) {
+    size_t i, count;
+    ir_value* found[3] = { NULL, NULL, NULL };
+
+    /* must not be written to */
+    if (vec_size(vec->writes))
+        return;
+    /* must not be trying to access individual members */
+    if (vec->members[0] || vec->members[1] || vec->members[2])
+        return;
+    /* should be actually used otherwise it won't be generated anyway */
+    count = vec_size(vec->reads);
+    if (!count)
+        return;
+
+    /* may only be used directly as function parameters, so if we find some other instruction cancel */
+    for (i = 0; i != count; ++i) {
+        /* we only split vectors if they're used directly as parameter to a call only! */
+        ir_instr *user = vec->reads[i];
+        if ((user->opcode < INSTR_CALL0 || user->opcode > INSTR_CALL8) && user->opcode != VINSTR_NRCALL)
+            return;
+    }
+
+    vec->flags |= IR_FLAG_SPLIT_VECTOR;
+
+    /* find existing floats making up the split */
+    count = vec_size(self->const_floats);
+    for (i = 0; i != count; ++i) {
+        ir_value *c = self->const_floats[i];
+        if (!found[0] && c->constval.vfloat == vec->constval.vvec.x)
+            found[0] = c;
+        if (!found[1] && c->constval.vfloat == vec->constval.vvec.y)
+            found[1] = c;
+        if (!found[2] && c->constval.vfloat == vec->constval.vvec.z)
+            found[2] = c;
+        if (found[0] && found[1] && found[2])
+            break;
+    }
+
+    /* generate floats for not yet found components */
+    if (!found[0])
+        found[0] = ir_builder_imm_float(self, vec->constval.vvec.x, true);
+    if (!found[1]) {
+        if (vec->constval.vvec.y == vec->constval.vvec.x)
+            found[1] = found[0];
+        else
+            found[1] = ir_builder_imm_float(self, vec->constval.vvec.y, true);
+    }
+    if (!found[2]) {
+        if (vec->constval.vvec.z == vec->constval.vvec.x)
+            found[2] = found[0];
+        else if (vec->constval.vvec.z == vec->constval.vvec.y)
+            found[2] = found[1];
+        else
+            found[2] = ir_builder_imm_float(self, vec->constval.vvec.z, true);
+    }
+
+    /* the .members array should be safe to use here. */
+    vec->members[0] = found[0];
+    vec->members[1] = found[1];
+    vec->members[2] = found[2];
+
+    /* register the readers for these floats */
+    count = vec_size(vec->reads);
+    for (i = 0; i != count; ++i) {
+        vec_push(found[0]->reads, vec->reads[i]);
+        vec_push(found[1]->reads, vec->reads[i]);
+        vec_push(found[2]->reads, vec->reads[i]);
+    }
+}
+
+static void ir_builder_split_vectors(ir_builder *self) {
+    size_t i, count = vec_size(self->globals);
+    for (i = 0; i != count; ++i) {
+        ir_value *v = self->globals[i];
+        if (v->vtype != TYPE_VECTOR || !v->name || v->name[0] != '#')
+            continue;
+        ir_builder_split_vector(self, self->globals[i]);
+    }
+}
+
 bool ir_builder_generate(ir_builder *self, const char *filename)
 {
     prog_section_statement_t stmt;
     size_t i;
     char  *lnofile = NULL;
 
+    if (OPTS_FLAG(SPLIT_VECTOR_PARAMETERS)) {
+        ir_builder_collect_reusables(self);
+        if (vec_size(self->const_floats) > 0)
+            ir_builder_split_vectors(self);
+    }
+
     for (i = 0; i < vec_size(self->fields); ++i)
     {
         ir_builder_prepare_field(self->code, self->fields[i]);
@@ -3930,7 +4104,7 @@ bool ir_builder_generate(ir_builder *self, const char *filename)
     }
 
     if (vec_size(self->code->globals) >= 65536) {
-        irerror(vec_last(self->globals)->context, "This progs file would require more globals than the metadata can handle. Bailing out.");
+        irerror(vec_last(self->globals)->context, "This progs file would require more globals than the metadata can handle (%u). Bailing out.", (unsigned int)vec_size(self->code->globals));
         return false;
     }
 
diff --git a/ir.h b/ir.h
index 5b948358fdee732766bf818a66653350460c823a..aa06179ee5b7e10dab2519612859bb04aa200eb2 100644 (file)
--- a/ir.h
+++ b/ir.h
@@ -43,12 +43,14 @@ typedef struct {
 } ir_life_entry_t;
 
 enum {
-    IR_FLAG_HAS_ARRAYS           = 1 << 0,
-    IR_FLAG_HAS_UNINITIALIZED    = 1 << 1,
-    IR_FLAG_HAS_GOTO             = 1 << 2,
-    IR_FLAG_INCLUDE_DEF          = 1 << 3,
-    IR_FLAG_ERASEABLE            = 1 << 4,
-    IR_FLAG_BLOCK_COVERAGE       = 1 << 5,
+    IR_FLAG_HAS_ARRAYS              = 1 << 0,
+    IR_FLAG_HAS_UNINITIALIZED       = 1 << 1,
+    IR_FLAG_HAS_GOTO                = 1 << 2,
+    IR_FLAG_INCLUDE_DEF             = 1 << 3,
+    IR_FLAG_ERASABLE                = 1 << 4,
+    IR_FLAG_BLOCK_COVERAGE          = 1 << 5,
+
+    IR_FLAG_SPLIT_VECTOR            = 1 << 6,
 
     IR_FLAG_LAST,
     IR_FLAG_MASK_NO_OVERLAP      = (IR_FLAG_HAS_ARRAYS | IR_FLAG_HAS_UNINITIALIZED),
@@ -170,6 +172,7 @@ bool GMQCC_WARN ir_block_create_store_op(ir_block*, lex_ctx_t, int op, ir_value
 bool GMQCC_WARN ir_block_create_storep(ir_block*, lex_ctx_t, ir_value *target, ir_value *what);
 ir_value*       ir_block_create_load_from_ent(ir_block*, lex_ctx_t, const char *label, ir_value *ent, ir_value *field, int outype);
 ir_value*       ir_block_create_fieldaddress(ir_block*, lex_ctx_t, const char *label, ir_value *entity, ir_value *field);
+bool GMQCC_WARN ir_block_create_state_op(ir_block*, lex_ctx_t, ir_value *frame, ir_value *think);
 
 /* This is to create an instruction of the form
  * <outtype>%label := opcode a, b
@@ -253,6 +256,7 @@ struct ir_builder_s {
     ir_function **functions;
     ir_value    **globals;
     ir_value    **fields;
+    ir_value    **const_floats; /* for reusing them in vector-splits, TODO: sort this or use a radix-tree */
 
     ht            htfunctions;
     ht            htglobals;
diff --git a/lexer.c b/lexer.c
index 5ad0a90a537f558e2ba0512e79a771f8181426a6..9ed9c39f25742afd29caff28e37578e3bfb6aebe 100644 (file)
--- a/lexer.c
+++ b/lexer.c
@@ -681,7 +681,7 @@ static bool lex_finish_frames(lex_file *lex)
 static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
 {
     utf8ch_t chr = 0;
-    int ch = 0;
+    int ch = 0, texttype = 0;
     int nextch;
     bool hex;
     bool oct;
@@ -716,13 +716,12 @@ static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
             case '\\': break;
             case '\'': break;
             case '"':  break;
-            case 'a':  ch = '\a'; break;
-            case 'b':  ch = '\b'; break;
-            case 'r':  ch = '\r'; break;
-            case 'n':  ch = '\n'; break;
-            case 't':  ch = '\t'; break;
-            case 'f':  ch = '\f'; break;
-            case 'v':  ch = '\v'; break;
+            case 'a': ch = '\a'; break;
+            case 'r': ch = '\r'; break;
+            case 'n': ch = '\n'; break;
+            case 't': ch = '\t'; break;
+            case 'f': ch = '\f'; break;
+            case 'v': ch = '\v'; break;
             case 'x':
             case 'X':
                 /* same procedure as in fteqcc */
@@ -825,7 +824,15 @@ static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
                 else
                     ch = chr;
                 break;
-            case '\n':  ch = '\n'; break;
+
+            /* high bit text */
+            case 'b': case 's':
+                texttype ^= 128;
+                continue;
+
+            case '\n':
+                ch = '\n';
+                break;
 
             default:
                 lexwarn(lex, WARN_UNKNOWN_CONTROL_SEQUENCE, "unrecognized control sequence: \\%c", ch);
@@ -833,7 +840,7 @@ static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
                 lex_tokench(lex, '\\');
             }
             /* add the character finally */
-            lex_tokench(lex, ch);
+            lex_tokench(lex, ch | texttype);
         }
         else
             lex_tokench(lex, ch);
@@ -1332,6 +1339,8 @@ int lex_do(lex_file *lex)
         } else if (!strcmp(v, "vector")) {
             lex->tok.ttype = TOKEN_TYPENAME;
             lex->tok.constval.t = TYPE_VECTOR;
+        } else if (!strcmp(v, "_length")) {
+            lex->tok.ttype = TOKEN_OPERATOR;
         } else {
             size_t kw;
             for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_qc); ++kw) {
diff --git a/lexer.h b/lexer.h
index 41a135549f5cce35b22aeb20203ab6ecfd4e8a2e..9187dee089bce496322363f13a4d395e5f00214d 100644 (file)
--- a/lexer.h
+++ b/lexer.h
@@ -176,71 +176,72 @@ typedef struct {
 #define opid3(a,b,c) (((uint8_t)a<<16)|((uint8_t)b<<8)|(uint8_t)c)
 
 static const oper_info c_operators[] = {
-    { "(",   0, opid1('('),         ASSOC_LEFT,  99, OP_PREFIX, false}, /* paren expression - non function call */
+    { "(",       0, opid1('('),         ASSOC_LEFT,  99, OP_PREFIX, false}, /* paren expression - non function call */
+    { "_length", 1, opid3('l','e','n'), ASSOC_RIGHT, 98, OP_PREFIX, true},
 
-    { "++",  1, opid3('S','+','+'), ASSOC_LEFT,  17, OP_SUFFIX, false},
-    { "--",  1, opid3('S','-','-'), ASSOC_LEFT,  17, OP_SUFFIX, false},
-    { ".",   2, opid1('.'),         ASSOC_LEFT,  17, 0,         false},
-    { "(",   0, opid1('('),         ASSOC_LEFT,  17, 0,         false}, /* function call */
-    { "[",   2, opid1('['),         ASSOC_LEFT,  17, 0,         false}, /* array subscript */
+    { "++",     1, opid3('S','+','+'), ASSOC_LEFT,  17, OP_SUFFIX, false},
+    { "--",     1, opid3('S','-','-'), ASSOC_LEFT,  17, OP_SUFFIX, false},
+    { ".",      2, opid1('.'),         ASSOC_LEFT,  17, 0,         false},
+    { "(",      0, opid1('('),         ASSOC_LEFT,  17, 0,         false}, /* function call */
+    { "[",      2, opid1('['),         ASSOC_LEFT,  17, 0,         false}, /* array subscript */
 
-    { "++",  1, opid3('+','+','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
-    { "--",  1, opid3('-','-','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
+    { "++",     1, opid3('+','+','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
+    { "--",     1, opid3('-','-','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
 
-    { "**",  2, opid2('*','*'),     ASSOC_RIGHT, 14, 0,         true},
-    { "!",   1, opid2('!','P'),     ASSOC_RIGHT, 14, OP_PREFIX, true},
-    { "~",   1, opid2('~','P'),     ASSOC_RIGHT, 14, OP_PREFIX, true},
-    { "+",   1, opid2('+','P'),     ASSOC_RIGHT, 14, OP_PREFIX, false},
-    { "-",   1, opid2('-','P'),     ASSOC_RIGHT, 14, OP_PREFIX, true},
-/*  { "&",   1, opid2('&','P'),     ASSOC_RIGHT, 14, OP_PREFIX, false}, */
+    { "**",     2, opid2('*','*'),     ASSOC_RIGHT, 14, 0,         true},
+    { "!",      1, opid2('!','P'),     ASSOC_RIGHT, 14, OP_PREFIX, true},
+    { "~",      1, opid2('~','P'),     ASSOC_RIGHT, 14, OP_PREFIX, true},
+    { "+",      1, opid2('+','P'),     ASSOC_RIGHT, 14, OP_PREFIX, false},
+    { "-",      1, opid2('-','P'),     ASSOC_RIGHT, 14, OP_PREFIX, true},
+/*  { "&",      1, opid2('&','P'),     ASSOC_RIGHT, 14, OP_PREFIX, false}, */
 
-    { "*",   2, opid1('*'),         ASSOC_LEFT,  13, 0,         true},
-    { "/",   2, opid1('/'),         ASSOC_LEFT,  13, 0,         true},
-    { "%",   2, opid1('%'),         ASSOC_LEFT,  13, 0,         true},
-    { "><",  2, opid2('>','<'),     ASSOC_LEFT,  13, 0,         true},
+    { "*",      2, opid1('*'),         ASSOC_LEFT,  13, 0,         true},
+    { "/",      2, opid1('/'),         ASSOC_LEFT,  13, 0,         true},
+    { "%",      2, opid1('%'),         ASSOC_LEFT,  13, 0,         true},
+    { "><",     2, opid2('>','<'),     ASSOC_LEFT,  13, 0,         true},
 
-    { "+",   2, opid1('+'),         ASSOC_LEFT,  12, 0,         true},
-    { "-",   2, opid1('-'),         ASSOC_LEFT,  12, 0,         true},
+    { "+",      2, opid1('+'),         ASSOC_LEFT,  12, 0,         true},
+    { "-",      2, opid1('-'),         ASSOC_LEFT,  12, 0,         true},
 
-    { "<<",  2, opid2('<','<'),     ASSOC_LEFT,  11, 0,         true},
-    { ">>",  2, opid2('>','>'),     ASSOC_LEFT,  11, 0,         true},
+    { "<<",     2, opid2('<','<'),     ASSOC_LEFT,  11, 0,         true},
+    { ">>",     2, opid2('>','>'),     ASSOC_LEFT,  11, 0,         true},
 
-    { "<",   2, opid1('<'),         ASSOC_LEFT,  10, 0,         false},
-    { ">",   2, opid1('>'),         ASSOC_LEFT,  10, 0,         false},
-    { "<=>", 2, opid3('<','=','>'), ASSOC_LEFT,  10, 0,         true},
-    { "<=",  2, opid2('<','='),     ASSOC_LEFT,  10, 0,         false},
-    { ">=",  2, opid2('>','='),     ASSOC_LEFT,  10, 0,         false},
+    { "<",      2, opid1('<'),         ASSOC_LEFT,  10, 0,         false},
+    { ">",      2, opid1('>'),         ASSOC_LEFT,  10, 0,         false},
+    { "<=>",    2, opid3('<','=','>'), ASSOC_LEFT,  10, 0,         true},
+    { "<=",     2, opid2('<','='),     ASSOC_LEFT,  10, 0,         false},
+    { ">=",     2, opid2('>','='),     ASSOC_LEFT,  10, 0,         false},
 
-    { "==",  2, opid2('=','='),     ASSOC_LEFT,  9,  0,         true},
-    { "!=",  2, opid2('!','='),     ASSOC_LEFT,  9,  0,         true},
+    { "==",     2, opid2('=','='),     ASSOC_LEFT,  9,  0,         true},
+    { "!=",     2, opid2('!','='),     ASSOC_LEFT,  9,  0,         true},
 
-    { "&",   2, opid1('&'),         ASSOC_LEFT,  8,  0,         true},
+    { "&",      2, opid1('&'),         ASSOC_LEFT,  8,  0,         true},
 
-    { "^",   2, opid1('^'),         ASSOC_LEFT,  7,  0,         true},
+    { "^",      2, opid1('^'),         ASSOC_LEFT,  7,  0,         true},
 
-    { "|",   2, opid1('|'),         ASSOC_LEFT,  6,  0,         true},
+    { "|",      2, opid1('|'),         ASSOC_LEFT,  6,  0,         true},
 
-    { "&&",  2, opid2('&','&'),     ASSOC_LEFT,  5,  0,         true},
+    { "&&",     2, opid2('&','&'),     ASSOC_LEFT,  5,  0,         true},
 
-    { "||",  2, opid2('|','|'),     ASSOC_LEFT,  4,  0,         true},
+    { "||",     2, opid2('|','|'),     ASSOC_LEFT,  4,  0,         true},
 
-    { "?",   3, opid2('?',':'),     ASSOC_RIGHT, 3,  0,         true},
+    { "?",      3, opid2('?',':'),     ASSOC_RIGHT, 3,  0,         true},
 
-    { "=",   2, opid1('='),         ASSOC_RIGHT, 2,  0,         false},
-    { "+=",  2, opid2('+','='),     ASSOC_RIGHT, 2,  0,         false},
-    { "-=",  2, opid2('-','='),     ASSOC_RIGHT, 2,  0,         false},
-    { "*=",  2, opid2('*','='),     ASSOC_RIGHT, 2,  0,         false},
-    { "/=",  2, opid2('/','='),     ASSOC_RIGHT, 2,  0,         false},
-    { "%=",  2, opid2('%','='),     ASSOC_RIGHT, 2,  0,         false},
-    { ">>=", 2, opid3('>','>','='), ASSOC_RIGHT, 2,  0,         false},
-    { "<<=", 2, opid3('<','<','='), ASSOC_RIGHT, 2,  0,         false},
-    { "&=",  2, opid2('&','='),     ASSOC_RIGHT, 2,  0,         false},
-    { "^=",  2, opid2('^','='),     ASSOC_RIGHT, 2,  0,         false},
-    { "|=",  2, opid2('|','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "=",      2, opid1('='),         ASSOC_RIGHT, 2,  0,         false},
+    { "+=",     2, opid2('+','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "-=",     2, opid2('-','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "*=",     2, opid2('*','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "/=",     2, opid2('/','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "%=",     2, opid2('%','='),     ASSOC_RIGHT, 2,  0,         false},
+    { ">>=",    2, opid3('>','>','='), ASSOC_RIGHT, 2,  0,         false},
+    { "<<=",    2, opid3('<','<','='), ASSOC_RIGHT, 2,  0,         false},
+    { "&=",     2, opid2('&','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "^=",     2, opid2('^','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "|=",     2, opid2('|','='),     ASSOC_RIGHT, 2,  0,         false},
 
-    { ":",   0, opid2(':','?'),     ASSOC_RIGHT, 1,  0,         false},
+    { ":",      0, opid2(':','?'),     ASSOC_RIGHT, 1,  0,         false},
 
-    { ",",   2, opid1(','),         ASSOC_LEFT,  0,  0,         false}
+    { ",",      2, opid1(','),         ASSOC_LEFT,  0,  0,         false}
 };
 
 static const oper_info fte_operators[] = {
diff --git a/main.c b/main.c
index 156a3f0c1af5c81c4c72d7ac70dc64087ea5c29c..ff15b20ec329802c1c2167fe76866fccbf92ef22 100644 (file)
--- a/main.c
+++ b/main.c
@@ -85,6 +85,7 @@ static int usage(void) {
             "  -Ono-<name>            disable specific optimization\n"
             "  -Ohelp                 list optimizations\n");
     con_out("  -force-crc=num         force a specific checksum into the header\n");
+    con_out("  -state-fps=num         emulate OP_STATE with the specified FPS\n");
     con_out("  -coverage              add coverage support\n");
     return -1;
 }
@@ -217,6 +218,11 @@ static bool options_parse(int argc, char **argv) {
                 OPTS_OPTION_U16 (OPTION_FORCED_CRC) = strtol(argarg, NULL, 0);
                 continue;
             }
+            if (options_long_gcc("state-fps", &argc, &argv, &argarg)) {
+                OPTS_OPTION_U32(OPTION_STATE_FPS) = strtol(argarg, NULL, 0);
+                opts_set(opts.flags, EMULATE_STATE, true);
+                continue;
+            }
             if (options_long_gcc("redirout", &argc, &argv, &redirout)) {
                 con_change(redirout, redirerr);
                 continue;
diff --git a/opts.c b/opts.c
index 79980247333fecd7aa105ef4f0bc52f79e0db62f..5d68355832ab7b55df6da1d28251cd89736c0060 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);
@@ -101,6 +102,8 @@ static void opts_setdefault(void) {
     opts_set(opts.flags, LEGACY_VECTOR_MATHS,            true);
     opts_set(opts.flags, DARKPLACES_STRING_TABLE_BUG,    true);
 
+    /* options */
+    OPTS_OPTION_U32(OPTION_STATE_FPS) = 10;
 }
 
 void opts_backup_non_Wall() {
@@ -193,9 +196,9 @@ void opts_setoptimlevel(unsigned int level) {
  * from a progs.src.
  */
 static char *opts_ini_rstrip(char *s) {
-    char *p = s + strlen(s);
-    while(p > s && util_isspace(*--p))
-        *p = '\0';
+    char *p = s + strlen(s) - 1;
+    while (p > s && util_isspace(*p))
+        *p = '\0', p--;
     return s;
 }
 
@@ -215,8 +218,9 @@ static char *opts_ini_next(const char *s, char c) {
 
 static size_t opts_ini_parse (
     fs_file_t *filehandle,
-    char *(*loadhandle)(const char *, const char *, const char *),
-    char **errorhandle
+    char *(*loadhandle)(const char *, const char *, const char *, char **),
+    char **errorhandle,
+    char **parse_file
 ) {
     size_t linesize;
     size_t lineno             = 1;
@@ -262,7 +266,7 @@ static size_t opts_ini_parse (
         } else if (*parse_beg && *parse_beg != ';') {
             /* not a comment, must be a name value pair :) */
             if (*(parse_end = opts_ini_next(parse_beg, '=')) != '=')
-                  parse_end = opts_ini_next(parse_beg, ':');
+                parse_end = opts_ini_next(parse_beg, ':');
 
             if (*parse_end == '=' || *parse_end == ':') {
                 *parse_end = '\0'; /* terminate bro */
@@ -276,8 +280,20 @@ static size_t opts_ini_parse (
                 util_strncpy(oldname_data, read_name, sizeof(oldname_data));
                 oldname_data[sizeof(oldname_data) - 1] ='\0';
 
-                if ((*errorhandle = loadhandle(section_data, read_name, read_value)) && !error)
+                if ((*errorhandle = loadhandle(section_data, read_name, read_value, parse_file)) && !error)
                     error = lineno;
+            } else if (!strcmp(section_data, "includes")) {
+                /* Includes are special */
+                if (*(parse_end = opts_ini_next(parse_beg, '=')) == '='
+                ||  *(parse_end = opts_ini_next(parse_beg, ':')) == ':') {
+                    static const char *invalid_include = "invalid use of include";
+                    vec_append(*errorhandle, strlen(invalid_include), invalid_include);
+                    error = lineno;
+                } else {
+                    read_name = opts_ini_rstrip(parse_beg);
+                    if ((*errorhandle = loadhandle(section_data, read_name, read_name, parse_file)) && !error)
+                        error = lineno;
+                }
             } else if (!error) {
                 /* otherwise set error to the current line number */
                 error = lineno;
@@ -287,6 +303,7 @@ static size_t opts_ini_parse (
     }
     mem_d(line);
     return error;
+
 }
 
 /*
@@ -298,7 +315,7 @@ static bool opts_ini_bool(const char *value) {
     return !!strtol(value, NULL, 10);
 }
 
-static char *opts_ini_load(const char *section, const char *name, const char *value) {
+static char *opts_ini_load(const char *section, const char *name, const char *value, char **parse_file) {
     char *error = NULL;
     bool  found = false;
 
@@ -310,6 +327,26 @@ static char *opts_ini_load(const char *section, const char *name, const char *va
     #undef GMQCC_TYPE_OPTIMIZATIONS
     #undef GMQCC_TYPE_WARNS
 
+    /* deal with includes */
+    if (!strcmp(section, "includes")) {
+        static const char *include_error_beg = "failed to open file `";
+        static const char *include_error_end = "' for inclusion";
+        fs_file_t *file = fs_file_open(value, "r");
+        found = true;
+        if (!file) {
+            vec_append(error, strlen(include_error_beg), include_error_beg);
+            vec_append(error, strlen(value), value);
+            vec_append(error, strlen(include_error_end), include_error_end);
+        } else {
+            if (opts_ini_parse(file, &opts_ini_load, &error, parse_file) != 0)
+                found = false;
+            /* Change the file name */
+            mem_d(*parse_file);
+            *parse_file = util_strdup(value);
+            fs_file_close(file);
+        }
+    }
+
     /* flags */
     #define GMQCC_TYPE_FLAGS
     #define GMQCC_DEFINE_FLAG(X)                                       \
@@ -348,24 +385,29 @@ static char *opts_ini_load(const char *section, const char *name, const char *va
 
     /* nothing was found ever! */
     if (!found) {
-        if (strcmp(section, "flags")         &&
-            strcmp(section, "warnings")      &&
+        if (strcmp(section, "includes") &&
+            strcmp(section, "flags")    &&
+            strcmp(section, "warnings") &&
             strcmp(section, "optimizations"))
         {
-            vec_append(error, 17,             "invalid section `");
+            static const char *invalid_section = "invalid_section `";
+            vec_append(error, strlen(invalid_section), invalid_section);
             vec_append(error, strlen(section), section);
-            vec_push  (error, '`');
-            vec_push  (error, '\0');
-        } else {
-            vec_append(error, 18,             "invalid variable `");
+            vec_push(error, '`');
+        } else if (strcmp(section, "includes")) {
+            static const char *invalid_variable = "invalid_variable `";
+            static const char *in_section = "` in section: `";
+            vec_append(error, strlen(invalid_variable), invalid_variable);
             vec_append(error, strlen(name), name);
-            vec_push  (error, '`');
-            vec_append(error, 14,              " in section: `");
+            vec_append(error, strlen(in_section), in_section);
             vec_append(error, strlen(section), section);
-            vec_push  (error, '`');
-            vec_push  (error, '\0');
+            vec_push(error, '`');
+        } else {
+            static const char *expected_something = "expected something";
+            vec_append(error, strlen(expected_something), expected_something);
         }
     }
+    vec_push(error, '\0');
     return error;
 }
 
@@ -380,6 +422,7 @@ void opts_ini_init(const char *file) {
      *  gmqcc.cfg
      */
     char       *error = NULL;
+    char       *parse_file = NULL;
     size_t     line;
     fs_file_t  *ini;
 
@@ -394,11 +437,13 @@ void opts_ini_init(const char *file) {
 
     con_out("found ini file `%s`\n", file);
 
-    if ((line = opts_ini_parse(ini, &opts_ini_load, &error)) != 0) {
+    parse_file = util_strdup(file);
+    if ((line = opts_ini_parse(ini, &opts_ini_load, &error, &parse_file)) != 0) {
         /* there was a parse error with the ini file */
-        con_printmsg(LVL_ERROR, file, line, 0 /*TODO: column for ini error*/, "error", error);
+        con_printmsg(LVL_ERROR, parse_file, line, 0 /*TODO: column for ini error*/, "error", error);
         vec_free(error);
     }
+    mem_d(parse_file);
 
     fs_file_close(ini);
 }
index b2292452b2a63ec940c89325bd59d1d0ad9ee107..92cff1463513ad6e04a05e0f9e21fed3fc5bad24 100644 (file)
--- a/opts.def
+++ b/opts.def
@@ -32,6 +32,7 @@
     GMQCC_DEFINE_FLAG(FTEPP)
     GMQCC_DEFINE_FLAG(FTEPP_PREDEFS)
     GMQCC_DEFINE_FLAG(FTEPP_MATHDEFS)
+    GMQCC_DEFINE_FLAG(FTEPP_INDIRECT_EXPANSION)
     GMQCC_DEFINE_FLAG(RELAXED_SWITCH)
     GMQCC_DEFINE_FLAG(SHORT_LOGIC)
     GMQCC_DEFINE_FLAG(PERL_LOGIC)
@@ -56,6 +57,9 @@
     GMQCC_DEFINE_FLAG(UNSAFE_VARARGS)
     GMQCC_DEFINE_FLAG(TYPELESS_STORES)
     GMQCC_DEFINE_FLAG(SORT_OPERANDS)
+    GMQCC_DEFINE_FLAG(EMULATE_STATE)
+    GMQCC_DEFINE_FLAG(ARITHMETIC_EXCEPTIONS)
+    GMQCC_DEFINE_FLAG(SPLIT_VECTOR_PARAMETERS)
 #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
     GMQCC_DEFINE_FLAG(STATISTICS)
     GMQCC_DEFINE_FLAG(PROGSRC)
     GMQCC_DEFINE_FLAG(COVERAGE)
+    GMQCC_DEFINE_FLAG(STATE_FPS)
 #endif
 
 /* some cleanup so we don't have to */
index eaa14903d7681416e69b53fdbbc76c121bc678a0..4a09583593a97c5cebe19090d9f5368e7ed89e7e 100644 (file)
--- a/parser.c
+++ b/parser.c
@@ -953,6 +953,9 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
                 }
             }
             (void)check_write_to(ctx, exprs[0]);
+            /* When we're a vector of part of an entity field we use STOREP */
+            if (ast_istype(exprs[0], ast_member) && ast_istype(((ast_member*)exprs[0])->owner, ast_entfield))
+                assignop = INSTR_STOREP_F;
             out = (ast_expression*)ast_store_new(ctx, assignop, exprs[0], exprs[1]);
             break;
         case opid3('+','+','P'):
@@ -1145,6 +1148,22 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
             out = (ast_expression*)asbinstore;
             break;
 
+        case opid3('l', 'e', 'n'):
+            if (exprs[0]->vtype != TYPE_STRING && exprs[0]->vtype != TYPE_ARRAY) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(ast_ctx(exprs[0]), "invalid type for length operator: %s", ty1);
+                return false;
+            }
+            /* strings must be const, arrays are statically sized */
+            if (exprs[0]->vtype == TYPE_STRING &&
+                !(((ast_value*)exprs[0])->hasvalue && ((ast_value*)exprs[0])->cvq == CV_CONST))
+            {
+                compile_error(ast_ctx(exprs[0]), "operand of length operator not a valid constant expression");
+                return false;
+            }
+            out = fold_op(parser->fold, op, exprs);
+            break;
+
         case opid2('~', 'P'):
             if (exprs[0]->vtype != TYPE_FLOAT && exprs[0]->vtype != TYPE_VECTOR) {
                 ast_type_to_string(exprs[0], ty1, sizeof(ty1));
@@ -1293,7 +1312,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 +1567,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));
@@ -3142,7 +3161,7 @@ static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression *
         if (parser->tok == TOKEN_IDENT)
             typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
         if (typevar || parser->tok == TOKEN_TYPENAME) {
-            if (!parse_variable(parser, block, false, CV_NONE, typevar, false, false, 0, NULL)) {
+            if (!parse_variable(parser, block, true, CV_NONE, typevar, false, false, 0, NULL)) {
                 ast_delete(switchnode);
                 return false;
             }
@@ -3154,7 +3173,7 @@ static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression *
                 ast_delete(switchnode);
                 return false;
             }
-            if (!parse_variable(parser, block, false, cvq, NULL, noref, is_static, qflags, NULL)) {
+            if (!parse_variable(parser, block, true, cvq, NULL, noref, is_static, qflags, NULL)) {
                 ast_delete(switchnode);
                 return false;
             }
@@ -3462,7 +3481,7 @@ static bool parse_statement(parser_t *parser, ast_block *block, ast_expression *
     {
         if (cvq == CV_WRONG)
             return false;
-        return parse_variable(parser, block, true, cvq, NULL, noref, is_static, qflags, vstring);
+        return parse_variable(parser, block, false, cvq, NULL, noref, is_static, qflags, vstring);
     }
     else if (parser->tok == TOKEN_KEYWORD)
     {
@@ -4015,69 +4034,83 @@ static bool parse_function_body(parser_t *parser, ast_value *var)
     }
 
     if (has_frame_think) {
-        lex_ctx_t ctx;
-        ast_expression *self_frame;
-        ast_expression *self_nextthink;
-        ast_expression *self_think;
-        ast_expression *time_plus_1;
-        ast_store *store_frame;
-        ast_store *store_nextthink;
-        ast_store *store_think;
-
-        ctx = parser_ctx(parser);
-        self_frame     = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_frame);
-        self_nextthink = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_nextthink);
-        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));
-
-        if (!self_frame || !self_nextthink || !self_think || !time_plus_1) {
-            if (self_frame)     ast_delete(self_frame);
-            if (self_nextthink) ast_delete(self_nextthink);
-            if (self_think)     ast_delete(self_think);
-            if (time_plus_1)    ast_delete(time_plus_1);
-            retval = false;
-        }
-
-        if (retval)
-        {
-            store_frame     = ast_store_new(ctx, INSTR_STOREP_F,   self_frame,     framenum);
-            store_nextthink = ast_store_new(ctx, INSTR_STOREP_F,   self_nextthink, time_plus_1);
-            store_think     = ast_store_new(ctx, INSTR_STOREP_FNC, self_think,     nextthink);
-
-            if (!store_frame) {
-                ast_delete(self_frame);
-                retval = false;
-            }
-            if (!store_nextthink) {
-                ast_delete(self_nextthink);
-                retval = false;
-            }
-            if (!store_think) {
-                ast_delete(self_think);
-                retval = false;
+        if (!OPTS_FLAG(EMULATE_STATE)) {
+            ast_state *state_op = ast_state_new(parser_ctx(parser), framenum, nextthink);
+            if (!ast_block_add_expr(block, (ast_expression*)state_op)) {
+                parseerror(parser, "failed to generate state op for [frame,think]");
+                ast_unref(nextthink);
+                ast_unref(framenum);
+                ast_delete(block);
+                return false;
             }
-            if (!retval) {
-                if (store_frame)     ast_delete(store_frame);
-                if (store_nextthink) ast_delete(store_nextthink);
-                if (store_think)     ast_delete(store_think);
+        } else {
+            /* emulate OP_STATE in code: */
+            lex_ctx_t ctx;
+            ast_expression *self_frame;
+            ast_expression *self_nextthink;
+            ast_expression *self_think;
+            ast_expression *time_plus_1;
+            ast_store *store_frame;
+            ast_store *store_nextthink;
+            ast_store *store_think;
+
+            float frame_delta = 1.0f / (float)OPTS_OPTION_U32(OPTION_STATE_FPS);
+
+            ctx = parser_ctx(parser);
+            self_frame     = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_frame);
+            self_nextthink = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_nextthink);
+            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, false));
+
+            if (!self_frame || !self_nextthink || !self_think || !time_plus_1) {
+                if (self_frame)     ast_delete(self_frame);
+                if (self_nextthink) ast_delete(self_nextthink);
+                if (self_think)     ast_delete(self_think);
+                if (time_plus_1)    ast_delete(time_plus_1);
                 retval = false;
             }
-            if (!ast_block_add_expr(block, (ast_expression*)store_frame) ||
-                !ast_block_add_expr(block, (ast_expression*)store_nextthink) ||
-                !ast_block_add_expr(block, (ast_expression*)store_think))
+
+            if (retval)
             {
-                retval = false;
+                store_frame     = ast_store_new(ctx, INSTR_STOREP_F,   self_frame,     framenum);
+                store_nextthink = ast_store_new(ctx, INSTR_STOREP_F,   self_nextthink, time_plus_1);
+                store_think     = ast_store_new(ctx, INSTR_STOREP_FNC, self_think,     nextthink);
+
+                if (!store_frame) {
+                    ast_delete(self_frame);
+                    retval = false;
+                }
+                if (!store_nextthink) {
+                    ast_delete(self_nextthink);
+                    retval = false;
+                }
+                if (!store_think) {
+                    ast_delete(self_think);
+                    retval = false;
+                }
+                if (!retval) {
+                    if (store_frame)     ast_delete(store_frame);
+                    if (store_nextthink) ast_delete(store_nextthink);
+                    if (store_think)     ast_delete(store_think);
+                    retval = false;
+                }
+                if (!ast_block_add_expr(block, (ast_expression*)store_frame) ||
+                    !ast_block_add_expr(block, (ast_expression*)store_nextthink) ||
+                    !ast_block_add_expr(block, (ast_expression*)store_think))
+                {
+                    retval = false;
+                }
             }
-        }
 
-        if (!retval) {
-            parseerror(parser, "failed to generate code for [frame,think]");
-            ast_unref(nextthink);
-            ast_unref(framenum);
-            ast_delete(block);
-            return false;
+            if (!retval) {
+                parseerror(parser, "failed to generate code for [frame,think]");
+                ast_unref(nextthink);
+                ast_unref(framenum);
+                ast_delete(block);
+                return false;
+            }
         }
     }
 
@@ -4155,7 +4188,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 +4246,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 +4279,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 +4345,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 +4408,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;
 
@@ -4679,12 +4712,17 @@ static ast_value *parse_parameter_list(parser_t *parser, ast_value *var)
                 }
                 if (parser->tok == TOKEN_IDENT) {
                     argcounter = util_strdup(parser_tokval(parser));
+                    ast_value_set_name(param, argcounter);
                     if (!parser_next(parser) || parser->tok != ')') {
                         parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
                         goto on_error;
                     }
                 }
             }
+            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC && param->name[0] == '<') {
+                parseerror(parser, "parameter name omitted");
+                goto on_error;
+            }
         }
     }
 
@@ -5146,6 +5184,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");
@@ -5609,6 +5648,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
@@ -5816,15 +5856,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;
+
+            /* 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 (!localblock || is_static) {
-                cval = (ast_value*)cexp;
+            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");
@@ -5872,6 +5941,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:
@@ -5944,7 +6020,7 @@ static bool parser_global_statement(parser_t *parser)
     {
         if (cvq == CV_WRONG)
             return false;
-        return parse_variable(parser, NULL, true, cvq, NULL, noref, is_static, qflags, vstring);
+        return parse_variable(parser, NULL, false, cvq, NULL, noref, is_static, qflags, vstring);
     }
     else if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "enum"))
     {
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/stat.c b/stat.c
index bf92c98db416be3c7fec695c706d8a3aaecc13dd..650bccdf040a0f83a324c70b94a545086d70aef4 100644 (file)
--- a/stat.c
+++ b/stat.c
@@ -141,7 +141,7 @@ void *stat_mem_allocate(size_t size, size_t line, const char *file, const char *
     info->expr = expr;
     info->prev = NULL;
     info->next = stat_mem_block_root;
-    
+
     /* Write identifier */
     memcpy(info + 1, IDENT_MEM, IDENT_SIZE);
 
@@ -167,7 +167,7 @@ void *stat_mem_allocate(size_t size, size_t line, const char *file, const char *
 void stat_mem_deallocate(void *ptr, size_t line, const char *file) {
     stat_mem_block_t *info  = NULL;
     char             *ident = (char *)ptr - IDENT_SIZE;
-    
+
     if (GMQCC_UNLIKELY(!ptr))
         return;
 
@@ -176,7 +176,7 @@ void stat_mem_deallocate(void *ptr, size_t line, const char *file) {
     if (!strcmp(ident, IDENT_VEC)) {
         vector_t         *vec   = (vector_t*)((char *)ptr - IDENT_VEC_TOP);
         stat_mem_block_t *block = (stat_mem_block_t*)((char *)vec - IDENT_MEM_TOP);
-        
+
         VALGRIND_MAKE_MEM_DEFINED(block, sizeof(stat_mem_block_t));
         con_err("internal warning: invalid use of mem_d:\n");
         con_err("internal warning:    vector (used elements: %u, allocated elements: %u)\n",
diff --git a/test.c b/test.c
index 370861ed9f1becd34df5a5a495a72024d3d8083e..6d8e9ad7192bf08ff2e583c5b3be9c343d57e37b 100644 (file)
--- a/test.c
+++ b/test.c
@@ -806,19 +806,21 @@ static bool task_propagate(const char *curdir, size_t *pad, const char *defs) {
                 } else {
                     /* Preprocessing (qcflags mean shit all here we don't allow them) */
                     if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
-                        util_snprintf(buf, sizeof(buf), "%s -E %s/%s -o %s",
+                        util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s -o %s",
                             task_bins[TASK_COMPILE],
                             directories[i],
                             tmpl->sourcefile,
+                            tmpl->compileflags,
                             tmpl->tempfilename
                         );
                     } else {
-                        util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s/%s -o %s",
+                        util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s/%s %s -o %s",
                             task_bins[TASK_COMPILE],
                             curdir,
                             defs,
                             directories[i],
                             tmpl->sourcefile,
+                            tmpl->compileflags,
                             tmpl->tempfilename
                         );
                     }
@@ -1100,6 +1102,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 +1170,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..8d75a91
--- /dev/null
@@ -0,0 +1,4 @@
+I: arithexcept.qc
+D: arithmetic exceptions (overflow)
+T: -fail
+C: -std=fteqcc -farithmetic-exceptions -DOVERFLOW
diff --git a/tests/arithexcept_uf.tmpl b/tests/arithexcept_uf.tmpl
new file mode 100644 (file)
index 0000000..567fce2
--- /dev/null
@@ -0,0 +1,4 @@
+I: arithexcept.qc
+D: arithmetic exceptions (underflow)
+T: -fail
+C: -std=fteqcc -farithmetic-exceptions -DUNDERFLOW
index ed8a5df96b552bd1ded7923e5e994f457f120e35..f6ea408ad9ed7f26ff6b812f07a7de719dd82095 100644 (file)
@@ -3,18 +3,18 @@
 // builtins.  I no event shall you even consider adding
 // these individually per test.
 
-void   (string, ...)    print     = #1;
-string (float)          ftos      = #2;
-entity ()               spawn     = #3;
-void   (entity)         kill      = #4;
-string (vector)         vtos      = #5;
-void   (string)         error     = #6;
-float  (vector)         vlen      = #7;
-string (entity)         etos      = #8;
-float  (string)         stof      = #9;
-string (...)            strcat    = #10;
-float  (string, string) strcmp    = #11;
-vector (vector)         normalize = #12;
-float  (float)          sqrt      = #13;
-float  (float)          floor     = #14;
-float  (float, float)   pow       = #15;
+void   (string str, ...)          print     = #1;
+string (float val)                ftos      = #2;
+entity ()                         spawn     = #3;
+void   (entity ent)               kill      = #4;
+string (vector vec)               vtos      = #5;
+void   (string str)               error     = #6;
+float  (vector vec)               vlen      = #7;
+string (entity ent)               etos      = #8;
+float  (string str)               stof      = #9;
+string (...)                      strcat    = #10;
+float  (string str1, string str2) strcmp    = #11;
+vector (vector vec)               normalize = #12;
+float  (float val)                sqrt      = #13;
+float  (float val)                floor     = #14;
+float  (float val1, float val2)   pow       = #15;
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
diff --git a/tests/length.qc b/tests/length.qc
new file mode 100644 (file)
index 0000000..ba5f01d
--- /dev/null
@@ -0,0 +1,23 @@
+const string a = "hello world"; // 11
+float b[] = { 1, 2, 3 };        // 3
+float c[5] = { 5, 4, 3, 2, 1 }; // 5
+const float d[] = { 1 };        // 1
+
+void main() {
+    print(ftos(_length a), "\n"); // 11
+    print(ftos(_length b), "\n"); // 3
+    print(ftos(_length c), "\n"); // 5
+    print(ftos(_length d), "\n"); // 1
+
+    static float al = _length(a);
+    static float bl = _length(b);
+    static float cl = _length(c);
+    static float dl = _length(d);
+
+    print(ftos(al), "\n"); // 11
+    print(ftos(bl), "\n"); // 3
+    print(ftos(cl), "\n"); // 5
+    print(ftos(dl), "\n"); // 1
+
+    print(ftos(_length "hello world"), "\n"); // 11
+}
diff --git a/tests/length.tmpl b/tests/length.tmpl
new file mode 100644 (file)
index 0000000..292b6ad
--- /dev/null
@@ -0,0 +1,13 @@
+I: length.qc
+D: length operator
+T: -execute
+C: -std=gmqcc
+M: 11
+M: 3
+M: 5
+M: 1
+M: 11
+M: 3
+M: 5
+M: 1
+M: 11
index ae614e77b5f27177728cf088d303baefb20e702c..4bd8d154bdedb8028fbcf40ae2642ba58d0de6c8 100644 (file)
@@ -3,8 +3,7 @@
 #endif
 
 void   (...)            print     = #1;
-string (float)          ftos      = #2;
-
+string (float val)      ftos      = #2;
 
 NORETURN void error(...)  = #6;
 
diff --git a/tests/paramomit.qc b/tests/paramomit.qc
new file mode 100644 (file)
index 0000000..48d8d59
--- /dev/null
@@ -0,0 +1,3 @@
+void foo(float) {
+
+}
diff --git a/tests/paramomit.tmpl b/tests/paramomit.tmpl
new file mode 100644 (file)
index 0000000..50c5373
--- /dev/null
@@ -0,0 +1,4 @@
+I: paramomit.qc
+D: test parameter omit
+T: -fail
+C: -std=fteqcc
index 5225a7c6ae2c11c0d88582363beac197d9dacc7f..e32795a22ffc7ad1e47b5e97cc8d0b0e9568add2 100644 (file)
@@ -1,7 +1,7 @@
 var float foo = 0;
 
 void funcall() {}
-void bar(string) {}
+void bar(string str) {}
 
 void main(string str) {
     string pl;
diff --git a/tests/ppindirectexpand.qc b/tests/ppindirectexpand.qc
new file mode 100644 (file)
index 0000000..09333f9
--- /dev/null
@@ -0,0 +1,6 @@
+#define STR1(x) #x
+#define STR2(x) STR1(x)
+#define THE_ANSWER 42
+#define THE_ANSWER_STR STR2(THE_ANSWER)
+
+THE_ANSWER_STR
diff --git a/tests/ppindirectexpand.tmpl b/tests/ppindirectexpand.tmpl
new file mode 100644 (file)
index 0000000..8813f65
--- /dev/null
@@ -0,0 +1,7 @@
+I: ppindirectexpand.qc
+D: test preprocessor indirect macro expansion
+T: -pp
+C: -fftepp-indirect-expansion -std=gmqcc
+F: -no-defs
+M: "42"
+
diff --git a/tests/split-vectors.qc b/tests/split-vectors.qc
new file mode 100644 (file)
index 0000000..e98ed9f
--- /dev/null
@@ -0,0 +1,6 @@
+void main() {
+    print(vtos('1 2 3'), "\n");
+    print(vtos('4 5 6'), "\n");
+    print(vtos('7 8 9'), "\n");
+    print(vtos('1 5 9'), "\n");
+}
diff --git a/tests/split-vectors.tmpl b/tests/split-vectors.tmpl
new file mode 100644 (file)
index 0000000..6701d32
--- /dev/null
@@ -0,0 +1,8 @@
+I: split-vectors.qc
+D: test -fsplit-vector-parameters
+T: -execute
+C: -std=fteqcc -Wall -Werror -fsplit-vector-parameters
+M: '1 2 3'
+M: '4 5 6'
+M: '7 8 9'
+M: '1 5 9'
diff --git a/tests/state-emu.tmpl b/tests/state-emu.tmpl
new file mode 100644 (file)
index 0000000..1d96adc
--- /dev/null
@@ -0,0 +1,9 @@
+I: state.qc
+D: test emulated state ops
+T: -execute
+C: -std=gmqcc -femulate-state
+M: st1, .frame=1, .nextthink=10.1 (now: 10)
+M: st2, .frame=2, .nextthink=11.1 (now: 11)
+M: st3, .frame=0, .nextthink=12.1 (now: 12)
+M: st1, .frame=1, .nextthink=13.1 (now: 13)
+M: st2, .frame=2, .nextthink=14.1 (now: 14)
diff --git a/tests/state.qc b/tests/state.qc
new file mode 100644 (file)
index 0000000..9f99ccc
--- /dev/null
@@ -0,0 +1,39 @@
+float  time;
+entity self;
+
+.void() think;
+.float  nextthink;
+.float  frame;
+
+void stprint(string fun) {
+    print(fun,
+          ", .frame=", ftos(self.frame),
+          ", .nextthink=", ftos(self.nextthink),
+          " (now: ", ftos(time), ")\n");
+}
+
+void st1() = [1, st2] { stprint("st1"); }
+void st2() = [2, st3] { stprint("st2"); }
+void st3() = [0, st1] { stprint("st3"); }
+
+void main() {
+    entity ea = spawn();
+    entity eb = spawn();
+
+    time = 10;
+    self = ea;
+
+    self.think     = st1;
+    self.nextthink = time;
+    self.frame     = 100;
+
+    self.think();
+    time = 11;
+    self.think();
+    time = 12;
+    self.think();
+    time = 13;
+    self.think();
+    time = 14;
+    self.think();
+};
diff --git a/tests/state.tmpl b/tests/state.tmpl
new file mode 100644 (file)
index 0000000..6e9cf67
--- /dev/null
@@ -0,0 +1,9 @@
+I: state.qc
+D: test state ops
+T: -execute
+C: -std=gmqcc
+M: st1, .frame=1, .nextthink=10.1 (now: 10)
+M: st2, .frame=2, .nextthink=11.1 (now: 11)
+M: st3, .frame=0, .nextthink=12.1 (now: 12)
+M: st1, .frame=1, .nextthink=13.1 (now: 13)
+M: st2, .frame=2, .nextthink=14.1 (now: 14)
index e9bc13b1b48efbabee4a2de9c3f0a8c3f60079a7..a51844413803533e7213b68d67e6dafd97c9ee44 100644 (file)
@@ -1,5 +1,5 @@
-typedef void(...)     ptype;
-typedef string(float) funcsf;
+typedef void(...)       ptype;
+typedef string(float a) funcsf;
 
 ptype print = #1;
 funcsf ftos = #2;