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->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) )
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
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,
{
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:
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++ */
} 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);
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
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;
}
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;
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
" -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.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
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 (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, 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;
+ }
}
}
while (*line != '\0' && *line != ' ' &&
*line != '\t' && *line != '\n') line++;
}
- vec_push(argv, '\0');
+ vec_push(argv, NULL);
}
--- /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)