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)
{
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
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.
.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++ */
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
+
[warnings]
#Generate a warning about variables which are declared but never
#used. This can be avoided by adding the ‘noref’ keyword in front
BUILTINS = true
+ #When comparing an inexact value such as `1.0/3.0' the result is
+ #pathologically wrong. Enabling this will trigger a compiler warning
+ #on such expressions.
+ INEXACT_COMPARES = true
+
+
[optimizations]
#Some general peephole optimizations. For instance the code `a = b
#+ c` typically generates 2 instructions, an ADD and a STORE. This
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() {
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)
#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 */
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 (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, 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 (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));
++ 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;
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;
- if (!localblock || is_static) {
- cval = (ast_value*)cexp;
+ /* deal with foldable constants: */
+ if (localblock &&
+ var->cvq == CV_CONST && cval && cval->hasvalue && cval->cvq == CV_CONST && !cval->isfield)
+ {
+ /* remove it from the current locals */
+ if (isvector) {
+ for (i = 0; i < 3; ++i) {
+ vec_pop(parser->_locals);
+ vec_pop(localblock->collect);
+ }
+ }
+ /* do sanity checking, this function really needs refactoring */
+ if (vec_last(parser->_locals) != (ast_expression*)var)
+ parseerror(parser, "internal error: unexpected change in local variable handling");
+ else
+ vec_pop(parser->_locals);
+ if (vec_last(localblock->locals) != var)
+ parseerror(parser, "internal error: unexpected change in local variable handling (2)");
+ else
+ vec_pop(localblock->locals);
+ /* push it to the to-be-generated globals */
+ vec_push(parser->globals, (ast_expression*)var);
+ if (isvector)
+ for (i = 0; i < 3; ++i)
+ vec_push(parser->globals, (ast_expression*)last_me[i]);
+ folded_const = true;
+ }
+
+ if (folded_const || !localblock || is_static) {
if (cval != parser->nil &&
- (!ast_istype(cval, ast_value) || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield))
+ (!cval || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield))
)
{
parseerror(parser, "initializer is non constant");
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:
while (*line != '\0' && *line != ' ' &&
*line != '\t' && *line != '\n') line++;
}
- vec_push(argv, '\0');
+ vec_push(argv, NULL);
}
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]),