Merge branch 'cooking' of github.com:graphitemaster/gmqcc into cooking

This commit is contained in:
Dale Weiler 2014-05-25 02:56:40 -04:00
commit 05b349c72f
16 changed files with 331 additions and 66 deletions

59
ast.c
View file

@ -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 <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;

17
ast.h
View file

@ -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.

View file

@ -168,6 +168,11 @@ DEBUG OPTION. Print the code's intermediate representation after the
optimization and finalization passes to stdout before generating the
binary. The instructions will be enumerated, and values will contain a
list of liferanges.
.It Fl force-crc= Ns Ar CRC
Force the produced progs file to use the specified CRC.
.It Fl state-fps= Ns Ar NUM
Activate \-femulate-state and set the emulated FPS to
.Ar NUM Ns .
.El
.Sh COMPILE WARNINGS
.Bl -tag -width Ds
@ -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

56
exec.c
View file

@ -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++ */

14
gmqcc.h
View file

@ -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);

View file

@ -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

30
ir.c
View file

@ -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;

1
ir.h
View file

@ -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
* <outtype>%label := opcode a, b

6
main.c
View file

@ -85,6 +85,7 @@ static int usage(void) {
" -Ono-<name> disable specific optimization\n"
" -Ohelp list optimizations\n");
con_out(" -force-crc=num force a specific checksum into the header\n");
con_out(" -state-fps=num emulate OP_STATE with the specified FPS\n");
con_out(" -coverage add coverage support\n");
return -1;
}
@ -217,6 +218,11 @@ static bool options_parse(int argc, char **argv) {
OPTS_OPTION_U16 (OPTION_FORCED_CRC) = strtol(argarg, NULL, 0);
continue;
}
if (options_long_gcc("state-fps", &argc, &argv, &argarg)) {
OPTS_OPTION_U32(OPTION_STATE_FPS) = strtol(argarg, NULL, 0);
opts_set(opts.flags, EMULATE_STATE, true);
continue;
}
if (options_long_gcc("redirout", &argc, &argv, &redirout)) {
con_change(redirout, redirerr);
continue;

2
opts.c
View file

@ -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() {

View file

@ -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 */

View file

@ -4015,6 +4015,17 @@ static bool parse_function_body(parser_t *parser, ast_value *var)
}
if (has_frame_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;
@ -4024,13 +4035,15 @@ static bool parse_function_body(parser_t *parser, ast_value *var)
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, 0.1f));
gbl_time, (ast_expression*)fold_constgen_float(parser->fold, frame_delta));
if (!self_frame || !self_nextthink || !self_think || !time_plus_1) {
if (self_frame) ast_delete(self_frame);
@ -4080,6 +4093,7 @@ static bool parse_function_body(parser_t *parser, ast_value *var)
return false;
}
}
}
if (var->hasvalue) {
if (!(var->expression.flags & AST_FLAG_ACCUMULATE)) {

2
test.c
View file

@ -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);
}

9
tests/state-emu.tmpl Normal file
View file

@ -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)

39
tests/state.qc Normal file
View file

@ -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();
};

9
tests/state.tmpl Normal file
View file

@ -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)