GITINFO != git describe --always
.endif
-CFLAGS += -Wall -Wextra -Werror -Wstrict-aliasing -Wno-attributes
-
.if $(CC) == clang
CFLAGS += -Weverything\
-Wno-padded\
-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++
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 += \
-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++)
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;
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)
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;
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)
{
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... */
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);
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;
}
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);
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) {
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;
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,
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) )
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
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.
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
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
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
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
{
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;
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:
}
}
-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;
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;
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++ */
#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;
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;
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;
* 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));
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;
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);
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) {
/*
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;
}
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;
}
static GMQCC_INLINE ast_expression *fold_op_not(fold_t *fold, ast_value *a) {
if (isfloat(a)) {
if (fold_can_1(a))
- return fold_constgen_float(fold, !fold_immvalue_float(a));
+ return fold_constgen_float(fold, !fold_immvalue_float(a), false);
} else if (isvector(a)) {
if (fold_can_1(a))
- return fold_constgen_float(fold, vec3_notf(fold_immvalue_vector(a)));
+ return fold_constgen_float(fold, vec3_notf(fold_immvalue_vector(a)), false);
} else if (isstring(a)) {
if (fold_can_1(a)) {
if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
- return fold_constgen_float(fold, !fold_immvalue_string(a));
+ return fold_constgen_float(fold, !fold_immvalue_string(a), false);
else
- return fold_constgen_float(fold, !fold_immvalue_string(a) || !*fold_immvalue_string(a));
+ return fold_constgen_float(fold, !fold_immvalue_string(a) || !*fold_immvalue_string(a), false);
}
}
return NULL;
static 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;
}
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;
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,
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))
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))
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))
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;
}
((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
);
}
}
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];
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);
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))
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;
}
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");
* 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);
}
++ch;
}
break;
- case TOKEN_WHITE:
+ /*case TOKEN_WHITE:
ftepp_out(ftepp, " ", false);
- break;
+ break;*/
case TOKEN_EOL:
ftepp_out(ftepp, "\\n", false);
break;
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;
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);
+ }
}
}
macro_params_find(macro, macro->output[o+1]->value, &pi))
{
++o;
-
+
ftepp_stringify(ftepp, ¶ms[pi]);
break;
}
lex_ctx_t ctx;
char lineno[128];
char *filename;
+ char *parsename = NULL;
char *old_includename;
(void)ftepp_next(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);
} 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;
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);
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]) & (1U << (unsigned)((i)%32))))
#define OPTS_FLAG(i) OPTS_GENERIC(opts.flags, (i))
#define OPTS_WARN(i) OPTS_GENERIC(opts.warn, (i))
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
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));
+}
DATADIR := $(PREFIX)/share
MANDIR := $(DATADIR)/man
+# default flags
+CFLAGS += -Wall -Wextra -Werror -Wstrict-aliasing -Wno-attributes -O2
+
# compiler
CC ?= clang
-observertrans \
-abstract \
-statictrans \
- -castfcnptr
+ -castfcnptr \
+ -shiftimplementation \
+ -shiftnegative \
+ -boolcompare \
+ -infloops \
+ -sysunrecog
#always the right rule
default: all
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)
(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
)
);
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,
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,
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(
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;
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);
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;
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);
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);
}
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;
* 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;
}
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;
* 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;
}
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]);
}
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;
}
} 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),
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
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;
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;
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 */
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);
lex_tokench(lex, '\\');
}
/* add the character finally */
- lex_tokench(lex, ch);
+ lex_tokench(lex, ch | texttype);
}
else
lex_tokench(lex, ch);
} 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) {
#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[] = {
" -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;
}
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;
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);
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() {
* 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;
}
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;
} 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 */
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;
}
mem_d(line);
return error;
+
}
/*
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;
#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) \
/* 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;
}
* gmqcc.cfg
*/
char *error = NULL;
+ char *parse_file = NULL;
size_t line;
fs_file_t *ini;
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);
}
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)
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 */
}
}
(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'):
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));
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);
}
}
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));
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;
}
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;
}
{
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)
{
}
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;
+ }
}
}
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;
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);
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;
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;
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;
}
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;
+ }
}
}
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");
}
}
}
+ memcpy(last_me, me, sizeof(me));
me[0] = me[1] = me[2] = NULL;
cleanvar = false;
/* Part 2.2
} 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");
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:
{
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"))
{
/* 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 *);
info->expr = expr;
info->prev = NULL;
info->next = stat_mem_block_root;
-
+
/* Write identifier */
memcpy(info + 1, IDENT_MEM, IDENT_SIZE);
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;
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",
} 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
);
}
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));
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]),
--- /dev/null
+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
--- /dev/null
+I: arithexcept.qc
+D: arithmetic exceptions (divide by zero)
+T: -fail
+C: -std=fteqcc -farithmetic-exceptions -DDIVBYZERO
--- /dev/null
+I: arithexcept.qc
+D: arithmetic exceptions (overflow)
+T: -fail
+C: -std=fteqcc -farithmetic-exceptions -DOVERFLOW
--- /dev/null
+I: arithexcept.qc
+D: arithmetic exceptions (underflow)
+T: -fail
+C: -std=fteqcc -farithmetic-exceptions -DUNDERFLOW
// 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;
--- /dev/null
+void main() {
+ const float a = 1.0 / 3.0;
+ const float b = 0.33333333333;
+ if (a == b) {
+ // Should trigger warning
+ }
+}
--- /dev/null
+I: inexact-local.qc
+D: inexact comparisons
+T: -fail
+C: -std=gmqcc -Winexact-compares -Wall -Werror
--- /dev/null
+const float a = 1.0 / 3.0;
+const float b = 0.33333333333;
+
+void main() {
+ if (a == b) {
+ // Should trigger warning
+ }
+}
--- /dev/null
+I: inexact.qc
+D: inexact comparisons
+T: -fail
+C: -std=gmqcc -Winexact-compares -Wall -Werror
--- /dev/null
+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
+}
--- /dev/null
+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
#endif
void (...) print = #1;
-string (float) ftos = #2;
-
+string (float val) ftos = #2;
NORETURN void error(...) = #6;
--- /dev/null
+void foo(float) {
+
+}
--- /dev/null
+I: paramomit.qc
+D: test parameter omit
+T: -fail
+C: -std=fteqcc
var float foo = 0;
void funcall() {}
-void bar(string) {}
+void bar(string str) {}
void main(string str) {
string pl;
--- /dev/null
+#define STR1(x) #x
+#define STR2(x) STR1(x)
+#define THE_ANSWER 42
+#define THE_ANSWER_STR STR2(THE_ANSWER)
+
+THE_ANSWER_STR
--- /dev/null
+I: ppindirectexpand.qc
+D: test preprocessor indirect macro expansion
+T: -pp
+C: -fftepp-indirect-expansion -std=gmqcc
+F: -no-defs
+M: "42"
+
--- /dev/null
+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");
+}
--- /dev/null
+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'
--- /dev/null
+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)
--- /dev/null
+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();
+};
--- /dev/null
+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)
-typedef void(...) ptype;
-typedef string(float) funcsf;
+typedef void(...) ptype;
+typedef string(float a) funcsf;
ptype print = #1;
funcsf ftos = #2;