use INSTR_STATE by default, add -femulate-state to switch to emulation, and -state-fps=NUM to set its emulation FPS

This commit is contained in:
Wolfgang Bumiller 2014-04-08 14:34:55 +02:00
parent 0d8b0d419c
commit 8dafdfc5e2
10 changed files with 198 additions and 58 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 bool ast_binstore_codegen(ast_binstore*, ast_function*, bool lvalue, ir_value**);
static void ast_binary_delete(ast_binary*); static void ast_binary_delete(ast_binary*);
static bool ast_binary_codegen(ast_binary*, ast_function*, bool lvalue, ir_value**); 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. */ /* It must not be possible to get here. */
static GMQCC_NORETURN void _ast_node_destroy(ast_node *self) 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; 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_call* ast_call_new(lex_ctx_t ctx,
ast_expression *funcexpr) ast_expression *funcexpr)
{ {
@ -3347,6 +3368,44 @@ bool ast_goto_codegen(ast_goto *self, ast_function *func, bool lvalue, ir_value
return true; 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) bool ast_call_codegen(ast_call *self, ast_function *func, bool lvalue, ir_value **out)
{ {
ast_expression_codegen *cgen; 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_label_s ast_label;
typedef struct ast_goto_s ast_goto; typedef struct ast_goto_s ast_goto;
typedef struct ast_argpipe_s ast_argpipe; typedef struct ast_argpipe_s ast_argpipe;
typedef struct ast_state_s ast_state;
enum { enum {
AST_FLAG_VARIADIC = 1 << 0, AST_FLAG_VARIADIC = 1 << 0,
@ -111,7 +112,8 @@ enum {
TYPE_ast_switch, /* 18 */ TYPE_ast_switch, /* 18 */
TYPE_ast_label, /* 19 */ TYPE_ast_label, /* 19 */
TYPE_ast_goto, /* 20 */ 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) ) #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); ast_goto* ast_goto_new(lex_ctx_t ctx, const char *name);
void ast_goto_set_label(ast_goto*, ast_label*); 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 /* CALL node
* *
* Contains an ast_expression as target, rather than an ast_function/value. * 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 optimization and finalization passes to stdout before generating the
binary. The instructions will be enumerated, and values will contain a binary. The instructions will be enumerated, and values will contain a
list of liferanges. 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 .El
.Sh COMPILE WARNINGS .Sh COMPILE WARNINGS
.Bl -tag -width Ds .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. In commutative instructions, always put the lower-numbered operand first.
This shaves off 1 byte of entropy from all these instructions, reducing This shaves off 1 byte of entropy from all these instructions, reducing
compressed size of the output file. 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 .El
.Sh OPTIMIZATIONS .Sh OPTIMIZATIONS
.Bl -tag -width Ds .Bl -tag -width Ds

View file

@ -310,6 +310,11 @@
SORT_OPERANDS = false 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] [warnings]
#Generate a warning about variables which are declared but never #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; 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) static bool ir_block_create_store(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what)
{ {
int op = 0; 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) { if (instr->opcode == INSTR_STATE) {
irerror(block->context, "TODO: state instruction"); stmt.opcode = instr->opcode;
return false; 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; 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); 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_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); 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 /* This is to create an instruction of the form
* <outtype>%label := opcode a, b * <outtype>%label := opcode a, b

6
main.c
View file

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

View file

@ -56,6 +56,7 @@
GMQCC_DEFINE_FLAG(UNSAFE_VARARGS) GMQCC_DEFINE_FLAG(UNSAFE_VARARGS)
GMQCC_DEFINE_FLAG(TYPELESS_STORES) GMQCC_DEFINE_FLAG(TYPELESS_STORES)
GMQCC_DEFINE_FLAG(SORT_OPERANDS) GMQCC_DEFINE_FLAG(SORT_OPERANDS)
GMQCC_DEFINE_FLAG(EMULATE_STATE)
#endif #endif
/* warning flags */ /* warning flags */
@ -135,6 +136,7 @@
GMQCC_DEFINE_FLAG(STATISTICS) GMQCC_DEFINE_FLAG(STATISTICS)
GMQCC_DEFINE_FLAG(PROGSRC) GMQCC_DEFINE_FLAG(PROGSRC)
GMQCC_DEFINE_FLAG(COVERAGE) GMQCC_DEFINE_FLAG(COVERAGE)
GMQCC_DEFINE_FLAG(STATE_FPS)
#endif #endif
/* some cleanup so we don't have to */ /* some cleanup so we don't have to */

124
parser.c
View file

@ -4015,69 +4015,83 @@ static bool parse_function_body(parser_t *parser, ast_value *var)
} }
if (has_frame_think) { if (has_frame_think) {
lex_ctx_t ctx; if (!OPTS_FLAG(EMULATE_STATE)) {
ast_expression *self_frame; ast_state *state_op = ast_state_new(parser_ctx(parser), framenum, nextthink);
ast_expression *self_nextthink; if (!ast_block_add_expr(block, (ast_expression*)state_op)) {
ast_expression *self_think; parseerror(parser, "failed to generate state op for [frame,think]");
ast_expression *time_plus_1; ast_unref(nextthink);
ast_store *store_frame; ast_unref(framenum);
ast_store *store_nextthink; ast_delete(block);
ast_store *store_think; 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); float frame_delta = 1.0f / (float)OPTS_OPTION_U32(OPTION_STATE_FPS);
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, ctx = parser_ctx(parser);
gbl_time, (ast_expression*)fold_constgen_float(parser->fold, 0.1f)); 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) { time_plus_1 = (ast_expression*)ast_binary_new(ctx, INSTR_ADD_F,
if (self_frame) ast_delete(self_frame); gbl_time, (ast_expression*)fold_constgen_float(parser->fold, frame_delta));
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) if (!self_frame || !self_nextthink || !self_think || !time_plus_1) {
{ if (self_frame) ast_delete(self_frame);
store_frame = ast_store_new(ctx, INSTR_STOREP_F, self_frame, framenum); if (self_nextthink) ast_delete(self_nextthink);
store_nextthink = ast_store_new(ctx, INSTR_STOREP_F, self_nextthink, time_plus_1); if (self_think) ast_delete(self_think);
store_think = ast_store_new(ctx, INSTR_STOREP_FNC, self_think, nextthink); if (time_plus_1) ast_delete(time_plus_1);
if (!store_frame) {
ast_delete(self_frame);
retval = false; retval = false;
} }
if (!store_nextthink) {
ast_delete(self_nextthink); if (retval)
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; 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) { if (!store_frame) {
parseerror(parser, "failed to generate code for [frame,think]"); ast_delete(self_frame);
ast_unref(nextthink); retval = false;
ast_unref(framenum); }
ast_delete(block); if (!store_nextthink) {
return false; 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;
}
} }
} }