mirror of
https://github.com/DarkPlacesEngine/gmqcc.git
synced 2024-11-24 04:41:25 +00:00
Merge branch 'cooking' of github.com:graphitemaster/gmqcc into cooking
This commit is contained in:
commit
05b349c72f
16 changed files with 331 additions and 66 deletions
59
ast.c
59
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 <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
17
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.
|
||||
|
|
10
doc/gmqcc.1
10
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
|
||||
|
|
56
exec.c
56
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++ */
|
||||
|
|
14
gmqcc.h
14
gmqcc.h
|
@ -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);
|
||||
|
|
|
@ -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
30
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;
|
||||
|
|
1
ir.h
1
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
|
||||
* <outtype>%label := opcode a, b
|
||||
|
|
6
main.c
6
main.c
|
@ -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
2
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() {
|
||||
|
|
2
opts.def
2
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 */
|
||||
|
|
16
parser.c
16
parser.c
|
@ -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
2
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);
|
||||
}
|
||||
|
||||
|
||||
|
|
9
tests/state-emu.tmpl
Normal file
9
tests/state-emu.tmpl
Normal 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
39
tests/state.qc
Normal 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
9
tests/state.tmpl
Normal 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)
|
Loading…
Reference in a new issue