Merge branch 'cooking'

Conflicts:
	test.c
This commit is contained in:
Dale Weiler 2014-12-10 02:43:42 -05:00
commit ac8c7d730a
48 changed files with 1963 additions and 333 deletions

View file

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

View file

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

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

@ -75,6 +75,7 @@ static void ast_binstore_delete(ast_binstore*);
static bool ast_binstore_codegen(ast_binstore*, ast_function*, bool lvalue, ir_value**);
static void ast_binary_delete(ast_binary*);
static bool ast_binary_codegen(ast_binary*, ast_function*, bool lvalue, ir_value**);
static bool ast_state_codegen(ast_state*, ast_function*, bool lvalue, ir_value**);
/* It must not be possible to get here. */
static GMQCC_NORETURN void _ast_node_destroy(ast_node *self)
@ -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
View file

@ -54,6 +54,7 @@ typedef struct ast_switch_s ast_switch;
typedef struct ast_label_s ast_label;
typedef struct ast_goto_s ast_goto;
typedef struct ast_argpipe_s ast_argpipe;
typedef struct ast_state_s ast_state;
enum {
AST_FLAG_VARIADIC = 1 << 0,
@ -111,7 +112,8 @@ enum {
TYPE_ast_switch, /* 18 */
TYPE_ast_label, /* 19 */
TYPE_ast_goto, /* 20 */
TYPE_ast_argpipe /* 21 */
TYPE_ast_argpipe, /* 21 */
TYPE_ast_state /* 22 */
};
#define ast_istype(x, t) ( ((ast_node*)x)->nodetype == (TYPE_##t) )
@ -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.

View file

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

@ -55,8 +55,16 @@ qc_program_t* prog_load(const char *filename, bool skipversion)
{
prog_header_t header;
qc_program_t *prog;
size_t i;
fs_file_t *file = fs_file_open(filename, "rb");
/* we need all those in order to support INSTR_STATE: */
bool has_self = false,
has_time = false,
has_think = false,
has_nextthink = false,
has_frame = false;
if (!file)
return NULL;
@ -137,6 +145,36 @@ qc_program_t* prog_load(const char *filename, bool skipversion)
memset(vec_add(prog->entitydata, prog->entityfields), 0, prog->entityfields * sizeof(prog->entitydata[0]));
prog->entities = 1;
/* cache some globals and fields from names */
for (i = 0; i < vec_size(prog->defs); ++i) {
const char *name = prog_getstring(prog, prog->defs[i].name);
if (!strcmp(name, "self")) {
prog->cached_globals.self = prog->defs[i].offset;
has_self = true;
}
else if (!strcmp(name, "time")) {
prog->cached_globals.time = prog->defs[i].offset;
has_time = true;
}
}
for (i = 0; i < vec_size(prog->fields); ++i) {
const char *name = prog_getstring(prog, prog->fields[i].name);
if (!strcmp(name, "think")) {
prog->cached_fields.think = prog->fields[i].offset;
has_think = true;
}
else if (!strcmp(name, "nextthink")) {
prog->cached_fields.nextthink = prog->fields[i].offset;
has_nextthink = true;
}
else if (!strcmp(name, "frame")) {
prog->cached_fields.frame = prog->fields[i].offset;
has_frame = true;
}
}
if (has_self && has_time && has_think && has_nextthink && has_frame)
prog->supports_state = true;
return prog;
error:
@ -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++ */

921
fold.c

File diff suppressed because it is too large Load diff

63
ftepp.c
View file

@ -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, &params[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
View file

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

View file

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

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

View file

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

View file

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

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

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

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

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

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

87
opts.c
View file

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

View file

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

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

View file

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

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

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

@ -0,0 +1,4 @@
I: arithexcept.qc
D: arithmetic exceptions (divide by zero)
T: -fail
C: -std=fteqcc -farithmetic-exceptions -DDIVBYZERO

View file

@ -0,0 +1,4 @@
I: arithexcept.qc
D: arithmetic exceptions (overflow)
T: -fail
C: -std=fteqcc -farithmetic-exceptions -DOVERFLOW

View file

@ -0,0 +1,4 @@
I: arithexcept.qc
D: arithmetic exceptions (underflow)
T: -fail
C: -std=fteqcc -farithmetic-exceptions -DUNDERFLOW

View file

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

View file

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

@ -0,0 +1,3 @@
void foo(float) {
}

4
tests/paramomit.tmpl Normal file
View file

@ -0,0 +1,4 @@
I: paramomit.qc
D: test parameter omit
T: -fail
C: -std=fteqcc

View file

@ -1,7 +1,7 @@
var float foo = 0;
void funcall() {}
void bar(string) {}
void bar(string str) {}
void main(string str) {
string pl;

View 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

View 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
View 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
View 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
View file

@ -0,0 +1,9 @@
I: state.qc
D: test emulated state ops
T: -execute
C: -std=gmqcc -femulate-state
M: st1, .frame=1, .nextthink=10.1 (now: 10)
M: st2, .frame=2, .nextthink=11.1 (now: 11)
M: st3, .frame=0, .nextthink=12.1 (now: 12)
M: st1, .frame=1, .nextthink=13.1 (now: 13)
M: st2, .frame=2, .nextthink=14.1 (now: 14)

39
tests/state.qc Normal file
View file

@ -0,0 +1,39 @@
float time;
entity self;
.void() think;
.float nextthink;
.float frame;
void stprint(string fun) {
print(fun,
", .frame=", ftos(self.frame),
", .nextthink=", ftos(self.nextthink),
" (now: ", ftos(time), ")\n");
}
void st1() = [1, st2] { stprint("st1"); }
void st2() = [2, st3] { stprint("st2"); }
void st3() = [0, st1] { stprint("st3"); }
void main() {
entity ea = spawn();
entity eb = spawn();
time = 10;
self = ea;
self.think = st1;
self.nextthink = time;
self.frame = 100;
self.think();
time = 11;
self.think();
time = 12;
self.think();
time = 13;
self.think();
time = 14;
self.think();
};

9
tests/state.tmpl Normal file
View file

@ -0,0 +1,9 @@
I: state.qc
D: test state ops
T: -execute
C: -std=gmqcc
M: st1, .frame=1, .nextthink=10.1 (now: 10)
M: st2, .frame=2, .nextthink=11.1 (now: 11)
M: st3, .frame=0, .nextthink=12.1 (now: 12)
M: st1, .frame=1, .nextthink=13.1 (now: 13)
M: st2, .frame=2, .nextthink=14.1 (now: 14)

View file

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