mirror of
https://github.com/DarkPlacesEngine/gmqcc.git
synced 2025-02-20 18:32:01 +00:00
Merge branch 'cooking'
Conflicts: test.c
This commit is contained in:
commit
ac8c7d730a
48 changed files with 1963 additions and 333 deletions
|
@ -11,8 +11,6 @@ GITINFO :=
|
|||
GITINFO != git describe --always
|
||||
.endif
|
||||
|
||||
CFLAGS += -Wall -Wextra -Werror -Wstrict-aliasing -Wno-attributes
|
||||
|
||||
.if $(CC) == clang
|
||||
CFLAGS += -Weverything\
|
||||
-Wno-padded\
|
||||
|
@ -22,6 +20,9 @@ CFLAGS += -Wall -Wextra -Werror -Wstrict-aliasing -Wno-attributes
|
|||
-Wno-float-equal\
|
||||
-Wno-unknown-warning-option\
|
||||
-Wno-cast-align\
|
||||
-Wno-assign-enum\
|
||||
-Wno-empty-body\
|
||||
-Wno-date-time\
|
||||
-pedantic-errors
|
||||
.else
|
||||
. if $(CC) != g++
|
||||
|
|
6
Makefile
6
Makefile
|
@ -4,8 +4,7 @@ UNAME ?= $(shell uname)
|
|||
CYGWIN = $(findstring CYGWIN, $(UNAME))
|
||||
MINGW = $(findstring MINGW, $(UNAME))
|
||||
|
||||
CFLAGS += -Wall -Wextra -Werror -Wstrict-aliasing -Wno-attributes
|
||||
#turn on tons of warnings if clang is present
|
||||
# turn on tons of warnings if clang is present
|
||||
# but also turn off the STUPID ONES
|
||||
ifeq ($(CC), clang)
|
||||
CFLAGS += \
|
||||
|
@ -17,6 +16,9 @@ ifeq ($(CC), clang)
|
|||
-Wno-float-equal \
|
||||
-Wno-unknown-warning-option \
|
||||
-Wno-cast-align \
|
||||
-Wno-assign-enum \
|
||||
-Wno-empty-body \
|
||||
-Wno-date-time \
|
||||
-pedantic-errors
|
||||
else
|
||||
ifneq ($(CC), g++)
|
||||
|
|
3
ansi.c
3
ansi.c
|
@ -62,6 +62,9 @@ int platform_vasprintf(char **dat, const char *fmt, va_list args) {
|
|||
len = vsnprintf(buf, sizeof(buf), fmt, cpy);
|
||||
va_end (cpy);
|
||||
|
||||
if (len < 0)
|
||||
return len;
|
||||
|
||||
if (len < (int)sizeof(buf)) {
|
||||
*dat = util_strdup(buf);
|
||||
return len;
|
||||
|
|
70
ast.c
70
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)
|
||||
|
@ -359,6 +360,7 @@ ast_value* ast_value_new(lex_ctx_t ctx, const char *name, int t)
|
|||
self->cvq = CV_NONE;
|
||||
self->hasvalue = false;
|
||||
self->isimm = false;
|
||||
self->inexact = false;
|
||||
self->uses = 0;
|
||||
memset(&self->constval, 0, sizeof(self->constval));
|
||||
self->initlist = NULL;
|
||||
|
@ -972,6 +974,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)
|
||||
{
|
||||
|
@ -1431,7 +1453,7 @@ bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
|
|||
if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
|
||||
self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
|
||||
if (self->expression.flags & AST_FLAG_ERASEABLE)
|
||||
self->ir_v->flags |= IR_FLAG_ERASEABLE;
|
||||
self->ir_v->flags |= IR_FLAG_ERASABLE;
|
||||
if (self->expression.flags & AST_FLAG_BLOCK_COVERAGE)
|
||||
func->flags |= IR_FLAG_BLOCK_COVERAGE;
|
||||
/* The function is filled later on ast_function_codegen... */
|
||||
|
@ -1479,7 +1501,7 @@ bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
|
|||
if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
|
||||
self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
|
||||
if (self->expression.flags & AST_FLAG_ERASEABLE)
|
||||
self->ir_v->flags |= IR_FLAG_ERASEABLE;
|
||||
self->ir_v->flags |= IR_FLAG_ERASABLE;
|
||||
|
||||
namelen = strlen(self->name);
|
||||
name = (char*)mem_a(namelen + 16);
|
||||
|
@ -1514,7 +1536,7 @@ bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
|
|||
self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
|
||||
|
||||
if (self->expression.flags & AST_FLAG_ERASEABLE)
|
||||
self->ir_v->flags |= IR_FLAG_ERASEABLE;
|
||||
self->ir_v->flags |= IR_FLAG_ERASABLE;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1548,7 +1570,7 @@ bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
|
|||
if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
|
||||
v->flags |= IR_FLAG_INCLUDE_DEF;
|
||||
if (self->expression.flags & AST_FLAG_ERASEABLE)
|
||||
self->ir_v->flags |= IR_FLAG_ERASEABLE;
|
||||
self->ir_v->flags |= IR_FLAG_ERASABLE;
|
||||
|
||||
namelen = strlen(self->name);
|
||||
name = (char*)mem_a(namelen + 16);
|
||||
|
@ -1593,7 +1615,7 @@ bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
|
|||
if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
|
||||
self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
|
||||
if (self->expression.flags & AST_FLAG_ERASEABLE)
|
||||
self->ir_v->flags |= IR_FLAG_ERASEABLE;
|
||||
self->ir_v->flags |= IR_FLAG_ERASABLE;
|
||||
|
||||
/* initialize */
|
||||
if (self->hasvalue) {
|
||||
|
@ -3347,6 +3369,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;
|
||||
|
|
18
ast.h
18
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) )
|
||||
|
@ -214,6 +216,7 @@ struct ast_value_s
|
|||
bool isfield; /* this declares a field */
|
||||
bool isimm; /* an immediate, not just const */
|
||||
bool hasvalue;
|
||||
bool inexact; /* inexact coming from folded expression */
|
||||
basic_value_t constval;
|
||||
/* for TYPE_ARRAY we have an optional vector
|
||||
* of constants when an initializer list
|
||||
|
@ -579,6 +582,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.
|
||||
|
|
48
doc/gmqcc.1
48
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
|
||||
|
@ -344,6 +349,10 @@ will search its intrinsics table for something that matches that
|
|||
function name by appending "__builtin_" to it. This behaviour may
|
||||
be unexpected, so enabling this will produce a diagnostic when
|
||||
such a function is resolved to a builtin.
|
||||
.It Fl W Ns Cm inexact-compares
|
||||
When comparing an inexact value such as `1.0/3.0' the result is
|
||||
pathologically wrong. Enabling this will trigger a compiler warning
|
||||
on such expressions.
|
||||
.El
|
||||
.Sh COMPILE FLAGS
|
||||
.Bl -tag -width Ds
|
||||
|
@ -417,6 +426,25 @@ M_SQRT2
|
|||
M_SQRT1_2
|
||||
M_TAU
|
||||
.Ed
|
||||
.It Fl f Ns Cm ftepp-indirect-expansion
|
||||
Enable indirect macro expansion. This only works in combination
|
||||
with '-fftepp' and is currently not included by '-std=fteqcc'.
|
||||
Enabling this behavior will allow the preprocessor to operate more
|
||||
like the standard C preprocessor in that it will allow arguments
|
||||
of macros which are macro-expanded to be substituted into the
|
||||
definition of the macro.
|
||||
.Pp
|
||||
As an example:
|
||||
.Bd -literal -offset indent
|
||||
#define STR1(x) #x
|
||||
#define STR2(x) STR1(x)
|
||||
#define THE_ANSWER 42
|
||||
#define THE_ANSWER_STR STR2(THE_ANSWER) /* "42" */
|
||||
|
||||
.Ed
|
||||
With this enabled, an expansion of THE_ANSWER_STR will yield
|
||||
the string "42". With this disabled an expansion of THE_ANSWER_STR
|
||||
will yield "THE_ANSWER"
|
||||
.It Fl f Ns Cm relaxed-switch
|
||||
Allow switch cases to use non constant variables.
|
||||
.It Fl f Ns Cm short-logic
|
||||
|
@ -577,6 +605,26 @@ 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.
|
||||
.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,
|
||||
the following flag will produce diagnostics for what triggered that
|
||||
exception.
|
||||
.It Fl f Ns Cm split-vector-parameters
|
||||
With this flag immediate vector literals which only ever appear as function
|
||||
parameters won't be stored as vector immediates. Instead, the 3 floats making
|
||||
up the vector will be copied separately. Essentially this turns a vector-store
|
||||
instruction into 3 float-store instructions for such cases. This increases
|
||||
code size but can dramatically reduce the amount of vector globals, which is
|
||||
after all limited to 64k. There's at least one known codebase where this
|
||||
lowers the number of globals from over 80k down to around 3k. In other code
|
||||
bases it doesn't reduce the globals at all but only increases code size.
|
||||
Just try it and see whether it helps you.
|
||||
.El
|
||||
.Sh OPTIMIZATIONS
|
||||
.Bl -tag -width Ds
|
||||
|
|
60
exec.c
60
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:
|
||||
|
@ -912,7 +950,7 @@ static void prog_main_setparams(qc_program_t *prog) {
|
|||
}
|
||||
}
|
||||
|
||||
void prog_disasm_function(qc_program_t *prog, size_t id);
|
||||
static void prog_disasm_function(qc_program_t *prog, size_t id);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
size_t i;
|
||||
|
@ -1227,7 +1265,7 @@ int main(int argc, char **argv) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
void prog_disasm_function(qc_program_t *prog, size_t id) {
|
||||
static void prog_disasm_function(qc_program_t *prog, size_t id) {
|
||||
prog_section_function_t *fdef = prog->functions + id;
|
||||
prog_section_statement_t *st;
|
||||
|
||||
|
@ -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++ */
|
||||
|
|
63
ftepp.c
63
ftepp.c
|
@ -703,9 +703,9 @@ static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token)
|
|||
++ch;
|
||||
}
|
||||
break;
|
||||
case TOKEN_WHITE:
|
||||
/*case TOKEN_WHITE:
|
||||
ftepp_out(ftepp, " ", false);
|
||||
break;
|
||||
break;*/
|
||||
case TOKEN_EOL:
|
||||
ftepp_out(ftepp, "\\n", false);
|
||||
break;
|
||||
|
@ -734,6 +734,7 @@ static void ftepp_recursion_footer(ftepp_t *ftepp)
|
|||
ftepp_out(ftepp, "\n#pragma pop(line)\n", false);
|
||||
}
|
||||
|
||||
static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline);
|
||||
static void ftepp_param_out(ftepp_t *ftepp, macroparam *param)
|
||||
{
|
||||
size_t i;
|
||||
|
@ -742,8 +743,13 @@ static void ftepp_param_out(ftepp_t *ftepp, macroparam *param)
|
|||
out = param->tokens[i];
|
||||
if (out->token == TOKEN_EOL)
|
||||
ftepp_out(ftepp, "\n", false);
|
||||
else
|
||||
ftepp_out(ftepp, out->value, false);
|
||||
else {
|
||||
ppmacro *find = ftepp_macro_find(ftepp, out->value);
|
||||
if (OPTS_FLAG(FTEPP_INDIRECT_EXPANSION) && find && !find->has_params)
|
||||
ftepp_macro_expand(ftepp, find, NULL, false);
|
||||
else
|
||||
ftepp_out(ftepp, out->value, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -837,7 +843,7 @@ static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *param
|
|||
macro_params_find(macro, macro->output[o+1]->value, &pi))
|
||||
{
|
||||
++o;
|
||||
|
||||
|
||||
ftepp_stringify(ftepp, ¶ms[pi]);
|
||||
break;
|
||||
}
|
||||
|
@ -1455,6 +1461,7 @@ static bool ftepp_include(ftepp_t *ftepp)
|
|||
lex_ctx_t ctx;
|
||||
char lineno[128];
|
||||
char *filename;
|
||||
char *parsename = NULL;
|
||||
char *old_includename;
|
||||
|
||||
(void)ftepp_next(ftepp);
|
||||
|
@ -1462,28 +1469,56 @@ static bool ftepp_include(ftepp_t *ftepp)
|
|||
return false;
|
||||
|
||||
if (ftepp->token != TOKEN_STRINGCONST) {
|
||||
ftepp_error(ftepp, "expected filename to include");
|
||||
return false;
|
||||
ppmacro *macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
|
||||
if (macro) {
|
||||
char *backup = ftepp->output_string;
|
||||
ftepp->output_string = NULL;
|
||||
if (ftepp_macro_expand(ftepp, macro, NULL, true)) {
|
||||
parsename = util_strdup(ftepp->output_string);
|
||||
vec_free(ftepp->output_string);
|
||||
ftepp->output_string = backup;
|
||||
} else {
|
||||
ftepp->output_string = backup;
|
||||
ftepp_error(ftepp, "expected filename to include");
|
||||
return false;
|
||||
}
|
||||
} else if (OPTS_FLAG(FTEPP_PREDEFS)) {
|
||||
/* Well it could be a predefine like __LINE__ */
|
||||
char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp));
|
||||
if (predef) {
|
||||
parsename = predef(ftepp);
|
||||
} else {
|
||||
ftepp_error(ftepp, "expected filename to include");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ftepp->output_on) {
|
||||
ftepp_next(ftepp);
|
||||
(void)ftepp_next(ftepp);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (parsename)
|
||||
unescape(parsename, parsename);
|
||||
else {
|
||||
char *tokval = ftepp_tokval(ftepp);
|
||||
unescape(tokval, tokval);
|
||||
parsename = util_strdup(tokval);
|
||||
}
|
||||
|
||||
ctx = ftepp_ctx(ftepp);
|
||||
|
||||
unescape(ftepp_tokval(ftepp), ftepp_tokval(ftepp));
|
||||
|
||||
ftepp_out(ftepp, "\n#pragma file(", false);
|
||||
ftepp_out(ftepp, ftepp_tokval(ftepp), false);
|
||||
ftepp_out(ftepp, parsename, false);
|
||||
ftepp_out(ftepp, ")\n#pragma line(1)\n", false);
|
||||
|
||||
filename = ftepp_include_find(ftepp, ftepp_tokval(ftepp));
|
||||
filename = ftepp_include_find(ftepp, parsename);
|
||||
if (!filename) {
|
||||
ftepp_error(ftepp, "failed to open include file `%s`", ftepp_tokval(ftepp));
|
||||
ftepp_error(ftepp, "failed to open include file `%s`", parsename);
|
||||
mem_d(parsename);
|
||||
return false;
|
||||
}
|
||||
mem_d(parsename);
|
||||
inlex = lex_open(filename);
|
||||
if (!inlex) {
|
||||
ftepp_error(ftepp, "open failed on include file `%s`", filename);
|
||||
|
|
28
gmqcc.h
28
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);
|
||||
|
@ -973,7 +987,7 @@ typedef struct {
|
|||
|
||||
extern opts_cmd_t opts;
|
||||
|
||||
#define OPTS_GENERIC(f,i) (!! (((f)[(i)/32]) & (1<< (unsigned)((i)%32))))
|
||||
#define OPTS_GENERIC(f,i) (!! (((f)[(i)/32]) & (1U << (unsigned)((i)%32))))
|
||||
|
||||
#define OPTS_FLAG(i) OPTS_GENERIC(opts.flags, (i))
|
||||
#define OPTS_WARN(i) OPTS_GENERIC(opts.warn, (i))
|
||||
|
|
|
@ -84,6 +84,25 @@
|
|||
FTEPP_MATHDEFS = false
|
||||
|
||||
|
||||
#Enable indirect macro expansion. This only works in combination
|
||||
#with '-fftepp' and is currently not included by '-std=fteqcc'.
|
||||
#Enabling this behavior will allow the preprocessor to operate more
|
||||
#like the standard C preprocessor in that it will allow arguments
|
||||
#of macros which are macro-expanded to be substituted into the
|
||||
#definition of the macro. As an example:
|
||||
#
|
||||
# #define STR1(x) #x
|
||||
# #define STR2(x) STR1(x)
|
||||
# #define THE_ANSWER 42
|
||||
# #define THE_ANSWER_STR STR2(THE_ANSWER) /* "42" */
|
||||
#
|
||||
#With this enabled, an expansion of THE_ANSWER_STR will yield
|
||||
#the string "42". With this disabled an expansion of THE_ANSWER_STR
|
||||
#will yield "THE_ANSWER"
|
||||
|
||||
FTEPP_INDIRECT_EXPANSION = false
|
||||
|
||||
|
||||
#Allow switch cases to use non constant variables.
|
||||
|
||||
RELAXED_SWITCH = true
|
||||
|
@ -310,6 +329,22 @@
|
|||
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
|
||||
#exception.
|
||||
ARITHMETIC_EXCEPTIONS = false
|
||||
|
||||
#Split vector-literals which are only used dirctly as function parameters
|
||||
#into 3 floats stored separately to reduce the number of globals at the
|
||||
#expense of additional instructions.
|
||||
SPLIT_VECTOR_PARAMETERS = false
|
||||
|
||||
[warnings]
|
||||
#Generate a warning about variables which are declared but never
|
||||
|
@ -568,6 +603,12 @@
|
|||
BUILTINS = true
|
||||
|
||||
|
||||
#When comparing an inexact value such as `1.0/3.0' the result is
|
||||
#pathologically wrong. Enabling this will trigger a compiler warning
|
||||
#on such expressions.
|
||||
INEXACT_COMPARES = true
|
||||
|
||||
|
||||
[optimizations]
|
||||
#Some general peephole optimizations. For instance the code `a = b
|
||||
#+ c` typically generates 2 instructions, an ADD and a STORE. This
|
||||
|
|
120
hash.c
120
hash.c
|
@ -223,26 +223,114 @@ static GMQCC_FORCEINLINE uint32_t hash_murmur(const void *GMQCC_RESTRICT key, si
|
|||
return hash_murmur_result(hash, carry, length);
|
||||
}
|
||||
|
||||
size_t hash(const char *key) {
|
||||
const char *s = key;
|
||||
const char *a = s;
|
||||
/*
|
||||
* The following hash function implements it's own strlen to avoid using libc's
|
||||
* which isn't always slow but isn't always fastest either.
|
||||
*
|
||||
* Some things to note about this strlen that are otherwise confusing to grasp
|
||||
* at first is that it does intentionally depend on undefined behavior.
|
||||
*
|
||||
* The first step to the strlen is to ensure alignment before checking words,
|
||||
* without this step we risk crossing a page boundry with the word check and
|
||||
* that would cause a crash.
|
||||
*
|
||||
* The second step to the strlen contains intentional undefined behavior. When
|
||||
* accessing a word of any size, the first byte of that word is accessible if
|
||||
* and only if the whole word is accessible because words are aligned. This is
|
||||
* indicated by the fact that size / alignment always divides the page size.
|
||||
* One could argue that an architecture exists where size_t and alignment are
|
||||
* different, if that were the case, the alignment will always assume to be the
|
||||
* size of the type (size_t). So it's always safe in that regard.
|
||||
*
|
||||
* In other words, an aligned 2^n load cannot cross a page boundry unless
|
||||
* n > log2(PAGE_SIZE). There are no known architectures which support such
|
||||
* a wide load larger than PAGE_SIZE.
|
||||
*
|
||||
* Valgrind and address sanatizer may choke on this because they're strictly
|
||||
* trying to find bugs, it's a false positive to assume this is a bug when it's
|
||||
* intentional. To prevent these false positives, both things need to be taught
|
||||
* about the intentional behavior; for address sanatizer this can be done with
|
||||
* a compiler attribute, effectively preventing the function from being
|
||||
* instrumented. Valgrind requires a little more work as there is no way to
|
||||
* downright prevent a function from being instrumented, instead we can mark
|
||||
* + sizeof(size_t) bytes ahead of each byte we're reading as we calculate
|
||||
* the length of the string, then we can make that additional + sizeof(size_t)
|
||||
* on the end undefined after the length has been calculated.
|
||||
*
|
||||
* If the compiler doesn't have the attribute to disable address sanatizer
|
||||
* instrumentation we fall back to using libc's strlen instead. This isn't the
|
||||
* best solution. On windows we can assume this method always because neither
|
||||
* address sanatizer or valgrind exist.
|
||||
*/
|
||||
|
||||
/* Some compilers expose this */
|
||||
#if defined(__has_feature)
|
||||
# if __has_feature(address_sanitizer)
|
||||
# define ASAN_DISABLE __attribute__((no_sanitize_address))
|
||||
# define HAS_ASAN_DISABLE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* If they don't try to find by version the attriubte was introduces */
|
||||
#if defined(__GNUC__) && __GNUC__ >= 4
|
||||
# define ASAN_DISABLE __attribute__((no_sanitize_address))
|
||||
# define HAS_ASAN_DISABLE
|
||||
#elif defined(__clang__) && __clang_major__ >= 3
|
||||
# define ASAN_DISABLE __attribute__((no_sanatize_address))
|
||||
# define HAS_ASAN_DISABLE
|
||||
/* On windows asan doesn't exist */
|
||||
#elif defined(_WIN32)
|
||||
# define ASAN_DISABLE /* nothing */
|
||||
# define HAS_ASAN_DISABLE
|
||||
#endif
|
||||
|
||||
#ifndef HAS_ASAN_DISABLE
|
||||
# include <string.h>
|
||||
#endif
|
||||
|
||||
#ifndef NVALGRIND
|
||||
# include <valgrind/valgrind.h>
|
||||
# include <valgrind/memcheck.h>
|
||||
#else
|
||||
# define VALGRIND_MAKE_MEM_DEFINED(PTR, REDZONE_SIZE)
|
||||
# define VALGRIND_MAKE_MEM_NOACCESS(PTR, REDZONE_SIZE)
|
||||
#endif
|
||||
|
||||
#ifdef HAS_ASAN_DISABLE
|
||||
#define STRLEN_ALIGN (sizeof(size_t))
|
||||
#define STRLEN_ONES ((size_t)-1/UCHAR_MAX)
|
||||
#define STRLEN_HIGHS (STRLEN_ONES * (UCHAR_MAX/2+1))
|
||||
#define STRLEN_HASZERO(X) (((X)-STRLEN_ONES) & ~(X) & STRLEN_HIGHS)
|
||||
|
||||
static ASAN_DISABLE size_t hash_strlen(const char *key) {
|
||||
const char *s = key;
|
||||
const char *a = s;
|
||||
const size_t *w;
|
||||
|
||||
for (; (uintptr_t)s % sizeof(size_t); s++)
|
||||
if (!*s)
|
||||
return hash_murmur((const void *)key, s-a);
|
||||
for (; (uintptr_t)s % STRLEN_ALIGN; s++) {
|
||||
if (*s)
|
||||
continue;
|
||||
return s-a;
|
||||
}
|
||||
|
||||
VALGRIND_MAKE_MEM_DEFINED(s, STRLEN_ALIGN);
|
||||
for (w = (const size_t *)s; !STRLEN_HASZERO(*w); w++) {
|
||||
/* Make the next word legal to access */
|
||||
VALGRIND_MAKE_MEM_DEFINED(w + STRLEN_ALIGN, STRLEN_ALIGN);
|
||||
}
|
||||
|
||||
for (w = (const size_t*)s; !((*w-(size_t)-1/UCHAR_MAX) & ~*w & ((size_t)-1/UCHAR_MAX) * (UCHAR_MAX / 2 + 1)); w++);
|
||||
for (s = (const char *)w; *s; s++);
|
||||
|
||||
return hash_murmur((const void *)key, s-a);
|
||||
/* It's not legal to access this area anymore */
|
||||
VALGRIND_MAKE_MEM_NOACCESS(s + 1, STRLEN_ALIGN);
|
||||
return s-a;
|
||||
}
|
||||
#else
|
||||
static GMQCC_INLINE size_t hash_strlen(const char *key) {
|
||||
return strlen(key);
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef HASH_ROTL32
|
||||
#undef HASH_MURMUR_MASK1
|
||||
#undef HASH_MURMUR_MASK2
|
||||
#undef HASH_MURMUR_SEED
|
||||
#undef HASH_MURMUR_SAFEREAD
|
||||
#undef HASH_MURMUR_BLOCK
|
||||
#undef HASH_MURMUR_BYTES
|
||||
#undef HASH_MURMUR_TAIL
|
||||
size_t hash(const char *key) {
|
||||
return hash_murmur((const void *)key, hash_strlen(key));
|
||||
}
|
||||
|
|
21
include.mk
21
include.mk
|
@ -5,6 +5,9 @@ BINDIR := $(PREFIX)/bin
|
|||
DATADIR := $(PREFIX)/share
|
||||
MANDIR := $(DATADIR)/man
|
||||
|
||||
# default flags
|
||||
CFLAGS += -Wall -Wextra -Werror -Wstrict-aliasing -Wno-attributes -O2
|
||||
|
||||
# compiler
|
||||
CC ?= clang
|
||||
|
||||
|
@ -101,7 +104,12 @@ SPLINTFLAGS = \
|
|||
-observertrans \
|
||||
-abstract \
|
||||
-statictrans \
|
||||
-castfcnptr
|
||||
-castfcnptr \
|
||||
-shiftimplementation \
|
||||
-shiftnegative \
|
||||
-boolcompare \
|
||||
-infloops \
|
||||
-sysunrecog
|
||||
|
||||
#always the right rule
|
||||
default: all
|
||||
|
@ -111,20 +119,23 @@ uninstall:
|
|||
rm -f $(DESTDIR)$(BINDIR)/gmqcc
|
||||
rm -f $(DESTDIR)$(BINDIR)/qcvm
|
||||
rm -f $(DESTDIR)$(BINDIR)/gmqpak
|
||||
rm -f $(DESTDIR)$(MANDIR)/man1/doc/gmqcc.1
|
||||
rm -f $(DESTDIR)$(MANDIR)/man1/doc/qcvm.1
|
||||
rm -f $(DESTDIR)$(MANDIR)/man1/doc/gmqpak.1
|
||||
rm -f $(DESTDIR)$(MANDIR)/man1/gmqcc.1
|
||||
rm -f $(DESTDIR)$(MANDIR)/man1/qcvm.1
|
||||
rm -f $(DESTDIR)$(MANDIR)/man1/gmqpak.1
|
||||
|
||||
#style rule
|
||||
STYLE_MATCH = \( -name '*.[ch]' -or -name '*.def' -or -name '*.qc' \)
|
||||
|
||||
# splint cannot parse the MSVC source
|
||||
SPLINT_MATCH = \( -name '*.[ch]' -and ! -name 'msvc.c' -and ! -path './doc/*' \)
|
||||
|
||||
style:
|
||||
find . -type f $(STYLE_MATCH) -exec sed -i 's/ *$$//' '{}' ';'
|
||||
find . -type f $(STYLE_MATCH) -exec sed -i -e '$$a\' '{}' ';'
|
||||
find . -type f $(STYLE_MATCH) -exec sed -i 's/\t/ /g' '{}' ';'
|
||||
|
||||
splint:
|
||||
@splint $(SPLINTFLAGS) *.c *.h
|
||||
@splint $(SPLINTFLAGS) `find . -type f $(SPLINT_MATCH)`
|
||||
|
||||
gource:
|
||||
@gource $(GOURCEFLAGS)
|
||||
|
|
8
intrin.c
8
intrin.c
|
@ -422,7 +422,7 @@ static ast_expression *intrin_atanh(intrin_t *intrin) {
|
|||
(ast_expression*)ast_binary_new(
|
||||
intrin_ctx(intrin),
|
||||
INSTR_MUL_F,
|
||||
(ast_expression*)fold_constgen_float(intrin->fold, 0.5),
|
||||
(ast_expression*)fold_constgen_float(intrin->fold, 0.5, false),
|
||||
(ast_expression*)calllog
|
||||
)
|
||||
);
|
||||
|
@ -496,7 +496,7 @@ static ast_expression *intrin_exp(intrin_t *intrin) {
|
|||
intrin_ctx(intrin),
|
||||
INSTR_LT,
|
||||
(ast_expression*)i,
|
||||
(ast_expression*)fold_constgen_float(intrin->fold, 200.0f)
|
||||
(ast_expression*)fold_constgen_float(intrin->fold, 200.0f, false)
|
||||
),
|
||||
false,
|
||||
NULL,
|
||||
|
@ -1027,7 +1027,7 @@ static ast_expression *intrin_pow(intrin_t *intrin) {
|
|||
intrin_ctx(intrin),
|
||||
INSTR_GT,
|
||||
(ast_expression*)callfabs,
|
||||
(ast_expression*)fold_constgen_float(intrin->fold, QC_POW_EPSILON)
|
||||
(ast_expression*)fold_constgen_float(intrin->fold, QC_POW_EPSILON, false)
|
||||
),
|
||||
/* pre not */
|
||||
false,
|
||||
|
@ -1911,7 +1911,7 @@ static ast_expression *intrin_log_variant(intrin_t *intrin, const char *name, fl
|
|||
vec_push(value->expression.params, arg1);
|
||||
|
||||
vec_push(callln->params, (ast_expression*)arg1);
|
||||
vec_push(callln->params, (ast_expression*)fold_constgen_float(intrin->fold, base));
|
||||
vec_push(callln->params, (ast_expression*)fold_constgen_float(intrin->fold, base, false));
|
||||
|
||||
vec_push(body->exprs,
|
||||
(ast_expression*)ast_return_new(
|
||||
|
|
194
ir.c
194
ir.c
|
@ -1121,6 +1121,20 @@ ir_value* ir_value_var(const char *name, int storetype, int vtype)
|
|||
return self;
|
||||
}
|
||||
|
||||
/* helper function */
|
||||
static ir_value* ir_builder_imm_float(ir_builder *self, float value, bool add_to_list) {
|
||||
ir_value *v = ir_value_var("#IMMEDIATE", store_global, TYPE_FLOAT);
|
||||
v->flags |= IR_FLAG_ERASABLE;
|
||||
v->hasvalue = true;
|
||||
v->cvq = CV_CONST;
|
||||
v->constval.vfloat = value;
|
||||
|
||||
vec_push(self->globals, v);
|
||||
if (add_to_list)
|
||||
vec_push(self->const_floats, v);
|
||||
return v;
|
||||
}
|
||||
|
||||
ir_value* ir_value_vector_member(ir_value *self, unsigned int member)
|
||||
{
|
||||
char *name;
|
||||
|
@ -1206,9 +1220,11 @@ void ir_value_delete(ir_value* self)
|
|||
if (self->vtype == TYPE_STRING)
|
||||
mem_d((void*)self->constval.vstring);
|
||||
}
|
||||
for (i = 0; i < 3; ++i) {
|
||||
if (self->members[i])
|
||||
ir_value_delete(self->members[i]);
|
||||
if (!(self->flags & IR_FLAG_SPLIT_VECTOR)) {
|
||||
for (i = 0; i < 3; ++i) {
|
||||
if (self->members[i])
|
||||
ir_value_delete(self->members[i]);
|
||||
}
|
||||
}
|
||||
vec_free(self->reads);
|
||||
vec_free(self->writes);
|
||||
|
@ -1537,6 +1553,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;
|
||||
|
@ -3109,7 +3145,21 @@ static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *bloc
|
|||
stmt.opcode = type_store_instr[param->vtype];
|
||||
stmt.o1.u1 = ir_value_code_addr(param);
|
||||
stmt.o2.u1 = OFS_PARM0 + 3 * p;
|
||||
code_push_statement(code, &stmt, instr->context);
|
||||
|
||||
if (param->vtype == TYPE_VECTOR && (param->flags & IR_FLAG_SPLIT_VECTOR)) {
|
||||
/* fetch 3 separate floats */
|
||||
stmt.opcode = INSTR_STORE_F;
|
||||
stmt.o1.u1 = ir_value_code_addr(param->members[0]);
|
||||
code_push_statement(code, &stmt, instr->context);
|
||||
stmt.o2.u1++;
|
||||
stmt.o1.u1 = ir_value_code_addr(param->members[1]);
|
||||
code_push_statement(code, &stmt, instr->context);
|
||||
stmt.o2.u1++;
|
||||
stmt.o1.u1 = ir_value_code_addr(param->members[2]);
|
||||
code_push_statement(code, &stmt, instr->context);
|
||||
}
|
||||
else
|
||||
code_push_statement(code, &stmt, instr->context);
|
||||
}
|
||||
/* Now handle extparams */
|
||||
first = vec_size(instr->params);
|
||||
|
@ -3138,7 +3188,20 @@ static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *bloc
|
|||
stmt.opcode = type_store_instr[param->vtype];
|
||||
stmt.o1.u1 = ir_value_code_addr(param);
|
||||
stmt.o2.u1 = ir_value_code_addr(targetparam);
|
||||
code_push_statement(code, &stmt, instr->context);
|
||||
if (param->vtype == TYPE_VECTOR && (param->flags & IR_FLAG_SPLIT_VECTOR)) {
|
||||
/* fetch 3 separate floats */
|
||||
stmt.opcode = INSTR_STORE_F;
|
||||
stmt.o1.u1 = ir_value_code_addr(param->members[0]);
|
||||
code_push_statement(code, &stmt, instr->context);
|
||||
stmt.o2.u1++;
|
||||
stmt.o1.u1 = ir_value_code_addr(param->members[1]);
|
||||
code_push_statement(code, &stmt, instr->context);
|
||||
stmt.o2.u1++;
|
||||
stmt.o1.u1 = ir_value_code_addr(param->members[2]);
|
||||
code_push_statement(code, &stmt, instr->context);
|
||||
}
|
||||
else
|
||||
code_push_statement(code, &stmt, instr->context);
|
||||
}
|
||||
|
||||
stmt.opcode = INSTR_CALL0 + vec_size(instr->params);
|
||||
|
@ -3167,8 +3230,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;
|
||||
|
@ -3512,7 +3581,7 @@ static bool gen_global_function_code(ir_builder *ir, ir_value *global)
|
|||
* If there is no definition and the thing is eraseable, we can ignore
|
||||
* outputting the function to begin with.
|
||||
*/
|
||||
if (global->flags & IR_FLAG_ERASEABLE && irfun->code_function_def < 0) {
|
||||
if (global->flags & IR_FLAG_ERASABLE && irfun->code_function_def < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3609,6 +3678,10 @@ static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool isloc
|
|||
prog_section_def_t def;
|
||||
bool pushdef = opts.optimizeoff;
|
||||
|
||||
/* we don't generate split-vectors */
|
||||
if (global->vtype == TYPE_VECTOR && (global->flags & IR_FLAG_SPLIT_VECTOR))
|
||||
return true;
|
||||
|
||||
def.type = global->vtype;
|
||||
def.offset = vec_size(self->code->globals);
|
||||
def.name = 0;
|
||||
|
@ -3620,7 +3693,7 @@ static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool isloc
|
|||
* if we're eraseable and the function isn't referenced ignore outputting
|
||||
* the function.
|
||||
*/
|
||||
if (global->flags & IR_FLAG_ERASEABLE && vec_size(global->reads) == 0) {
|
||||
if (global->flags & IR_FLAG_ERASABLE && vec_size(global->reads) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3859,12 +3932,113 @@ static bool ir_builder_gen_field(ir_builder *self, ir_value *field)
|
|||
return field->code.globaladdr >= 0;
|
||||
}
|
||||
|
||||
static void ir_builder_collect_reusables(ir_builder *builder) {
|
||||
size_t i;
|
||||
ir_value **reusables = NULL;
|
||||
for (i = 0; i < vec_size(builder->globals); ++i) {
|
||||
ir_value *value = builder->globals[i];
|
||||
if (value->vtype != TYPE_FLOAT || !value->hasvalue)
|
||||
continue;
|
||||
if (value->cvq == CV_CONST || (value->name && value->name[0] == '#')) {
|
||||
vec_push(reusables, value);
|
||||
}
|
||||
}
|
||||
builder->const_floats = reusables;
|
||||
}
|
||||
|
||||
static void ir_builder_split_vector(ir_builder *self, ir_value *vec) {
|
||||
size_t i, count;
|
||||
ir_value* found[3] = { NULL, NULL, NULL };
|
||||
|
||||
/* must not be written to */
|
||||
if (vec_size(vec->writes))
|
||||
return;
|
||||
/* must not be trying to access individual members */
|
||||
if (vec->members[0] || vec->members[1] || vec->members[2])
|
||||
return;
|
||||
/* should be actually used otherwise it won't be generated anyway */
|
||||
count = vec_size(vec->reads);
|
||||
if (!count)
|
||||
return;
|
||||
|
||||
/* may only be used directly as function parameters, so if we find some other instruction cancel */
|
||||
for (i = 0; i != count; ++i) {
|
||||
/* we only split vectors if they're used directly as parameter to a call only! */
|
||||
ir_instr *user = vec->reads[i];
|
||||
if ((user->opcode < INSTR_CALL0 || user->opcode > INSTR_CALL8) && user->opcode != VINSTR_NRCALL)
|
||||
return;
|
||||
}
|
||||
|
||||
vec->flags |= IR_FLAG_SPLIT_VECTOR;
|
||||
|
||||
/* find existing floats making up the split */
|
||||
count = vec_size(self->const_floats);
|
||||
for (i = 0; i != count; ++i) {
|
||||
ir_value *c = self->const_floats[i];
|
||||
if (!found[0] && c->constval.vfloat == vec->constval.vvec.x)
|
||||
found[0] = c;
|
||||
if (!found[1] && c->constval.vfloat == vec->constval.vvec.y)
|
||||
found[1] = c;
|
||||
if (!found[2] && c->constval.vfloat == vec->constval.vvec.z)
|
||||
found[2] = c;
|
||||
if (found[0] && found[1] && found[2])
|
||||
break;
|
||||
}
|
||||
|
||||
/* generate floats for not yet found components */
|
||||
if (!found[0])
|
||||
found[0] = ir_builder_imm_float(self, vec->constval.vvec.x, true);
|
||||
if (!found[1]) {
|
||||
if (vec->constval.vvec.y == vec->constval.vvec.x)
|
||||
found[1] = found[0];
|
||||
else
|
||||
found[1] = ir_builder_imm_float(self, vec->constval.vvec.y, true);
|
||||
}
|
||||
if (!found[2]) {
|
||||
if (vec->constval.vvec.z == vec->constval.vvec.x)
|
||||
found[2] = found[0];
|
||||
else if (vec->constval.vvec.z == vec->constval.vvec.y)
|
||||
found[2] = found[1];
|
||||
else
|
||||
found[2] = ir_builder_imm_float(self, vec->constval.vvec.z, true);
|
||||
}
|
||||
|
||||
/* the .members array should be safe to use here. */
|
||||
vec->members[0] = found[0];
|
||||
vec->members[1] = found[1];
|
||||
vec->members[2] = found[2];
|
||||
|
||||
/* register the readers for these floats */
|
||||
count = vec_size(vec->reads);
|
||||
for (i = 0; i != count; ++i) {
|
||||
vec_push(found[0]->reads, vec->reads[i]);
|
||||
vec_push(found[1]->reads, vec->reads[i]);
|
||||
vec_push(found[2]->reads, vec->reads[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void ir_builder_split_vectors(ir_builder *self) {
|
||||
size_t i, count = vec_size(self->globals);
|
||||
for (i = 0; i != count; ++i) {
|
||||
ir_value *v = self->globals[i];
|
||||
if (v->vtype != TYPE_VECTOR || !v->name || v->name[0] != '#')
|
||||
continue;
|
||||
ir_builder_split_vector(self, self->globals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool ir_builder_generate(ir_builder *self, const char *filename)
|
||||
{
|
||||
prog_section_statement_t stmt;
|
||||
size_t i;
|
||||
char *lnofile = NULL;
|
||||
|
||||
if (OPTS_FLAG(SPLIT_VECTOR_PARAMETERS)) {
|
||||
ir_builder_collect_reusables(self);
|
||||
if (vec_size(self->const_floats) > 0)
|
||||
ir_builder_split_vectors(self);
|
||||
}
|
||||
|
||||
for (i = 0; i < vec_size(self->fields); ++i)
|
||||
{
|
||||
ir_builder_prepare_field(self->code, self->fields[i]);
|
||||
|
@ -3930,7 +4104,7 @@ bool ir_builder_generate(ir_builder *self, const char *filename)
|
|||
}
|
||||
|
||||
if (vec_size(self->code->globals) >= 65536) {
|
||||
irerror(vec_last(self->globals)->context, "This progs file would require more globals than the metadata can handle. Bailing out.");
|
||||
irerror(vec_last(self->globals)->context, "This progs file would require more globals than the metadata can handle (%u). Bailing out.", (unsigned int)vec_size(self->code->globals));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
16
ir.h
16
ir.h
|
@ -43,12 +43,14 @@ typedef struct {
|
|||
} ir_life_entry_t;
|
||||
|
||||
enum {
|
||||
IR_FLAG_HAS_ARRAYS = 1 << 0,
|
||||
IR_FLAG_HAS_UNINITIALIZED = 1 << 1,
|
||||
IR_FLAG_HAS_GOTO = 1 << 2,
|
||||
IR_FLAG_INCLUDE_DEF = 1 << 3,
|
||||
IR_FLAG_ERASEABLE = 1 << 4,
|
||||
IR_FLAG_BLOCK_COVERAGE = 1 << 5,
|
||||
IR_FLAG_HAS_ARRAYS = 1 << 0,
|
||||
IR_FLAG_HAS_UNINITIALIZED = 1 << 1,
|
||||
IR_FLAG_HAS_GOTO = 1 << 2,
|
||||
IR_FLAG_INCLUDE_DEF = 1 << 3,
|
||||
IR_FLAG_ERASABLE = 1 << 4,
|
||||
IR_FLAG_BLOCK_COVERAGE = 1 << 5,
|
||||
|
||||
IR_FLAG_SPLIT_VECTOR = 1 << 6,
|
||||
|
||||
IR_FLAG_LAST,
|
||||
IR_FLAG_MASK_NO_OVERLAP = (IR_FLAG_HAS_ARRAYS | IR_FLAG_HAS_UNINITIALIZED),
|
||||
|
@ -170,6 +172,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
|
||||
|
@ -253,6 +256,7 @@ struct ir_builder_s {
|
|||
ir_function **functions;
|
||||
ir_value **globals;
|
||||
ir_value **fields;
|
||||
ir_value **const_floats; /* for reusing them in vector-splits, TODO: sort this or use a radix-tree */
|
||||
|
||||
ht htfunctions;
|
||||
ht htglobals;
|
||||
|
|
29
lexer.c
29
lexer.c
|
@ -681,7 +681,7 @@ static bool lex_finish_frames(lex_file *lex)
|
|||
static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
|
||||
{
|
||||
utf8ch_t chr = 0;
|
||||
int ch = 0;
|
||||
int ch = 0, texttype = 0;
|
||||
int nextch;
|
||||
bool hex;
|
||||
bool oct;
|
||||
|
@ -716,13 +716,12 @@ static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
|
|||
case '\\': break;
|
||||
case '\'': break;
|
||||
case '"': break;
|
||||
case 'a': ch = '\a'; break;
|
||||
case 'b': ch = '\b'; break;
|
||||
case 'r': ch = '\r'; break;
|
||||
case 'n': ch = '\n'; break;
|
||||
case 't': ch = '\t'; break;
|
||||
case 'f': ch = '\f'; break;
|
||||
case 'v': ch = '\v'; break;
|
||||
case 'a': ch = '\a'; break;
|
||||
case 'r': ch = '\r'; break;
|
||||
case 'n': ch = '\n'; break;
|
||||
case 't': ch = '\t'; break;
|
||||
case 'f': ch = '\f'; break;
|
||||
case 'v': ch = '\v'; break;
|
||||
case 'x':
|
||||
case 'X':
|
||||
/* same procedure as in fteqcc */
|
||||
|
@ -825,7 +824,15 @@ static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
|
|||
else
|
||||
ch = chr;
|
||||
break;
|
||||
case '\n': ch = '\n'; break;
|
||||
|
||||
/* high bit text */
|
||||
case 'b': case 's':
|
||||
texttype ^= 128;
|
||||
continue;
|
||||
|
||||
case '\n':
|
||||
ch = '\n';
|
||||
break;
|
||||
|
||||
default:
|
||||
lexwarn(lex, WARN_UNKNOWN_CONTROL_SEQUENCE, "unrecognized control sequence: \\%c", ch);
|
||||
|
@ -833,7 +840,7 @@ static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
|
|||
lex_tokench(lex, '\\');
|
||||
}
|
||||
/* add the character finally */
|
||||
lex_tokench(lex, ch);
|
||||
lex_tokench(lex, ch | texttype);
|
||||
}
|
||||
else
|
||||
lex_tokench(lex, ch);
|
||||
|
@ -1332,6 +1339,8 @@ int lex_do(lex_file *lex)
|
|||
} else if (!strcmp(v, "vector")) {
|
||||
lex->tok.ttype = TOKEN_TYPENAME;
|
||||
lex->tok.constval.t = TYPE_VECTOR;
|
||||
} else if (!strcmp(v, "_length")) {
|
||||
lex->tok.ttype = TOKEN_OPERATOR;
|
||||
} else {
|
||||
size_t kw;
|
||||
for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_qc); ++kw) {
|
||||
|
|
97
lexer.h
97
lexer.h
|
@ -176,71 +176,72 @@ typedef struct {
|
|||
#define opid3(a,b,c) (((uint8_t)a<<16)|((uint8_t)b<<8)|(uint8_t)c)
|
||||
|
||||
static const oper_info c_operators[] = {
|
||||
{ "(", 0, opid1('('), ASSOC_LEFT, 99, OP_PREFIX, false}, /* paren expression - non function call */
|
||||
{ "(", 0, opid1('('), ASSOC_LEFT, 99, OP_PREFIX, false}, /* paren expression - non function call */
|
||||
{ "_length", 1, opid3('l','e','n'), ASSOC_RIGHT, 98, OP_PREFIX, true},
|
||||
|
||||
{ "++", 1, opid3('S','+','+'), ASSOC_LEFT, 17, OP_SUFFIX, false},
|
||||
{ "--", 1, opid3('S','-','-'), ASSOC_LEFT, 17, OP_SUFFIX, false},
|
||||
{ ".", 2, opid1('.'), ASSOC_LEFT, 17, 0, false},
|
||||
{ "(", 0, opid1('('), ASSOC_LEFT, 17, 0, false}, /* function call */
|
||||
{ "[", 2, opid1('['), ASSOC_LEFT, 17, 0, false}, /* array subscript */
|
||||
{ "++", 1, opid3('S','+','+'), ASSOC_LEFT, 17, OP_SUFFIX, false},
|
||||
{ "--", 1, opid3('S','-','-'), ASSOC_LEFT, 17, OP_SUFFIX, false},
|
||||
{ ".", 2, opid1('.'), ASSOC_LEFT, 17, 0, false},
|
||||
{ "(", 0, opid1('('), ASSOC_LEFT, 17, 0, false}, /* function call */
|
||||
{ "[", 2, opid1('['), ASSOC_LEFT, 17, 0, false}, /* array subscript */
|
||||
|
||||
{ "++", 1, opid3('+','+','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
|
||||
{ "--", 1, opid3('-','-','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
|
||||
{ "++", 1, opid3('+','+','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
|
||||
{ "--", 1, opid3('-','-','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
|
||||
|
||||
{ "**", 2, opid2('*','*'), ASSOC_RIGHT, 14, 0, true},
|
||||
{ "!", 1, opid2('!','P'), ASSOC_RIGHT, 14, OP_PREFIX, true},
|
||||
{ "~", 1, opid2('~','P'), ASSOC_RIGHT, 14, OP_PREFIX, true},
|
||||
{ "+", 1, opid2('+','P'), ASSOC_RIGHT, 14, OP_PREFIX, false},
|
||||
{ "-", 1, opid2('-','P'), ASSOC_RIGHT, 14, OP_PREFIX, true},
|
||||
/* { "&", 1, opid2('&','P'), ASSOC_RIGHT, 14, OP_PREFIX, false}, */
|
||||
{ "**", 2, opid2('*','*'), ASSOC_RIGHT, 14, 0, true},
|
||||
{ "!", 1, opid2('!','P'), ASSOC_RIGHT, 14, OP_PREFIX, true},
|
||||
{ "~", 1, opid2('~','P'), ASSOC_RIGHT, 14, OP_PREFIX, true},
|
||||
{ "+", 1, opid2('+','P'), ASSOC_RIGHT, 14, OP_PREFIX, false},
|
||||
{ "-", 1, opid2('-','P'), ASSOC_RIGHT, 14, OP_PREFIX, true},
|
||||
/* { "&", 1, opid2('&','P'), ASSOC_RIGHT, 14, OP_PREFIX, false}, */
|
||||
|
||||
{ "*", 2, opid1('*'), ASSOC_LEFT, 13, 0, true},
|
||||
{ "/", 2, opid1('/'), ASSOC_LEFT, 13, 0, true},
|
||||
{ "%", 2, opid1('%'), ASSOC_LEFT, 13, 0, true},
|
||||
{ "><", 2, opid2('>','<'), ASSOC_LEFT, 13, 0, true},
|
||||
{ "*", 2, opid1('*'), ASSOC_LEFT, 13, 0, true},
|
||||
{ "/", 2, opid1('/'), ASSOC_LEFT, 13, 0, true},
|
||||
{ "%", 2, opid1('%'), ASSOC_LEFT, 13, 0, true},
|
||||
{ "><", 2, opid2('>','<'), ASSOC_LEFT, 13, 0, true},
|
||||
|
||||
{ "+", 2, opid1('+'), ASSOC_LEFT, 12, 0, true},
|
||||
{ "-", 2, opid1('-'), ASSOC_LEFT, 12, 0, true},
|
||||
{ "+", 2, opid1('+'), ASSOC_LEFT, 12, 0, true},
|
||||
{ "-", 2, opid1('-'), ASSOC_LEFT, 12, 0, true},
|
||||
|
||||
{ "<<", 2, opid2('<','<'), ASSOC_LEFT, 11, 0, true},
|
||||
{ ">>", 2, opid2('>','>'), ASSOC_LEFT, 11, 0, true},
|
||||
{ "<<", 2, opid2('<','<'), ASSOC_LEFT, 11, 0, true},
|
||||
{ ">>", 2, opid2('>','>'), ASSOC_LEFT, 11, 0, true},
|
||||
|
||||
{ "<", 2, opid1('<'), ASSOC_LEFT, 10, 0, false},
|
||||
{ ">", 2, opid1('>'), ASSOC_LEFT, 10, 0, false},
|
||||
{ "<=>", 2, opid3('<','=','>'), ASSOC_LEFT, 10, 0, true},
|
||||
{ "<=", 2, opid2('<','='), ASSOC_LEFT, 10, 0, false},
|
||||
{ ">=", 2, opid2('>','='), ASSOC_LEFT, 10, 0, false},
|
||||
{ "<", 2, opid1('<'), ASSOC_LEFT, 10, 0, false},
|
||||
{ ">", 2, opid1('>'), ASSOC_LEFT, 10, 0, false},
|
||||
{ "<=>", 2, opid3('<','=','>'), ASSOC_LEFT, 10, 0, true},
|
||||
{ "<=", 2, opid2('<','='), ASSOC_LEFT, 10, 0, false},
|
||||
{ ">=", 2, opid2('>','='), ASSOC_LEFT, 10, 0, false},
|
||||
|
||||
{ "==", 2, opid2('=','='), ASSOC_LEFT, 9, 0, true},
|
||||
{ "!=", 2, opid2('!','='), ASSOC_LEFT, 9, 0, true},
|
||||
{ "==", 2, opid2('=','='), ASSOC_LEFT, 9, 0, true},
|
||||
{ "!=", 2, opid2('!','='), ASSOC_LEFT, 9, 0, true},
|
||||
|
||||
{ "&", 2, opid1('&'), ASSOC_LEFT, 8, 0, true},
|
||||
{ "&", 2, opid1('&'), ASSOC_LEFT, 8, 0, true},
|
||||
|
||||
{ "^", 2, opid1('^'), ASSOC_LEFT, 7, 0, true},
|
||||
{ "^", 2, opid1('^'), ASSOC_LEFT, 7, 0, true},
|
||||
|
||||
{ "|", 2, opid1('|'), ASSOC_LEFT, 6, 0, true},
|
||||
{ "|", 2, opid1('|'), ASSOC_LEFT, 6, 0, true},
|
||||
|
||||
{ "&&", 2, opid2('&','&'), ASSOC_LEFT, 5, 0, true},
|
||||
{ "&&", 2, opid2('&','&'), ASSOC_LEFT, 5, 0, true},
|
||||
|
||||
{ "||", 2, opid2('|','|'), ASSOC_LEFT, 4, 0, true},
|
||||
{ "||", 2, opid2('|','|'), ASSOC_LEFT, 4, 0, true},
|
||||
|
||||
{ "?", 3, opid2('?',':'), ASSOC_RIGHT, 3, 0, true},
|
||||
{ "?", 3, opid2('?',':'), ASSOC_RIGHT, 3, 0, true},
|
||||
|
||||
{ "=", 2, opid1('='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "+=", 2, opid2('+','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "-=", 2, opid2('-','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "*=", 2, opid2('*','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "/=", 2, opid2('/','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "%=", 2, opid2('%','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ ">>=", 2, opid3('>','>','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "<<=", 2, opid3('<','<','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "&=", 2, opid2('&','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "^=", 2, opid2('^','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "|=", 2, opid2('|','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "=", 2, opid1('='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "+=", 2, opid2('+','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "-=", 2, opid2('-','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "*=", 2, opid2('*','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "/=", 2, opid2('/','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "%=", 2, opid2('%','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ ">>=", 2, opid3('>','>','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "<<=", 2, opid3('<','<','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "&=", 2, opid2('&','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "^=", 2, opid2('^','='), ASSOC_RIGHT, 2, 0, false},
|
||||
{ "|=", 2, opid2('|','='), ASSOC_RIGHT, 2, 0, false},
|
||||
|
||||
{ ":", 0, opid2(':','?'), ASSOC_RIGHT, 1, 0, false},
|
||||
{ ":", 0, opid2(':','?'), ASSOC_RIGHT, 1, 0, false},
|
||||
|
||||
{ ",", 2, opid1(','), ASSOC_LEFT, 0, 0, false}
|
||||
{ ",", 2, opid1(','), ASSOC_LEFT, 0, 0, false}
|
||||
};
|
||||
|
||||
static const oper_info fte_operators[] = {
|
||||
|
|
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;
|
||||
|
|
87
opts.c
87
opts.c
|
@ -93,6 +93,7 @@ static void opts_setdefault(void) {
|
|||
opts_set(opts.warn, WARN_CONST_OVERWRITE, true);
|
||||
opts_set(opts.warn, WARN_DIRECTIVE_INMACRO, true);
|
||||
opts_set(opts.warn, WARN_BUILTINS, true);
|
||||
opts_set(opts.warn, WARN_INEXACT_COMPARES, true);
|
||||
|
||||
/* flags */
|
||||
opts_set(opts.flags, ADJUST_VECTOR_FIELDS, true);
|
||||
|
@ -101,6 +102,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() {
|
||||
|
@ -193,9 +196,9 @@ void opts_setoptimlevel(unsigned int level) {
|
|||
* from a progs.src.
|
||||
*/
|
||||
static char *opts_ini_rstrip(char *s) {
|
||||
char *p = s + strlen(s);
|
||||
while(p > s && util_isspace(*--p))
|
||||
*p = '\0';
|
||||
char *p = s + strlen(s) - 1;
|
||||
while (p > s && util_isspace(*p))
|
||||
*p = '\0', p--;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -215,8 +218,9 @@ static char *opts_ini_next(const char *s, char c) {
|
|||
|
||||
static size_t opts_ini_parse (
|
||||
fs_file_t *filehandle,
|
||||
char *(*loadhandle)(const char *, const char *, const char *),
|
||||
char **errorhandle
|
||||
char *(*loadhandle)(const char *, const char *, const char *, char **),
|
||||
char **errorhandle,
|
||||
char **parse_file
|
||||
) {
|
||||
size_t linesize;
|
||||
size_t lineno = 1;
|
||||
|
@ -262,7 +266,7 @@ static size_t opts_ini_parse (
|
|||
} else if (*parse_beg && *parse_beg != ';') {
|
||||
/* not a comment, must be a name value pair :) */
|
||||
if (*(parse_end = opts_ini_next(parse_beg, '=')) != '=')
|
||||
parse_end = opts_ini_next(parse_beg, ':');
|
||||
parse_end = opts_ini_next(parse_beg, ':');
|
||||
|
||||
if (*parse_end == '=' || *parse_end == ':') {
|
||||
*parse_end = '\0'; /* terminate bro */
|
||||
|
@ -276,8 +280,20 @@ static size_t opts_ini_parse (
|
|||
util_strncpy(oldname_data, read_name, sizeof(oldname_data));
|
||||
oldname_data[sizeof(oldname_data) - 1] ='\0';
|
||||
|
||||
if ((*errorhandle = loadhandle(section_data, read_name, read_value)) && !error)
|
||||
if ((*errorhandle = loadhandle(section_data, read_name, read_value, parse_file)) && !error)
|
||||
error = lineno;
|
||||
} else if (!strcmp(section_data, "includes")) {
|
||||
/* Includes are special */
|
||||
if (*(parse_end = opts_ini_next(parse_beg, '=')) == '='
|
||||
|| *(parse_end = opts_ini_next(parse_beg, ':')) == ':') {
|
||||
static const char *invalid_include = "invalid use of include";
|
||||
vec_append(*errorhandle, strlen(invalid_include), invalid_include);
|
||||
error = lineno;
|
||||
} else {
|
||||
read_name = opts_ini_rstrip(parse_beg);
|
||||
if ((*errorhandle = loadhandle(section_data, read_name, read_name, parse_file)) && !error)
|
||||
error = lineno;
|
||||
}
|
||||
} else if (!error) {
|
||||
/* otherwise set error to the current line number */
|
||||
error = lineno;
|
||||
|
@ -287,6 +303,7 @@ static size_t opts_ini_parse (
|
|||
}
|
||||
mem_d(line);
|
||||
return error;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -298,7 +315,7 @@ static bool opts_ini_bool(const char *value) {
|
|||
return !!strtol(value, NULL, 10);
|
||||
}
|
||||
|
||||
static char *opts_ini_load(const char *section, const char *name, const char *value) {
|
||||
static char *opts_ini_load(const char *section, const char *name, const char *value, char **parse_file) {
|
||||
char *error = NULL;
|
||||
bool found = false;
|
||||
|
||||
|
@ -310,6 +327,26 @@ static char *opts_ini_load(const char *section, const char *name, const char *va
|
|||
#undef GMQCC_TYPE_OPTIMIZATIONS
|
||||
#undef GMQCC_TYPE_WARNS
|
||||
|
||||
/* deal with includes */
|
||||
if (!strcmp(section, "includes")) {
|
||||
static const char *include_error_beg = "failed to open file `";
|
||||
static const char *include_error_end = "' for inclusion";
|
||||
fs_file_t *file = fs_file_open(value, "r");
|
||||
found = true;
|
||||
if (!file) {
|
||||
vec_append(error, strlen(include_error_beg), include_error_beg);
|
||||
vec_append(error, strlen(value), value);
|
||||
vec_append(error, strlen(include_error_end), include_error_end);
|
||||
} else {
|
||||
if (opts_ini_parse(file, &opts_ini_load, &error, parse_file) != 0)
|
||||
found = false;
|
||||
/* Change the file name */
|
||||
mem_d(*parse_file);
|
||||
*parse_file = util_strdup(value);
|
||||
fs_file_close(file);
|
||||
}
|
||||
}
|
||||
|
||||
/* flags */
|
||||
#define GMQCC_TYPE_FLAGS
|
||||
#define GMQCC_DEFINE_FLAG(X) \
|
||||
|
@ -348,24 +385,29 @@ static char *opts_ini_load(const char *section, const char *name, const char *va
|
|||
|
||||
/* nothing was found ever! */
|
||||
if (!found) {
|
||||
if (strcmp(section, "flags") &&
|
||||
strcmp(section, "warnings") &&
|
||||
if (strcmp(section, "includes") &&
|
||||
strcmp(section, "flags") &&
|
||||
strcmp(section, "warnings") &&
|
||||
strcmp(section, "optimizations"))
|
||||
{
|
||||
vec_append(error, 17, "invalid section `");
|
||||
static const char *invalid_section = "invalid_section `";
|
||||
vec_append(error, strlen(invalid_section), invalid_section);
|
||||
vec_append(error, strlen(section), section);
|
||||
vec_push (error, '`');
|
||||
vec_push (error, '\0');
|
||||
} else {
|
||||
vec_append(error, 18, "invalid variable `");
|
||||
vec_push(error, '`');
|
||||
} else if (strcmp(section, "includes")) {
|
||||
static const char *invalid_variable = "invalid_variable `";
|
||||
static const char *in_section = "` in section: `";
|
||||
vec_append(error, strlen(invalid_variable), invalid_variable);
|
||||
vec_append(error, strlen(name), name);
|
||||
vec_push (error, '`');
|
||||
vec_append(error, 14, " in section: `");
|
||||
vec_append(error, strlen(in_section), in_section);
|
||||
vec_append(error, strlen(section), section);
|
||||
vec_push (error, '`');
|
||||
vec_push (error, '\0');
|
||||
vec_push(error, '`');
|
||||
} else {
|
||||
static const char *expected_something = "expected something";
|
||||
vec_append(error, strlen(expected_something), expected_something);
|
||||
}
|
||||
}
|
||||
vec_push(error, '\0');
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -380,6 +422,7 @@ void opts_ini_init(const char *file) {
|
|||
* gmqcc.cfg
|
||||
*/
|
||||
char *error = NULL;
|
||||
char *parse_file = NULL;
|
||||
size_t line;
|
||||
fs_file_t *ini;
|
||||
|
||||
|
@ -394,11 +437,13 @@ void opts_ini_init(const char *file) {
|
|||
|
||||
con_out("found ini file `%s`\n", file);
|
||||
|
||||
if ((line = opts_ini_parse(ini, &opts_ini_load, &error)) != 0) {
|
||||
parse_file = util_strdup(file);
|
||||
if ((line = opts_ini_parse(ini, &opts_ini_load, &error, &parse_file)) != 0) {
|
||||
/* there was a parse error with the ini file */
|
||||
con_printmsg(LVL_ERROR, file, line, 0 /*TODO: column for ini error*/, "error", error);
|
||||
con_printmsg(LVL_ERROR, parse_file, line, 0 /*TODO: column for ini error*/, "error", error);
|
||||
vec_free(error);
|
||||
}
|
||||
mem_d(parse_file);
|
||||
|
||||
fs_file_close(ini);
|
||||
}
|
||||
|
|
6
opts.def
6
opts.def
|
@ -32,6 +32,7 @@
|
|||
GMQCC_DEFINE_FLAG(FTEPP)
|
||||
GMQCC_DEFINE_FLAG(FTEPP_PREDEFS)
|
||||
GMQCC_DEFINE_FLAG(FTEPP_MATHDEFS)
|
||||
GMQCC_DEFINE_FLAG(FTEPP_INDIRECT_EXPANSION)
|
||||
GMQCC_DEFINE_FLAG(RELAXED_SWITCH)
|
||||
GMQCC_DEFINE_FLAG(SHORT_LOGIC)
|
||||
GMQCC_DEFINE_FLAG(PERL_LOGIC)
|
||||
|
@ -56,6 +57,9 @@
|
|||
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)
|
||||
GMQCC_DEFINE_FLAG(SPLIT_VECTOR_PARAMETERS)
|
||||
#endif
|
||||
|
||||
/* warning flags */
|
||||
|
@ -98,6 +102,7 @@
|
|||
GMQCC_DEFINE_FLAG(CONST_OVERWRITE)
|
||||
GMQCC_DEFINE_FLAG(DIRECTIVE_INMACRO)
|
||||
GMQCC_DEFINE_FLAG(BUILTINS)
|
||||
GMQCC_DEFINE_FLAG(INEXACT_COMPARES)
|
||||
#endif
|
||||
|
||||
#ifdef GMQCC_TYPE_OPTIMIZATIONS
|
||||
|
@ -135,6 +140,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 */
|
||||
|
|
216
parser.c
216
parser.c
|
@ -953,6 +953,9 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
|
|||
}
|
||||
}
|
||||
(void)check_write_to(ctx, exprs[0]);
|
||||
/* When we're a vector of part of an entity field we use STOREP */
|
||||
if (ast_istype(exprs[0], ast_member) && ast_istype(((ast_member*)exprs[0])->owner, ast_entfield))
|
||||
assignop = INSTR_STOREP_F;
|
||||
out = (ast_expression*)ast_store_new(ctx, assignop, exprs[0], exprs[1]);
|
||||
break;
|
||||
case opid3('+','+','P'):
|
||||
|
@ -1145,6 +1148,22 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
|
|||
out = (ast_expression*)asbinstore;
|
||||
break;
|
||||
|
||||
case opid3('l', 'e', 'n'):
|
||||
if (exprs[0]->vtype != TYPE_STRING && exprs[0]->vtype != TYPE_ARRAY) {
|
||||
ast_type_to_string(exprs[0], ty1, sizeof(ty1));
|
||||
compile_error(ast_ctx(exprs[0]), "invalid type for length operator: %s", ty1);
|
||||
return false;
|
||||
}
|
||||
/* strings must be const, arrays are statically sized */
|
||||
if (exprs[0]->vtype == TYPE_STRING &&
|
||||
!(((ast_value*)exprs[0])->hasvalue && ((ast_value*)exprs[0])->cvq == CV_CONST))
|
||||
{
|
||||
compile_error(ast_ctx(exprs[0]), "operand of length operator not a valid constant expression");
|
||||
return false;
|
||||
}
|
||||
out = fold_op(parser->fold, op, exprs);
|
||||
break;
|
||||
|
||||
case opid2('~', 'P'):
|
||||
if (exprs[0]->vtype != TYPE_FLOAT && exprs[0]->vtype != TYPE_VECTOR) {
|
||||
ast_type_to_string(exprs[0], ty1, sizeof(ty1));
|
||||
|
@ -1293,7 +1312,7 @@ static bool parser_close_call(parser_t *parser, shunt *sy)
|
|||
if ((fun->flags & AST_FLAG_VARIADIC) &&
|
||||
!(/*funval->cvq == CV_CONST && */ funval->hasvalue && funval->constval.vfunc->builtin))
|
||||
{
|
||||
call->va_count = (ast_expression*)fold_constgen_float(parser->fold, (qcfloat_t)paramcount);
|
||||
call->va_count = (ast_expression*)fold_constgen_float(parser->fold, (qcfloat_t)paramcount, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1548,14 +1567,14 @@ static bool parse_sya_operand(parser_t *parser, shunt *sy, bool with_labels)
|
|||
return true;
|
||||
}
|
||||
else if (parser->tok == TOKEN_FLOATCONST) {
|
||||
ast_expression *val = fold_constgen_float(parser->fold, (parser_token(parser)->constval.f));
|
||||
ast_expression *val = fold_constgen_float(parser->fold, (parser_token(parser)->constval.f), false);
|
||||
if (!val)
|
||||
return false;
|
||||
vec_push(sy->out, syexp(parser_ctx(parser), val));
|
||||
return true;
|
||||
}
|
||||
else if (parser->tok == TOKEN_INTCONST || parser->tok == TOKEN_CHARCONST) {
|
||||
ast_expression *val = fold_constgen_float(parser->fold, (qcfloat_t)(parser_token(parser)->constval.i));
|
||||
ast_expression *val = fold_constgen_float(parser->fold, (qcfloat_t)(parser_token(parser)->constval.i), false);
|
||||
if (!val)
|
||||
return false;
|
||||
vec_push(sy->out, syexp(parser_ctx(parser), val));
|
||||
|
@ -3142,7 +3161,7 @@ static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression *
|
|||
if (parser->tok == TOKEN_IDENT)
|
||||
typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
|
||||
if (typevar || parser->tok == TOKEN_TYPENAME) {
|
||||
if (!parse_variable(parser, block, false, CV_NONE, typevar, false, false, 0, NULL)) {
|
||||
if (!parse_variable(parser, block, true, CV_NONE, typevar, false, false, 0, NULL)) {
|
||||
ast_delete(switchnode);
|
||||
return false;
|
||||
}
|
||||
|
@ -3154,7 +3173,7 @@ static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression *
|
|||
ast_delete(switchnode);
|
||||
return false;
|
||||
}
|
||||
if (!parse_variable(parser, block, false, cvq, NULL, noref, is_static, qflags, NULL)) {
|
||||
if (!parse_variable(parser, block, true, cvq, NULL, noref, is_static, qflags, NULL)) {
|
||||
ast_delete(switchnode);
|
||||
return false;
|
||||
}
|
||||
|
@ -3462,7 +3481,7 @@ static bool parse_statement(parser_t *parser, ast_block *block, ast_expression *
|
|||
{
|
||||
if (cvq == CV_WRONG)
|
||||
return false;
|
||||
return parse_variable(parser, block, true, cvq, NULL, noref, is_static, qflags, vstring);
|
||||
return parse_variable(parser, block, false, cvq, NULL, noref, is_static, qflags, vstring);
|
||||
}
|
||||
else if (parser->tok == TOKEN_KEYWORD)
|
||||
{
|
||||
|
@ -4015,69 +4034,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, 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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4155,7 +4188,7 @@ static bool parse_function_body(parser_t *parser, ast_value *var)
|
|||
goto enderrfn;
|
||||
}
|
||||
func->varargs = varargs;
|
||||
func->fixedparams = (ast_value*)fold_constgen_float(parser->fold, vec_size(var->expression.params));
|
||||
func->fixedparams = (ast_value*)fold_constgen_float(parser->fold, vec_size(var->expression.params), false);
|
||||
}
|
||||
|
||||
parser->function = func;
|
||||
|
@ -4213,7 +4246,7 @@ static ast_expression *array_accessor_split(
|
|||
|
||||
cmp = ast_binary_new(ctx, INSTR_LT,
|
||||
(ast_expression*)index,
|
||||
(ast_expression*)fold_constgen_float(parser->fold, middle));
|
||||
(ast_expression*)fold_constgen_float(parser->fold, middle, false));
|
||||
if (!cmp) {
|
||||
ast_delete(left);
|
||||
ast_delete(right);
|
||||
|
@ -4246,7 +4279,7 @@ static ast_expression *array_setter_node(parser_t *parser, ast_value *array, ast
|
|||
if (value->expression.vtype == TYPE_FIELD && value->expression.next->vtype == TYPE_VECTOR)
|
||||
assignop = INSTR_STORE_V;
|
||||
|
||||
subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from));
|
||||
subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false));
|
||||
if (!subscript)
|
||||
return NULL;
|
||||
|
||||
|
@ -4312,7 +4345,7 @@ static ast_expression *array_field_setter_node(
|
|||
if (value->expression.vtype == TYPE_FIELD && value->expression.next->vtype == TYPE_VECTOR)
|
||||
assignop = INSTR_STOREP_V;
|
||||
|
||||
subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from));
|
||||
subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false));
|
||||
if (!subscript)
|
||||
return NULL;
|
||||
|
||||
|
@ -4375,7 +4408,7 @@ static ast_expression *array_getter_node(parser_t *parser, ast_value *array, ast
|
|||
ast_return *ret;
|
||||
ast_array_index *subscript;
|
||||
|
||||
subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from));
|
||||
subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false));
|
||||
if (!subscript)
|
||||
return NULL;
|
||||
|
||||
|
@ -4679,12 +4712,17 @@ static ast_value *parse_parameter_list(parser_t *parser, ast_value *var)
|
|||
}
|
||||
if (parser->tok == TOKEN_IDENT) {
|
||||
argcounter = util_strdup(parser_tokval(parser));
|
||||
ast_value_set_name(param, argcounter);
|
||||
if (!parser_next(parser) || parser->tok != ')') {
|
||||
parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC && param->name[0] == '<') {
|
||||
parseerror(parser, "parameter name omitted");
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5146,6 +5184,7 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
|
|||
bool wasarray = false;
|
||||
|
||||
ast_member *me[3] = { NULL, NULL, NULL };
|
||||
ast_member *last_me[3] = { NULL, NULL, NULL };
|
||||
|
||||
if (!localblock && is_static)
|
||||
parseerror(parser, "`static` qualifier is not supported in global scope");
|
||||
|
@ -5609,6 +5648,7 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
|
|||
}
|
||||
}
|
||||
}
|
||||
memcpy(last_me, me, sizeof(me));
|
||||
me[0] = me[1] = me[2] = NULL;
|
||||
cleanvar = false;
|
||||
/* Part 2.2
|
||||
|
@ -5816,15 +5856,44 @@ skipvar:
|
|||
} else {
|
||||
ast_expression *cexp;
|
||||
ast_value *cval;
|
||||
bool folded_const = false;
|
||||
|
||||
cexp = parse_expression_leave(parser, true, false, false);
|
||||
if (!cexp)
|
||||
break;
|
||||
cval = ast_istype(cexp, ast_value) ? (ast_value*)cexp : NULL;
|
||||
|
||||
if (!localblock || is_static) {
|
||||
cval = (ast_value*)cexp;
|
||||
/* deal with foldable constants: */
|
||||
if (localblock &&
|
||||
var->cvq == CV_CONST && cval && cval->hasvalue && cval->cvq == CV_CONST && !cval->isfield)
|
||||
{
|
||||
/* remove it from the current locals */
|
||||
if (isvector) {
|
||||
for (i = 0; i < 3; ++i) {
|
||||
vec_pop(parser->_locals);
|
||||
vec_pop(localblock->collect);
|
||||
}
|
||||
}
|
||||
/* do sanity checking, this function really needs refactoring */
|
||||
if (vec_last(parser->_locals) != (ast_expression*)var)
|
||||
parseerror(parser, "internal error: unexpected change in local variable handling");
|
||||
else
|
||||
vec_pop(parser->_locals);
|
||||
if (vec_last(localblock->locals) != var)
|
||||
parseerror(parser, "internal error: unexpected change in local variable handling (2)");
|
||||
else
|
||||
vec_pop(localblock->locals);
|
||||
/* push it to the to-be-generated globals */
|
||||
vec_push(parser->globals, (ast_expression*)var);
|
||||
if (isvector)
|
||||
for (i = 0; i < 3; ++i)
|
||||
vec_push(parser->globals, (ast_expression*)last_me[i]);
|
||||
folded_const = true;
|
||||
}
|
||||
|
||||
if (folded_const || !localblock || is_static) {
|
||||
if (cval != parser->nil &&
|
||||
(!ast_istype(cval, ast_value) || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield))
|
||||
(!cval || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield))
|
||||
)
|
||||
{
|
||||
parseerror(parser, "initializer is non constant");
|
||||
|
@ -5872,6 +5941,13 @@ skipvar:
|
|||
vec_free(sy.argc);
|
||||
var->cvq = cvq;
|
||||
}
|
||||
/* a constant initialized to an inexact value should be marked inexact:
|
||||
* const float x = <inexact>; should propagate the inexact flag
|
||||
*/
|
||||
if (var->cvq == CV_CONST && var->expression.vtype == TYPE_FLOAT) {
|
||||
if (cval && cval->hasvalue && cval->cvq == CV_CONST)
|
||||
var->inexact = cval->inexact;
|
||||
}
|
||||
}
|
||||
|
||||
another:
|
||||
|
@ -5944,7 +6020,7 @@ static bool parser_global_statement(parser_t *parser)
|
|||
{
|
||||
if (cvq == CV_WRONG)
|
||||
return false;
|
||||
return parse_variable(parser, NULL, true, cvq, NULL, noref, is_static, qflags, vstring);
|
||||
return parse_variable(parser, NULL, false, cvq, NULL, noref, is_static, qflags, vstring);
|
||||
}
|
||||
else if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "enum"))
|
||||
{
|
||||
|
|
2
parser.h
2
parser.h
|
@ -127,7 +127,7 @@ ast_expression *parser_find_global(parser_t *parser, const char *name);
|
|||
/* fold.c */
|
||||
fold_t *fold_init (parser_t *);
|
||||
void fold_cleanup (fold_t *);
|
||||
ast_expression *fold_constgen_float (fold_t *, qcfloat_t);
|
||||
ast_expression *fold_constgen_float (fold_t *, qcfloat_t, bool);
|
||||
ast_expression *fold_constgen_vector(fold_t *, vec3_t);
|
||||
ast_expression *fold_constgen_string(fold_t *, const char *, bool);
|
||||
bool fold_generate (fold_t *, ir_builder *);
|
||||
|
|
6
stat.c
6
stat.c
|
@ -141,7 +141,7 @@ void *stat_mem_allocate(size_t size, size_t line, const char *file, const char *
|
|||
info->expr = expr;
|
||||
info->prev = NULL;
|
||||
info->next = stat_mem_block_root;
|
||||
|
||||
|
||||
/* Write identifier */
|
||||
memcpy(info + 1, IDENT_MEM, IDENT_SIZE);
|
||||
|
||||
|
@ -167,7 +167,7 @@ void *stat_mem_allocate(size_t size, size_t line, const char *file, const char *
|
|||
void stat_mem_deallocate(void *ptr, size_t line, const char *file) {
|
||||
stat_mem_block_t *info = NULL;
|
||||
char *ident = (char *)ptr - IDENT_SIZE;
|
||||
|
||||
|
||||
if (GMQCC_UNLIKELY(!ptr))
|
||||
return;
|
||||
|
||||
|
@ -176,7 +176,7 @@ void stat_mem_deallocate(void *ptr, size_t line, const char *file) {
|
|||
if (!strcmp(ident, IDENT_VEC)) {
|
||||
vector_t *vec = (vector_t*)((char *)ptr - IDENT_VEC_TOP);
|
||||
stat_mem_block_t *block = (stat_mem_block_t*)((char *)vec - IDENT_MEM_TOP);
|
||||
|
||||
|
||||
VALGRIND_MAKE_MEM_DEFINED(block, sizeof(stat_mem_block_t));
|
||||
con_err("internal warning: invalid use of mem_d:\n");
|
||||
con_err("internal warning: vector (used elements: %u, allocated elements: %u)\n",
|
||||
|
|
11
test.c
11
test.c
|
@ -806,19 +806,21 @@ static bool task_propagate(const char *curdir, size_t *pad, const char *defs) {
|
|||
} else {
|
||||
/* Preprocessing (qcflags mean shit all here we don't allow them) */
|
||||
if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
|
||||
util_snprintf(buf, sizeof(buf), "%s -E %s/%s -o %s",
|
||||
util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s -o %s",
|
||||
task_bins[TASK_COMPILE],
|
||||
directories[i],
|
||||
tmpl->sourcefile,
|
||||
tmpl->compileflags,
|
||||
tmpl->tempfilename
|
||||
);
|
||||
} else {
|
||||
util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s/%s -o %s",
|
||||
util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s/%s %s -o %s",
|
||||
task_bins[TASK_COMPILE],
|
||||
curdir,
|
||||
defs,
|
||||
directories[i],
|
||||
tmpl->sourcefile,
|
||||
tmpl->compileflags,
|
||||
tmpl->tempfilename
|
||||
);
|
||||
}
|
||||
|
@ -1100,6 +1102,7 @@ static size_t task_schedualize(size_t *pad) {
|
|||
size_t i = 0;
|
||||
size_t j = 0;
|
||||
size_t failed = 0;
|
||||
int status = 0;
|
||||
|
||||
util_snprintf(space[0], sizeof(space[0]), "%d", (int)vec_size(task_tasks));
|
||||
|
||||
|
@ -1167,7 +1170,9 @@ static size_t task_schedualize(size_t *pad) {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (task_pclose(task_tasks[i].runhandles) != EXIT_SUCCESS && strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) {
|
||||
status = task_pclose(task_tasks[i].runhandles);
|
||||
if ((!strcmp(task_tasks[i].tmpl->proceduretype, "-fail") && status == EXIT_SUCCESS)
|
||||
|| ( strcmp(task_tasks[i].tmpl->proceduretype, "-fail") && status == EXIT_FAILURE)) {
|
||||
con_out("failure: `%s` %*s %*s\n",
|
||||
task_tasks[i].tmpl->description,
|
||||
(pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
|
||||
|
|
13
tests/arithexcept.qc
Normal file
13
tests/arithexcept.qc
Normal file
|
@ -0,0 +1,13 @@
|
|||
const float huge = 340282346638528859811704183484516925440.000000; // FLT_MAX
|
||||
|
||||
#ifdef DIVBYZERO
|
||||
const float a = 1.0 / 0.0;
|
||||
#endif
|
||||
|
||||
#ifdef OVERFLOW
|
||||
const float a = huge * huge;
|
||||
#endif
|
||||
|
||||
#ifdef UNDERFLOW
|
||||
const float a = 1 / huge;
|
||||
#endif
|
4
tests/arithexcept.tmpl
Normal file
4
tests/arithexcept.tmpl
Normal file
|
@ -0,0 +1,4 @@
|
|||
I: arithexcept.qc
|
||||
D: arithmetic exceptions (divide by zero)
|
||||
T: -fail
|
||||
C: -std=fteqcc -farithmetic-exceptions -DDIVBYZERO
|
4
tests/arithexcept_of.tmpl
Normal file
4
tests/arithexcept_of.tmpl
Normal file
|
@ -0,0 +1,4 @@
|
|||
I: arithexcept.qc
|
||||
D: arithmetic exceptions (overflow)
|
||||
T: -fail
|
||||
C: -std=fteqcc -farithmetic-exceptions -DOVERFLOW
|
4
tests/arithexcept_uf.tmpl
Normal file
4
tests/arithexcept_uf.tmpl
Normal file
|
@ -0,0 +1,4 @@
|
|||
I: arithexcept.qc
|
||||
D: arithmetic exceptions (underflow)
|
||||
T: -fail
|
||||
C: -std=fteqcc -farithmetic-exceptions -DUNDERFLOW
|
|
@ -3,18 +3,18 @@
|
|||
// builtins. I no event shall you even consider adding
|
||||
// these individually per test.
|
||||
|
||||
void (string, ...) print = #1;
|
||||
string (float) ftos = #2;
|
||||
entity () spawn = #3;
|
||||
void (entity) kill = #4;
|
||||
string (vector) vtos = #5;
|
||||
void (string) error = #6;
|
||||
float (vector) vlen = #7;
|
||||
string (entity) etos = #8;
|
||||
float (string) stof = #9;
|
||||
string (...) strcat = #10;
|
||||
float (string, string) strcmp = #11;
|
||||
vector (vector) normalize = #12;
|
||||
float (float) sqrt = #13;
|
||||
float (float) floor = #14;
|
||||
float (float, float) pow = #15;
|
||||
void (string str, ...) print = #1;
|
||||
string (float val) ftos = #2;
|
||||
entity () spawn = #3;
|
||||
void (entity ent) kill = #4;
|
||||
string (vector vec) vtos = #5;
|
||||
void (string str) error = #6;
|
||||
float (vector vec) vlen = #7;
|
||||
string (entity ent) etos = #8;
|
||||
float (string str) stof = #9;
|
||||
string (...) strcat = #10;
|
||||
float (string str1, string str2) strcmp = #11;
|
||||
vector (vector vec) normalize = #12;
|
||||
float (float val) sqrt = #13;
|
||||
float (float val) floor = #14;
|
||||
float (float val1, float val2) pow = #15;
|
||||
|
|
7
tests/inexact-local.qc
Normal file
7
tests/inexact-local.qc
Normal file
|
@ -0,0 +1,7 @@
|
|||
void main() {
|
||||
const float a = 1.0 / 3.0;
|
||||
const float b = 0.33333333333;
|
||||
if (a == b) {
|
||||
// Should trigger warning
|
||||
}
|
||||
}
|
4
tests/inexact-local.tmpl
Normal file
4
tests/inexact-local.tmpl
Normal file
|
@ -0,0 +1,4 @@
|
|||
I: inexact-local.qc
|
||||
D: inexact comparisons
|
||||
T: -fail
|
||||
C: -std=gmqcc -Winexact-compares -Wall -Werror
|
8
tests/inexact.qc
Normal file
8
tests/inexact.qc
Normal file
|
@ -0,0 +1,8 @@
|
|||
const float a = 1.0 / 3.0;
|
||||
const float b = 0.33333333333;
|
||||
|
||||
void main() {
|
||||
if (a == b) {
|
||||
// Should trigger warning
|
||||
}
|
||||
}
|
4
tests/inexact.tmpl
Normal file
4
tests/inexact.tmpl
Normal file
|
@ -0,0 +1,4 @@
|
|||
I: inexact.qc
|
||||
D: inexact comparisons
|
||||
T: -fail
|
||||
C: -std=gmqcc -Winexact-compares -Wall -Werror
|
23
tests/length.qc
Normal file
23
tests/length.qc
Normal file
|
@ -0,0 +1,23 @@
|
|||
const string a = "hello world"; // 11
|
||||
float b[] = { 1, 2, 3 }; // 3
|
||||
float c[5] = { 5, 4, 3, 2, 1 }; // 5
|
||||
const float d[] = { 1 }; // 1
|
||||
|
||||
void main() {
|
||||
print(ftos(_length a), "\n"); // 11
|
||||
print(ftos(_length b), "\n"); // 3
|
||||
print(ftos(_length c), "\n"); // 5
|
||||
print(ftos(_length d), "\n"); // 1
|
||||
|
||||
static float al = _length(a);
|
||||
static float bl = _length(b);
|
||||
static float cl = _length(c);
|
||||
static float dl = _length(d);
|
||||
|
||||
print(ftos(al), "\n"); // 11
|
||||
print(ftos(bl), "\n"); // 3
|
||||
print(ftos(cl), "\n"); // 5
|
||||
print(ftos(dl), "\n"); // 1
|
||||
|
||||
print(ftos(_length "hello world"), "\n"); // 11
|
||||
}
|
13
tests/length.tmpl
Normal file
13
tests/length.tmpl
Normal file
|
@ -0,0 +1,13 @@
|
|||
I: length.qc
|
||||
D: length operator
|
||||
T: -execute
|
||||
C: -std=gmqcc
|
||||
M: 11
|
||||
M: 3
|
||||
M: 5
|
||||
M: 1
|
||||
M: 11
|
||||
M: 3
|
||||
M: 5
|
||||
M: 1
|
||||
M: 11
|
|
@ -3,8 +3,7 @@
|
|||
#endif
|
||||
|
||||
void (...) print = #1;
|
||||
string (float) ftos = #2;
|
||||
|
||||
string (float val) ftos = #2;
|
||||
|
||||
NORETURN void error(...) = #6;
|
||||
|
||||
|
|
3
tests/paramomit.qc
Normal file
3
tests/paramomit.qc
Normal file
|
@ -0,0 +1,3 @@
|
|||
void foo(float) {
|
||||
|
||||
}
|
4
tests/paramomit.tmpl
Normal file
4
tests/paramomit.tmpl
Normal file
|
@ -0,0 +1,4 @@
|
|||
I: paramomit.qc
|
||||
D: test parameter omit
|
||||
T: -fail
|
||||
C: -std=fteqcc
|
|
@ -1,7 +1,7 @@
|
|||
var float foo = 0;
|
||||
|
||||
void funcall() {}
|
||||
void bar(string) {}
|
||||
void bar(string str) {}
|
||||
|
||||
void main(string str) {
|
||||
string pl;
|
||||
|
|
6
tests/ppindirectexpand.qc
Normal file
6
tests/ppindirectexpand.qc
Normal file
|
@ -0,0 +1,6 @@
|
|||
#define STR1(x) #x
|
||||
#define STR2(x) STR1(x)
|
||||
#define THE_ANSWER 42
|
||||
#define THE_ANSWER_STR STR2(THE_ANSWER)
|
||||
|
||||
THE_ANSWER_STR
|
7
tests/ppindirectexpand.tmpl
Normal file
7
tests/ppindirectexpand.tmpl
Normal file
|
@ -0,0 +1,7 @@
|
|||
I: ppindirectexpand.qc
|
||||
D: test preprocessor indirect macro expansion
|
||||
T: -pp
|
||||
C: -fftepp-indirect-expansion -std=gmqcc
|
||||
F: -no-defs
|
||||
M: "42"
|
||||
|
6
tests/split-vectors.qc
Normal file
6
tests/split-vectors.qc
Normal file
|
@ -0,0 +1,6 @@
|
|||
void main() {
|
||||
print(vtos('1 2 3'), "\n");
|
||||
print(vtos('4 5 6'), "\n");
|
||||
print(vtos('7 8 9'), "\n");
|
||||
print(vtos('1 5 9'), "\n");
|
||||
}
|
8
tests/split-vectors.tmpl
Normal file
8
tests/split-vectors.tmpl
Normal file
|
@ -0,0 +1,8 @@
|
|||
I: split-vectors.qc
|
||||
D: test -fsplit-vector-parameters
|
||||
T: -execute
|
||||
C: -std=fteqcc -Wall -Werror -fsplit-vector-parameters
|
||||
M: '1 2 3'
|
||||
M: '4 5 6'
|
||||
M: '7 8 9'
|
||||
M: '1 5 9'
|
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)
|
|
@ -1,5 +1,5 @@
|
|||
typedef void(...) ptype;
|
||||
typedef string(float) funcsf;
|
||||
typedef void(...) ptype;
|
||||
typedef string(float a) funcsf;
|
||||
|
||||
ptype print = #1;
|
||||
funcsf ftos = #2;
|
||||
|
|
Loading…
Reference in a new issue