diff --git a/ast.c b/ast.c index d7e3d7a..8b3f110 100644 --- a/ast.c +++ b/ast.c @@ -75,6 +75,7 @@ static void ast_binstore_delete(ast_binstore*); static bool ast_binstore_codegen(ast_binstore*, ast_function*, bool lvalue, ir_value**); static void ast_binary_delete(ast_binary*); static bool ast_binary_codegen(ast_binary*, ast_function*, bool lvalue, ir_value**); +static bool ast_state_codegen(ast_state*, ast_function*, bool lvalue, ir_value**); /* It must not be possible to get here. */ static GMQCC_NORETURN void _ast_node_destroy(ast_node *self) @@ -972,6 +973,26 @@ void ast_goto_set_label(ast_goto *self, ast_label *label) self->target = label; } +ast_state* ast_state_new(lex_ctx_t ctx, ast_expression *frame, ast_expression *think) +{ + ast_instantiate(ast_state, ctx, ast_state_delete); + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_state_codegen); + self->framenum = frame; + self->nextthink = think; + return self; +} + +void ast_state_delete(ast_state *self) +{ + if (self->framenum) + ast_unref(self->framenum); + if (self->nextthink) + ast_unref(self->nextthink); + + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + ast_call* ast_call_new(lex_ctx_t ctx, ast_expression *funcexpr) { @@ -3347,6 +3368,44 @@ bool ast_goto_codegen(ast_goto *self, ast_function *func, bool lvalue, ir_value return true; } +#include +bool ast_state_codegen(ast_state *self, ast_function *func, bool lvalue, ir_value **out) +{ + ast_expression_codegen *cgen; + + ir_value *frameval, *thinkval; + + if (lvalue) { + compile_error(ast_ctx(self), "not an l-value (state operation)"); + return false; + } + if (self->expression.outr) { + compile_error(ast_ctx(self), "internal error: ast_state cannot be reused!"); + return false; + } + *out = NULL; + + cgen = self->framenum->codegen; + if (!(*cgen)((ast_expression*)(self->framenum), func, false, &frameval)) + return false; + if (!frameval) + return false; + + cgen = self->nextthink->codegen; + if (!(*cgen)((ast_expression*)(self->nextthink), func, false, &thinkval)) + return false; + if (!frameval) + return false; + + if (!ir_block_create_state_op(func->curblock, ast_ctx(self), frameval, thinkval)) { + compile_error(ast_ctx(self), "failed to create STATE instruction"); + return false; + } + + self->expression.outr = (ir_value*)1; + return true; +} + bool ast_call_codegen(ast_call *self, ast_function *func, bool lvalue, ir_value **out) { ast_expression_codegen *cgen; diff --git a/ast.h b/ast.h index 7cc7a6c..dd7c5c1 100644 --- a/ast.h +++ b/ast.h @@ -54,6 +54,7 @@ typedef struct ast_switch_s ast_switch; typedef struct ast_label_s ast_label; typedef struct ast_goto_s ast_goto; typedef struct ast_argpipe_s ast_argpipe; +typedef struct ast_state_s ast_state; enum { AST_FLAG_VARIADIC = 1 << 0, @@ -111,7 +112,8 @@ enum { TYPE_ast_switch, /* 18 */ TYPE_ast_label, /* 19 */ TYPE_ast_goto, /* 20 */ - TYPE_ast_argpipe /* 21 */ + TYPE_ast_argpipe, /* 21 */ + TYPE_ast_state /* 22 */ }; #define ast_istype(x, t) ( ((ast_node*)x)->nodetype == (TYPE_##t) ) @@ -579,6 +581,19 @@ struct ast_goto_s ast_goto* ast_goto_new(lex_ctx_t ctx, const char *name); void ast_goto_set_label(ast_goto*, ast_label*); +/* STATE node + * + * For frame/think state updates: void foo() [framenum, nextthink] {} + */ +struct ast_state_s +{ + ast_expression expression; + ast_expression *framenum; + ast_expression *nextthink; +}; +ast_state* ast_state_new(lex_ctx_t ctx, ast_expression *frame, ast_expression *think); +void ast_state_delete(ast_state*); + /* CALL node * * Contains an ast_expression as target, rather than an ast_function/value. diff --git a/doc/gmqcc.1 b/doc/gmqcc.1 index b5d39ea..66d2e4e 100644 --- a/doc/gmqcc.1 +++ b/doc/gmqcc.1 @@ -168,6 +168,11 @@ DEBUG OPTION. Print the code's intermediate representation after the optimization and finalization passes to stdout before generating the binary. The instructions will be enumerated, and values will contain a list of liferanges. +.It Fl force-crc= Ns Ar CRC +Force the produced progs file to use the specified CRC. +.It Fl state-fps= Ns Ar NUM +Activate \-femulate-state and set the emulated FPS to +.Ar NUM Ns . .El .Sh COMPILE WARNINGS .Bl -tag -width Ds @@ -577,6 +582,11 @@ breaks decompilers, but causes the output file to be better compressible. In commutative instructions, always put the lower-numbered operand first. This shaves off 1 byte of entropy from all these instructions, reducing compressed size of the output file. +.It Fl f Ns Cm emulate-state +Emulate OP_STATE operations in code rather than using the instruction. +The desired fps can be set via -state-fps=NUM, defaults to 10. +Specifying \-state-fps implicitly sets this flag. Defaults to off in all +standards. .El .Sh OPTIMIZATIONS .Bl -tag -width Ds diff --git a/exec.c b/exec.c index 29a04a3..fde8196 100644 --- a/exec.c +++ b/exec.c @@ -55,8 +55,16 @@ qc_program_t* prog_load(const char *filename, bool skipversion) { prog_header_t header; qc_program_t *prog; + size_t i; fs_file_t *file = fs_file_open(filename, "rb"); + /* we need all those in order to support INSTR_STATE: */ + bool has_self = false, + has_time = false, + has_think = false, + has_nextthink = false, + has_frame = false; + if (!file) return NULL; @@ -137,6 +145,36 @@ qc_program_t* prog_load(const char *filename, bool skipversion) memset(vec_add(prog->entitydata, prog->entityfields), 0, prog->entityfields * sizeof(prog->entitydata[0])); prog->entities = 1; + /* cache some globals and fields from names */ + for (i = 0; i < vec_size(prog->defs); ++i) { + const char *name = prog_getstring(prog, prog->defs[i].name); + if (!strcmp(name, "self")) { + prog->cached_globals.self = prog->defs[i].offset; + has_self = true; + } + else if (!strcmp(name, "time")) { + prog->cached_globals.time = prog->defs[i].offset; + has_time = true; + } + } + for (i = 0; i < vec_size(prog->fields); ++i) { + const char *name = prog_getstring(prog, prog->fields[i].name); + if (!strcmp(name, "think")) { + prog->cached_fields.think = prog->fields[i].offset; + has_think = true; + } + else if (!strcmp(name, "nextthink")) { + prog->cached_fields.nextthink = prog->fields[i].offset; + has_nextthink = true; + } + else if (!strcmp(name, "frame")) { + prog->cached_fields.frame = prog->fields[i].offset; + has_frame = true; + } + } + if (has_self && has_time && has_think && has_nextthink && has_frame) + prog->supports_state = true; + return prog; error: @@ -1574,8 +1612,24 @@ while (prog->vmerror == 0) { break; case INSTR_STATE: - qcvmerror(prog, "`%s` tried to execute a STATE operation", prog->filename); + { + qcfloat_t *nextthink; + qcfloat_t *time; + qcfloat_t *frame; + if (!prog->supports_state) { + qcvmerror(prog, "`%s` tried to execute a STATE operation but misses its defs!", prog->filename); + goto cleanup; + } + ed = prog_getedict(prog, prog->globals[prog->cached_globals.self]); + ((qcint_t*)ed)[prog->cached_fields.think] = OPB->function; + + frame = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.frame]; + *frame = OPA->_float; + nextthink = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.nextthink]; + time = (qcfloat_t*)(prog->globals + prog->cached_globals.time); + *nextthink = *time + 0.1; break; + } case INSTR_GOTO: st += st->o1.s1 - 1; /* offset the s++ */ diff --git a/gmqcc.h b/gmqcc.h index 835931d..7834a24 100644 --- a/gmqcc.h +++ b/gmqcc.h @@ -789,17 +789,17 @@ typedef struct { } qc_exec_stack_t; typedef struct qc_program_s { - char *filename; + char *filename; prog_section_statement_t *code; prog_section_def_t *defs; prog_section_def_t *fields; prog_section_function_t *functions; - char *strings; - qcint_t *globals; - qcint_t *entitydata; - bool *entitypool; + char *strings; + qcint_t *globals; + qcint_t *entitydata; + bool *entitypool; - const char* *function_stack; + const char* *function_stack; uint16_t crc16; @@ -825,6 +825,20 @@ typedef struct qc_program_s { size_t xflags; int argc; /* current arg count for debugging */ + + /* cached fields */ + struct { + qcint_t frame; + qcint_t nextthink; + qcint_t think; + } cached_fields; + + struct { + qcint_t self; + qcint_t time; + } cached_globals; + + bool supports_state; /* is INSTR_STATE supported? */ } qc_program_t; qc_program_t* prog_load (const char *filename, bool ignoreversion); diff --git a/gmqcc.ini.example b/gmqcc.ini.example index 20680d3..290e1bd 100644 --- a/gmqcc.ini.example +++ b/gmqcc.ini.example @@ -310,6 +310,11 @@ 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 + [warnings] #Generate a warning about variables which are declared but never diff --git a/ir.c b/ir.c index 7df8bdb..ef782f9 100644 --- a/ir.c +++ b/ir.c @@ -1537,6 +1537,26 @@ bool ir_block_create_store_op(ir_block *self, lex_ctx_t ctx, int op, ir_value *t return true; } +bool ir_block_create_state_op(ir_block *self, lex_ctx_t ctx, ir_value *frame, ir_value *think) +{ + ir_instr *in; + if (!ir_check_unreachable(self)) + return false; + + in = ir_instr_new(ctx, self, INSTR_STATE); + if (!in) + return false; + + if (!ir_instr_op(in, 0, frame, false) || + !ir_instr_op(in, 1, think, false)) + { + ir_instr_delete(in); + return false; + } + vec_push(self->instr, in); + return true; +} + static bool ir_block_create_store(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what) { int op = 0; @@ -3167,8 +3187,14 @@ static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *bloc } if (instr->opcode == INSTR_STATE) { - irerror(block->context, "TODO: state instruction"); - return false; + stmt.opcode = instr->opcode; + if (instr->_ops[0]) + stmt.o1.u1 = ir_value_code_addr(instr->_ops[0]); + if (instr->_ops[1]) + stmt.o2.u1 = ir_value_code_addr(instr->_ops[1]); + stmt.o3.u1 = 0; + code_push_statement(code, &stmt, instr->context); + continue; } stmt.opcode = instr->opcode; diff --git a/ir.h b/ir.h index 5b94835..d0fd787 100644 --- a/ir.h +++ b/ir.h @@ -170,6 +170,7 @@ bool GMQCC_WARN ir_block_create_store_op(ir_block*, lex_ctx_t, int op, ir_value bool GMQCC_WARN ir_block_create_storep(ir_block*, lex_ctx_t, ir_value *target, ir_value *what); ir_value* ir_block_create_load_from_ent(ir_block*, lex_ctx_t, const char *label, ir_value *ent, ir_value *field, int outype); ir_value* ir_block_create_fieldaddress(ir_block*, lex_ctx_t, const char *label, ir_value *entity, ir_value *field); +bool GMQCC_WARN ir_block_create_state_op(ir_block*, lex_ctx_t, ir_value *frame, ir_value *think); /* This is to create an instruction of the form * %label := opcode a, b diff --git a/main.c b/main.c index 156a3f0..ff15b20 100644 --- a/main.c +++ b/main.c @@ -85,6 +85,7 @@ static int usage(void) { " -Ono- disable specific optimization\n" " -Ohelp list optimizations\n"); con_out(" -force-crc=num force a specific checksum into the header\n"); + con_out(" -state-fps=num emulate OP_STATE with the specified FPS\n"); con_out(" -coverage add coverage support\n"); return -1; } @@ -217,6 +218,11 @@ static bool options_parse(int argc, char **argv) { OPTS_OPTION_U16 (OPTION_FORCED_CRC) = strtol(argarg, NULL, 0); continue; } + if (options_long_gcc("state-fps", &argc, &argv, &argarg)) { + OPTS_OPTION_U32(OPTION_STATE_FPS) = strtol(argarg, NULL, 0); + opts_set(opts.flags, EMULATE_STATE, true); + continue; + } if (options_long_gcc("redirout", &argc, &argv, &redirout)) { con_change(redirout, redirerr); continue; diff --git a/opts.c b/opts.c index 7998024..34d76bb 100644 --- a/opts.c +++ b/opts.c @@ -101,6 +101,8 @@ static void opts_setdefault(void) { opts_set(opts.flags, LEGACY_VECTOR_MATHS, true); opts_set(opts.flags, DARKPLACES_STRING_TABLE_BUG, true); + /* options */ + OPTS_OPTION_U32(OPTION_STATE_FPS) = 10; } void opts_backup_non_Wall() { diff --git a/opts.def b/opts.def index b229245..d311f87 100644 --- a/opts.def +++ b/opts.def @@ -56,6 +56,7 @@ GMQCC_DEFINE_FLAG(UNSAFE_VARARGS) GMQCC_DEFINE_FLAG(TYPELESS_STORES) GMQCC_DEFINE_FLAG(SORT_OPERANDS) + GMQCC_DEFINE_FLAG(EMULATE_STATE) #endif /* warning flags */ @@ -135,6 +136,7 @@ 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 */ diff --git a/parser.c b/parser.c index eaa1490..0643743 100644 --- a/parser.c +++ b/parser.c @@ -4015,69 +4015,83 @@ static bool parse_function_body(parser_t *parser, ast_value *var) } if (has_frame_think) { - lex_ctx_t ctx; - ast_expression *self_frame; - ast_expression *self_nextthink; - ast_expression *self_think; - ast_expression *time_plus_1; - ast_store *store_frame; - ast_store *store_nextthink; - ast_store *store_think; + 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; + } + } 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; - 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); + float frame_delta = 1.0f / (float)OPTS_OPTION_U32(OPTION_STATE_FPS); - time_plus_1 = (ast_expression*)ast_binary_new(ctx, INSTR_ADD_F, - gbl_time, (ast_expression*)fold_constgen_float(parser->fold, 0.1f)); + 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); - 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; - } + time_plus_1 = (ast_expression*)ast_binary_new(ctx, INSTR_ADD_F, + gbl_time, (ast_expression*)fold_constgen_float(parser->fold, frame_delta)); - 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); + 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 (!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)) + + 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 (!retval) { - parseerror(parser, "failed to generate code for [frame,think]"); - ast_unref(nextthink); - ast_unref(framenum); - ast_delete(block); - return false; + 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; + } } } diff --git a/test.c b/test.c index c726b02..f1f714f 100644 --- a/test.c +++ b/test.c @@ -85,7 +85,7 @@ static fs_file_t **task_popen(const char *command, const char *mode) { while (*line != '\0' && *line != ' ' && *line != '\t' && *line != '\n') line++; } - vec_push(argv, '\0'); + vec_push(argv, NULL); } diff --git a/tests/state-emu.tmpl b/tests/state-emu.tmpl new file mode 100644 index 0000000..1d96adc --- /dev/null +++ b/tests/state-emu.tmpl @@ -0,0 +1,9 @@ +I: state.qc +D: test emulated state ops +T: -execute +C: -std=gmqcc -femulate-state +M: st1, .frame=1, .nextthink=10.1 (now: 10) +M: st2, .frame=2, .nextthink=11.1 (now: 11) +M: st3, .frame=0, .nextthink=12.1 (now: 12) +M: st1, .frame=1, .nextthink=13.1 (now: 13) +M: st2, .frame=2, .nextthink=14.1 (now: 14) diff --git a/tests/state.qc b/tests/state.qc new file mode 100644 index 0000000..9f99ccc --- /dev/null +++ b/tests/state.qc @@ -0,0 +1,39 @@ +float time; +entity self; + +.void() think; +.float nextthink; +.float frame; + +void stprint(string fun) { + print(fun, + ", .frame=", ftos(self.frame), + ", .nextthink=", ftos(self.nextthink), + " (now: ", ftos(time), ")\n"); +} + +void st1() = [1, st2] { stprint("st1"); } +void st2() = [2, st3] { stprint("st2"); } +void st3() = [0, st1] { stprint("st3"); } + +void main() { + entity ea = spawn(); + entity eb = spawn(); + + time = 10; + self = ea; + + self.think = st1; + self.nextthink = time; + self.frame = 100; + + self.think(); + time = 11; + self.think(); + time = 12; + self.think(); + time = 13; + self.think(); + time = 14; + self.think(); +}; diff --git a/tests/state.tmpl b/tests/state.tmpl new file mode 100644 index 0000000..6e9cf67 --- /dev/null +++ b/tests/state.tmpl @@ -0,0 +1,9 @@ +I: state.qc +D: test state ops +T: -execute +C: -std=gmqcc +M: st1, .frame=1, .nextthink=10.1 (now: 10) +M: st2, .frame=2, .nextthink=11.1 (now: 11) +M: st3, .frame=0, .nextthink=12.1 (now: 12) +M: st1, .frame=1, .nextthink=13.1 (now: 13) +M: st2, .frame=2, .nextthink=14.1 (now: 14)