From: Dale Weiler Date: Thu, 15 Jan 2015 02:48:47 +0000 (-0500) Subject: .c -> .cpp X-Git-Tag: xonotic-v0.8.2~56 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=65362d93aa4678209bfeeba92fb5aa41f5955777;p=xonotic%2Fgmqcc.git .c -> .cpp --- diff --git a/Makefile b/Makefile index 91bedc6..89d8805 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,48 @@ -CC ?= clang -CFLAGS = -MD -std=gnu99 -Wall -Wextra -pedantic-errors -g3 -LDFLAGS = -lm +CXX ?= clang++ +CXXFLAGS = \ + -std=c++11 \ + -Wall \ + -Wextra \ + -ffast-math \ + -fno-exceptions \ + -fno-rtti \ + -MD -CSRCS = ast.c code.c conout.c fold.c ftepp.c intrin.c ir.c lexer.c main.c opts.c parser.c stat.c utf8.c util.c -TSRCS = conout.c opts.c stat.c test.c util.c -VSRCS = exec.c stat.c util.c +CSRCS = \ + ast.cpp \ + code.cpp \ + conout.cpp \ + fold.cpp \ + ftepp.cpp \ + intrin.cpp \ + ir.cpp \ + lexer.cpp \ + main.cpp \ + opts.cpp \ + parser.cpp \ + stat.cpp \ + utf8.cpp \ + util.cpp -COBJS = $(CSRCS:.c=.o) -TOBJS = $(TSRCS:.c=.o) -VOBJS = $(VSRCS:.c=.o) +TSRCS = \ + conout.cpp \ + opts.cpp \ + stat.cpp \ + test.cpp \ + util.cpp -CDEPS = $(CSRCS:.c=.d) -TDEPS = $(TSRCS:.c=.d) -VDEPS = $(VSRCS:.c=.d) +VSRCS = \ + exec.cpp \ + stat.cpp \ + util.cpp + +COBJS = $(CSRCS:.cpp=.o) +TOBJS = $(TSRCS:.cpp=.o) +VOBJS = $(VSRCS:.cpp=.o) + +CDEPS = $(CSRCS:.cpp=.d) +TDEPS = $(TSRCS:.cpp=.d) +VDEPS = $(VSRCS:.cpp=.d) CBIN = gmqcc TBIN = testsuite @@ -21,16 +51,16 @@ VBIN = qcvm all: $(CBIN) $(TBIN) $(VBIN) $(CBIN): $(COBJS) - $(CC) $(COBJS) $(LDFLAGS) -o $@ + $(CXX) $(COBJS) -o $@ $(TBIN): $(TOBJS) - $(CC) $(TOBJS) $(LDFLAGS) -o $@ + $(CXX) $(TOBJS) -o $@ $(VBIN): $(VOBJS) - $(CC) $(VOBJS) $(LDFLAGS) -o $@ + $(CXX) $(VOBJS) -o $@ -.c.o: - $(CC) -c $(CFLAGS) $< -o $@ +.cpp.o: + $(CXX) -c $(CXXFLAGS) $< -o $@ test: $(CBIN) $(TBIN) $(VBIN) @./$(TBIN) diff --git a/ast.c b/ast.c deleted file mode 100644 index 29e298a..0000000 --- a/ast.c +++ /dev/null @@ -1,3463 +0,0 @@ -#include -#include - -#include "gmqcc.h" -#include "ast.h" -#include "parser.h" - -#define ast_instantiate(T, ctx, destroyfn) \ - T* self = (T*)mem_a(sizeof(T)); \ - if (!self) { \ - return NULL; \ - } \ - ast_node_init((ast_node*)self, ctx, TYPE_##T); \ - ( (ast_node*)self )->destroy = (ast_node_delete*)destroyfn - -/* - * forward declarations, these need not be in ast.h for obvious - * static reasons. - */ -static bool ast_member_codegen(ast_member*, ast_function*, bool lvalue, ir_value**); -static void ast_array_index_delete(ast_array_index*); -static bool ast_array_index_codegen(ast_array_index*, ast_function*, bool lvalue, ir_value**); -static void ast_argpipe_delete(ast_argpipe*); -static bool ast_argpipe_codegen(ast_argpipe*, ast_function*, bool lvalue, ir_value**); -static void ast_store_delete(ast_store*); -static bool ast_store_codegen(ast_store*, ast_function*, bool lvalue, ir_value**); -static void ast_ifthen_delete(ast_ifthen*); -static bool ast_ifthen_codegen(ast_ifthen*, ast_function*, bool lvalue, ir_value**); -static void ast_ternary_delete(ast_ternary*); -static bool ast_ternary_codegen(ast_ternary*, ast_function*, bool lvalue, ir_value**); -static void ast_loop_delete(ast_loop*); -static bool ast_loop_codegen(ast_loop*, ast_function*, bool lvalue, ir_value**); -static void ast_breakcont_delete(ast_breakcont*); -static bool ast_breakcont_codegen(ast_breakcont*, ast_function*, bool lvalue, ir_value**); -static void ast_switch_delete(ast_switch*); -static bool ast_switch_codegen(ast_switch*, ast_function*, bool lvalue, ir_value**); -static void ast_label_delete(ast_label*); -static void ast_label_register_goto(ast_label*, ast_goto*); -static bool ast_label_codegen(ast_label*, ast_function*, bool lvalue, ir_value**); -static bool ast_goto_codegen(ast_goto*, ast_function*, bool lvalue, ir_value**); -static void ast_goto_delete(ast_goto*); -static void ast_call_delete(ast_call*); -static bool ast_call_codegen(ast_call*, ast_function*, bool lvalue, ir_value**); -static bool ast_block_codegen(ast_block*, ast_function*, bool lvalue, ir_value**); -static void ast_unary_delete(ast_unary*); -static bool ast_unary_codegen(ast_unary*, ast_function*, bool lvalue, ir_value**); -static void ast_entfield_delete(ast_entfield*); -static bool ast_entfield_codegen(ast_entfield*, ast_function*, bool lvalue, ir_value**); -static void ast_return_delete(ast_return*); -static bool ast_return_codegen(ast_return*, ast_function*, bool lvalue, ir_value**); -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) -{ - (void)self; - con_err("ast node missing destroy()\n"); - exit(EXIT_FAILURE); -} - -/* Initialize main ast node aprts */ -static void ast_node_init(ast_node *self, lex_ctx_t ctx, int nodetype) -{ - self->context = ctx; - self->destroy = &_ast_node_destroy; - self->keep = false; - self->nodetype = nodetype; - self->side_effects = false; -} - -/* weight and side effects */ -static void _ast_propagate_effects(ast_node *self, ast_node *other) -{ - if (ast_side_effects(other)) - ast_side_effects(self) = true; -} -#define ast_propagate_effects(s,o) _ast_propagate_effects(((ast_node*)(s)), ((ast_node*)(o))) - -/* General expression initialization */ -static void ast_expression_init(ast_expression *self, - ast_expression_codegen *codegen) -{ - self->codegen = codegen; - self->vtype = TYPE_VOID; - self->next = NULL; - self->outl = NULL; - self->outr = NULL; - self->params = NULL; - self->count = 0; - self->varparam = NULL; - self->flags = 0; - if (OPTS_OPTION_BOOL(OPTION_COVERAGE)) - self->flags |= AST_FLAG_BLOCK_COVERAGE; -} - -static void ast_expression_delete(ast_expression *self) -{ - size_t i; - if (self->next) - ast_delete(self->next); - for (i = 0; i < vec_size(self->params); ++i) { - ast_delete(self->params[i]); - } - vec_free(self->params); - if (self->varparam) - ast_delete(self->varparam); -} - -static void ast_expression_delete_full(ast_expression *self) -{ - ast_expression_delete(self); - mem_d(self); -} - -ast_value* ast_value_copy(const ast_value *self) -{ - size_t i; - const ast_expression *fromex; - ast_expression *selfex; - ast_value *cp = ast_value_new(self->expression.node.context, self->name, self->expression.vtype); - if (self->expression.next) { - cp->expression.next = ast_type_copy(self->expression.node.context, self->expression.next); - } - fromex = &self->expression; - selfex = &cp->expression; - selfex->count = fromex->count; - selfex->flags = fromex->flags; - for (i = 0; i < vec_size(fromex->params); ++i) { - ast_value *v = ast_value_copy(fromex->params[i]); - vec_push(selfex->params, v); - } - return cp; -} - -void ast_type_adopt_impl(ast_expression *self, const ast_expression *other) -{ - size_t i; - const ast_expression *fromex; - ast_expression *selfex; - self->vtype = other->vtype; - if (other->next) { - self->next = (ast_expression*)ast_type_copy(ast_ctx(self), other->next); - } - fromex = other; - selfex = self; - selfex->count = fromex->count; - selfex->flags = fromex->flags; - for (i = 0; i < vec_size(fromex->params); ++i) { - ast_value *v = ast_value_copy(fromex->params[i]); - vec_push(selfex->params, v); - } -} - -static ast_expression* ast_shallow_type(lex_ctx_t ctx, int vtype) -{ - ast_instantiate(ast_expression, ctx, ast_expression_delete_full); - ast_expression_init(self, NULL); - self->codegen = NULL; - self->next = NULL; - self->vtype = vtype; - return self; -} - -ast_expression* ast_type_copy(lex_ctx_t ctx, const ast_expression *ex) -{ - size_t i; - const ast_expression *fromex; - ast_expression *selfex; - - if (!ex) - return NULL; - else - { - ast_instantiate(ast_expression, ctx, ast_expression_delete_full); - ast_expression_init(self, NULL); - - fromex = ex; - selfex = self; - - /* This may never be codegen()d */ - selfex->codegen = NULL; - - selfex->vtype = fromex->vtype; - if (fromex->next) - selfex->next = ast_type_copy(ctx, fromex->next); - else - selfex->next = NULL; - - selfex->count = fromex->count; - selfex->flags = fromex->flags; - for (i = 0; i < vec_size(fromex->params); ++i) { - ast_value *v = ast_value_copy(fromex->params[i]); - vec_push(selfex->params, v); - } - - return self; - } -} - -bool ast_compare_type(ast_expression *a, ast_expression *b) -{ - if (a->vtype == TYPE_NIL || - b->vtype == TYPE_NIL) - return true; - if (a->vtype != b->vtype) - return false; - if (!a->next != !b->next) - return false; - if (vec_size(a->params) != vec_size(b->params)) - return false; - if ((a->flags & AST_FLAG_TYPE_MASK) != - (b->flags & AST_FLAG_TYPE_MASK) ) - { - return false; - } - if (vec_size(a->params)) { - size_t i; - for (i = 0; i < vec_size(a->params); ++i) { - if (!ast_compare_type((ast_expression*)a->params[i], - (ast_expression*)b->params[i])) - return false; - } - } - if (a->next) - return ast_compare_type(a->next, b->next); - return true; -} - -static size_t ast_type_to_string_impl(ast_expression *e, char *buf, size_t bufsize, size_t pos) -{ - const char *typestr; - size_t typelen; - size_t i; - - if (!e) { - if (pos + 6 >= bufsize) - goto full; - util_strncpy(buf + pos, "(null)", 6); - return pos + 6; - } - - if (pos + 1 >= bufsize) - goto full; - - switch (e->vtype) { - case TYPE_VARIANT: - util_strncpy(buf + pos, "(variant)", 9); - return pos + 9; - - case TYPE_FIELD: - buf[pos++] = '.'; - return ast_type_to_string_impl(e->next, buf, bufsize, pos); - - case TYPE_POINTER: - if (pos + 3 >= bufsize) - goto full; - buf[pos++] = '*'; - buf[pos++] = '('; - pos = ast_type_to_string_impl(e->next, buf, bufsize, pos); - if (pos + 1 >= bufsize) - goto full; - buf[pos++] = ')'; - return pos; - - case TYPE_FUNCTION: - pos = ast_type_to_string_impl(e->next, buf, bufsize, pos); - if (pos + 2 >= bufsize) - goto full; - if (!vec_size(e->params)) { - buf[pos++] = '('; - buf[pos++] = ')'; - return pos; - } - buf[pos++] = '('; - pos = ast_type_to_string_impl((ast_expression*)(e->params[0]), buf, bufsize, pos); - for (i = 1; i < vec_size(e->params); ++i) { - if (pos + 2 >= bufsize) - goto full; - buf[pos++] = ','; - buf[pos++] = ' '; - pos = ast_type_to_string_impl((ast_expression*)(e->params[i]), buf, bufsize, pos); - } - if (pos + 1 >= bufsize) - goto full; - buf[pos++] = ')'; - return pos; - - case TYPE_ARRAY: - pos = ast_type_to_string_impl(e->next, buf, bufsize, pos); - if (pos + 1 >= bufsize) - goto full; - buf[pos++] = '['; - pos += util_snprintf(buf + pos, bufsize - pos - 1, "%i", (int)e->count); - if (pos + 1 >= bufsize) - goto full; - buf[pos++] = ']'; - return pos; - - default: - typestr = type_name[e->vtype]; - typelen = strlen(typestr); - if (pos + typelen >= bufsize) - goto full; - util_strncpy(buf + pos, typestr, typelen); - return pos + typelen; - } - -full: - buf[bufsize-3] = '.'; - buf[bufsize-2] = '.'; - buf[bufsize-1] = '.'; - return bufsize; -} - -void ast_type_to_string(ast_expression *e, char *buf, size_t bufsize) -{ - size_t pos = ast_type_to_string_impl(e, buf, bufsize-1, 0); - buf[pos] = 0; -} - -static bool ast_value_codegen(ast_value *self, ast_function *func, bool lvalue, ir_value **out); -ast_value* ast_value_new(lex_ctx_t ctx, const char *name, int t) -{ - ast_instantiate(ast_value, ctx, ast_value_delete); - ast_expression_init((ast_expression*)self, - (ast_expression_codegen*)&ast_value_codegen); - self->expression.node.keep = true; /* keep */ - - self->name = name ? util_strdup(name) : NULL; - self->expression.vtype = t; - self->expression.next = NULL; - self->isfield = false; - 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; - - self->ir_v = NULL; - self->ir_values = NULL; - self->ir_value_count = 0; - - self->setter = NULL; - self->getter = NULL; - self->desc = NULL; - - self->argcounter = NULL; - self->intrinsic = false; - - return self; -} - -void ast_value_delete(ast_value* self) -{ - if (self->name) - mem_d((void*)self->name); - if (self->argcounter) - mem_d((void*)self->argcounter); - if (self->hasvalue) { - switch (self->expression.vtype) - { - case TYPE_STRING: - mem_d((void*)self->constval.vstring); - break; - case TYPE_FUNCTION: - /* unlink us from the function node */ - self->constval.vfunc->vtype = NULL; - break; - /* NOTE: delete function? currently collected in - * the parser structure - */ - default: - break; - } - } - if (self->ir_values) - mem_d(self->ir_values); - - if (self->desc) - mem_d(self->desc); - - if (self->initlist) { - if (self->expression.next->vtype == TYPE_STRING) { - /* strings are allocated, free them */ - size_t i, len = vec_size(self->initlist); - /* in theory, len should be expression.count - * but let's not take any chances */ - for (i = 0; i < len; ++i) { - if (self->initlist[i].vstring) - mem_d(self->initlist[i].vstring); - } - } - vec_free(self->initlist); - } - - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -void ast_value_params_add(ast_value *self, ast_value *p) -{ - vec_push(self->expression.params, p); -} - -bool ast_value_set_name(ast_value *self, const char *name) -{ - if (self->name) - mem_d((void*)self->name); - self->name = util_strdup(name); - return !!self->name; -} - -ast_binary* ast_binary_new(lex_ctx_t ctx, int op, - ast_expression* left, ast_expression* right) -{ - ast_instantiate(ast_binary, ctx, ast_binary_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_binary_codegen); - - if (ast_istype(right, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) { - ast_unary *unary = ((ast_unary*)right); - ast_expression *normal = unary->operand; - - /* make a-(-b) => a + b */ - if (unary->op == VINSTR_NEG_F || unary->op == VINSTR_NEG_V) { - if (op == INSTR_SUB_F) { - op = INSTR_ADD_F; - right = normal; - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - } else if (op == INSTR_SUB_V) { - op = INSTR_ADD_V; - right = normal; - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - } - } - } - - self->op = op; - self->left = left; - self->right = right; - self->right_first = false; - - ast_propagate_effects(self, left); - ast_propagate_effects(self, right); - - if (op >= INSTR_EQ_F && op <= INSTR_GT) - self->expression.vtype = TYPE_FLOAT; - else if (op == INSTR_AND || op == INSTR_OR) { - if (OPTS_FLAG(PERL_LOGIC)) - ast_type_adopt(self, right); - else - self->expression.vtype = TYPE_FLOAT; - } - else if (op == INSTR_BITAND || op == INSTR_BITOR) - self->expression.vtype = TYPE_FLOAT; - else if (op == INSTR_MUL_VF || op == INSTR_MUL_FV) - self->expression.vtype = TYPE_VECTOR; - else if (op == INSTR_MUL_V) - self->expression.vtype = TYPE_FLOAT; - else - self->expression.vtype = left->vtype; - - /* references all */ - self->refs = AST_REF_ALL; - - return self; -} - -void ast_binary_delete(ast_binary *self) -{ - if (self->refs & AST_REF_LEFT) ast_unref(self->left); - if (self->refs & AST_REF_RIGHT) ast_unref(self->right); - - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_binstore* ast_binstore_new(lex_ctx_t ctx, int storop, int op, - ast_expression* left, ast_expression* right) -{ - ast_instantiate(ast_binstore, ctx, ast_binstore_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_binstore_codegen); - - ast_side_effects(self) = true; - - self->opstore = storop; - self->opbin = op; - self->dest = left; - self->source = right; - - self->keep_dest = false; - - ast_type_adopt(self, left); - return self; -} - -void ast_binstore_delete(ast_binstore *self) -{ - if (!self->keep_dest) - ast_unref(self->dest); - ast_unref(self->source); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_unary* ast_unary_new(lex_ctx_t ctx, int op, - ast_expression *expr) -{ - ast_instantiate(ast_unary, ctx, ast_unary_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_unary_codegen); - - self->op = op; - self->operand = expr; - - - if (ast_istype(expr, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) { - ast_unary *prev = (ast_unary*)((ast_unary*)expr)->operand; - - /* Handle for double negation */ - if (((ast_unary*)expr)->op == op) - prev = (ast_unary*)((ast_unary*)expr)->operand; - - if (ast_istype(prev, ast_unary)) { - ast_expression_delete((ast_expression*)self); - mem_d(self); - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - return prev; - } - } - - ast_propagate_effects(self, expr); - - if ((op >= INSTR_NOT_F && op <= INSTR_NOT_FNC) || op == VINSTR_NEG_F) { - self->expression.vtype = TYPE_FLOAT; - } else if (op == VINSTR_NEG_V) { - self->expression.vtype = TYPE_VECTOR; - } else { - compile_error(ctx, "cannot determine type of unary operation %s", util_instr_str[op]); - } - - return self; -} - -void ast_unary_delete(ast_unary *self) -{ - if (self->operand) ast_unref(self->operand); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_return* ast_return_new(lex_ctx_t ctx, ast_expression *expr) -{ - ast_instantiate(ast_return, ctx, ast_return_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_return_codegen); - - self->operand = expr; - - if (expr) - ast_propagate_effects(self, expr); - - return self; -} - -void ast_return_delete(ast_return *self) -{ - if (self->operand) - ast_unref(self->operand); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_entfield* ast_entfield_new(lex_ctx_t ctx, ast_expression *entity, ast_expression *field) -{ - if (field->vtype != TYPE_FIELD) { - compile_error(ctx, "ast_entfield_new with expression not of type field"); - return NULL; - } - return ast_entfield_new_force(ctx, entity, field, field->next); -} - -ast_entfield* ast_entfield_new_force(lex_ctx_t ctx, ast_expression *entity, ast_expression *field, const ast_expression *outtype) -{ - ast_instantiate(ast_entfield, ctx, ast_entfield_delete); - - if (!outtype) { - mem_d(self); - /* Error: field has no type... */ - return NULL; - } - - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_entfield_codegen); - - self->entity = entity; - self->field = field; - ast_propagate_effects(self, entity); - ast_propagate_effects(self, field); - - ast_type_adopt(self, outtype); - return self; -} - -void ast_entfield_delete(ast_entfield *self) -{ - ast_unref(self->entity); - ast_unref(self->field); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_member* ast_member_new(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const char *name) -{ - ast_instantiate(ast_member, ctx, ast_member_delete); - if (field >= 3) { - mem_d(self); - return NULL; - } - - if (owner->vtype != TYPE_VECTOR && - owner->vtype != TYPE_FIELD) { - compile_error(ctx, "member-access on an invalid owner of type %s", type_name[owner->vtype]); - mem_d(self); - return NULL; - } - - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_member_codegen); - self->expression.node.keep = true; /* keep */ - - if (owner->vtype == TYPE_VECTOR) { - self->expression.vtype = TYPE_FLOAT; - self->expression.next = NULL; - } else { - self->expression.vtype = TYPE_FIELD; - self->expression.next = ast_shallow_type(ctx, TYPE_FLOAT); - } - - self->rvalue = false; - self->owner = owner; - ast_propagate_effects(self, owner); - - self->field = field; - if (name) - self->name = util_strdup(name); - else - self->name = NULL; - - return self; -} - -void ast_member_delete(ast_member *self) -{ - /* The owner is always an ast_value, which has .keep=true, - * also: ast_members are usually deleted after the owner, thus - * this will cause invalid access - ast_unref(self->owner); - * once we allow (expression).x to access a vector-member, we need - * to change this: preferably by creating an alternate ast node for this - * purpose that is not garbage-collected. - */ - ast_expression_delete((ast_expression*)self); - mem_d(self->name); - mem_d(self); -} - -bool ast_member_set_name(ast_member *self, const char *name) -{ - if (self->name) - mem_d((void*)self->name); - self->name = util_strdup(name); - return !!self->name; -} - -ast_array_index* ast_array_index_new(lex_ctx_t ctx, ast_expression *array, ast_expression *index) -{ - ast_expression *outtype; - ast_instantiate(ast_array_index, ctx, ast_array_index_delete); - - outtype = array->next; - if (!outtype) { - mem_d(self); - /* Error: field has no type... */ - return NULL; - } - - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_array_index_codegen); - - self->array = array; - self->index = index; - ast_propagate_effects(self, array); - ast_propagate_effects(self, index); - - ast_type_adopt(self, outtype); - if (array->vtype == TYPE_FIELD && outtype->vtype == TYPE_ARRAY) { - if (self->expression.vtype != TYPE_ARRAY) { - compile_error(ast_ctx(self), "array_index node on type"); - ast_array_index_delete(self); - return NULL; - } - self->array = outtype; - self->expression.vtype = TYPE_FIELD; - } - - return self; -} - -void ast_array_index_delete(ast_array_index *self) -{ - if (self->array) - ast_unref(self->array); - if (self->index) - ast_unref(self->index); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_argpipe* ast_argpipe_new(lex_ctx_t ctx, ast_expression *index) -{ - ast_instantiate(ast_argpipe, ctx, ast_argpipe_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_argpipe_codegen); - self->index = index; - self->expression.vtype = TYPE_NOEXPR; - return self; -} - -void ast_argpipe_delete(ast_argpipe *self) -{ - if (self->index) - ast_unref(self->index); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_ifthen* ast_ifthen_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse) -{ - ast_instantiate(ast_ifthen, ctx, ast_ifthen_delete); - if (!ontrue && !onfalse) { - /* because it is invalid */ - mem_d(self); - return NULL; - } - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_ifthen_codegen); - - self->cond = cond; - self->on_true = ontrue; - self->on_false = onfalse; - ast_propagate_effects(self, cond); - if (ontrue) - ast_propagate_effects(self, ontrue); - if (onfalse) - ast_propagate_effects(self, onfalse); - - return self; -} - -void ast_ifthen_delete(ast_ifthen *self) -{ - ast_unref(self->cond); - if (self->on_true) - ast_unref(self->on_true); - if (self->on_false) - ast_unref(self->on_false); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_ternary* ast_ternary_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse) -{ - ast_expression *exprtype = ontrue; - ast_instantiate(ast_ternary, ctx, ast_ternary_delete); - /* This time NEITHER must be NULL */ - if (!ontrue || !onfalse) { - mem_d(self); - return NULL; - } - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_ternary_codegen); - - self->cond = cond; - self->on_true = ontrue; - self->on_false = onfalse; - ast_propagate_effects(self, cond); - ast_propagate_effects(self, ontrue); - ast_propagate_effects(self, onfalse); - - if (ontrue->vtype == TYPE_NIL) - exprtype = onfalse; - ast_type_adopt(self, exprtype); - - return self; -} - -void ast_ternary_delete(ast_ternary *self) -{ - /* the if()s are only there because computed-gotos can set them - * to NULL - */ - if (self->cond) ast_unref(self->cond); - if (self->on_true) ast_unref(self->on_true); - if (self->on_false) ast_unref(self->on_false); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_loop* ast_loop_new(lex_ctx_t ctx, - ast_expression *initexpr, - ast_expression *precond, bool pre_not, - ast_expression *postcond, bool post_not, - ast_expression *increment, - ast_expression *body) -{ - ast_instantiate(ast_loop, ctx, ast_loop_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_loop_codegen); - - self->initexpr = initexpr; - self->precond = precond; - self->postcond = postcond; - self->increment = increment; - self->body = body; - - self->pre_not = pre_not; - self->post_not = post_not; - - if (initexpr) - ast_propagate_effects(self, initexpr); - if (precond) - ast_propagate_effects(self, precond); - if (postcond) - ast_propagate_effects(self, postcond); - if (increment) - ast_propagate_effects(self, increment); - if (body) - ast_propagate_effects(self, body); - - return self; -} - -void ast_loop_delete(ast_loop *self) -{ - if (self->initexpr) - ast_unref(self->initexpr); - if (self->precond) - ast_unref(self->precond); - if (self->postcond) - ast_unref(self->postcond); - if (self->increment) - ast_unref(self->increment); - if (self->body) - ast_unref(self->body); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_breakcont* ast_breakcont_new(lex_ctx_t ctx, bool iscont, unsigned int levels) -{ - ast_instantiate(ast_breakcont, ctx, ast_breakcont_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_breakcont_codegen); - - self->is_continue = iscont; - self->levels = levels; - - return self; -} - -void ast_breakcont_delete(ast_breakcont *self) -{ - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_switch* ast_switch_new(lex_ctx_t ctx, ast_expression *op) -{ - ast_instantiate(ast_switch, ctx, ast_switch_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_switch_codegen); - - self->operand = op; - self->cases = NULL; - - ast_propagate_effects(self, op); - - return self; -} - -void ast_switch_delete(ast_switch *self) -{ - size_t i; - ast_unref(self->operand); - - for (i = 0; i < vec_size(self->cases); ++i) { - if (self->cases[i].value) - ast_unref(self->cases[i].value); - ast_unref(self->cases[i].code); - } - vec_free(self->cases); - - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_label* ast_label_new(lex_ctx_t ctx, const char *name, bool undefined) -{ - ast_instantiate(ast_label, ctx, ast_label_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_label_codegen); - - self->expression.vtype = TYPE_NOEXPR; - - self->name = util_strdup(name); - self->irblock = NULL; - self->gotos = NULL; - self->undefined = undefined; - - return self; -} - -void ast_label_delete(ast_label *self) -{ - mem_d((void*)self->name); - vec_free(self->gotos); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -static void ast_label_register_goto(ast_label *self, ast_goto *g) -{ - vec_push(self->gotos, g); -} - -ast_goto* ast_goto_new(lex_ctx_t ctx, const char *name) -{ - ast_instantiate(ast_goto, ctx, ast_goto_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_goto_codegen); - - self->name = util_strdup(name); - self->target = NULL; - self->irblock_from = NULL; - - return self; -} - -void ast_goto_delete(ast_goto *self) -{ - mem_d((void*)self->name); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -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) -{ - ast_instantiate(ast_call, ctx, ast_call_delete); - if (!funcexpr->next) { - compile_error(ctx, "not a function"); - mem_d(self); - return NULL; - } - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_call_codegen); - - ast_side_effects(self) = true; - - self->params = NULL; - self->func = funcexpr; - self->va_count = NULL; - - ast_type_adopt(self, funcexpr->next); - - return self; -} - -void ast_call_delete(ast_call *self) -{ - size_t i; - for (i = 0; i < vec_size(self->params); ++i) - ast_unref(self->params[i]); - vec_free(self->params); - - if (self->func) - ast_unref(self->func); - - if (self->va_count) - ast_unref(self->va_count); - - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -static bool ast_call_check_vararg(ast_call *self, ast_expression *va_type, ast_expression *exp_type) -{ - char texp[1024]; - char tgot[1024]; - if (!exp_type) - return true; - if (!va_type || !ast_compare_type(va_type, exp_type)) - { - if (va_type && exp_type) - { - ast_type_to_string(va_type, tgot, sizeof(tgot)); - ast_type_to_string(exp_type, texp, sizeof(texp)); - if (OPTS_FLAG(UNSAFE_VARARGS)) { - if (compile_warning(ast_ctx(self), WARN_UNSAFE_TYPES, - "piped variadic argument differs in type: constrained to type %s, expected type %s", - tgot, texp)) - return false; - } else { - compile_error(ast_ctx(self), - "piped variadic argument differs in type: constrained to type %s, expected type %s", - tgot, texp); - return false; - } - } - else - { - ast_type_to_string(exp_type, texp, sizeof(texp)); - if (OPTS_FLAG(UNSAFE_VARARGS)) { - if (compile_warning(ast_ctx(self), WARN_UNSAFE_TYPES, - "piped variadic argument may differ in type: expected type %s", - texp)) - return false; - } else { - compile_error(ast_ctx(self), - "piped variadic argument may differ in type: expected type %s", - texp); - return false; - } - } - } - return true; -} - -bool ast_call_check_types(ast_call *self, ast_expression *va_type) -{ - char texp[1024]; - char tgot[1024]; - size_t i; - bool retval = true; - const ast_expression *func = self->func; - size_t count = vec_size(self->params); - if (count > vec_size(func->params)) - count = vec_size(func->params); - - for (i = 0; i < count; ++i) { - if (ast_istype(self->params[i], ast_argpipe)) { - /* warn about type safety instead */ - if (i+1 != count) { - compile_error(ast_ctx(self), "argpipe must be the last parameter to a function call"); - return false; - } - if (!ast_call_check_vararg(self, va_type, (ast_expression*)func->params[i])) - retval = false; - } - else if (!ast_compare_type(self->params[i], (ast_expression*)(func->params[i]))) - { - ast_type_to_string(self->params[i], tgot, sizeof(tgot)); - ast_type_to_string((ast_expression*)func->params[i], texp, sizeof(texp)); - compile_error(ast_ctx(self), "invalid type for parameter %u in function call: expected %s, got %s", - (unsigned int)(i+1), texp, tgot); - /* we don't immediately return */ - retval = false; - } - } - count = vec_size(self->params); - if (count > vec_size(func->params) && func->varparam) { - for (; i < count; ++i) { - if (ast_istype(self->params[i], ast_argpipe)) { - /* warn about type safety instead */ - if (i+1 != count) { - compile_error(ast_ctx(self), "argpipe must be the last parameter to a function call"); - return false; - } - if (!ast_call_check_vararg(self, va_type, func->varparam)) - retval = false; - } - else if (!ast_compare_type(self->params[i], func->varparam)) - { - ast_type_to_string(self->params[i], tgot, sizeof(tgot)); - ast_type_to_string(func->varparam, texp, sizeof(texp)); - compile_error(ast_ctx(self), "invalid type for variadic parameter %u in function call: expected %s, got %s", - (unsigned int)(i+1), texp, tgot); - /* we don't immediately return */ - retval = false; - } - } - } - return retval; -} - -ast_store* ast_store_new(lex_ctx_t ctx, int op, - ast_expression *dest, ast_expression *source) -{ - ast_instantiate(ast_store, ctx, ast_store_delete); - ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_store_codegen); - - ast_side_effects(self) = true; - - self->op = op; - self->dest = dest; - self->source = source; - - ast_type_adopt(self, dest); - - return self; -} - -void ast_store_delete(ast_store *self) -{ - ast_unref(self->dest); - ast_unref(self->source); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -ast_block* ast_block_new(lex_ctx_t ctx) -{ - ast_instantiate(ast_block, ctx, ast_block_delete); - ast_expression_init((ast_expression*)self, - (ast_expression_codegen*)&ast_block_codegen); - - self->locals = NULL; - self->exprs = NULL; - self->collect = NULL; - - return self; -} - -bool ast_block_add_expr(ast_block *self, ast_expression *e) -{ - ast_propagate_effects(self, e); - vec_push(self->exprs, e); - if (self->expression.next) { - ast_delete(self->expression.next); - self->expression.next = NULL; - } - ast_type_adopt(self, e); - return true; -} - -void ast_block_collect(ast_block *self, ast_expression *expr) -{ - vec_push(self->collect, expr); - expr->node.keep = true; -} - -void ast_block_delete(ast_block *self) -{ - size_t i; - for (i = 0; i < vec_size(self->exprs); ++i) - ast_unref(self->exprs[i]); - vec_free(self->exprs); - for (i = 0; i < vec_size(self->locals); ++i) - ast_delete(self->locals[i]); - vec_free(self->locals); - for (i = 0; i < vec_size(self->collect); ++i) - ast_delete(self->collect[i]); - vec_free(self->collect); - ast_expression_delete((ast_expression*)self); - mem_d(self); -} - -void ast_block_set_type(ast_block *self, ast_expression *from) -{ - if (self->expression.next) - ast_delete(self->expression.next); - ast_type_adopt(self, from); -} - -ast_function* ast_function_new(lex_ctx_t ctx, const char *name, ast_value *vtype) -{ - ast_instantiate(ast_function, ctx, ast_function_delete); - - if (!vtype) { - compile_error(ast_ctx(self), "internal error: ast_function_new condition 0"); - goto cleanup; - } else if (vtype->hasvalue || vtype->expression.vtype != TYPE_FUNCTION) { - compile_error(ast_ctx(self), "internal error: ast_function_new condition %i %i type=%i (probably 2 bodies?)", - (int)!vtype, - (int)vtype->hasvalue, - vtype->expression.vtype); - goto cleanup; - } - - self->vtype = vtype; - self->name = name ? util_strdup(name) : NULL; - self->blocks = NULL; - - self->labelcount = 0; - self->builtin = 0; - - self->ir_func = NULL; - self->curblock = NULL; - - self->breakblocks = NULL; - self->continueblocks = NULL; - - vtype->hasvalue = true; - vtype->constval.vfunc = self; - - self->varargs = NULL; - self->argc = NULL; - self->fixedparams = NULL; - self->return_value = NULL; - - self->static_names = NULL; - self->static_count = 0; - - return self; - -cleanup: - mem_d(self); - return NULL; -} - -void ast_function_delete(ast_function *self) -{ - size_t i; - if (self->name) - mem_d((void*)self->name); - if (self->vtype) { - /* ast_value_delete(self->vtype); */ - self->vtype->hasvalue = false; - self->vtype->constval.vfunc = NULL; - /* We use unref - if it was stored in a global table it is supposed - * to be deleted from *there* - */ - ast_unref(self->vtype); - } - for (i = 0; i < vec_size(self->static_names); ++i) - mem_d(self->static_names[i]); - vec_free(self->static_names); - for (i = 0; i < vec_size(self->blocks); ++i) - ast_delete(self->blocks[i]); - vec_free(self->blocks); - vec_free(self->breakblocks); - vec_free(self->continueblocks); - if (self->varargs) - ast_delete(self->varargs); - if (self->argc) - ast_delete(self->argc); - if (self->fixedparams) - ast_unref(self->fixedparams); - if (self->return_value) - ast_unref(self->return_value); - mem_d(self); -} - -const char* ast_function_label(ast_function *self, const char *prefix) -{ - size_t id; - size_t len; - char *from; - - if (!OPTS_OPTION_BOOL(OPTION_DUMP) && - !OPTS_OPTION_BOOL(OPTION_DUMPFIN) && - !OPTS_OPTION_BOOL(OPTION_DEBUG)) - { - return NULL; - } - - id = (self->labelcount++); - len = strlen(prefix); - - from = self->labelbuf + sizeof(self->labelbuf)-1; - *from-- = 0; - do { - *from-- = (id%10) + '0'; - id /= 10; - } while (id); - ++from; - memcpy(from - len, prefix, len); - return from - len; -} - -/*********************************************************************/ -/* AST codegen part - * by convention you must never pass NULL to the 'ir_value **out' - * parameter. If you really don't care about the output, pass a dummy. - * But I can't imagine a pituation where the output is truly unnecessary. - */ - -static void _ast_codegen_output_type(ast_expression *self, ir_value *out) -{ - if (out->vtype == TYPE_FIELD) - out->fieldtype = self->next->vtype; - if (out->vtype == TYPE_FUNCTION) - out->outtype = self->next->vtype; -} - -#define codegen_output_type(a,o) (_ast_codegen_output_type(&((a)->expression),(o))) - -bool ast_value_codegen(ast_value *self, ast_function *func, bool lvalue, ir_value **out) -{ - (void)func; - (void)lvalue; - if (self->expression.vtype == TYPE_NIL) { - *out = func->ir_func->owner->nil; - return true; - } - /* NOTE: This is the codegen for a variable used in an expression. - * It is not the codegen to generate the value. For this purpose, - * ast_local_codegen and ast_global_codegen are to be used before this - * is executed. ast_function_codegen should take care of its locals, - * and the ast-user should take care of ast_global_codegen to be used - * on all the globals. - */ - if (!self->ir_v) { - char tname[1024]; /* typename is reserved in C++ */ - ast_type_to_string((ast_expression*)self, tname, sizeof(tname)); - compile_error(ast_ctx(self), "ast_value used before generated %s %s", tname, self->name); - return false; - } - *out = self->ir_v; - return true; -} - -static bool ast_global_array_set(ast_value *self) -{ - size_t count = vec_size(self->initlist); - size_t i; - - if (count > self->expression.count) { - compile_error(ast_ctx(self), "too many elements in initializer"); - count = self->expression.count; - } - else if (count < self->expression.count) { - /* add this? - compile_warning(ast_ctx(self), "not all elements are initialized"); - */ - } - - for (i = 0; i != count; ++i) { - switch (self->expression.next->vtype) { - case TYPE_FLOAT: - if (!ir_value_set_float(self->ir_values[i], self->initlist[i].vfloat)) - return false; - break; - case TYPE_VECTOR: - if (!ir_value_set_vector(self->ir_values[i], self->initlist[i].vvec)) - return false; - break; - case TYPE_STRING: - if (!ir_value_set_string(self->ir_values[i], self->initlist[i].vstring)) - return false; - break; - case TYPE_ARRAY: - /* we don't support them in any other place yet either */ - compile_error(ast_ctx(self), "TODO: nested arrays"); - return false; - case TYPE_FUNCTION: - /* this requiers a bit more work - similar to the fields I suppose */ - compile_error(ast_ctx(self), "global of type function not properly generated"); - return false; - case TYPE_FIELD: - if (!self->initlist[i].vfield) { - compile_error(ast_ctx(self), "field constant without vfield set"); - return false; - } - if (!self->initlist[i].vfield->ir_v) { - compile_error(ast_ctx(self), "field constant generated before its field"); - return false; - } - if (!ir_value_set_field(self->ir_values[i], self->initlist[i].vfield->ir_v)) - return false; - break; - default: - compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype); - break; - } - } - return true; -} - -static bool check_array(ast_value *self, ast_value *array) -{ - if (array->expression.flags & AST_FLAG_ARRAY_INIT && !array->initlist) { - compile_error(ast_ctx(self), "array without size: %s", self->name); - return false; - } - /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */ - if (!array->expression.count || array->expression.count > OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE)) { - compile_error(ast_ctx(self), "Invalid array of size %lu", (unsigned long)array->expression.count); - return false; - } - return true; -} - -bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield) -{ - ir_value *v = NULL; - - if (self->expression.vtype == TYPE_NIL) { - compile_error(ast_ctx(self), "internal error: trying to generate a variable of TYPE_NIL"); - return false; - } - - if (self->hasvalue && self->expression.vtype == TYPE_FUNCTION) - { - ir_function *func = ir_builder_create_function(ir, self->name, self->expression.next->vtype); - if (!func) - return false; - func->context = ast_ctx(self); - func->value->context = ast_ctx(self); - - self->constval.vfunc->ir_func = func; - self->ir_v = func->value; - 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_ERASABLE; - if (self->expression.flags & AST_FLAG_BLOCK_COVERAGE) - func->flags |= IR_FLAG_BLOCK_COVERAGE; - /* The function is filled later on ast_function_codegen... */ - return true; - } - - if (isfield && self->expression.vtype == TYPE_FIELD) { - ast_expression *fieldtype = self->expression.next; - - if (self->hasvalue) { - compile_error(ast_ctx(self), "TODO: constant field pointers with value"); - goto error; - } - - if (fieldtype->vtype == TYPE_ARRAY) { - size_t ai; - char *name; - size_t namelen; - - ast_expression *elemtype; - int vtype; - ast_value *array = (ast_value*)fieldtype; - - if (!ast_istype(fieldtype, ast_value)) { - compile_error(ast_ctx(self), "internal error: ast_value required"); - return false; - } - - if (!check_array(self, array)) - return false; - - elemtype = array->expression.next; - vtype = elemtype->vtype; - - v = ir_builder_create_field(ir, self->name, vtype); - if (!v) { - compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", self->name); - return false; - } - v->context = ast_ctx(self); - v->unique_life = true; - v->locked = true; - array->ir_v = self->ir_v = v; - - 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_ERASABLE; - - namelen = strlen(self->name); - name = (char*)mem_a(namelen + 16); - util_strncpy(name, self->name, namelen); - - array->ir_values = (ir_value**)mem_a(sizeof(array->ir_values[0]) * array->expression.count); - array->ir_values[0] = v; - for (ai = 1; ai < array->expression.count; ++ai) { - util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai); - array->ir_values[ai] = ir_builder_create_field(ir, name, vtype); - if (!array->ir_values[ai]) { - mem_d(name); - compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", name); - return false; - } - array->ir_values[ai]->context = ast_ctx(self); - array->ir_values[ai]->unique_life = true; - array->ir_values[ai]->locked = true; - if (self->expression.flags & AST_FLAG_INCLUDE_DEF) - self->ir_values[ai]->flags |= IR_FLAG_INCLUDE_DEF; - } - mem_d(name); - } - else - { - v = ir_builder_create_field(ir, self->name, self->expression.next->vtype); - if (!v) - return false; - v->context = ast_ctx(self); - self->ir_v = v; - 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_ERASABLE; - } - return true; - } - - if (self->expression.vtype == TYPE_ARRAY) { - size_t ai; - char *name; - size_t namelen; - - ast_expression *elemtype = self->expression.next; - int vtype = elemtype->vtype; - - if (self->expression.flags & AST_FLAG_ARRAY_INIT && !self->expression.count) { - compile_error(ast_ctx(self), "array `%s' has no size", self->name); - return false; - } - - /* same as with field arrays */ - if (!check_array(self, self)) - return false; - - v = ir_builder_create_global(ir, self->name, vtype); - if (!v) { - compile_error(ast_ctx(self), "ir_builder_create_global failed `%s`", self->name); - return false; - } - v->context = ast_ctx(self); - v->unique_life = true; - v->locked = true; - - 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_ERASABLE; - - namelen = strlen(self->name); - name = (char*)mem_a(namelen + 16); - util_strncpy(name, self->name, namelen); - - self->ir_values = (ir_value**)mem_a(sizeof(self->ir_values[0]) * self->expression.count); - self->ir_values[0] = v; - for (ai = 1; ai < self->expression.count; ++ai) { - util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai); - self->ir_values[ai] = ir_builder_create_global(ir, name, vtype); - if (!self->ir_values[ai]) { - mem_d(name); - compile_error(ast_ctx(self), "ir_builder_create_global failed `%s`", name); - return false; - } - self->ir_values[ai]->context = ast_ctx(self); - self->ir_values[ai]->unique_life = true; - self->ir_values[ai]->locked = true; - if (self->expression.flags & AST_FLAG_INCLUDE_DEF) - self->ir_values[ai]->flags |= IR_FLAG_INCLUDE_DEF; - } - mem_d(name); - } - else - { - /* Arrays don't do this since there's no "array" value which spans across the - * whole thing. - */ - v = ir_builder_create_global(ir, self->name, self->expression.vtype); - if (!v) { - compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", self->name); - return false; - } - codegen_output_type(self, v); - v->context = ast_ctx(self); - } - - /* link us to the ir_value */ - v->cvq = self->cvq; - self->ir_v = v; - - 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_ERASABLE; - - /* initialize */ - if (self->hasvalue) { - switch (self->expression.vtype) - { - case TYPE_FLOAT: - if (!ir_value_set_float(v, self->constval.vfloat)) - goto error; - break; - case TYPE_VECTOR: - if (!ir_value_set_vector(v, self->constval.vvec)) - goto error; - break; - case TYPE_STRING: - if (!ir_value_set_string(v, self->constval.vstring)) - goto error; - break; - case TYPE_ARRAY: - ast_global_array_set(self); - break; - case TYPE_FUNCTION: - compile_error(ast_ctx(self), "global of type function not properly generated"); - goto error; - /* Cannot generate an IR value for a function, - * need a pointer pointing to a function rather. - */ - case TYPE_FIELD: - if (!self->constval.vfield) { - compile_error(ast_ctx(self), "field constant without vfield set"); - goto error; - } - if (!self->constval.vfield->ir_v) { - compile_error(ast_ctx(self), "field constant generated before its field"); - goto error; - } - if (!ir_value_set_field(v, self->constval.vfield->ir_v)) - goto error; - break; - default: - compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype); - break; - } - } - return true; - -error: /* clean up */ - if(v) ir_value_delete(v); - return false; -} - -static bool ast_local_codegen(ast_value *self, ir_function *func, bool param) -{ - ir_value *v = NULL; - - if (self->expression.vtype == TYPE_NIL) { - compile_error(ast_ctx(self), "internal error: trying to generate a variable of TYPE_NIL"); - return false; - } - - if (self->hasvalue && self->expression.vtype == TYPE_FUNCTION) - { - /* Do we allow local functions? I think not... - * this is NOT a function pointer atm. - */ - return false; - } - - if (self->expression.vtype == TYPE_ARRAY) { - size_t ai; - char *name; - size_t namelen; - - ast_expression *elemtype = self->expression.next; - int vtype = elemtype->vtype; - - func->flags |= IR_FLAG_HAS_ARRAYS; - - if (param && !(self->expression.flags & AST_FLAG_IS_VARARG)) { - compile_error(ast_ctx(self), "array-parameters are not supported"); - return false; - } - - /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */ - if (!check_array(self, self)) - return false; - - self->ir_values = (ir_value**)mem_a(sizeof(self->ir_values[0]) * self->expression.count); - if (!self->ir_values) { - compile_error(ast_ctx(self), "failed to allocate array values"); - return false; - } - - v = ir_function_create_local(func, self->name, vtype, param); - if (!v) { - compile_error(ast_ctx(self), "internal error: ir_function_create_local failed"); - return false; - } - v->context = ast_ctx(self); - v->unique_life = true; - v->locked = true; - - namelen = strlen(self->name); - name = (char*)mem_a(namelen + 16); - util_strncpy(name, self->name, namelen); - - self->ir_values[0] = v; - for (ai = 1; ai < self->expression.count; ++ai) { - util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai); - self->ir_values[ai] = ir_function_create_local(func, name, vtype, param); - if (!self->ir_values[ai]) { - compile_error(ast_ctx(self), "internal_error: ir_builder_create_global failed on `%s`", name); - return false; - } - self->ir_values[ai]->context = ast_ctx(self); - self->ir_values[ai]->unique_life = true; - self->ir_values[ai]->locked = true; - } - mem_d(name); - } - else - { - v = ir_function_create_local(func, self->name, self->expression.vtype, param); - if (!v) - return false; - codegen_output_type(self, v); - v->context = ast_ctx(self); - } - - /* A constant local... hmmm... - * I suppose the IR will have to deal with this - */ - if (self->hasvalue) { - switch (self->expression.vtype) - { - case TYPE_FLOAT: - if (!ir_value_set_float(v, self->constval.vfloat)) - goto error; - break; - case TYPE_VECTOR: - if (!ir_value_set_vector(v, self->constval.vvec)) - goto error; - break; - case TYPE_STRING: - if (!ir_value_set_string(v, self->constval.vstring)) - goto error; - break; - default: - compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype); - break; - } - } - - /* link us to the ir_value */ - v->cvq = self->cvq; - self->ir_v = v; - - if (!ast_generate_accessors(self, func->owner)) - return false; - return true; - -error: /* clean up */ - ir_value_delete(v); - return false; -} - -bool ast_generate_accessors(ast_value *self, ir_builder *ir) -{ - size_t i; - bool warn = OPTS_WARN(WARN_USED_UNINITIALIZED); - if (!self->setter || !self->getter) - return true; - for (i = 0; i < self->expression.count; ++i) { - if (!self->ir_values) { - compile_error(ast_ctx(self), "internal error: no array values generated for `%s`", self->name); - return false; - } - if (!self->ir_values[i]) { - compile_error(ast_ctx(self), "internal error: not all array values have been generated for `%s`", self->name); - return false; - } - if (self->ir_values[i]->life) { - compile_error(ast_ctx(self), "internal error: function containing `%s` already generated", self->name); - return false; - } - } - - opts_set(opts.warn, WARN_USED_UNINITIALIZED, false); - if (self->setter) { - if (!ast_global_codegen (self->setter, ir, false) || - !ast_function_codegen(self->setter->constval.vfunc, ir) || - !ir_function_finalize(self->setter->constval.vfunc->ir_func)) - { - compile_error(ast_ctx(self), "internal error: failed to generate setter for `%s`", self->name); - opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn); - return false; - } - } - if (self->getter) { - if (!ast_global_codegen (self->getter, ir, false) || - !ast_function_codegen(self->getter->constval.vfunc, ir) || - !ir_function_finalize(self->getter->constval.vfunc->ir_func)) - { - compile_error(ast_ctx(self), "internal error: failed to generate getter for `%s`", self->name); - opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn); - return false; - } - } - for (i = 0; i < self->expression.count; ++i) { - vec_free(self->ir_values[i]->life); - } - opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn); - return true; -} - -bool ast_function_codegen(ast_function *self, ir_builder *ir) -{ - ir_function *irf; - ir_value *dummy; - ast_expression *ec; - ast_expression_codegen *cgen; - - size_t i; - - (void)ir; - - irf = self->ir_func; - if (!irf) { - compile_error(ast_ctx(self), "internal error: ast_function's related ast_value was not generated yet"); - return false; - } - - /* fill the parameter list */ - ec = &self->vtype->expression; - for (i = 0; i < vec_size(ec->params); ++i) - { - if (ec->params[i]->expression.vtype == TYPE_FIELD) - vec_push(irf->params, ec->params[i]->expression.next->vtype); - else - vec_push(irf->params, ec->params[i]->expression.vtype); - if (!self->builtin) { - if (!ast_local_codegen(ec->params[i], self->ir_func, true)) - return false; - } - } - - if (self->varargs) { - if (!ast_local_codegen(self->varargs, self->ir_func, true)) - return false; - irf->max_varargs = self->varargs->expression.count; - } - - if (self->builtin) { - irf->builtin = self->builtin; - return true; - } - - /* have a local return value variable? */ - if (self->return_value) { - if (!ast_local_codegen(self->return_value, self->ir_func, false)) - return false; - } - - if (!vec_size(self->blocks)) { - compile_error(ast_ctx(self), "function `%s` has no body", self->name); - return false; - } - - irf->first = self->curblock = ir_function_create_block(ast_ctx(self), irf, "entry"); - if (!self->curblock) { - compile_error(ast_ctx(self), "failed to allocate entry block for `%s`", self->name); - return false; - } - - if (self->argc) { - ir_value *va_count; - ir_value *fixed; - ir_value *sub; - if (!ast_local_codegen(self->argc, self->ir_func, true)) - return false; - cgen = self->argc->expression.codegen; - if (!(*cgen)((ast_expression*)(self->argc), self, false, &va_count)) - return false; - cgen = self->fixedparams->expression.codegen; - if (!(*cgen)((ast_expression*)(self->fixedparams), self, false, &fixed)) - return false; - sub = ir_block_create_binop(self->curblock, ast_ctx(self), - ast_function_label(self, "va_count"), INSTR_SUB_F, - ir_builder_get_va_count(ir), fixed); - if (!sub) - return false; - if (!ir_block_create_store_op(self->curblock, ast_ctx(self), INSTR_STORE_F, - va_count, sub)) - { - return false; - } - } - - for (i = 0; i < vec_size(self->blocks); ++i) { - cgen = self->blocks[i]->expression.codegen; - if (!(*cgen)((ast_expression*)self->blocks[i], self, false, &dummy)) - return false; - } - - /* TODO: check return types */ - if (!self->curblock->final) - { - if (!self->vtype->expression.next || - self->vtype->expression.next->vtype == TYPE_VOID) - { - return ir_block_create_return(self->curblock, ast_ctx(self), NULL); - } - else if (vec_size(self->curblock->entries) || self->curblock == irf->first) - { - if (self->return_value) { - cgen = self->return_value->expression.codegen; - if (!(*cgen)((ast_expression*)(self->return_value), self, false, &dummy)) - return false; - return ir_block_create_return(self->curblock, ast_ctx(self), dummy); - } - else if (compile_warning(ast_ctx(self), WARN_MISSING_RETURN_VALUES, - "control reaches end of non-void function (`%s`) via %s", - self->name, self->curblock->label)) - { - return false; - } - return ir_block_create_return(self->curblock, ast_ctx(self), NULL); - } - } - return true; -} - -static bool starts_a_label(ast_expression *ex) -{ - while (ex && ast_istype(ex, ast_block)) { - ast_block *b = (ast_block*)ex; - ex = b->exprs[0]; - } - if (!ex) - return false; - return ast_istype(ex, ast_label); -} - -/* Note, you will not see ast_block_codegen generate ir_blocks. - * To the AST and the IR, blocks are 2 different things. - * In the AST it represents a block of code, usually enclosed in - * curly braces {...}. - * While in the IR it represents a block in terms of control-flow. - */ -bool ast_block_codegen(ast_block *self, ast_function *func, bool lvalue, ir_value **out) -{ - size_t i; - - /* We don't use this - * Note: an ast-representation using the comma-operator - * of the form: (a, b, c) = x should not assign to c... - */ - if (lvalue) { - compile_error(ast_ctx(self), "not an l-value (code-block)"); - return false; - } - - if (self->expression.outr) { - *out = self->expression.outr; - return true; - } - - /* output is NULL at first, we'll have each expression - * assign to out output, thus, a comma-operator represention - * using an ast_block will return the last generated value, - * so: (b, c) + a executed both b and c, and returns c, - * which is then added to a. - */ - *out = NULL; - - /* generate locals */ - for (i = 0; i < vec_size(self->locals); ++i) - { - if (!ast_local_codegen(self->locals[i], func->ir_func, false)) { - if (OPTS_OPTION_BOOL(OPTION_DEBUG)) - compile_error(ast_ctx(self), "failed to generate local `%s`", self->locals[i]->name); - return false; - } - } - - for (i = 0; i < vec_size(self->exprs); ++i) - { - ast_expression_codegen *gen; - if (func->curblock->final && !starts_a_label(self->exprs[i])) { - if (compile_warning(ast_ctx(self->exprs[i]), WARN_UNREACHABLE_CODE, "unreachable statement")) - return false; - continue; - } - gen = self->exprs[i]->codegen; - if (!(*gen)(self->exprs[i], func, false, out)) - return false; - } - - self->expression.outr = *out; - - return true; -} - -bool ast_store_codegen(ast_store *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *left = NULL; - ir_value *right = NULL; - - ast_value *arr; - ast_value *idx = 0; - ast_array_index *ai = NULL; - - if (lvalue && self->expression.outl) { - *out = self->expression.outl; - return true; - } - - if (!lvalue && self->expression.outr) { - *out = self->expression.outr; - return true; - } - - if (ast_istype(self->dest, ast_array_index)) - { - - ai = (ast_array_index*)self->dest; - idx = (ast_value*)ai->index; - - if (ast_istype(ai->index, ast_value) && idx->hasvalue && idx->cvq == CV_CONST) - ai = NULL; - } - - if (ai) { - /* we need to call the setter */ - ir_value *iridx, *funval; - ir_instr *call; - - if (lvalue) { - compile_error(ast_ctx(self), "array-subscript assignment cannot produce lvalues"); - return false; - } - - arr = (ast_value*)ai->array; - if (!ast_istype(ai->array, ast_value) || !arr->setter) { - compile_error(ast_ctx(self), "value has no setter (%s)", arr->name); - return false; - } - - cgen = idx->expression.codegen; - if (!(*cgen)((ast_expression*)(idx), func, false, &iridx)) - return false; - - cgen = arr->setter->expression.codegen; - if (!(*cgen)((ast_expression*)(arr->setter), func, true, &funval)) - return false; - - cgen = self->source->codegen; - if (!(*cgen)((ast_expression*)(self->source), func, false, &right)) - return false; - - call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "store"), funval, false); - if (!call) - return false; - ir_call_param(call, iridx); - ir_call_param(call, right); - self->expression.outr = right; - } - else - { - /* regular code */ - - cgen = self->dest->codegen; - /* lvalue! */ - if (!(*cgen)((ast_expression*)(self->dest), func, true, &left)) - return false; - self->expression.outl = left; - - cgen = self->source->codegen; - /* rvalue! */ - if (!(*cgen)((ast_expression*)(self->source), func, false, &right)) - return false; - - if (!ir_block_create_store_op(func->curblock, ast_ctx(self), self->op, left, right)) - return false; - self->expression.outr = right; - } - - /* Theoretically, an assinment returns its left side as an - * lvalue, if we don't need an lvalue though, we return - * the right side as an rvalue, otherwise we have to - * somehow know whether or not we need to dereference the pointer - * on the left side - that is: OP_LOAD if it was an address. - * Also: in original QC we cannot OP_LOADP *anyway*. - */ - *out = (lvalue ? left : right); - - return true; -} - -bool ast_binary_codegen(ast_binary *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *left, *right; - - /* A binary operation cannot yield an l-value */ - if (lvalue) { - compile_error(ast_ctx(self), "not an l-value (binop)"); - return false; - } - - if (self->expression.outr) { - *out = self->expression.outr; - return true; - } - - if ((OPTS_FLAG(SHORT_LOGIC) || OPTS_FLAG(PERL_LOGIC)) && - (self->op == INSTR_AND || self->op == INSTR_OR)) - { - /* NOTE: The short-logic path will ignore right_first */ - - /* short circuit evaluation */ - ir_block *other, *merge; - ir_block *from_left, *from_right; - ir_instr *phi; - size_t merge_id; - - /* prepare end-block */ - merge_id = vec_size(func->ir_func->blocks); - merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "sce_merge")); - - /* generate the left expression */ - cgen = self->left->codegen; - if (!(*cgen)((ast_expression*)(self->left), func, false, &left)) - return false; - /* remember the block */ - from_left = func->curblock; - - /* create a new block for the right expression */ - other = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "sce_other")); - if (self->op == INSTR_AND) { - /* on AND: left==true -> other */ - if (!ir_block_create_if(func->curblock, ast_ctx(self), left, other, merge)) - return false; - } else { - /* on OR: left==false -> other */ - if (!ir_block_create_if(func->curblock, ast_ctx(self), left, merge, other)) - return false; - } - /* use the likely flag */ - vec_last(func->curblock->instr)->likely = true; - - /* enter the right-expression's block */ - func->curblock = other; - /* generate */ - cgen = self->right->codegen; - if (!(*cgen)((ast_expression*)(self->right), func, false, &right)) - return false; - /* remember block */ - from_right = func->curblock; - - /* jump to the merge block */ - if (!ir_block_create_jump(func->curblock, ast_ctx(self), merge)) - return false; - - vec_remove(func->ir_func->blocks, merge_id, 1); - vec_push(func->ir_func->blocks, merge); - - func->curblock = merge; - phi = ir_block_create_phi(func->curblock, ast_ctx(self), - ast_function_label(func, "sce_value"), - self->expression.vtype); - ir_phi_add(phi, from_left, left); - ir_phi_add(phi, from_right, right); - *out = ir_phi_value(phi); - if (!*out) - return false; - - if (!OPTS_FLAG(PERL_LOGIC)) { - /* cast-to-bool */ - if (OPTS_FLAG(CORRECT_LOGIC) && (*out)->vtype == TYPE_VECTOR) { - *out = ir_block_create_unary(func->curblock, ast_ctx(self), - ast_function_label(func, "sce_bool_v"), - INSTR_NOT_V, *out); - if (!*out) - return false; - *out = ir_block_create_unary(func->curblock, ast_ctx(self), - ast_function_label(func, "sce_bool"), - INSTR_NOT_F, *out); - if (!*out) - return false; - } - else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && (*out)->vtype == TYPE_STRING) { - *out = ir_block_create_unary(func->curblock, ast_ctx(self), - ast_function_label(func, "sce_bool_s"), - INSTR_NOT_S, *out); - if (!*out) - return false; - *out = ir_block_create_unary(func->curblock, ast_ctx(self), - ast_function_label(func, "sce_bool"), - INSTR_NOT_F, *out); - if (!*out) - return false; - } - else { - *out = ir_block_create_binop(func->curblock, ast_ctx(self), - ast_function_label(func, "sce_bool"), - INSTR_AND, *out, *out); - if (!*out) - return false; - } - } - - self->expression.outr = *out; - codegen_output_type(self, *out); - return true; - } - - if (self->right_first) { - cgen = self->right->codegen; - if (!(*cgen)((ast_expression*)(self->right), func, false, &right)) - return false; - cgen = self->left->codegen; - if (!(*cgen)((ast_expression*)(self->left), func, false, &left)) - return false; - } else { - cgen = self->left->codegen; - if (!(*cgen)((ast_expression*)(self->left), func, false, &left)) - return false; - cgen = self->right->codegen; - if (!(*cgen)((ast_expression*)(self->right), func, false, &right)) - return false; - } - - *out = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "bin"), - self->op, left, right); - if (!*out) - return false; - self->expression.outr = *out; - codegen_output_type(self, *out); - - return true; -} - -bool ast_binstore_codegen(ast_binstore *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *leftl = NULL, *leftr, *right, *bin; - - ast_value *arr; - ast_value *idx = 0; - ast_array_index *ai = NULL; - ir_value *iridx = NULL; - - if (lvalue && self->expression.outl) { - *out = self->expression.outl; - return true; - } - - if (!lvalue && self->expression.outr) { - *out = self->expression.outr; - return true; - } - - if (ast_istype(self->dest, ast_array_index)) - { - - ai = (ast_array_index*)self->dest; - idx = (ast_value*)ai->index; - - if (ast_istype(ai->index, ast_value) && idx->hasvalue && idx->cvq == CV_CONST) - ai = NULL; - } - - /* for a binstore we need both an lvalue and an rvalue for the left side */ - /* rvalue of destination! */ - if (ai) { - cgen = idx->expression.codegen; - if (!(*cgen)((ast_expression*)(idx), func, false, &iridx)) - return false; - } - cgen = self->dest->codegen; - if (!(*cgen)((ast_expression*)(self->dest), func, false, &leftr)) - return false; - - /* source as rvalue only */ - cgen = self->source->codegen; - if (!(*cgen)((ast_expression*)(self->source), func, false, &right)) - return false; - - /* now the binary */ - bin = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "binst"), - self->opbin, leftr, right); - self->expression.outr = bin; - - - if (ai) { - /* we need to call the setter */ - ir_value *funval; - ir_instr *call; - - if (lvalue) { - compile_error(ast_ctx(self), "array-subscript assignment cannot produce lvalues"); - return false; - } - - arr = (ast_value*)ai->array; - if (!ast_istype(ai->array, ast_value) || !arr->setter) { - compile_error(ast_ctx(self), "value has no setter (%s)", arr->name); - return false; - } - - cgen = arr->setter->expression.codegen; - if (!(*cgen)((ast_expression*)(arr->setter), func, true, &funval)) - return false; - - call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "store"), funval, false); - if (!call) - return false; - ir_call_param(call, iridx); - ir_call_param(call, bin); - self->expression.outr = bin; - } else { - /* now store them */ - cgen = self->dest->codegen; - /* lvalue of destination */ - if (!(*cgen)((ast_expression*)(self->dest), func, true, &leftl)) - return false; - self->expression.outl = leftl; - - if (!ir_block_create_store_op(func->curblock, ast_ctx(self), self->opstore, leftl, bin)) - return false; - self->expression.outr = bin; - } - - /* Theoretically, an assinment returns its left side as an - * lvalue, if we don't need an lvalue though, we return - * the right side as an rvalue, otherwise we have to - * somehow know whether or not we need to dereference the pointer - * on the left side - that is: OP_LOAD if it was an address. - * Also: in original QC we cannot OP_LOADP *anyway*. - */ - *out = (lvalue ? leftl : bin); - - return true; -} - -bool ast_unary_codegen(ast_unary *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *operand; - - /* An unary operation cannot yield an l-value */ - if (lvalue) { - compile_error(ast_ctx(self), "not an l-value (binop)"); - return false; - } - - if (self->expression.outr) { - *out = self->expression.outr; - return true; - } - - cgen = self->operand->codegen; - /* lvalue! */ - if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand)) - return false; - - *out = ir_block_create_unary(func->curblock, ast_ctx(self), ast_function_label(func, "unary"), - self->op, operand); - if (!*out) - return false; - self->expression.outr = *out; - - return true; -} - -bool ast_return_codegen(ast_return *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *operand; - - *out = NULL; - - /* In the context of a return operation, we don't actually return - * anything... - */ - if (lvalue) { - compile_error(ast_ctx(self), "return-expression is not an l-value"); - return false; - } - - if (self->expression.outr) { - compile_error(ast_ctx(self), "internal error: ast_return cannot be reused, it bears no result!"); - return false; - } - self->expression.outr = (ir_value*)1; - - if (self->operand) { - cgen = self->operand->codegen; - /* lvalue! */ - if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand)) - return false; - - if (!ir_block_create_return(func->curblock, ast_ctx(self), operand)) - return false; - } else { - if (!ir_block_create_return(func->curblock, ast_ctx(self), NULL)) - return false; - } - - return true; -} - -bool ast_entfield_codegen(ast_entfield *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *ent, *field; - - /* This function needs to take the 'lvalue' flag into account! - * As lvalue we provide a field-pointer, as rvalue we provide the - * value in a temp. - */ - - if (lvalue && self->expression.outl) { - *out = self->expression.outl; - return true; - } - - if (!lvalue && self->expression.outr) { - *out = self->expression.outr; - return true; - } - - cgen = self->entity->codegen; - if (!(*cgen)((ast_expression*)(self->entity), func, false, &ent)) - return false; - - cgen = self->field->codegen; - if (!(*cgen)((ast_expression*)(self->field), func, false, &field)) - return false; - - if (lvalue) { - /* address! */ - *out = ir_block_create_fieldaddress(func->curblock, ast_ctx(self), ast_function_label(func, "efa"), - ent, field); - } else { - *out = ir_block_create_load_from_ent(func->curblock, ast_ctx(self), ast_function_label(func, "efv"), - ent, field, self->expression.vtype); - /* Done AFTER error checking: - codegen_output_type(self, *out); - */ - } - if (!*out) { - compile_error(ast_ctx(self), "failed to create %s instruction (output type %s)", - (lvalue ? "ADDRESS" : "FIELD"), - type_name[self->expression.vtype]); - return false; - } - if (!lvalue) - codegen_output_type(self, *out); - - if (lvalue) - self->expression.outl = *out; - else - self->expression.outr = *out; - - /* Hm that should be it... */ - return true; -} - -bool ast_member_codegen(ast_member *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - ir_value *vec; - - /* in QC this is always an lvalue */ - if (lvalue && self->rvalue) { - compile_error(ast_ctx(self), "not an l-value (member access)"); - return false; - } - if (self->expression.outl) { - *out = self->expression.outl; - return true; - } - - cgen = self->owner->codegen; - if (!(*cgen)((ast_expression*)(self->owner), func, false, &vec)) - return false; - - if (vec->vtype != TYPE_VECTOR && - !(vec->vtype == TYPE_FIELD && self->owner->next->vtype == TYPE_VECTOR)) - { - return false; - } - - *out = ir_value_vector_member(vec, self->field); - self->expression.outl = *out; - - return (*out != NULL); -} - -bool ast_array_index_codegen(ast_array_index *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_value *arr; - ast_value *idx; - - if (!lvalue && self->expression.outr) { - *out = self->expression.outr; - return true; - } - if (lvalue && self->expression.outl) { - *out = self->expression.outl; - return true; - } - - if (!ast_istype(self->array, ast_value)) { - compile_error(ast_ctx(self), "array indexing this way is not supported"); - /* note this would actually be pointer indexing because the left side is - * not an actual array but (hopefully) an indexable expression. - * Once we get integer arithmetic, and GADDRESS/GSTORE/GLOAD instruction - * support this path will be filled. - */ - return false; - } - - arr = (ast_value*)self->array; - idx = (ast_value*)self->index; - - if (!ast_istype(self->index, ast_value) || !idx->hasvalue || idx->cvq != CV_CONST) { - /* Time to use accessor functions */ - ast_expression_codegen *cgen; - ir_value *iridx, *funval; - ir_instr *call; - - if (lvalue) { - compile_error(ast_ctx(self), "(.2) array indexing here needs a compile-time constant"); - return false; - } - - if (!arr->getter) { - compile_error(ast_ctx(self), "value has no getter, don't know how to index it"); - return false; - } - - cgen = self->index->codegen; - if (!(*cgen)((ast_expression*)(self->index), func, false, &iridx)) - return false; - - cgen = arr->getter->expression.codegen; - if (!(*cgen)((ast_expression*)(arr->getter), func, true, &funval)) - return false; - - call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "fetch"), funval, false); - if (!call) - return false; - ir_call_param(call, iridx); - - *out = ir_call_value(call); - self->expression.outr = *out; - (*out)->vtype = self->expression.vtype; - codegen_output_type(self, *out); - return true; - } - - if (idx->expression.vtype == TYPE_FLOAT) { - unsigned int arridx = idx->constval.vfloat; - if (arridx >= self->array->count) - { - compile_error(ast_ctx(self), "array index out of bounds: %i", arridx); - return false; - } - *out = arr->ir_values[arridx]; - } - else if (idx->expression.vtype == TYPE_INTEGER) { - unsigned int arridx = idx->constval.vint; - if (arridx >= self->array->count) - { - compile_error(ast_ctx(self), "array index out of bounds: %i", arridx); - return false; - } - *out = arr->ir_values[arridx]; - } - else { - compile_error(ast_ctx(self), "array indexing here needs an integer constant"); - return false; - } - (*out)->vtype = self->expression.vtype; - codegen_output_type(self, *out); - return true; -} - -bool ast_argpipe_codegen(ast_argpipe *self, ast_function *func, bool lvalue, ir_value **out) -{ - *out = NULL; - if (lvalue) { - compile_error(ast_ctx(self), "argpipe node: not an lvalue"); - return false; - } - (void)func; - (void)out; - compile_error(ast_ctx(self), "TODO: argpipe codegen not implemented"); - return false; -} - -bool ast_ifthen_codegen(ast_ifthen *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - - ir_value *condval; - ir_value *dummy; - - ir_block *cond; - ir_block *ontrue; - ir_block *onfalse; - ir_block *ontrue_endblock = NULL; - ir_block *onfalse_endblock = NULL; - ir_block *merge = NULL; - int fold = 0; - - /* We don't output any value, thus also don't care about r/lvalue */ - (void)out; - (void)lvalue; - - if (self->expression.outr) { - compile_error(ast_ctx(self), "internal error: ast_ifthen cannot be reused, it bears no result!"); - return false; - } - self->expression.outr = (ir_value*)1; - - /* generate the condition */ - cgen = self->cond->codegen; - if (!(*cgen)((ast_expression*)(self->cond), func, false, &condval)) - return false; - /* update the block which will get the jump - because short-logic or ternaries may have changed this */ - cond = func->curblock; - - /* try constant folding away the condition */ - if ((fold = fold_cond_ifthen(condval, func, self)) != -1) - return fold; - - if (self->on_true) { - /* create on-true block */ - ontrue = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "ontrue")); - if (!ontrue) - return false; - - /* enter the block */ - func->curblock = ontrue; - - /* generate */ - cgen = self->on_true->codegen; - if (!(*cgen)((ast_expression*)(self->on_true), func, false, &dummy)) - return false; - - /* we now need to work from the current endpoint */ - ontrue_endblock = func->curblock; - } else - ontrue = NULL; - - /* on-false path */ - if (self->on_false) { - /* create on-false block */ - onfalse = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "onfalse")); - if (!onfalse) - return false; - - /* enter the block */ - func->curblock = onfalse; - - /* generate */ - cgen = self->on_false->codegen; - if (!(*cgen)((ast_expression*)(self->on_false), func, false, &dummy)) - return false; - - /* we now need to work from the current endpoint */ - onfalse_endblock = func->curblock; - } else - onfalse = NULL; - - /* Merge block were they all merge in to */ - if (!ontrue || !onfalse || !ontrue_endblock->final || !onfalse_endblock->final) - { - merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "endif")); - if (!merge) - return false; - /* add jumps ot the merge block */ - if (ontrue && !ontrue_endblock->final && !ir_block_create_jump(ontrue_endblock, ast_ctx(self), merge)) - return false; - if (onfalse && !onfalse_endblock->final && !ir_block_create_jump(onfalse_endblock, ast_ctx(self), merge)) - return false; - - /* Now enter the merge block */ - func->curblock = merge; - } - - /* we create the if here, that way all blocks are ordered :) - */ - if (!ir_block_create_if(cond, ast_ctx(self), condval, - (ontrue ? ontrue : merge), - (onfalse ? onfalse : merge))) - { - return false; - } - - return true; -} - -bool ast_ternary_codegen(ast_ternary *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - - ir_value *condval; - ir_value *trueval, *falseval; - ir_instr *phi; - - ir_block *cond = func->curblock; - ir_block *cond_out = NULL; - ir_block *ontrue, *ontrue_out = NULL; - ir_block *onfalse, *onfalse_out = NULL; - ir_block *merge; - int fold = 0; - - /* Ternary can never create an lvalue... */ - if (lvalue) - return false; - - /* In theory it shouldn't be possible to pass through a node twice, but - * in case we add any kind of optimization pass for the AST itself, it - * may still happen, thus we remember a created ir_value and simply return one - * if it already exists. - */ - if (self->expression.outr) { - *out = self->expression.outr; - return true; - } - - /* In the following, contraty to ast_ifthen, we assume both paths exist. */ - - /* generate the condition */ - func->curblock = cond; - cgen = self->cond->codegen; - if (!(*cgen)((ast_expression*)(self->cond), func, false, &condval)) - return false; - cond_out = func->curblock; - - /* try constant folding away the condition */ - if ((fold = fold_cond_ternary(condval, func, self)) != -1) - return fold; - - /* create on-true block */ - ontrue = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_T")); - if (!ontrue) - return false; - else - { - /* enter the block */ - func->curblock = ontrue; - - /* generate */ - cgen = self->on_true->codegen; - if (!(*cgen)((ast_expression*)(self->on_true), func, false, &trueval)) - return false; - - ontrue_out = func->curblock; - } - - /* create on-false block */ - onfalse = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_F")); - if (!onfalse) - return false; - else - { - /* enter the block */ - func->curblock = onfalse; - - /* generate */ - cgen = self->on_false->codegen; - if (!(*cgen)((ast_expression*)(self->on_false), func, false, &falseval)) - return false; - - onfalse_out = func->curblock; - } - - /* create merge block */ - merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_out")); - if (!merge) - return false; - /* jump to merge block */ - if (!ir_block_create_jump(ontrue_out, ast_ctx(self), merge)) - return false; - if (!ir_block_create_jump(onfalse_out, ast_ctx(self), merge)) - return false; - - /* create if instruction */ - if (!ir_block_create_if(cond_out, ast_ctx(self), condval, ontrue, onfalse)) - return false; - - /* Now enter the merge block */ - func->curblock = merge; - - /* Here, now, we need a PHI node - * but first some sanity checking... - */ - if (trueval->vtype != falseval->vtype && trueval->vtype != TYPE_NIL && falseval->vtype != TYPE_NIL) { - /* error("ternary with different types on the two sides"); */ - compile_error(ast_ctx(self), "internal error: ternary operand types invalid"); - return false; - } - - /* create PHI */ - phi = ir_block_create_phi(merge, ast_ctx(self), ast_function_label(func, "phi"), self->expression.vtype); - if (!phi) { - compile_error(ast_ctx(self), "internal error: failed to generate phi node"); - return false; - } - ir_phi_add(phi, ontrue_out, trueval); - ir_phi_add(phi, onfalse_out, falseval); - - self->expression.outr = ir_phi_value(phi); - *out = self->expression.outr; - - codegen_output_type(self, *out); - - return true; -} - -bool ast_loop_codegen(ast_loop *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - - ir_value *dummy = NULL; - ir_value *precond = NULL; - ir_value *postcond = NULL; - - /* Since we insert some jumps "late" so we have blocks - * ordered "nicely", we need to keep track of the actual end-blocks - * of expressions to add the jumps to. - */ - ir_block *bbody = NULL, *end_bbody = NULL; - ir_block *bprecond = NULL, *end_bprecond = NULL; - ir_block *bpostcond = NULL, *end_bpostcond = NULL; - ir_block *bincrement = NULL, *end_bincrement = NULL; - ir_block *bout = NULL, *bin = NULL; - - /* let's at least move the outgoing block to the end */ - size_t bout_id; - - /* 'break' and 'continue' need to be able to find the right blocks */ - ir_block *bcontinue = NULL; - ir_block *bbreak = NULL; - - ir_block *tmpblock = NULL; - - (void)lvalue; - (void)out; - - if (self->expression.outr) { - compile_error(ast_ctx(self), "internal error: ast_loop cannot be reused, it bears no result!"); - return false; - } - self->expression.outr = (ir_value*)1; - - /* NOTE: - * Should we ever need some kind of block ordering, better make this function - * move blocks around than write a block ordering algorithm later... after all - * the ast and ir should work together, not against each other. - */ - - /* initexpr doesn't get its own block, it's pointless, it could create more blocks - * anyway if for example it contains a ternary. - */ - if (self->initexpr) - { - cgen = self->initexpr->codegen; - if (!(*cgen)((ast_expression*)(self->initexpr), func, false, &dummy)) - return false; - } - - /* Store the block from which we enter this chaos */ - bin = func->curblock; - - /* The pre-loop condition needs its own block since we - * need to be able to jump to the start of that expression. - */ - if (self->precond) - { - bprecond = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "pre_loop_cond")); - if (!bprecond) - return false; - - /* the pre-loop-condition the least important place to 'continue' at */ - bcontinue = bprecond; - - /* enter */ - func->curblock = bprecond; - - /* generate */ - cgen = self->precond->codegen; - if (!(*cgen)((ast_expression*)(self->precond), func, false, &precond)) - return false; - - end_bprecond = func->curblock; - } else { - bprecond = end_bprecond = NULL; - } - - /* Now the next blocks won't be ordered nicely, but we need to - * generate them this early for 'break' and 'continue'. - */ - if (self->increment) { - bincrement = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "loop_increment")); - if (!bincrement) - return false; - bcontinue = bincrement; /* increment comes before the pre-loop-condition */ - } else { - bincrement = end_bincrement = NULL; - } - - if (self->postcond) { - bpostcond = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "post_loop_cond")); - if (!bpostcond) - return false; - bcontinue = bpostcond; /* postcond comes before the increment */ - } else { - bpostcond = end_bpostcond = NULL; - } - - bout_id = vec_size(func->ir_func->blocks); - bout = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "after_loop")); - if (!bout) - return false; - bbreak = bout; - - /* The loop body... */ - /* if (self->body) */ - { - bbody = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "loop_body")); - if (!bbody) - return false; - - /* enter */ - func->curblock = bbody; - - vec_push(func->breakblocks, bbreak); - if (bcontinue) - vec_push(func->continueblocks, bcontinue); - else - vec_push(func->continueblocks, bbody); - - /* generate */ - if (self->body) { - cgen = self->body->codegen; - if (!(*cgen)((ast_expression*)(self->body), func, false, &dummy)) - return false; - } - - end_bbody = func->curblock; - vec_pop(func->breakblocks); - vec_pop(func->continueblocks); - } - - /* post-loop-condition */ - if (self->postcond) - { - /* enter */ - func->curblock = bpostcond; - - /* generate */ - cgen = self->postcond->codegen; - if (!(*cgen)((ast_expression*)(self->postcond), func, false, &postcond)) - return false; - - end_bpostcond = func->curblock; - } - - /* The incrementor */ - if (self->increment) - { - /* enter */ - func->curblock = bincrement; - - /* generate */ - cgen = self->increment->codegen; - if (!(*cgen)((ast_expression*)(self->increment), func, false, &dummy)) - return false; - - end_bincrement = func->curblock; - } - - /* In any case now, we continue from the outgoing block */ - func->curblock = bout; - - /* Now all blocks are in place */ - /* From 'bin' we jump to whatever comes first */ - if (bprecond) tmpblock = bprecond; - else tmpblock = bbody; /* can never be null */ - - /* DEAD CODE - else if (bpostcond) tmpblock = bpostcond; - else tmpblock = bout; - */ - - if (!ir_block_create_jump(bin, ast_ctx(self), tmpblock)) - return false; - - /* From precond */ - if (bprecond) - { - ir_block *ontrue, *onfalse; - ontrue = bbody; /* can never be null */ - - /* all of this is dead code - else if (bincrement) ontrue = bincrement; - else ontrue = bpostcond; - */ - - onfalse = bout; - if (self->pre_not) { - tmpblock = ontrue; - ontrue = onfalse; - onfalse = tmpblock; - } - if (!ir_block_create_if(end_bprecond, ast_ctx(self), precond, ontrue, onfalse)) - return false; - } - - /* from body */ - if (bbody) - { - if (bincrement) tmpblock = bincrement; - else if (bpostcond) tmpblock = bpostcond; - else if (bprecond) tmpblock = bprecond; - else tmpblock = bbody; - if (!end_bbody->final && !ir_block_create_jump(end_bbody, ast_ctx(self), tmpblock)) - return false; - } - - /* from increment */ - if (bincrement) - { - if (bpostcond) tmpblock = bpostcond; - else if (bprecond) tmpblock = bprecond; - else if (bbody) tmpblock = bbody; - else tmpblock = bout; - if (!ir_block_create_jump(end_bincrement, ast_ctx(self), tmpblock)) - return false; - } - - /* from postcond */ - if (bpostcond) - { - ir_block *ontrue, *onfalse; - if (bprecond) ontrue = bprecond; - else ontrue = bbody; /* can never be null */ - - /* all of this is dead code - else if (bincrement) ontrue = bincrement; - else ontrue = bpostcond; - */ - - onfalse = bout; - if (self->post_not) { - tmpblock = ontrue; - ontrue = onfalse; - onfalse = tmpblock; - } - if (!ir_block_create_if(end_bpostcond, ast_ctx(self), postcond, ontrue, onfalse)) - return false; - } - - /* Move 'bout' to the end */ - vec_remove(func->ir_func->blocks, bout_id, 1); - vec_push(func->ir_func->blocks, bout); - - return true; -} - -bool ast_breakcont_codegen(ast_breakcont *self, ast_function *func, bool lvalue, ir_value **out) -{ - ir_block *target; - - *out = NULL; - - if (lvalue) { - compile_error(ast_ctx(self), "break/continue expression is not an l-value"); - return false; - } - - if (self->expression.outr) { - compile_error(ast_ctx(self), "internal error: ast_breakcont cannot be reused!"); - return false; - } - self->expression.outr = (ir_value*)1; - - if (self->is_continue) - target = func->continueblocks[vec_size(func->continueblocks)-1-self->levels]; - else - target = func->breakblocks[vec_size(func->breakblocks)-1-self->levels]; - - if (!target) { - compile_error(ast_ctx(self), "%s is lacking a target block", (self->is_continue ? "continue" : "break")); - return false; - } - - if (!ir_block_create_jump(func->curblock, ast_ctx(self), target)) - return false; - return true; -} - -bool ast_switch_codegen(ast_switch *self, ast_function *func, bool lvalue, ir_value **out) -{ - ast_expression_codegen *cgen; - - ast_switch_case *def_case = NULL; - ir_block *def_bfall = NULL; - ir_block *def_bfall_to = NULL; - bool set_def_bfall_to = false; - - ir_value *dummy = NULL; - ir_value *irop = NULL; - ir_block *bout = NULL; - ir_block *bfall = NULL; - size_t bout_id; - size_t c; - - char typestr[1024]; - uint16_t cmpinstr; - - if (lvalue) { - compile_error(ast_ctx(self), "switch expression is not an l-value"); - return false; - } - - if (self->expression.outr) { - compile_error(ast_ctx(self), "internal error: ast_switch cannot be reused!"); - return false; - } - self->expression.outr = (ir_value*)1; - - (void)lvalue; - (void)out; - - cgen = self->operand->codegen; - if (!(*cgen)((ast_expression*)(self->operand), func, false, &irop)) - return false; - - if (!vec_size(self->cases)) - return true; - - cmpinstr = type_eq_instr[irop->vtype]; - if (cmpinstr >= VINSTR_END) { - ast_type_to_string(self->operand, typestr, sizeof(typestr)); - compile_error(ast_ctx(self), "invalid type to perform a switch on: %s", typestr); - return false; - } - - bout_id = vec_size(func->ir_func->blocks); - bout = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "after_switch")); - if (!bout) - return false; - - /* setup the break block */ - vec_push(func->breakblocks, bout); - - /* Now create all cases */ - for (c = 0; c < vec_size(self->cases); ++c) { - ir_value *cond, *val; - ir_block *bcase, *bnot; - size_t bnot_id; - - ast_switch_case *swcase = &self->cases[c]; - - if (swcase->value) { - /* A regular case */ - /* generate the condition operand */ - cgen = swcase->value->codegen; - if (!(*cgen)((ast_expression*)(swcase->value), func, false, &val)) - return false; - /* generate the condition */ - cond = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "switch_eq"), cmpinstr, irop, val); - if (!cond) - return false; - - bcase = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "case")); - bnot_id = vec_size(func->ir_func->blocks); - bnot = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "not_case")); - if (!bcase || !bnot) - return false; - if (set_def_bfall_to) { - set_def_bfall_to = false; - def_bfall_to = bcase; - } - if (!ir_block_create_if(func->curblock, ast_ctx(self), cond, bcase, bnot)) - return false; - - /* Make the previous case-end fall through */ - if (bfall && !bfall->final) { - if (!ir_block_create_jump(bfall, ast_ctx(self), bcase)) - return false; - } - - /* enter the case */ - func->curblock = bcase; - cgen = swcase->code->codegen; - if (!(*cgen)((ast_expression*)swcase->code, func, false, &dummy)) - return false; - - /* remember this block to fall through from */ - bfall = func->curblock; - - /* enter the else and move it down */ - func->curblock = bnot; - vec_remove(func->ir_func->blocks, bnot_id, 1); - vec_push(func->ir_func->blocks, bnot); - } else { - /* The default case */ - /* Remember where to fall through from: */ - def_bfall = bfall; - bfall = NULL; - /* remember which case it was */ - def_case = swcase; - /* And the next case will be remembered */ - set_def_bfall_to = true; - } - } - - /* Jump from the last bnot to bout */ - if (bfall && !bfall->final && !ir_block_create_jump(bfall, ast_ctx(self), bout)) { - /* - astwarning(ast_ctx(bfall), WARN_???, "missing break after last case"); - */ - return false; - } - - /* If there was a default case, put it down here */ - if (def_case) { - ir_block *bcase; - - /* No need to create an extra block */ - bcase = func->curblock; - - /* Insert the fallthrough jump */ - if (def_bfall && !def_bfall->final) { - if (!ir_block_create_jump(def_bfall, ast_ctx(self), bcase)) - return false; - } - - /* Now generate the default code */ - cgen = def_case->code->codegen; - if (!(*cgen)((ast_expression*)def_case->code, func, false, &dummy)) - return false; - - /* see if we need to fall through */ - if (def_bfall_to && !func->curblock->final) - { - if (!ir_block_create_jump(func->curblock, ast_ctx(self), def_bfall_to)) - return false; - } - } - - /* Jump from the last bnot to bout */ - if (!func->curblock->final && !ir_block_create_jump(func->curblock, ast_ctx(self), bout)) - return false; - /* enter the outgoing block */ - func->curblock = bout; - - /* restore the break block */ - vec_pop(func->breakblocks); - - /* Move 'bout' to the end, it's nicer */ - vec_remove(func->ir_func->blocks, bout_id, 1); - vec_push(func->ir_func->blocks, bout); - - return true; -} - -bool ast_label_codegen(ast_label *self, ast_function *func, bool lvalue, ir_value **out) -{ - size_t i; - ir_value *dummy; - - if (self->undefined) { - compile_error(ast_ctx(self), "internal error: ast_label never defined"); - return false; - } - - *out = NULL; - if (lvalue) { - compile_error(ast_ctx(self), "internal error: ast_label cannot be an lvalue"); - return false; - } - - /* simply create a new block and jump to it */ - self->irblock = ir_function_create_block(ast_ctx(self), func->ir_func, self->name); - if (!self->irblock) { - compile_error(ast_ctx(self), "failed to allocate label block `%s`", self->name); - return false; - } - if (!func->curblock->final) { - if (!ir_block_create_jump(func->curblock, ast_ctx(self), self->irblock)) - return false; - } - - /* enter the new block */ - func->curblock = self->irblock; - - /* Generate all the leftover gotos */ - for (i = 0; i < vec_size(self->gotos); ++i) { - if (!ast_goto_codegen(self->gotos[i], func, false, &dummy)) - return false; - } - - return true; -} - -bool ast_goto_codegen(ast_goto *self, ast_function *func, bool lvalue, ir_value **out) -{ - *out = NULL; - if (lvalue) { - compile_error(ast_ctx(self), "internal error: ast_goto cannot be an lvalue"); - return false; - } - - if (self->target->irblock) { - if (self->irblock_from) { - /* we already tried once, this is the callback */ - self->irblock_from->final = false; - if (!ir_block_create_goto(self->irblock_from, ast_ctx(self), self->target->irblock)) { - compile_error(ast_ctx(self), "failed to generate goto to `%s`", self->name); - return false; - } - } - else - { - if (!ir_block_create_goto(func->curblock, ast_ctx(self), self->target->irblock)) { - compile_error(ast_ctx(self), "failed to generate goto to `%s`", self->name); - return false; - } - } - } - else - { - /* the target has not yet been created... - * close this block in a sneaky way: - */ - func->curblock->final = true; - self->irblock_from = func->curblock; - ast_label_register_goto(self->target, self); - } - - return true; -} - -#include -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; - ir_value **params; - ir_instr *callinstr; - size_t i; - - ir_value *funval = NULL; - - /* return values are never lvalues */ - if (lvalue) { - compile_error(ast_ctx(self), "not an l-value (function call)"); - return false; - } - - if (self->expression.outr) { - *out = self->expression.outr; - return true; - } - - cgen = self->func->codegen; - if (!(*cgen)((ast_expression*)(self->func), func, false, &funval)) - return false; - if (!funval) - return false; - - params = NULL; - - /* parameters */ - for (i = 0; i < vec_size(self->params); ++i) - { - ir_value *param; - ast_expression *expr = self->params[i]; - - cgen = expr->codegen; - if (!(*cgen)(expr, func, false, ¶m)) - goto error; - if (!param) - goto error; - vec_push(params, param); - } - - /* varargs counter */ - if (self->va_count) { - ir_value *va_count; - ir_builder *builder = func->curblock->owner->owner; - cgen = self->va_count->codegen; - if (!(*cgen)((ast_expression*)(self->va_count), func, false, &va_count)) - return false; - if (!ir_block_create_store_op(func->curblock, ast_ctx(self), INSTR_STORE_F, - ir_builder_get_va_count(builder), va_count)) - { - return false; - } - } - - callinstr = ir_block_create_call(func->curblock, ast_ctx(self), - ast_function_label(func, "call"), - funval, !!(self->func->flags & AST_FLAG_NORETURN)); - if (!callinstr) - goto error; - - for (i = 0; i < vec_size(params); ++i) { - ir_call_param(callinstr, params[i]); - } - - *out = ir_call_value(callinstr); - self->expression.outr = *out; - - codegen_output_type(self, *out); - - vec_free(params); - return true; -error: - vec_free(params); - return false; -} diff --git a/ast.cpp b/ast.cpp new file mode 100644 index 0000000..29e298a --- /dev/null +++ b/ast.cpp @@ -0,0 +1,3463 @@ +#include +#include + +#include "gmqcc.h" +#include "ast.h" +#include "parser.h" + +#define ast_instantiate(T, ctx, destroyfn) \ + T* self = (T*)mem_a(sizeof(T)); \ + if (!self) { \ + return NULL; \ + } \ + ast_node_init((ast_node*)self, ctx, TYPE_##T); \ + ( (ast_node*)self )->destroy = (ast_node_delete*)destroyfn + +/* + * forward declarations, these need not be in ast.h for obvious + * static reasons. + */ +static bool ast_member_codegen(ast_member*, ast_function*, bool lvalue, ir_value**); +static void ast_array_index_delete(ast_array_index*); +static bool ast_array_index_codegen(ast_array_index*, ast_function*, bool lvalue, ir_value**); +static void ast_argpipe_delete(ast_argpipe*); +static bool ast_argpipe_codegen(ast_argpipe*, ast_function*, bool lvalue, ir_value**); +static void ast_store_delete(ast_store*); +static bool ast_store_codegen(ast_store*, ast_function*, bool lvalue, ir_value**); +static void ast_ifthen_delete(ast_ifthen*); +static bool ast_ifthen_codegen(ast_ifthen*, ast_function*, bool lvalue, ir_value**); +static void ast_ternary_delete(ast_ternary*); +static bool ast_ternary_codegen(ast_ternary*, ast_function*, bool lvalue, ir_value**); +static void ast_loop_delete(ast_loop*); +static bool ast_loop_codegen(ast_loop*, ast_function*, bool lvalue, ir_value**); +static void ast_breakcont_delete(ast_breakcont*); +static bool ast_breakcont_codegen(ast_breakcont*, ast_function*, bool lvalue, ir_value**); +static void ast_switch_delete(ast_switch*); +static bool ast_switch_codegen(ast_switch*, ast_function*, bool lvalue, ir_value**); +static void ast_label_delete(ast_label*); +static void ast_label_register_goto(ast_label*, ast_goto*); +static bool ast_label_codegen(ast_label*, ast_function*, bool lvalue, ir_value**); +static bool ast_goto_codegen(ast_goto*, ast_function*, bool lvalue, ir_value**); +static void ast_goto_delete(ast_goto*); +static void ast_call_delete(ast_call*); +static bool ast_call_codegen(ast_call*, ast_function*, bool lvalue, ir_value**); +static bool ast_block_codegen(ast_block*, ast_function*, bool lvalue, ir_value**); +static void ast_unary_delete(ast_unary*); +static bool ast_unary_codegen(ast_unary*, ast_function*, bool lvalue, ir_value**); +static void ast_entfield_delete(ast_entfield*); +static bool ast_entfield_codegen(ast_entfield*, ast_function*, bool lvalue, ir_value**); +static void ast_return_delete(ast_return*); +static bool ast_return_codegen(ast_return*, ast_function*, bool lvalue, ir_value**); +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) +{ + (void)self; + con_err("ast node missing destroy()\n"); + exit(EXIT_FAILURE); +} + +/* Initialize main ast node aprts */ +static void ast_node_init(ast_node *self, lex_ctx_t ctx, int nodetype) +{ + self->context = ctx; + self->destroy = &_ast_node_destroy; + self->keep = false; + self->nodetype = nodetype; + self->side_effects = false; +} + +/* weight and side effects */ +static void _ast_propagate_effects(ast_node *self, ast_node *other) +{ + if (ast_side_effects(other)) + ast_side_effects(self) = true; +} +#define ast_propagate_effects(s,o) _ast_propagate_effects(((ast_node*)(s)), ((ast_node*)(o))) + +/* General expression initialization */ +static void ast_expression_init(ast_expression *self, + ast_expression_codegen *codegen) +{ + self->codegen = codegen; + self->vtype = TYPE_VOID; + self->next = NULL; + self->outl = NULL; + self->outr = NULL; + self->params = NULL; + self->count = 0; + self->varparam = NULL; + self->flags = 0; + if (OPTS_OPTION_BOOL(OPTION_COVERAGE)) + self->flags |= AST_FLAG_BLOCK_COVERAGE; +} + +static void ast_expression_delete(ast_expression *self) +{ + size_t i; + if (self->next) + ast_delete(self->next); + for (i = 0; i < vec_size(self->params); ++i) { + ast_delete(self->params[i]); + } + vec_free(self->params); + if (self->varparam) + ast_delete(self->varparam); +} + +static void ast_expression_delete_full(ast_expression *self) +{ + ast_expression_delete(self); + mem_d(self); +} + +ast_value* ast_value_copy(const ast_value *self) +{ + size_t i; + const ast_expression *fromex; + ast_expression *selfex; + ast_value *cp = ast_value_new(self->expression.node.context, self->name, self->expression.vtype); + if (self->expression.next) { + cp->expression.next = ast_type_copy(self->expression.node.context, self->expression.next); + } + fromex = &self->expression; + selfex = &cp->expression; + selfex->count = fromex->count; + selfex->flags = fromex->flags; + for (i = 0; i < vec_size(fromex->params); ++i) { + ast_value *v = ast_value_copy(fromex->params[i]); + vec_push(selfex->params, v); + } + return cp; +} + +void ast_type_adopt_impl(ast_expression *self, const ast_expression *other) +{ + size_t i; + const ast_expression *fromex; + ast_expression *selfex; + self->vtype = other->vtype; + if (other->next) { + self->next = (ast_expression*)ast_type_copy(ast_ctx(self), other->next); + } + fromex = other; + selfex = self; + selfex->count = fromex->count; + selfex->flags = fromex->flags; + for (i = 0; i < vec_size(fromex->params); ++i) { + ast_value *v = ast_value_copy(fromex->params[i]); + vec_push(selfex->params, v); + } +} + +static ast_expression* ast_shallow_type(lex_ctx_t ctx, int vtype) +{ + ast_instantiate(ast_expression, ctx, ast_expression_delete_full); + ast_expression_init(self, NULL); + self->codegen = NULL; + self->next = NULL; + self->vtype = vtype; + return self; +} + +ast_expression* ast_type_copy(lex_ctx_t ctx, const ast_expression *ex) +{ + size_t i; + const ast_expression *fromex; + ast_expression *selfex; + + if (!ex) + return NULL; + else + { + ast_instantiate(ast_expression, ctx, ast_expression_delete_full); + ast_expression_init(self, NULL); + + fromex = ex; + selfex = self; + + /* This may never be codegen()d */ + selfex->codegen = NULL; + + selfex->vtype = fromex->vtype; + if (fromex->next) + selfex->next = ast_type_copy(ctx, fromex->next); + else + selfex->next = NULL; + + selfex->count = fromex->count; + selfex->flags = fromex->flags; + for (i = 0; i < vec_size(fromex->params); ++i) { + ast_value *v = ast_value_copy(fromex->params[i]); + vec_push(selfex->params, v); + } + + return self; + } +} + +bool ast_compare_type(ast_expression *a, ast_expression *b) +{ + if (a->vtype == TYPE_NIL || + b->vtype == TYPE_NIL) + return true; + if (a->vtype != b->vtype) + return false; + if (!a->next != !b->next) + return false; + if (vec_size(a->params) != vec_size(b->params)) + return false; + if ((a->flags & AST_FLAG_TYPE_MASK) != + (b->flags & AST_FLAG_TYPE_MASK) ) + { + return false; + } + if (vec_size(a->params)) { + size_t i; + for (i = 0; i < vec_size(a->params); ++i) { + if (!ast_compare_type((ast_expression*)a->params[i], + (ast_expression*)b->params[i])) + return false; + } + } + if (a->next) + return ast_compare_type(a->next, b->next); + return true; +} + +static size_t ast_type_to_string_impl(ast_expression *e, char *buf, size_t bufsize, size_t pos) +{ + const char *typestr; + size_t typelen; + size_t i; + + if (!e) { + if (pos + 6 >= bufsize) + goto full; + util_strncpy(buf + pos, "(null)", 6); + return pos + 6; + } + + if (pos + 1 >= bufsize) + goto full; + + switch (e->vtype) { + case TYPE_VARIANT: + util_strncpy(buf + pos, "(variant)", 9); + return pos + 9; + + case TYPE_FIELD: + buf[pos++] = '.'; + return ast_type_to_string_impl(e->next, buf, bufsize, pos); + + case TYPE_POINTER: + if (pos + 3 >= bufsize) + goto full; + buf[pos++] = '*'; + buf[pos++] = '('; + pos = ast_type_to_string_impl(e->next, buf, bufsize, pos); + if (pos + 1 >= bufsize) + goto full; + buf[pos++] = ')'; + return pos; + + case TYPE_FUNCTION: + pos = ast_type_to_string_impl(e->next, buf, bufsize, pos); + if (pos + 2 >= bufsize) + goto full; + if (!vec_size(e->params)) { + buf[pos++] = '('; + buf[pos++] = ')'; + return pos; + } + buf[pos++] = '('; + pos = ast_type_to_string_impl((ast_expression*)(e->params[0]), buf, bufsize, pos); + for (i = 1; i < vec_size(e->params); ++i) { + if (pos + 2 >= bufsize) + goto full; + buf[pos++] = ','; + buf[pos++] = ' '; + pos = ast_type_to_string_impl((ast_expression*)(e->params[i]), buf, bufsize, pos); + } + if (pos + 1 >= bufsize) + goto full; + buf[pos++] = ')'; + return pos; + + case TYPE_ARRAY: + pos = ast_type_to_string_impl(e->next, buf, bufsize, pos); + if (pos + 1 >= bufsize) + goto full; + buf[pos++] = '['; + pos += util_snprintf(buf + pos, bufsize - pos - 1, "%i", (int)e->count); + if (pos + 1 >= bufsize) + goto full; + buf[pos++] = ']'; + return pos; + + default: + typestr = type_name[e->vtype]; + typelen = strlen(typestr); + if (pos + typelen >= bufsize) + goto full; + util_strncpy(buf + pos, typestr, typelen); + return pos + typelen; + } + +full: + buf[bufsize-3] = '.'; + buf[bufsize-2] = '.'; + buf[bufsize-1] = '.'; + return bufsize; +} + +void ast_type_to_string(ast_expression *e, char *buf, size_t bufsize) +{ + size_t pos = ast_type_to_string_impl(e, buf, bufsize-1, 0); + buf[pos] = 0; +} + +static bool ast_value_codegen(ast_value *self, ast_function *func, bool lvalue, ir_value **out); +ast_value* ast_value_new(lex_ctx_t ctx, const char *name, int t) +{ + ast_instantiate(ast_value, ctx, ast_value_delete); + ast_expression_init((ast_expression*)self, + (ast_expression_codegen*)&ast_value_codegen); + self->expression.node.keep = true; /* keep */ + + self->name = name ? util_strdup(name) : NULL; + self->expression.vtype = t; + self->expression.next = NULL; + self->isfield = false; + 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; + + self->ir_v = NULL; + self->ir_values = NULL; + self->ir_value_count = 0; + + self->setter = NULL; + self->getter = NULL; + self->desc = NULL; + + self->argcounter = NULL; + self->intrinsic = false; + + return self; +} + +void ast_value_delete(ast_value* self) +{ + if (self->name) + mem_d((void*)self->name); + if (self->argcounter) + mem_d((void*)self->argcounter); + if (self->hasvalue) { + switch (self->expression.vtype) + { + case TYPE_STRING: + mem_d((void*)self->constval.vstring); + break; + case TYPE_FUNCTION: + /* unlink us from the function node */ + self->constval.vfunc->vtype = NULL; + break; + /* NOTE: delete function? currently collected in + * the parser structure + */ + default: + break; + } + } + if (self->ir_values) + mem_d(self->ir_values); + + if (self->desc) + mem_d(self->desc); + + if (self->initlist) { + if (self->expression.next->vtype == TYPE_STRING) { + /* strings are allocated, free them */ + size_t i, len = vec_size(self->initlist); + /* in theory, len should be expression.count + * but let's not take any chances */ + for (i = 0; i < len; ++i) { + if (self->initlist[i].vstring) + mem_d(self->initlist[i].vstring); + } + } + vec_free(self->initlist); + } + + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +void ast_value_params_add(ast_value *self, ast_value *p) +{ + vec_push(self->expression.params, p); +} + +bool ast_value_set_name(ast_value *self, const char *name) +{ + if (self->name) + mem_d((void*)self->name); + self->name = util_strdup(name); + return !!self->name; +} + +ast_binary* ast_binary_new(lex_ctx_t ctx, int op, + ast_expression* left, ast_expression* right) +{ + ast_instantiate(ast_binary, ctx, ast_binary_delete); + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_binary_codegen); + + if (ast_istype(right, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) { + ast_unary *unary = ((ast_unary*)right); + ast_expression *normal = unary->operand; + + /* make a-(-b) => a + b */ + if (unary->op == VINSTR_NEG_F || unary->op == VINSTR_NEG_V) { + if (op == INSTR_SUB_F) { + op = INSTR_ADD_F; + right = normal; + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + } else if (op == INSTR_SUB_V) { + op = INSTR_ADD_V; + right = normal; + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + } + } + } + + self->op = op; + self->left = left; + self->right = right; + self->right_first = false; + + ast_propagate_effects(self, left); + ast_propagate_effects(self, right); + + if (op >= INSTR_EQ_F && op <= INSTR_GT) + self->expression.vtype = TYPE_FLOAT; + else if (op == INSTR_AND || op == INSTR_OR) { + if (OPTS_FLAG(PERL_LOGIC)) + ast_type_adopt(self, right); + else + self->expression.vtype = TYPE_FLOAT; + } + else if (op == INSTR_BITAND || op == INSTR_BITOR) + self->expression.vtype = TYPE_FLOAT; + else if (op == INSTR_MUL_VF || op == INSTR_MUL_FV) + self->expression.vtype = TYPE_VECTOR; + else if (op == INSTR_MUL_V) + self->expression.vtype = TYPE_FLOAT; + else + self->expression.vtype = left->vtype; + + /* references all */ + self->refs = AST_REF_ALL; + + return self; +} + +void ast_binary_delete(ast_binary *self) +{ + if (self->refs & AST_REF_LEFT) ast_unref(self->left); + if (self->refs & AST_REF_RIGHT) ast_unref(self->right); + + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +ast_binstore* ast_binstore_new(lex_ctx_t ctx, int storop, int op, + ast_expression* left, ast_expression* right) +{ + ast_instantiate(ast_binstore, ctx, ast_binstore_delete); + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_binstore_codegen); + + ast_side_effects(self) = true; + + self->opstore = storop; + self->opbin = op; + self->dest = left; + self->source = right; + + self->keep_dest = false; + + ast_type_adopt(self, left); + return self; +} + +void ast_binstore_delete(ast_binstore *self) +{ + if (!self->keep_dest) + ast_unref(self->dest); + ast_unref(self->source); + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +ast_unary* ast_unary_new(lex_ctx_t ctx, int op, + ast_expression *expr) +{ + ast_instantiate(ast_unary, ctx, ast_unary_delete); + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_unary_codegen); + + self->op = op; + self->operand = expr; + + + if (ast_istype(expr, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) { + ast_unary *prev = (ast_unary*)((ast_unary*)expr)->operand; + + /* Handle for double negation */ + if (((ast_unary*)expr)->op == op) + prev = (ast_unary*)((ast_unary*)expr)->operand; + + if (ast_istype(prev, ast_unary)) { + ast_expression_delete((ast_expression*)self); + mem_d(self); + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + return prev; + } + } + + ast_propagate_effects(self, expr); + + if ((op >= INSTR_NOT_F && op <= INSTR_NOT_FNC) || op == VINSTR_NEG_F) { + self->expression.vtype = TYPE_FLOAT; + } else if (op == VINSTR_NEG_V) { + self->expression.vtype = TYPE_VECTOR; + } else { + compile_error(ctx, "cannot determine type of unary operation %s", util_instr_str[op]); + } + + return self; +} + +void ast_unary_delete(ast_unary *self) +{ + if (self->operand) ast_unref(self->operand); + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +ast_return* ast_return_new(lex_ctx_t ctx, ast_expression *expr) +{ + ast_instantiate(ast_return, ctx, ast_return_delete); + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_return_codegen); + + self->operand = expr; + + if (expr) + ast_propagate_effects(self, expr); + + return self; +} + +void ast_return_delete(ast_return *self) +{ + if (self->operand) + ast_unref(self->operand); + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +ast_entfield* ast_entfield_new(lex_ctx_t ctx, ast_expression *entity, ast_expression *field) +{ + if (field->vtype != TYPE_FIELD) { + compile_error(ctx, "ast_entfield_new with expression not of type field"); + return NULL; + } + return ast_entfield_new_force(ctx, entity, field, field->next); +} + +ast_entfield* ast_entfield_new_force(lex_ctx_t ctx, ast_expression *entity, ast_expression *field, const ast_expression *outtype) +{ + ast_instantiate(ast_entfield, ctx, ast_entfield_delete); + + if (!outtype) { + mem_d(self); + /* Error: field has no type... */ + return NULL; + } + + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_entfield_codegen); + + self->entity = entity; + self->field = field; + ast_propagate_effects(self, entity); + ast_propagate_effects(self, field); + + ast_type_adopt(self, outtype); + return self; +} + +void ast_entfield_delete(ast_entfield *self) +{ + ast_unref(self->entity); + ast_unref(self->field); + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +ast_member* ast_member_new(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const char *name) +{ + ast_instantiate(ast_member, ctx, ast_member_delete); + if (field >= 3) { + mem_d(self); + return NULL; + } + + if (owner->vtype != TYPE_VECTOR && + owner->vtype != TYPE_FIELD) { + compile_error(ctx, "member-access on an invalid owner of type %s", type_name[owner->vtype]); + mem_d(self); + return NULL; + } + + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_member_codegen); + self->expression.node.keep = true; /* keep */ + + if (owner->vtype == TYPE_VECTOR) { + self->expression.vtype = TYPE_FLOAT; + self->expression.next = NULL; + } else { + self->expression.vtype = TYPE_FIELD; + self->expression.next = ast_shallow_type(ctx, TYPE_FLOAT); + } + + self->rvalue = false; + self->owner = owner; + ast_propagate_effects(self, owner); + + self->field = field; + if (name) + self->name = util_strdup(name); + else + self->name = NULL; + + return self; +} + +void ast_member_delete(ast_member *self) +{ + /* The owner is always an ast_value, which has .keep=true, + * also: ast_members are usually deleted after the owner, thus + * this will cause invalid access + ast_unref(self->owner); + * once we allow (expression).x to access a vector-member, we need + * to change this: preferably by creating an alternate ast node for this + * purpose that is not garbage-collected. + */ + ast_expression_delete((ast_expression*)self); + mem_d(self->name); + mem_d(self); +} + +bool ast_member_set_name(ast_member *self, const char *name) +{ + if (self->name) + mem_d((void*)self->name); + self->name = util_strdup(name); + return !!self->name; +} + +ast_array_index* ast_array_index_new(lex_ctx_t ctx, ast_expression *array, ast_expression *index) +{ + ast_expression *outtype; + ast_instantiate(ast_array_index, ctx, ast_array_index_delete); + + outtype = array->next; + if (!outtype) { + mem_d(self); + /* Error: field has no type... */ + return NULL; + } + + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_array_index_codegen); + + self->array = array; + self->index = index; + ast_propagate_effects(self, array); + ast_propagate_effects(self, index); + + ast_type_adopt(self, outtype); + if (array->vtype == TYPE_FIELD && outtype->vtype == TYPE_ARRAY) { + if (self->expression.vtype != TYPE_ARRAY) { + compile_error(ast_ctx(self), "array_index node on type"); + ast_array_index_delete(self); + return NULL; + } + self->array = outtype; + self->expression.vtype = TYPE_FIELD; + } + + return self; +} + +void ast_array_index_delete(ast_array_index *self) +{ + if (self->array) + ast_unref(self->array); + if (self->index) + ast_unref(self->index); + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +ast_argpipe* ast_argpipe_new(lex_ctx_t ctx, ast_expression *index) +{ + ast_instantiate(ast_argpipe, ctx, ast_argpipe_delete); + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_argpipe_codegen); + self->index = index; + self->expression.vtype = TYPE_NOEXPR; + return self; +} + +void ast_argpipe_delete(ast_argpipe *self) +{ + if (self->index) + ast_unref(self->index); + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +ast_ifthen* ast_ifthen_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse) +{ + ast_instantiate(ast_ifthen, ctx, ast_ifthen_delete); + if (!ontrue && !onfalse) { + /* because it is invalid */ + mem_d(self); + return NULL; + } + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_ifthen_codegen); + + self->cond = cond; + self->on_true = ontrue; + self->on_false = onfalse; + ast_propagate_effects(self, cond); + if (ontrue) + ast_propagate_effects(self, ontrue); + if (onfalse) + ast_propagate_effects(self, onfalse); + + return self; +} + +void ast_ifthen_delete(ast_ifthen *self) +{ + ast_unref(self->cond); + if (self->on_true) + ast_unref(self->on_true); + if (self->on_false) + ast_unref(self->on_false); + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +ast_ternary* ast_ternary_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse) +{ + ast_expression *exprtype = ontrue; + ast_instantiate(ast_ternary, ctx, ast_ternary_delete); + /* This time NEITHER must be NULL */ + if (!ontrue || !onfalse) { + mem_d(self); + return NULL; + } + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_ternary_codegen); + + self->cond = cond; + self->on_true = ontrue; + self->on_false = onfalse; + ast_propagate_effects(self, cond); + ast_propagate_effects(self, ontrue); + ast_propagate_effects(self, onfalse); + + if (ontrue->vtype == TYPE_NIL) + exprtype = onfalse; + ast_type_adopt(self, exprtype); + + return self; +} + +void ast_ternary_delete(ast_ternary *self) +{ + /* the if()s are only there because computed-gotos can set them + * to NULL + */ + if (self->cond) ast_unref(self->cond); + if (self->on_true) ast_unref(self->on_true); + if (self->on_false) ast_unref(self->on_false); + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +ast_loop* ast_loop_new(lex_ctx_t ctx, + ast_expression *initexpr, + ast_expression *precond, bool pre_not, + ast_expression *postcond, bool post_not, + ast_expression *increment, + ast_expression *body) +{ + ast_instantiate(ast_loop, ctx, ast_loop_delete); + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_loop_codegen); + + self->initexpr = initexpr; + self->precond = precond; + self->postcond = postcond; + self->increment = increment; + self->body = body; + + self->pre_not = pre_not; + self->post_not = post_not; + + if (initexpr) + ast_propagate_effects(self, initexpr); + if (precond) + ast_propagate_effects(self, precond); + if (postcond) + ast_propagate_effects(self, postcond); + if (increment) + ast_propagate_effects(self, increment); + if (body) + ast_propagate_effects(self, body); + + return self; +} + +void ast_loop_delete(ast_loop *self) +{ + if (self->initexpr) + ast_unref(self->initexpr); + if (self->precond) + ast_unref(self->precond); + if (self->postcond) + ast_unref(self->postcond); + if (self->increment) + ast_unref(self->increment); + if (self->body) + ast_unref(self->body); + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +ast_breakcont* ast_breakcont_new(lex_ctx_t ctx, bool iscont, unsigned int levels) +{ + ast_instantiate(ast_breakcont, ctx, ast_breakcont_delete); + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_breakcont_codegen); + + self->is_continue = iscont; + self->levels = levels; + + return self; +} + +void ast_breakcont_delete(ast_breakcont *self) +{ + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +ast_switch* ast_switch_new(lex_ctx_t ctx, ast_expression *op) +{ + ast_instantiate(ast_switch, ctx, ast_switch_delete); + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_switch_codegen); + + self->operand = op; + self->cases = NULL; + + ast_propagate_effects(self, op); + + return self; +} + +void ast_switch_delete(ast_switch *self) +{ + size_t i; + ast_unref(self->operand); + + for (i = 0; i < vec_size(self->cases); ++i) { + if (self->cases[i].value) + ast_unref(self->cases[i].value); + ast_unref(self->cases[i].code); + } + vec_free(self->cases); + + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +ast_label* ast_label_new(lex_ctx_t ctx, const char *name, bool undefined) +{ + ast_instantiate(ast_label, ctx, ast_label_delete); + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_label_codegen); + + self->expression.vtype = TYPE_NOEXPR; + + self->name = util_strdup(name); + self->irblock = NULL; + self->gotos = NULL; + self->undefined = undefined; + + return self; +} + +void ast_label_delete(ast_label *self) +{ + mem_d((void*)self->name); + vec_free(self->gotos); + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +static void ast_label_register_goto(ast_label *self, ast_goto *g) +{ + vec_push(self->gotos, g); +} + +ast_goto* ast_goto_new(lex_ctx_t ctx, const char *name) +{ + ast_instantiate(ast_goto, ctx, ast_goto_delete); + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_goto_codegen); + + self->name = util_strdup(name); + self->target = NULL; + self->irblock_from = NULL; + + return self; +} + +void ast_goto_delete(ast_goto *self) +{ + mem_d((void*)self->name); + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +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) +{ + ast_instantiate(ast_call, ctx, ast_call_delete); + if (!funcexpr->next) { + compile_error(ctx, "not a function"); + mem_d(self); + return NULL; + } + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_call_codegen); + + ast_side_effects(self) = true; + + self->params = NULL; + self->func = funcexpr; + self->va_count = NULL; + + ast_type_adopt(self, funcexpr->next); + + return self; +} + +void ast_call_delete(ast_call *self) +{ + size_t i; + for (i = 0; i < vec_size(self->params); ++i) + ast_unref(self->params[i]); + vec_free(self->params); + + if (self->func) + ast_unref(self->func); + + if (self->va_count) + ast_unref(self->va_count); + + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +static bool ast_call_check_vararg(ast_call *self, ast_expression *va_type, ast_expression *exp_type) +{ + char texp[1024]; + char tgot[1024]; + if (!exp_type) + return true; + if (!va_type || !ast_compare_type(va_type, exp_type)) + { + if (va_type && exp_type) + { + ast_type_to_string(va_type, tgot, sizeof(tgot)); + ast_type_to_string(exp_type, texp, sizeof(texp)); + if (OPTS_FLAG(UNSAFE_VARARGS)) { + if (compile_warning(ast_ctx(self), WARN_UNSAFE_TYPES, + "piped variadic argument differs in type: constrained to type %s, expected type %s", + tgot, texp)) + return false; + } else { + compile_error(ast_ctx(self), + "piped variadic argument differs in type: constrained to type %s, expected type %s", + tgot, texp); + return false; + } + } + else + { + ast_type_to_string(exp_type, texp, sizeof(texp)); + if (OPTS_FLAG(UNSAFE_VARARGS)) { + if (compile_warning(ast_ctx(self), WARN_UNSAFE_TYPES, + "piped variadic argument may differ in type: expected type %s", + texp)) + return false; + } else { + compile_error(ast_ctx(self), + "piped variadic argument may differ in type: expected type %s", + texp); + return false; + } + } + } + return true; +} + +bool ast_call_check_types(ast_call *self, ast_expression *va_type) +{ + char texp[1024]; + char tgot[1024]; + size_t i; + bool retval = true; + const ast_expression *func = self->func; + size_t count = vec_size(self->params); + if (count > vec_size(func->params)) + count = vec_size(func->params); + + for (i = 0; i < count; ++i) { + if (ast_istype(self->params[i], ast_argpipe)) { + /* warn about type safety instead */ + if (i+1 != count) { + compile_error(ast_ctx(self), "argpipe must be the last parameter to a function call"); + return false; + } + if (!ast_call_check_vararg(self, va_type, (ast_expression*)func->params[i])) + retval = false; + } + else if (!ast_compare_type(self->params[i], (ast_expression*)(func->params[i]))) + { + ast_type_to_string(self->params[i], tgot, sizeof(tgot)); + ast_type_to_string((ast_expression*)func->params[i], texp, sizeof(texp)); + compile_error(ast_ctx(self), "invalid type for parameter %u in function call: expected %s, got %s", + (unsigned int)(i+1), texp, tgot); + /* we don't immediately return */ + retval = false; + } + } + count = vec_size(self->params); + if (count > vec_size(func->params) && func->varparam) { + for (; i < count; ++i) { + if (ast_istype(self->params[i], ast_argpipe)) { + /* warn about type safety instead */ + if (i+1 != count) { + compile_error(ast_ctx(self), "argpipe must be the last parameter to a function call"); + return false; + } + if (!ast_call_check_vararg(self, va_type, func->varparam)) + retval = false; + } + else if (!ast_compare_type(self->params[i], func->varparam)) + { + ast_type_to_string(self->params[i], tgot, sizeof(tgot)); + ast_type_to_string(func->varparam, texp, sizeof(texp)); + compile_error(ast_ctx(self), "invalid type for variadic parameter %u in function call: expected %s, got %s", + (unsigned int)(i+1), texp, tgot); + /* we don't immediately return */ + retval = false; + } + } + } + return retval; +} + +ast_store* ast_store_new(lex_ctx_t ctx, int op, + ast_expression *dest, ast_expression *source) +{ + ast_instantiate(ast_store, ctx, ast_store_delete); + ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_store_codegen); + + ast_side_effects(self) = true; + + self->op = op; + self->dest = dest; + self->source = source; + + ast_type_adopt(self, dest); + + return self; +} + +void ast_store_delete(ast_store *self) +{ + ast_unref(self->dest); + ast_unref(self->source); + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +ast_block* ast_block_new(lex_ctx_t ctx) +{ + ast_instantiate(ast_block, ctx, ast_block_delete); + ast_expression_init((ast_expression*)self, + (ast_expression_codegen*)&ast_block_codegen); + + self->locals = NULL; + self->exprs = NULL; + self->collect = NULL; + + return self; +} + +bool ast_block_add_expr(ast_block *self, ast_expression *e) +{ + ast_propagate_effects(self, e); + vec_push(self->exprs, e); + if (self->expression.next) { + ast_delete(self->expression.next); + self->expression.next = NULL; + } + ast_type_adopt(self, e); + return true; +} + +void ast_block_collect(ast_block *self, ast_expression *expr) +{ + vec_push(self->collect, expr); + expr->node.keep = true; +} + +void ast_block_delete(ast_block *self) +{ + size_t i; + for (i = 0; i < vec_size(self->exprs); ++i) + ast_unref(self->exprs[i]); + vec_free(self->exprs); + for (i = 0; i < vec_size(self->locals); ++i) + ast_delete(self->locals[i]); + vec_free(self->locals); + for (i = 0; i < vec_size(self->collect); ++i) + ast_delete(self->collect[i]); + vec_free(self->collect); + ast_expression_delete((ast_expression*)self); + mem_d(self); +} + +void ast_block_set_type(ast_block *self, ast_expression *from) +{ + if (self->expression.next) + ast_delete(self->expression.next); + ast_type_adopt(self, from); +} + +ast_function* ast_function_new(lex_ctx_t ctx, const char *name, ast_value *vtype) +{ + ast_instantiate(ast_function, ctx, ast_function_delete); + + if (!vtype) { + compile_error(ast_ctx(self), "internal error: ast_function_new condition 0"); + goto cleanup; + } else if (vtype->hasvalue || vtype->expression.vtype != TYPE_FUNCTION) { + compile_error(ast_ctx(self), "internal error: ast_function_new condition %i %i type=%i (probably 2 bodies?)", + (int)!vtype, + (int)vtype->hasvalue, + vtype->expression.vtype); + goto cleanup; + } + + self->vtype = vtype; + self->name = name ? util_strdup(name) : NULL; + self->blocks = NULL; + + self->labelcount = 0; + self->builtin = 0; + + self->ir_func = NULL; + self->curblock = NULL; + + self->breakblocks = NULL; + self->continueblocks = NULL; + + vtype->hasvalue = true; + vtype->constval.vfunc = self; + + self->varargs = NULL; + self->argc = NULL; + self->fixedparams = NULL; + self->return_value = NULL; + + self->static_names = NULL; + self->static_count = 0; + + return self; + +cleanup: + mem_d(self); + return NULL; +} + +void ast_function_delete(ast_function *self) +{ + size_t i; + if (self->name) + mem_d((void*)self->name); + if (self->vtype) { + /* ast_value_delete(self->vtype); */ + self->vtype->hasvalue = false; + self->vtype->constval.vfunc = NULL; + /* We use unref - if it was stored in a global table it is supposed + * to be deleted from *there* + */ + ast_unref(self->vtype); + } + for (i = 0; i < vec_size(self->static_names); ++i) + mem_d(self->static_names[i]); + vec_free(self->static_names); + for (i = 0; i < vec_size(self->blocks); ++i) + ast_delete(self->blocks[i]); + vec_free(self->blocks); + vec_free(self->breakblocks); + vec_free(self->continueblocks); + if (self->varargs) + ast_delete(self->varargs); + if (self->argc) + ast_delete(self->argc); + if (self->fixedparams) + ast_unref(self->fixedparams); + if (self->return_value) + ast_unref(self->return_value); + mem_d(self); +} + +const char* ast_function_label(ast_function *self, const char *prefix) +{ + size_t id; + size_t len; + char *from; + + if (!OPTS_OPTION_BOOL(OPTION_DUMP) && + !OPTS_OPTION_BOOL(OPTION_DUMPFIN) && + !OPTS_OPTION_BOOL(OPTION_DEBUG)) + { + return NULL; + } + + id = (self->labelcount++); + len = strlen(prefix); + + from = self->labelbuf + sizeof(self->labelbuf)-1; + *from-- = 0; + do { + *from-- = (id%10) + '0'; + id /= 10; + } while (id); + ++from; + memcpy(from - len, prefix, len); + return from - len; +} + +/*********************************************************************/ +/* AST codegen part + * by convention you must never pass NULL to the 'ir_value **out' + * parameter. If you really don't care about the output, pass a dummy. + * But I can't imagine a pituation where the output is truly unnecessary. + */ + +static void _ast_codegen_output_type(ast_expression *self, ir_value *out) +{ + if (out->vtype == TYPE_FIELD) + out->fieldtype = self->next->vtype; + if (out->vtype == TYPE_FUNCTION) + out->outtype = self->next->vtype; +} + +#define codegen_output_type(a,o) (_ast_codegen_output_type(&((a)->expression),(o))) + +bool ast_value_codegen(ast_value *self, ast_function *func, bool lvalue, ir_value **out) +{ + (void)func; + (void)lvalue; + if (self->expression.vtype == TYPE_NIL) { + *out = func->ir_func->owner->nil; + return true; + } + /* NOTE: This is the codegen for a variable used in an expression. + * It is not the codegen to generate the value. For this purpose, + * ast_local_codegen and ast_global_codegen are to be used before this + * is executed. ast_function_codegen should take care of its locals, + * and the ast-user should take care of ast_global_codegen to be used + * on all the globals. + */ + if (!self->ir_v) { + char tname[1024]; /* typename is reserved in C++ */ + ast_type_to_string((ast_expression*)self, tname, sizeof(tname)); + compile_error(ast_ctx(self), "ast_value used before generated %s %s", tname, self->name); + return false; + } + *out = self->ir_v; + return true; +} + +static bool ast_global_array_set(ast_value *self) +{ + size_t count = vec_size(self->initlist); + size_t i; + + if (count > self->expression.count) { + compile_error(ast_ctx(self), "too many elements in initializer"); + count = self->expression.count; + } + else if (count < self->expression.count) { + /* add this? + compile_warning(ast_ctx(self), "not all elements are initialized"); + */ + } + + for (i = 0; i != count; ++i) { + switch (self->expression.next->vtype) { + case TYPE_FLOAT: + if (!ir_value_set_float(self->ir_values[i], self->initlist[i].vfloat)) + return false; + break; + case TYPE_VECTOR: + if (!ir_value_set_vector(self->ir_values[i], self->initlist[i].vvec)) + return false; + break; + case TYPE_STRING: + if (!ir_value_set_string(self->ir_values[i], self->initlist[i].vstring)) + return false; + break; + case TYPE_ARRAY: + /* we don't support them in any other place yet either */ + compile_error(ast_ctx(self), "TODO: nested arrays"); + return false; + case TYPE_FUNCTION: + /* this requiers a bit more work - similar to the fields I suppose */ + compile_error(ast_ctx(self), "global of type function not properly generated"); + return false; + case TYPE_FIELD: + if (!self->initlist[i].vfield) { + compile_error(ast_ctx(self), "field constant without vfield set"); + return false; + } + if (!self->initlist[i].vfield->ir_v) { + compile_error(ast_ctx(self), "field constant generated before its field"); + return false; + } + if (!ir_value_set_field(self->ir_values[i], self->initlist[i].vfield->ir_v)) + return false; + break; + default: + compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype); + break; + } + } + return true; +} + +static bool check_array(ast_value *self, ast_value *array) +{ + if (array->expression.flags & AST_FLAG_ARRAY_INIT && !array->initlist) { + compile_error(ast_ctx(self), "array without size: %s", self->name); + return false; + } + /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */ + if (!array->expression.count || array->expression.count > OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE)) { + compile_error(ast_ctx(self), "Invalid array of size %lu", (unsigned long)array->expression.count); + return false; + } + return true; +} + +bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield) +{ + ir_value *v = NULL; + + if (self->expression.vtype == TYPE_NIL) { + compile_error(ast_ctx(self), "internal error: trying to generate a variable of TYPE_NIL"); + return false; + } + + if (self->hasvalue && self->expression.vtype == TYPE_FUNCTION) + { + ir_function *func = ir_builder_create_function(ir, self->name, self->expression.next->vtype); + if (!func) + return false; + func->context = ast_ctx(self); + func->value->context = ast_ctx(self); + + self->constval.vfunc->ir_func = func; + self->ir_v = func->value; + 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_ERASABLE; + if (self->expression.flags & AST_FLAG_BLOCK_COVERAGE) + func->flags |= IR_FLAG_BLOCK_COVERAGE; + /* The function is filled later on ast_function_codegen... */ + return true; + } + + if (isfield && self->expression.vtype == TYPE_FIELD) { + ast_expression *fieldtype = self->expression.next; + + if (self->hasvalue) { + compile_error(ast_ctx(self), "TODO: constant field pointers with value"); + goto error; + } + + if (fieldtype->vtype == TYPE_ARRAY) { + size_t ai; + char *name; + size_t namelen; + + ast_expression *elemtype; + int vtype; + ast_value *array = (ast_value*)fieldtype; + + if (!ast_istype(fieldtype, ast_value)) { + compile_error(ast_ctx(self), "internal error: ast_value required"); + return false; + } + + if (!check_array(self, array)) + return false; + + elemtype = array->expression.next; + vtype = elemtype->vtype; + + v = ir_builder_create_field(ir, self->name, vtype); + if (!v) { + compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", self->name); + return false; + } + v->context = ast_ctx(self); + v->unique_life = true; + v->locked = true; + array->ir_v = self->ir_v = v; + + 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_ERASABLE; + + namelen = strlen(self->name); + name = (char*)mem_a(namelen + 16); + util_strncpy(name, self->name, namelen); + + array->ir_values = (ir_value**)mem_a(sizeof(array->ir_values[0]) * array->expression.count); + array->ir_values[0] = v; + for (ai = 1; ai < array->expression.count; ++ai) { + util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai); + array->ir_values[ai] = ir_builder_create_field(ir, name, vtype); + if (!array->ir_values[ai]) { + mem_d(name); + compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", name); + return false; + } + array->ir_values[ai]->context = ast_ctx(self); + array->ir_values[ai]->unique_life = true; + array->ir_values[ai]->locked = true; + if (self->expression.flags & AST_FLAG_INCLUDE_DEF) + self->ir_values[ai]->flags |= IR_FLAG_INCLUDE_DEF; + } + mem_d(name); + } + else + { + v = ir_builder_create_field(ir, self->name, self->expression.next->vtype); + if (!v) + return false; + v->context = ast_ctx(self); + self->ir_v = v; + 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_ERASABLE; + } + return true; + } + + if (self->expression.vtype == TYPE_ARRAY) { + size_t ai; + char *name; + size_t namelen; + + ast_expression *elemtype = self->expression.next; + int vtype = elemtype->vtype; + + if (self->expression.flags & AST_FLAG_ARRAY_INIT && !self->expression.count) { + compile_error(ast_ctx(self), "array `%s' has no size", self->name); + return false; + } + + /* same as with field arrays */ + if (!check_array(self, self)) + return false; + + v = ir_builder_create_global(ir, self->name, vtype); + if (!v) { + compile_error(ast_ctx(self), "ir_builder_create_global failed `%s`", self->name); + return false; + } + v->context = ast_ctx(self); + v->unique_life = true; + v->locked = true; + + 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_ERASABLE; + + namelen = strlen(self->name); + name = (char*)mem_a(namelen + 16); + util_strncpy(name, self->name, namelen); + + self->ir_values = (ir_value**)mem_a(sizeof(self->ir_values[0]) * self->expression.count); + self->ir_values[0] = v; + for (ai = 1; ai < self->expression.count; ++ai) { + util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai); + self->ir_values[ai] = ir_builder_create_global(ir, name, vtype); + if (!self->ir_values[ai]) { + mem_d(name); + compile_error(ast_ctx(self), "ir_builder_create_global failed `%s`", name); + return false; + } + self->ir_values[ai]->context = ast_ctx(self); + self->ir_values[ai]->unique_life = true; + self->ir_values[ai]->locked = true; + if (self->expression.flags & AST_FLAG_INCLUDE_DEF) + self->ir_values[ai]->flags |= IR_FLAG_INCLUDE_DEF; + } + mem_d(name); + } + else + { + /* Arrays don't do this since there's no "array" value which spans across the + * whole thing. + */ + v = ir_builder_create_global(ir, self->name, self->expression.vtype); + if (!v) { + compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", self->name); + return false; + } + codegen_output_type(self, v); + v->context = ast_ctx(self); + } + + /* link us to the ir_value */ + v->cvq = self->cvq; + self->ir_v = v; + + 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_ERASABLE; + + /* initialize */ + if (self->hasvalue) { + switch (self->expression.vtype) + { + case TYPE_FLOAT: + if (!ir_value_set_float(v, self->constval.vfloat)) + goto error; + break; + case TYPE_VECTOR: + if (!ir_value_set_vector(v, self->constval.vvec)) + goto error; + break; + case TYPE_STRING: + if (!ir_value_set_string(v, self->constval.vstring)) + goto error; + break; + case TYPE_ARRAY: + ast_global_array_set(self); + break; + case TYPE_FUNCTION: + compile_error(ast_ctx(self), "global of type function not properly generated"); + goto error; + /* Cannot generate an IR value for a function, + * need a pointer pointing to a function rather. + */ + case TYPE_FIELD: + if (!self->constval.vfield) { + compile_error(ast_ctx(self), "field constant without vfield set"); + goto error; + } + if (!self->constval.vfield->ir_v) { + compile_error(ast_ctx(self), "field constant generated before its field"); + goto error; + } + if (!ir_value_set_field(v, self->constval.vfield->ir_v)) + goto error; + break; + default: + compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype); + break; + } + } + return true; + +error: /* clean up */ + if(v) ir_value_delete(v); + return false; +} + +static bool ast_local_codegen(ast_value *self, ir_function *func, bool param) +{ + ir_value *v = NULL; + + if (self->expression.vtype == TYPE_NIL) { + compile_error(ast_ctx(self), "internal error: trying to generate a variable of TYPE_NIL"); + return false; + } + + if (self->hasvalue && self->expression.vtype == TYPE_FUNCTION) + { + /* Do we allow local functions? I think not... + * this is NOT a function pointer atm. + */ + return false; + } + + if (self->expression.vtype == TYPE_ARRAY) { + size_t ai; + char *name; + size_t namelen; + + ast_expression *elemtype = self->expression.next; + int vtype = elemtype->vtype; + + func->flags |= IR_FLAG_HAS_ARRAYS; + + if (param && !(self->expression.flags & AST_FLAG_IS_VARARG)) { + compile_error(ast_ctx(self), "array-parameters are not supported"); + return false; + } + + /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */ + if (!check_array(self, self)) + return false; + + self->ir_values = (ir_value**)mem_a(sizeof(self->ir_values[0]) * self->expression.count); + if (!self->ir_values) { + compile_error(ast_ctx(self), "failed to allocate array values"); + return false; + } + + v = ir_function_create_local(func, self->name, vtype, param); + if (!v) { + compile_error(ast_ctx(self), "internal error: ir_function_create_local failed"); + return false; + } + v->context = ast_ctx(self); + v->unique_life = true; + v->locked = true; + + namelen = strlen(self->name); + name = (char*)mem_a(namelen + 16); + util_strncpy(name, self->name, namelen); + + self->ir_values[0] = v; + for (ai = 1; ai < self->expression.count; ++ai) { + util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai); + self->ir_values[ai] = ir_function_create_local(func, name, vtype, param); + if (!self->ir_values[ai]) { + compile_error(ast_ctx(self), "internal_error: ir_builder_create_global failed on `%s`", name); + return false; + } + self->ir_values[ai]->context = ast_ctx(self); + self->ir_values[ai]->unique_life = true; + self->ir_values[ai]->locked = true; + } + mem_d(name); + } + else + { + v = ir_function_create_local(func, self->name, self->expression.vtype, param); + if (!v) + return false; + codegen_output_type(self, v); + v->context = ast_ctx(self); + } + + /* A constant local... hmmm... + * I suppose the IR will have to deal with this + */ + if (self->hasvalue) { + switch (self->expression.vtype) + { + case TYPE_FLOAT: + if (!ir_value_set_float(v, self->constval.vfloat)) + goto error; + break; + case TYPE_VECTOR: + if (!ir_value_set_vector(v, self->constval.vvec)) + goto error; + break; + case TYPE_STRING: + if (!ir_value_set_string(v, self->constval.vstring)) + goto error; + break; + default: + compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype); + break; + } + } + + /* link us to the ir_value */ + v->cvq = self->cvq; + self->ir_v = v; + + if (!ast_generate_accessors(self, func->owner)) + return false; + return true; + +error: /* clean up */ + ir_value_delete(v); + return false; +} + +bool ast_generate_accessors(ast_value *self, ir_builder *ir) +{ + size_t i; + bool warn = OPTS_WARN(WARN_USED_UNINITIALIZED); + if (!self->setter || !self->getter) + return true; + for (i = 0; i < self->expression.count; ++i) { + if (!self->ir_values) { + compile_error(ast_ctx(self), "internal error: no array values generated for `%s`", self->name); + return false; + } + if (!self->ir_values[i]) { + compile_error(ast_ctx(self), "internal error: not all array values have been generated for `%s`", self->name); + return false; + } + if (self->ir_values[i]->life) { + compile_error(ast_ctx(self), "internal error: function containing `%s` already generated", self->name); + return false; + } + } + + opts_set(opts.warn, WARN_USED_UNINITIALIZED, false); + if (self->setter) { + if (!ast_global_codegen (self->setter, ir, false) || + !ast_function_codegen(self->setter->constval.vfunc, ir) || + !ir_function_finalize(self->setter->constval.vfunc->ir_func)) + { + compile_error(ast_ctx(self), "internal error: failed to generate setter for `%s`", self->name); + opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn); + return false; + } + } + if (self->getter) { + if (!ast_global_codegen (self->getter, ir, false) || + !ast_function_codegen(self->getter->constval.vfunc, ir) || + !ir_function_finalize(self->getter->constval.vfunc->ir_func)) + { + compile_error(ast_ctx(self), "internal error: failed to generate getter for `%s`", self->name); + opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn); + return false; + } + } + for (i = 0; i < self->expression.count; ++i) { + vec_free(self->ir_values[i]->life); + } + opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn); + return true; +} + +bool ast_function_codegen(ast_function *self, ir_builder *ir) +{ + ir_function *irf; + ir_value *dummy; + ast_expression *ec; + ast_expression_codegen *cgen; + + size_t i; + + (void)ir; + + irf = self->ir_func; + if (!irf) { + compile_error(ast_ctx(self), "internal error: ast_function's related ast_value was not generated yet"); + return false; + } + + /* fill the parameter list */ + ec = &self->vtype->expression; + for (i = 0; i < vec_size(ec->params); ++i) + { + if (ec->params[i]->expression.vtype == TYPE_FIELD) + vec_push(irf->params, ec->params[i]->expression.next->vtype); + else + vec_push(irf->params, ec->params[i]->expression.vtype); + if (!self->builtin) { + if (!ast_local_codegen(ec->params[i], self->ir_func, true)) + return false; + } + } + + if (self->varargs) { + if (!ast_local_codegen(self->varargs, self->ir_func, true)) + return false; + irf->max_varargs = self->varargs->expression.count; + } + + if (self->builtin) { + irf->builtin = self->builtin; + return true; + } + + /* have a local return value variable? */ + if (self->return_value) { + if (!ast_local_codegen(self->return_value, self->ir_func, false)) + return false; + } + + if (!vec_size(self->blocks)) { + compile_error(ast_ctx(self), "function `%s` has no body", self->name); + return false; + } + + irf->first = self->curblock = ir_function_create_block(ast_ctx(self), irf, "entry"); + if (!self->curblock) { + compile_error(ast_ctx(self), "failed to allocate entry block for `%s`", self->name); + return false; + } + + if (self->argc) { + ir_value *va_count; + ir_value *fixed; + ir_value *sub; + if (!ast_local_codegen(self->argc, self->ir_func, true)) + return false; + cgen = self->argc->expression.codegen; + if (!(*cgen)((ast_expression*)(self->argc), self, false, &va_count)) + return false; + cgen = self->fixedparams->expression.codegen; + if (!(*cgen)((ast_expression*)(self->fixedparams), self, false, &fixed)) + return false; + sub = ir_block_create_binop(self->curblock, ast_ctx(self), + ast_function_label(self, "va_count"), INSTR_SUB_F, + ir_builder_get_va_count(ir), fixed); + if (!sub) + return false; + if (!ir_block_create_store_op(self->curblock, ast_ctx(self), INSTR_STORE_F, + va_count, sub)) + { + return false; + } + } + + for (i = 0; i < vec_size(self->blocks); ++i) { + cgen = self->blocks[i]->expression.codegen; + if (!(*cgen)((ast_expression*)self->blocks[i], self, false, &dummy)) + return false; + } + + /* TODO: check return types */ + if (!self->curblock->final) + { + if (!self->vtype->expression.next || + self->vtype->expression.next->vtype == TYPE_VOID) + { + return ir_block_create_return(self->curblock, ast_ctx(self), NULL); + } + else if (vec_size(self->curblock->entries) || self->curblock == irf->first) + { + if (self->return_value) { + cgen = self->return_value->expression.codegen; + if (!(*cgen)((ast_expression*)(self->return_value), self, false, &dummy)) + return false; + return ir_block_create_return(self->curblock, ast_ctx(self), dummy); + } + else if (compile_warning(ast_ctx(self), WARN_MISSING_RETURN_VALUES, + "control reaches end of non-void function (`%s`) via %s", + self->name, self->curblock->label)) + { + return false; + } + return ir_block_create_return(self->curblock, ast_ctx(self), NULL); + } + } + return true; +} + +static bool starts_a_label(ast_expression *ex) +{ + while (ex && ast_istype(ex, ast_block)) { + ast_block *b = (ast_block*)ex; + ex = b->exprs[0]; + } + if (!ex) + return false; + return ast_istype(ex, ast_label); +} + +/* Note, you will not see ast_block_codegen generate ir_blocks. + * To the AST and the IR, blocks are 2 different things. + * In the AST it represents a block of code, usually enclosed in + * curly braces {...}. + * While in the IR it represents a block in terms of control-flow. + */ +bool ast_block_codegen(ast_block *self, ast_function *func, bool lvalue, ir_value **out) +{ + size_t i; + + /* We don't use this + * Note: an ast-representation using the comma-operator + * of the form: (a, b, c) = x should not assign to c... + */ + if (lvalue) { + compile_error(ast_ctx(self), "not an l-value (code-block)"); + return false; + } + + if (self->expression.outr) { + *out = self->expression.outr; + return true; + } + + /* output is NULL at first, we'll have each expression + * assign to out output, thus, a comma-operator represention + * using an ast_block will return the last generated value, + * so: (b, c) + a executed both b and c, and returns c, + * which is then added to a. + */ + *out = NULL; + + /* generate locals */ + for (i = 0; i < vec_size(self->locals); ++i) + { + if (!ast_local_codegen(self->locals[i], func->ir_func, false)) { + if (OPTS_OPTION_BOOL(OPTION_DEBUG)) + compile_error(ast_ctx(self), "failed to generate local `%s`", self->locals[i]->name); + return false; + } + } + + for (i = 0; i < vec_size(self->exprs); ++i) + { + ast_expression_codegen *gen; + if (func->curblock->final && !starts_a_label(self->exprs[i])) { + if (compile_warning(ast_ctx(self->exprs[i]), WARN_UNREACHABLE_CODE, "unreachable statement")) + return false; + continue; + } + gen = self->exprs[i]->codegen; + if (!(*gen)(self->exprs[i], func, false, out)) + return false; + } + + self->expression.outr = *out; + + return true; +} + +bool ast_store_codegen(ast_store *self, ast_function *func, bool lvalue, ir_value **out) +{ + ast_expression_codegen *cgen; + ir_value *left = NULL; + ir_value *right = NULL; + + ast_value *arr; + ast_value *idx = 0; + ast_array_index *ai = NULL; + + if (lvalue && self->expression.outl) { + *out = self->expression.outl; + return true; + } + + if (!lvalue && self->expression.outr) { + *out = self->expression.outr; + return true; + } + + if (ast_istype(self->dest, ast_array_index)) + { + + ai = (ast_array_index*)self->dest; + idx = (ast_value*)ai->index; + + if (ast_istype(ai->index, ast_value) && idx->hasvalue && idx->cvq == CV_CONST) + ai = NULL; + } + + if (ai) { + /* we need to call the setter */ + ir_value *iridx, *funval; + ir_instr *call; + + if (lvalue) { + compile_error(ast_ctx(self), "array-subscript assignment cannot produce lvalues"); + return false; + } + + arr = (ast_value*)ai->array; + if (!ast_istype(ai->array, ast_value) || !arr->setter) { + compile_error(ast_ctx(self), "value has no setter (%s)", arr->name); + return false; + } + + cgen = idx->expression.codegen; + if (!(*cgen)((ast_expression*)(idx), func, false, &iridx)) + return false; + + cgen = arr->setter->expression.codegen; + if (!(*cgen)((ast_expression*)(arr->setter), func, true, &funval)) + return false; + + cgen = self->source->codegen; + if (!(*cgen)((ast_expression*)(self->source), func, false, &right)) + return false; + + call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "store"), funval, false); + if (!call) + return false; + ir_call_param(call, iridx); + ir_call_param(call, right); + self->expression.outr = right; + } + else + { + /* regular code */ + + cgen = self->dest->codegen; + /* lvalue! */ + if (!(*cgen)((ast_expression*)(self->dest), func, true, &left)) + return false; + self->expression.outl = left; + + cgen = self->source->codegen; + /* rvalue! */ + if (!(*cgen)((ast_expression*)(self->source), func, false, &right)) + return false; + + if (!ir_block_create_store_op(func->curblock, ast_ctx(self), self->op, left, right)) + return false; + self->expression.outr = right; + } + + /* Theoretically, an assinment returns its left side as an + * lvalue, if we don't need an lvalue though, we return + * the right side as an rvalue, otherwise we have to + * somehow know whether or not we need to dereference the pointer + * on the left side - that is: OP_LOAD if it was an address. + * Also: in original QC we cannot OP_LOADP *anyway*. + */ + *out = (lvalue ? left : right); + + return true; +} + +bool ast_binary_codegen(ast_binary *self, ast_function *func, bool lvalue, ir_value **out) +{ + ast_expression_codegen *cgen; + ir_value *left, *right; + + /* A binary operation cannot yield an l-value */ + if (lvalue) { + compile_error(ast_ctx(self), "not an l-value (binop)"); + return false; + } + + if (self->expression.outr) { + *out = self->expression.outr; + return true; + } + + if ((OPTS_FLAG(SHORT_LOGIC) || OPTS_FLAG(PERL_LOGIC)) && + (self->op == INSTR_AND || self->op == INSTR_OR)) + { + /* NOTE: The short-logic path will ignore right_first */ + + /* short circuit evaluation */ + ir_block *other, *merge; + ir_block *from_left, *from_right; + ir_instr *phi; + size_t merge_id; + + /* prepare end-block */ + merge_id = vec_size(func->ir_func->blocks); + merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "sce_merge")); + + /* generate the left expression */ + cgen = self->left->codegen; + if (!(*cgen)((ast_expression*)(self->left), func, false, &left)) + return false; + /* remember the block */ + from_left = func->curblock; + + /* create a new block for the right expression */ + other = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "sce_other")); + if (self->op == INSTR_AND) { + /* on AND: left==true -> other */ + if (!ir_block_create_if(func->curblock, ast_ctx(self), left, other, merge)) + return false; + } else { + /* on OR: left==false -> other */ + if (!ir_block_create_if(func->curblock, ast_ctx(self), left, merge, other)) + return false; + } + /* use the likely flag */ + vec_last(func->curblock->instr)->likely = true; + + /* enter the right-expression's block */ + func->curblock = other; + /* generate */ + cgen = self->right->codegen; + if (!(*cgen)((ast_expression*)(self->right), func, false, &right)) + return false; + /* remember block */ + from_right = func->curblock; + + /* jump to the merge block */ + if (!ir_block_create_jump(func->curblock, ast_ctx(self), merge)) + return false; + + vec_remove(func->ir_func->blocks, merge_id, 1); + vec_push(func->ir_func->blocks, merge); + + func->curblock = merge; + phi = ir_block_create_phi(func->curblock, ast_ctx(self), + ast_function_label(func, "sce_value"), + self->expression.vtype); + ir_phi_add(phi, from_left, left); + ir_phi_add(phi, from_right, right); + *out = ir_phi_value(phi); + if (!*out) + return false; + + if (!OPTS_FLAG(PERL_LOGIC)) { + /* cast-to-bool */ + if (OPTS_FLAG(CORRECT_LOGIC) && (*out)->vtype == TYPE_VECTOR) { + *out = ir_block_create_unary(func->curblock, ast_ctx(self), + ast_function_label(func, "sce_bool_v"), + INSTR_NOT_V, *out); + if (!*out) + return false; + *out = ir_block_create_unary(func->curblock, ast_ctx(self), + ast_function_label(func, "sce_bool"), + INSTR_NOT_F, *out); + if (!*out) + return false; + } + else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && (*out)->vtype == TYPE_STRING) { + *out = ir_block_create_unary(func->curblock, ast_ctx(self), + ast_function_label(func, "sce_bool_s"), + INSTR_NOT_S, *out); + if (!*out) + return false; + *out = ir_block_create_unary(func->curblock, ast_ctx(self), + ast_function_label(func, "sce_bool"), + INSTR_NOT_F, *out); + if (!*out) + return false; + } + else { + *out = ir_block_create_binop(func->curblock, ast_ctx(self), + ast_function_label(func, "sce_bool"), + INSTR_AND, *out, *out); + if (!*out) + return false; + } + } + + self->expression.outr = *out; + codegen_output_type(self, *out); + return true; + } + + if (self->right_first) { + cgen = self->right->codegen; + if (!(*cgen)((ast_expression*)(self->right), func, false, &right)) + return false; + cgen = self->left->codegen; + if (!(*cgen)((ast_expression*)(self->left), func, false, &left)) + return false; + } else { + cgen = self->left->codegen; + if (!(*cgen)((ast_expression*)(self->left), func, false, &left)) + return false; + cgen = self->right->codegen; + if (!(*cgen)((ast_expression*)(self->right), func, false, &right)) + return false; + } + + *out = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "bin"), + self->op, left, right); + if (!*out) + return false; + self->expression.outr = *out; + codegen_output_type(self, *out); + + return true; +} + +bool ast_binstore_codegen(ast_binstore *self, ast_function *func, bool lvalue, ir_value **out) +{ + ast_expression_codegen *cgen; + ir_value *leftl = NULL, *leftr, *right, *bin; + + ast_value *arr; + ast_value *idx = 0; + ast_array_index *ai = NULL; + ir_value *iridx = NULL; + + if (lvalue && self->expression.outl) { + *out = self->expression.outl; + return true; + } + + if (!lvalue && self->expression.outr) { + *out = self->expression.outr; + return true; + } + + if (ast_istype(self->dest, ast_array_index)) + { + + ai = (ast_array_index*)self->dest; + idx = (ast_value*)ai->index; + + if (ast_istype(ai->index, ast_value) && idx->hasvalue && idx->cvq == CV_CONST) + ai = NULL; + } + + /* for a binstore we need both an lvalue and an rvalue for the left side */ + /* rvalue of destination! */ + if (ai) { + cgen = idx->expression.codegen; + if (!(*cgen)((ast_expression*)(idx), func, false, &iridx)) + return false; + } + cgen = self->dest->codegen; + if (!(*cgen)((ast_expression*)(self->dest), func, false, &leftr)) + return false; + + /* source as rvalue only */ + cgen = self->source->codegen; + if (!(*cgen)((ast_expression*)(self->source), func, false, &right)) + return false; + + /* now the binary */ + bin = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "binst"), + self->opbin, leftr, right); + self->expression.outr = bin; + + + if (ai) { + /* we need to call the setter */ + ir_value *funval; + ir_instr *call; + + if (lvalue) { + compile_error(ast_ctx(self), "array-subscript assignment cannot produce lvalues"); + return false; + } + + arr = (ast_value*)ai->array; + if (!ast_istype(ai->array, ast_value) || !arr->setter) { + compile_error(ast_ctx(self), "value has no setter (%s)", arr->name); + return false; + } + + cgen = arr->setter->expression.codegen; + if (!(*cgen)((ast_expression*)(arr->setter), func, true, &funval)) + return false; + + call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "store"), funval, false); + if (!call) + return false; + ir_call_param(call, iridx); + ir_call_param(call, bin); + self->expression.outr = bin; + } else { + /* now store them */ + cgen = self->dest->codegen; + /* lvalue of destination */ + if (!(*cgen)((ast_expression*)(self->dest), func, true, &leftl)) + return false; + self->expression.outl = leftl; + + if (!ir_block_create_store_op(func->curblock, ast_ctx(self), self->opstore, leftl, bin)) + return false; + self->expression.outr = bin; + } + + /* Theoretically, an assinment returns its left side as an + * lvalue, if we don't need an lvalue though, we return + * the right side as an rvalue, otherwise we have to + * somehow know whether or not we need to dereference the pointer + * on the left side - that is: OP_LOAD if it was an address. + * Also: in original QC we cannot OP_LOADP *anyway*. + */ + *out = (lvalue ? leftl : bin); + + return true; +} + +bool ast_unary_codegen(ast_unary *self, ast_function *func, bool lvalue, ir_value **out) +{ + ast_expression_codegen *cgen; + ir_value *operand; + + /* An unary operation cannot yield an l-value */ + if (lvalue) { + compile_error(ast_ctx(self), "not an l-value (binop)"); + return false; + } + + if (self->expression.outr) { + *out = self->expression.outr; + return true; + } + + cgen = self->operand->codegen; + /* lvalue! */ + if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand)) + return false; + + *out = ir_block_create_unary(func->curblock, ast_ctx(self), ast_function_label(func, "unary"), + self->op, operand); + if (!*out) + return false; + self->expression.outr = *out; + + return true; +} + +bool ast_return_codegen(ast_return *self, ast_function *func, bool lvalue, ir_value **out) +{ + ast_expression_codegen *cgen; + ir_value *operand; + + *out = NULL; + + /* In the context of a return operation, we don't actually return + * anything... + */ + if (lvalue) { + compile_error(ast_ctx(self), "return-expression is not an l-value"); + return false; + } + + if (self->expression.outr) { + compile_error(ast_ctx(self), "internal error: ast_return cannot be reused, it bears no result!"); + return false; + } + self->expression.outr = (ir_value*)1; + + if (self->operand) { + cgen = self->operand->codegen; + /* lvalue! */ + if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand)) + return false; + + if (!ir_block_create_return(func->curblock, ast_ctx(self), operand)) + return false; + } else { + if (!ir_block_create_return(func->curblock, ast_ctx(self), NULL)) + return false; + } + + return true; +} + +bool ast_entfield_codegen(ast_entfield *self, ast_function *func, bool lvalue, ir_value **out) +{ + ast_expression_codegen *cgen; + ir_value *ent, *field; + + /* This function needs to take the 'lvalue' flag into account! + * As lvalue we provide a field-pointer, as rvalue we provide the + * value in a temp. + */ + + if (lvalue && self->expression.outl) { + *out = self->expression.outl; + return true; + } + + if (!lvalue && self->expression.outr) { + *out = self->expression.outr; + return true; + } + + cgen = self->entity->codegen; + if (!(*cgen)((ast_expression*)(self->entity), func, false, &ent)) + return false; + + cgen = self->field->codegen; + if (!(*cgen)((ast_expression*)(self->field), func, false, &field)) + return false; + + if (lvalue) { + /* address! */ + *out = ir_block_create_fieldaddress(func->curblock, ast_ctx(self), ast_function_label(func, "efa"), + ent, field); + } else { + *out = ir_block_create_load_from_ent(func->curblock, ast_ctx(self), ast_function_label(func, "efv"), + ent, field, self->expression.vtype); + /* Done AFTER error checking: + codegen_output_type(self, *out); + */ + } + if (!*out) { + compile_error(ast_ctx(self), "failed to create %s instruction (output type %s)", + (lvalue ? "ADDRESS" : "FIELD"), + type_name[self->expression.vtype]); + return false; + } + if (!lvalue) + codegen_output_type(self, *out); + + if (lvalue) + self->expression.outl = *out; + else + self->expression.outr = *out; + + /* Hm that should be it... */ + return true; +} + +bool ast_member_codegen(ast_member *self, ast_function *func, bool lvalue, ir_value **out) +{ + ast_expression_codegen *cgen; + ir_value *vec; + + /* in QC this is always an lvalue */ + if (lvalue && self->rvalue) { + compile_error(ast_ctx(self), "not an l-value (member access)"); + return false; + } + if (self->expression.outl) { + *out = self->expression.outl; + return true; + } + + cgen = self->owner->codegen; + if (!(*cgen)((ast_expression*)(self->owner), func, false, &vec)) + return false; + + if (vec->vtype != TYPE_VECTOR && + !(vec->vtype == TYPE_FIELD && self->owner->next->vtype == TYPE_VECTOR)) + { + return false; + } + + *out = ir_value_vector_member(vec, self->field); + self->expression.outl = *out; + + return (*out != NULL); +} + +bool ast_array_index_codegen(ast_array_index *self, ast_function *func, bool lvalue, ir_value **out) +{ + ast_value *arr; + ast_value *idx; + + if (!lvalue && self->expression.outr) { + *out = self->expression.outr; + return true; + } + if (lvalue && self->expression.outl) { + *out = self->expression.outl; + return true; + } + + if (!ast_istype(self->array, ast_value)) { + compile_error(ast_ctx(self), "array indexing this way is not supported"); + /* note this would actually be pointer indexing because the left side is + * not an actual array but (hopefully) an indexable expression. + * Once we get integer arithmetic, and GADDRESS/GSTORE/GLOAD instruction + * support this path will be filled. + */ + return false; + } + + arr = (ast_value*)self->array; + idx = (ast_value*)self->index; + + if (!ast_istype(self->index, ast_value) || !idx->hasvalue || idx->cvq != CV_CONST) { + /* Time to use accessor functions */ + ast_expression_codegen *cgen; + ir_value *iridx, *funval; + ir_instr *call; + + if (lvalue) { + compile_error(ast_ctx(self), "(.2) array indexing here needs a compile-time constant"); + return false; + } + + if (!arr->getter) { + compile_error(ast_ctx(self), "value has no getter, don't know how to index it"); + return false; + } + + cgen = self->index->codegen; + if (!(*cgen)((ast_expression*)(self->index), func, false, &iridx)) + return false; + + cgen = arr->getter->expression.codegen; + if (!(*cgen)((ast_expression*)(arr->getter), func, true, &funval)) + return false; + + call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "fetch"), funval, false); + if (!call) + return false; + ir_call_param(call, iridx); + + *out = ir_call_value(call); + self->expression.outr = *out; + (*out)->vtype = self->expression.vtype; + codegen_output_type(self, *out); + return true; + } + + if (idx->expression.vtype == TYPE_FLOAT) { + unsigned int arridx = idx->constval.vfloat; + if (arridx >= self->array->count) + { + compile_error(ast_ctx(self), "array index out of bounds: %i", arridx); + return false; + } + *out = arr->ir_values[arridx]; + } + else if (idx->expression.vtype == TYPE_INTEGER) { + unsigned int arridx = idx->constval.vint; + if (arridx >= self->array->count) + { + compile_error(ast_ctx(self), "array index out of bounds: %i", arridx); + return false; + } + *out = arr->ir_values[arridx]; + } + else { + compile_error(ast_ctx(self), "array indexing here needs an integer constant"); + return false; + } + (*out)->vtype = self->expression.vtype; + codegen_output_type(self, *out); + return true; +} + +bool ast_argpipe_codegen(ast_argpipe *self, ast_function *func, bool lvalue, ir_value **out) +{ + *out = NULL; + if (lvalue) { + compile_error(ast_ctx(self), "argpipe node: not an lvalue"); + return false; + } + (void)func; + (void)out; + compile_error(ast_ctx(self), "TODO: argpipe codegen not implemented"); + return false; +} + +bool ast_ifthen_codegen(ast_ifthen *self, ast_function *func, bool lvalue, ir_value **out) +{ + ast_expression_codegen *cgen; + + ir_value *condval; + ir_value *dummy; + + ir_block *cond; + ir_block *ontrue; + ir_block *onfalse; + ir_block *ontrue_endblock = NULL; + ir_block *onfalse_endblock = NULL; + ir_block *merge = NULL; + int fold = 0; + + /* We don't output any value, thus also don't care about r/lvalue */ + (void)out; + (void)lvalue; + + if (self->expression.outr) { + compile_error(ast_ctx(self), "internal error: ast_ifthen cannot be reused, it bears no result!"); + return false; + } + self->expression.outr = (ir_value*)1; + + /* generate the condition */ + cgen = self->cond->codegen; + if (!(*cgen)((ast_expression*)(self->cond), func, false, &condval)) + return false; + /* update the block which will get the jump - because short-logic or ternaries may have changed this */ + cond = func->curblock; + + /* try constant folding away the condition */ + if ((fold = fold_cond_ifthen(condval, func, self)) != -1) + return fold; + + if (self->on_true) { + /* create on-true block */ + ontrue = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "ontrue")); + if (!ontrue) + return false; + + /* enter the block */ + func->curblock = ontrue; + + /* generate */ + cgen = self->on_true->codegen; + if (!(*cgen)((ast_expression*)(self->on_true), func, false, &dummy)) + return false; + + /* we now need to work from the current endpoint */ + ontrue_endblock = func->curblock; + } else + ontrue = NULL; + + /* on-false path */ + if (self->on_false) { + /* create on-false block */ + onfalse = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "onfalse")); + if (!onfalse) + return false; + + /* enter the block */ + func->curblock = onfalse; + + /* generate */ + cgen = self->on_false->codegen; + if (!(*cgen)((ast_expression*)(self->on_false), func, false, &dummy)) + return false; + + /* we now need to work from the current endpoint */ + onfalse_endblock = func->curblock; + } else + onfalse = NULL; + + /* Merge block were they all merge in to */ + if (!ontrue || !onfalse || !ontrue_endblock->final || !onfalse_endblock->final) + { + merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "endif")); + if (!merge) + return false; + /* add jumps ot the merge block */ + if (ontrue && !ontrue_endblock->final && !ir_block_create_jump(ontrue_endblock, ast_ctx(self), merge)) + return false; + if (onfalse && !onfalse_endblock->final && !ir_block_create_jump(onfalse_endblock, ast_ctx(self), merge)) + return false; + + /* Now enter the merge block */ + func->curblock = merge; + } + + /* we create the if here, that way all blocks are ordered :) + */ + if (!ir_block_create_if(cond, ast_ctx(self), condval, + (ontrue ? ontrue : merge), + (onfalse ? onfalse : merge))) + { + return false; + } + + return true; +} + +bool ast_ternary_codegen(ast_ternary *self, ast_function *func, bool lvalue, ir_value **out) +{ + ast_expression_codegen *cgen; + + ir_value *condval; + ir_value *trueval, *falseval; + ir_instr *phi; + + ir_block *cond = func->curblock; + ir_block *cond_out = NULL; + ir_block *ontrue, *ontrue_out = NULL; + ir_block *onfalse, *onfalse_out = NULL; + ir_block *merge; + int fold = 0; + + /* Ternary can never create an lvalue... */ + if (lvalue) + return false; + + /* In theory it shouldn't be possible to pass through a node twice, but + * in case we add any kind of optimization pass for the AST itself, it + * may still happen, thus we remember a created ir_value and simply return one + * if it already exists. + */ + if (self->expression.outr) { + *out = self->expression.outr; + return true; + } + + /* In the following, contraty to ast_ifthen, we assume both paths exist. */ + + /* generate the condition */ + func->curblock = cond; + cgen = self->cond->codegen; + if (!(*cgen)((ast_expression*)(self->cond), func, false, &condval)) + return false; + cond_out = func->curblock; + + /* try constant folding away the condition */ + if ((fold = fold_cond_ternary(condval, func, self)) != -1) + return fold; + + /* create on-true block */ + ontrue = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_T")); + if (!ontrue) + return false; + else + { + /* enter the block */ + func->curblock = ontrue; + + /* generate */ + cgen = self->on_true->codegen; + if (!(*cgen)((ast_expression*)(self->on_true), func, false, &trueval)) + return false; + + ontrue_out = func->curblock; + } + + /* create on-false block */ + onfalse = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_F")); + if (!onfalse) + return false; + else + { + /* enter the block */ + func->curblock = onfalse; + + /* generate */ + cgen = self->on_false->codegen; + if (!(*cgen)((ast_expression*)(self->on_false), func, false, &falseval)) + return false; + + onfalse_out = func->curblock; + } + + /* create merge block */ + merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_out")); + if (!merge) + return false; + /* jump to merge block */ + if (!ir_block_create_jump(ontrue_out, ast_ctx(self), merge)) + return false; + if (!ir_block_create_jump(onfalse_out, ast_ctx(self), merge)) + return false; + + /* create if instruction */ + if (!ir_block_create_if(cond_out, ast_ctx(self), condval, ontrue, onfalse)) + return false; + + /* Now enter the merge block */ + func->curblock = merge; + + /* Here, now, we need a PHI node + * but first some sanity checking... + */ + if (trueval->vtype != falseval->vtype && trueval->vtype != TYPE_NIL && falseval->vtype != TYPE_NIL) { + /* error("ternary with different types on the two sides"); */ + compile_error(ast_ctx(self), "internal error: ternary operand types invalid"); + return false; + } + + /* create PHI */ + phi = ir_block_create_phi(merge, ast_ctx(self), ast_function_label(func, "phi"), self->expression.vtype); + if (!phi) { + compile_error(ast_ctx(self), "internal error: failed to generate phi node"); + return false; + } + ir_phi_add(phi, ontrue_out, trueval); + ir_phi_add(phi, onfalse_out, falseval); + + self->expression.outr = ir_phi_value(phi); + *out = self->expression.outr; + + codegen_output_type(self, *out); + + return true; +} + +bool ast_loop_codegen(ast_loop *self, ast_function *func, bool lvalue, ir_value **out) +{ + ast_expression_codegen *cgen; + + ir_value *dummy = NULL; + ir_value *precond = NULL; + ir_value *postcond = NULL; + + /* Since we insert some jumps "late" so we have blocks + * ordered "nicely", we need to keep track of the actual end-blocks + * of expressions to add the jumps to. + */ + ir_block *bbody = NULL, *end_bbody = NULL; + ir_block *bprecond = NULL, *end_bprecond = NULL; + ir_block *bpostcond = NULL, *end_bpostcond = NULL; + ir_block *bincrement = NULL, *end_bincrement = NULL; + ir_block *bout = NULL, *bin = NULL; + + /* let's at least move the outgoing block to the end */ + size_t bout_id; + + /* 'break' and 'continue' need to be able to find the right blocks */ + ir_block *bcontinue = NULL; + ir_block *bbreak = NULL; + + ir_block *tmpblock = NULL; + + (void)lvalue; + (void)out; + + if (self->expression.outr) { + compile_error(ast_ctx(self), "internal error: ast_loop cannot be reused, it bears no result!"); + return false; + } + self->expression.outr = (ir_value*)1; + + /* NOTE: + * Should we ever need some kind of block ordering, better make this function + * move blocks around than write a block ordering algorithm later... after all + * the ast and ir should work together, not against each other. + */ + + /* initexpr doesn't get its own block, it's pointless, it could create more blocks + * anyway if for example it contains a ternary. + */ + if (self->initexpr) + { + cgen = self->initexpr->codegen; + if (!(*cgen)((ast_expression*)(self->initexpr), func, false, &dummy)) + return false; + } + + /* Store the block from which we enter this chaos */ + bin = func->curblock; + + /* The pre-loop condition needs its own block since we + * need to be able to jump to the start of that expression. + */ + if (self->precond) + { + bprecond = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "pre_loop_cond")); + if (!bprecond) + return false; + + /* the pre-loop-condition the least important place to 'continue' at */ + bcontinue = bprecond; + + /* enter */ + func->curblock = bprecond; + + /* generate */ + cgen = self->precond->codegen; + if (!(*cgen)((ast_expression*)(self->precond), func, false, &precond)) + return false; + + end_bprecond = func->curblock; + } else { + bprecond = end_bprecond = NULL; + } + + /* Now the next blocks won't be ordered nicely, but we need to + * generate them this early for 'break' and 'continue'. + */ + if (self->increment) { + bincrement = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "loop_increment")); + if (!bincrement) + return false; + bcontinue = bincrement; /* increment comes before the pre-loop-condition */ + } else { + bincrement = end_bincrement = NULL; + } + + if (self->postcond) { + bpostcond = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "post_loop_cond")); + if (!bpostcond) + return false; + bcontinue = bpostcond; /* postcond comes before the increment */ + } else { + bpostcond = end_bpostcond = NULL; + } + + bout_id = vec_size(func->ir_func->blocks); + bout = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "after_loop")); + if (!bout) + return false; + bbreak = bout; + + /* The loop body... */ + /* if (self->body) */ + { + bbody = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "loop_body")); + if (!bbody) + return false; + + /* enter */ + func->curblock = bbody; + + vec_push(func->breakblocks, bbreak); + if (bcontinue) + vec_push(func->continueblocks, bcontinue); + else + vec_push(func->continueblocks, bbody); + + /* generate */ + if (self->body) { + cgen = self->body->codegen; + if (!(*cgen)((ast_expression*)(self->body), func, false, &dummy)) + return false; + } + + end_bbody = func->curblock; + vec_pop(func->breakblocks); + vec_pop(func->continueblocks); + } + + /* post-loop-condition */ + if (self->postcond) + { + /* enter */ + func->curblock = bpostcond; + + /* generate */ + cgen = self->postcond->codegen; + if (!(*cgen)((ast_expression*)(self->postcond), func, false, &postcond)) + return false; + + end_bpostcond = func->curblock; + } + + /* The incrementor */ + if (self->increment) + { + /* enter */ + func->curblock = bincrement; + + /* generate */ + cgen = self->increment->codegen; + if (!(*cgen)((ast_expression*)(self->increment), func, false, &dummy)) + return false; + + end_bincrement = func->curblock; + } + + /* In any case now, we continue from the outgoing block */ + func->curblock = bout; + + /* Now all blocks are in place */ + /* From 'bin' we jump to whatever comes first */ + if (bprecond) tmpblock = bprecond; + else tmpblock = bbody; /* can never be null */ + + /* DEAD CODE + else if (bpostcond) tmpblock = bpostcond; + else tmpblock = bout; + */ + + if (!ir_block_create_jump(bin, ast_ctx(self), tmpblock)) + return false; + + /* From precond */ + if (bprecond) + { + ir_block *ontrue, *onfalse; + ontrue = bbody; /* can never be null */ + + /* all of this is dead code + else if (bincrement) ontrue = bincrement; + else ontrue = bpostcond; + */ + + onfalse = bout; + if (self->pre_not) { + tmpblock = ontrue; + ontrue = onfalse; + onfalse = tmpblock; + } + if (!ir_block_create_if(end_bprecond, ast_ctx(self), precond, ontrue, onfalse)) + return false; + } + + /* from body */ + if (bbody) + { + if (bincrement) tmpblock = bincrement; + else if (bpostcond) tmpblock = bpostcond; + else if (bprecond) tmpblock = bprecond; + else tmpblock = bbody; + if (!end_bbody->final && !ir_block_create_jump(end_bbody, ast_ctx(self), tmpblock)) + return false; + } + + /* from increment */ + if (bincrement) + { + if (bpostcond) tmpblock = bpostcond; + else if (bprecond) tmpblock = bprecond; + else if (bbody) tmpblock = bbody; + else tmpblock = bout; + if (!ir_block_create_jump(end_bincrement, ast_ctx(self), tmpblock)) + return false; + } + + /* from postcond */ + if (bpostcond) + { + ir_block *ontrue, *onfalse; + if (bprecond) ontrue = bprecond; + else ontrue = bbody; /* can never be null */ + + /* all of this is dead code + else if (bincrement) ontrue = bincrement; + else ontrue = bpostcond; + */ + + onfalse = bout; + if (self->post_not) { + tmpblock = ontrue; + ontrue = onfalse; + onfalse = tmpblock; + } + if (!ir_block_create_if(end_bpostcond, ast_ctx(self), postcond, ontrue, onfalse)) + return false; + } + + /* Move 'bout' to the end */ + vec_remove(func->ir_func->blocks, bout_id, 1); + vec_push(func->ir_func->blocks, bout); + + return true; +} + +bool ast_breakcont_codegen(ast_breakcont *self, ast_function *func, bool lvalue, ir_value **out) +{ + ir_block *target; + + *out = NULL; + + if (lvalue) { + compile_error(ast_ctx(self), "break/continue expression is not an l-value"); + return false; + } + + if (self->expression.outr) { + compile_error(ast_ctx(self), "internal error: ast_breakcont cannot be reused!"); + return false; + } + self->expression.outr = (ir_value*)1; + + if (self->is_continue) + target = func->continueblocks[vec_size(func->continueblocks)-1-self->levels]; + else + target = func->breakblocks[vec_size(func->breakblocks)-1-self->levels]; + + if (!target) { + compile_error(ast_ctx(self), "%s is lacking a target block", (self->is_continue ? "continue" : "break")); + return false; + } + + if (!ir_block_create_jump(func->curblock, ast_ctx(self), target)) + return false; + return true; +} + +bool ast_switch_codegen(ast_switch *self, ast_function *func, bool lvalue, ir_value **out) +{ + ast_expression_codegen *cgen; + + ast_switch_case *def_case = NULL; + ir_block *def_bfall = NULL; + ir_block *def_bfall_to = NULL; + bool set_def_bfall_to = false; + + ir_value *dummy = NULL; + ir_value *irop = NULL; + ir_block *bout = NULL; + ir_block *bfall = NULL; + size_t bout_id; + size_t c; + + char typestr[1024]; + uint16_t cmpinstr; + + if (lvalue) { + compile_error(ast_ctx(self), "switch expression is not an l-value"); + return false; + } + + if (self->expression.outr) { + compile_error(ast_ctx(self), "internal error: ast_switch cannot be reused!"); + return false; + } + self->expression.outr = (ir_value*)1; + + (void)lvalue; + (void)out; + + cgen = self->operand->codegen; + if (!(*cgen)((ast_expression*)(self->operand), func, false, &irop)) + return false; + + if (!vec_size(self->cases)) + return true; + + cmpinstr = type_eq_instr[irop->vtype]; + if (cmpinstr >= VINSTR_END) { + ast_type_to_string(self->operand, typestr, sizeof(typestr)); + compile_error(ast_ctx(self), "invalid type to perform a switch on: %s", typestr); + return false; + } + + bout_id = vec_size(func->ir_func->blocks); + bout = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "after_switch")); + if (!bout) + return false; + + /* setup the break block */ + vec_push(func->breakblocks, bout); + + /* Now create all cases */ + for (c = 0; c < vec_size(self->cases); ++c) { + ir_value *cond, *val; + ir_block *bcase, *bnot; + size_t bnot_id; + + ast_switch_case *swcase = &self->cases[c]; + + if (swcase->value) { + /* A regular case */ + /* generate the condition operand */ + cgen = swcase->value->codegen; + if (!(*cgen)((ast_expression*)(swcase->value), func, false, &val)) + return false; + /* generate the condition */ + cond = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "switch_eq"), cmpinstr, irop, val); + if (!cond) + return false; + + bcase = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "case")); + bnot_id = vec_size(func->ir_func->blocks); + bnot = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "not_case")); + if (!bcase || !bnot) + return false; + if (set_def_bfall_to) { + set_def_bfall_to = false; + def_bfall_to = bcase; + } + if (!ir_block_create_if(func->curblock, ast_ctx(self), cond, bcase, bnot)) + return false; + + /* Make the previous case-end fall through */ + if (bfall && !bfall->final) { + if (!ir_block_create_jump(bfall, ast_ctx(self), bcase)) + return false; + } + + /* enter the case */ + func->curblock = bcase; + cgen = swcase->code->codegen; + if (!(*cgen)((ast_expression*)swcase->code, func, false, &dummy)) + return false; + + /* remember this block to fall through from */ + bfall = func->curblock; + + /* enter the else and move it down */ + func->curblock = bnot; + vec_remove(func->ir_func->blocks, bnot_id, 1); + vec_push(func->ir_func->blocks, bnot); + } else { + /* The default case */ + /* Remember where to fall through from: */ + def_bfall = bfall; + bfall = NULL; + /* remember which case it was */ + def_case = swcase; + /* And the next case will be remembered */ + set_def_bfall_to = true; + } + } + + /* Jump from the last bnot to bout */ + if (bfall && !bfall->final && !ir_block_create_jump(bfall, ast_ctx(self), bout)) { + /* + astwarning(ast_ctx(bfall), WARN_???, "missing break after last case"); + */ + return false; + } + + /* If there was a default case, put it down here */ + if (def_case) { + ir_block *bcase; + + /* No need to create an extra block */ + bcase = func->curblock; + + /* Insert the fallthrough jump */ + if (def_bfall && !def_bfall->final) { + if (!ir_block_create_jump(def_bfall, ast_ctx(self), bcase)) + return false; + } + + /* Now generate the default code */ + cgen = def_case->code->codegen; + if (!(*cgen)((ast_expression*)def_case->code, func, false, &dummy)) + return false; + + /* see if we need to fall through */ + if (def_bfall_to && !func->curblock->final) + { + if (!ir_block_create_jump(func->curblock, ast_ctx(self), def_bfall_to)) + return false; + } + } + + /* Jump from the last bnot to bout */ + if (!func->curblock->final && !ir_block_create_jump(func->curblock, ast_ctx(self), bout)) + return false; + /* enter the outgoing block */ + func->curblock = bout; + + /* restore the break block */ + vec_pop(func->breakblocks); + + /* Move 'bout' to the end, it's nicer */ + vec_remove(func->ir_func->blocks, bout_id, 1); + vec_push(func->ir_func->blocks, bout); + + return true; +} + +bool ast_label_codegen(ast_label *self, ast_function *func, bool lvalue, ir_value **out) +{ + size_t i; + ir_value *dummy; + + if (self->undefined) { + compile_error(ast_ctx(self), "internal error: ast_label never defined"); + return false; + } + + *out = NULL; + if (lvalue) { + compile_error(ast_ctx(self), "internal error: ast_label cannot be an lvalue"); + return false; + } + + /* simply create a new block and jump to it */ + self->irblock = ir_function_create_block(ast_ctx(self), func->ir_func, self->name); + if (!self->irblock) { + compile_error(ast_ctx(self), "failed to allocate label block `%s`", self->name); + return false; + } + if (!func->curblock->final) { + if (!ir_block_create_jump(func->curblock, ast_ctx(self), self->irblock)) + return false; + } + + /* enter the new block */ + func->curblock = self->irblock; + + /* Generate all the leftover gotos */ + for (i = 0; i < vec_size(self->gotos); ++i) { + if (!ast_goto_codegen(self->gotos[i], func, false, &dummy)) + return false; + } + + return true; +} + +bool ast_goto_codegen(ast_goto *self, ast_function *func, bool lvalue, ir_value **out) +{ + *out = NULL; + if (lvalue) { + compile_error(ast_ctx(self), "internal error: ast_goto cannot be an lvalue"); + return false; + } + + if (self->target->irblock) { + if (self->irblock_from) { + /* we already tried once, this is the callback */ + self->irblock_from->final = false; + if (!ir_block_create_goto(self->irblock_from, ast_ctx(self), self->target->irblock)) { + compile_error(ast_ctx(self), "failed to generate goto to `%s`", self->name); + return false; + } + } + else + { + if (!ir_block_create_goto(func->curblock, ast_ctx(self), self->target->irblock)) { + compile_error(ast_ctx(self), "failed to generate goto to `%s`", self->name); + return false; + } + } + } + else + { + /* the target has not yet been created... + * close this block in a sneaky way: + */ + func->curblock->final = true; + self->irblock_from = func->curblock; + ast_label_register_goto(self->target, self); + } + + return true; +} + +#include +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; + ir_value **params; + ir_instr *callinstr; + size_t i; + + ir_value *funval = NULL; + + /* return values are never lvalues */ + if (lvalue) { + compile_error(ast_ctx(self), "not an l-value (function call)"); + return false; + } + + if (self->expression.outr) { + *out = self->expression.outr; + return true; + } + + cgen = self->func->codegen; + if (!(*cgen)((ast_expression*)(self->func), func, false, &funval)) + return false; + if (!funval) + return false; + + params = NULL; + + /* parameters */ + for (i = 0; i < vec_size(self->params); ++i) + { + ir_value *param; + ast_expression *expr = self->params[i]; + + cgen = expr->codegen; + if (!(*cgen)(expr, func, false, ¶m)) + goto error; + if (!param) + goto error; + vec_push(params, param); + } + + /* varargs counter */ + if (self->va_count) { + ir_value *va_count; + ir_builder *builder = func->curblock->owner->owner; + cgen = self->va_count->codegen; + if (!(*cgen)((ast_expression*)(self->va_count), func, false, &va_count)) + return false; + if (!ir_block_create_store_op(func->curblock, ast_ctx(self), INSTR_STORE_F, + ir_builder_get_va_count(builder), va_count)) + { + return false; + } + } + + callinstr = ir_block_create_call(func->curblock, ast_ctx(self), + ast_function_label(func, "call"), + funval, !!(self->func->flags & AST_FLAG_NORETURN)); + if (!callinstr) + goto error; + + for (i = 0; i < vec_size(params); ++i) { + ir_call_param(callinstr, params[i]); + } + + *out = ir_call_value(callinstr); + self->expression.outr = *out; + + codegen_output_type(self, *out); + + vec_free(params); + return true; +error: + vec_free(params); + return false; +} diff --git a/code.c b/code.c deleted file mode 100644 index 314d11b..0000000 --- a/code.c +++ /dev/null @@ -1,365 +0,0 @@ -#include -#include "gmqcc.h" - -/* - * We could use the old method of casting to uintptr_t then to void* - * or qcint_t; however, it's incredibly unsafe for two reasons. - * 1) The compilers aliasing optimization can legally make it unstable - * (it's undefined behaviour). - * - * 2) The cast itself depends on fresh storage (newly allocated in which - * ever function is using the cast macros), the contents of which are - * transferred in a way that the obligation to release storage is not - * propagated. - */ -typedef union { - void *enter; - qcint_t leave; -} code_hash_entry_t; - -/* Some sanity macros */ -#define CODE_HASH_ENTER(ENTRY) ((ENTRY).enter) -#define CODE_HASH_LEAVE(ENTRY) ((ENTRY).leave) - -void code_push_statement(code_t *code, prog_section_statement_t *stmt_in, lex_ctx_t ctx) -{ - prog_section_statement_t stmt = *stmt_in; - - if (OPTS_FLAG(TYPELESS_STORES)) { - switch (stmt.opcode) { - case INSTR_LOAD_S: - case INSTR_LOAD_ENT: - case INSTR_LOAD_FLD: - case INSTR_LOAD_FNC: - stmt.opcode = INSTR_LOAD_F; - break; - case INSTR_STORE_S: - case INSTR_STORE_ENT: - case INSTR_STORE_FLD: - case INSTR_STORE_FNC: - stmt.opcode = INSTR_STORE_F; - break; - case INSTR_STOREP_S: - case INSTR_STOREP_ENT: - case INSTR_STOREP_FLD: - case INSTR_STOREP_FNC: - stmt.opcode = INSTR_STOREP_F; - break; - } - } - - - if (OPTS_FLAG(SORT_OPERANDS)) { - uint16_t pair; - - switch (stmt.opcode) { - case INSTR_MUL_F: - case INSTR_MUL_V: - case INSTR_ADD_F: - case INSTR_EQ_F: - case INSTR_EQ_S: - case INSTR_EQ_E: - case INSTR_EQ_FNC: - case INSTR_NE_F: - case INSTR_NE_V: - case INSTR_NE_S: - case INSTR_NE_E: - case INSTR_NE_FNC: - case INSTR_AND: - case INSTR_OR: - case INSTR_BITAND: - case INSTR_BITOR: - if (stmt.o1.u1 < stmt.o2.u1) { - uint16_t a = stmt.o2.u1; - stmt.o1.u1 = stmt.o2.u1; - stmt.o2.u1 = a; - } - break; - - case INSTR_MUL_VF: pair = INSTR_MUL_FV; goto case_pair_gen; - case INSTR_MUL_FV: pair = INSTR_MUL_VF; goto case_pair_gen; - case INSTR_LT: pair = INSTR_GT; goto case_pair_gen; - case INSTR_GT: pair = INSTR_LT; goto case_pair_gen; - case INSTR_LE: pair = INSTR_GT; goto case_pair_gen; - case INSTR_GE: pair = INSTR_LE; - - case_pair_gen: - if (stmt.o1.u1 < stmt.o2.u1) { - uint16_t x = stmt.o1.u1; - stmt.o1.u1 = stmt.o2.u1; - stmt.o2.u1 = x; - stmt.opcode = pair; - } - break; - } - } - - vec_push(code->statements, stmt); - vec_push(code->linenums, (int)ctx.line); - vec_push(code->columnnums, (int)ctx.column); -} - -void code_pop_statement(code_t *code) -{ - vec_pop(code->statements); - vec_pop(code->linenums); - vec_pop(code->columnnums); -} - -code_t *code_init() { - static lex_ctx_t empty_ctx = {0, 0, 0}; - static prog_section_function_t empty_function = {0,0,0,0,0,0,0,{0,0,0,0,0,0,0,0}}; - static prog_section_statement_t empty_statement = {0,{0},{0},{0}}; - static prog_section_def_t empty_def = {0, 0, 0}; - - code_t *code = (code_t*)mem_a(sizeof(code_t)); - int i = 0; - - memset(code, 0, sizeof(code_t)); - code->entfields = 0; - code->string_cache = util_htnew(OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS) ? 0x100 : 1024); - - /* - * The way progs.dat is suppose to work is odd, there needs to be - * some null (empty) statements, functions, and 28 globals - */ - for(; i < 28; i++) - vec_push(code->globals, 0); - - vec_push(code->chars, '\0'); - vec_push(code->functions, empty_function); - - code_push_statement(code, &empty_statement, empty_ctx); - - vec_push(code->defs, empty_def); - vec_push(code->fields, empty_def); - - return code; -} - -void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin); - -uint32_t code_genstring(code_t *code, const char *str) { - size_t hash; - code_hash_entry_t existing; - - if (!str) - return 0; - - if (!*str) { - if (!code->string_cached_empty) { - code->string_cached_empty = vec_size(code->chars); - vec_push(code->chars, 0); - } - return code->string_cached_empty; - } - - if (OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS)) { - hash = ((unsigned char*)str)[strlen(str)-1]; - CODE_HASH_ENTER(existing) = code_util_str_htgeth(code->string_cache, str, hash); - } else { - hash = util_hthash(code->string_cache, str); - CODE_HASH_ENTER(existing) = util_htgeth(code->string_cache, str, hash); - } - - if (CODE_HASH_ENTER(existing)) - return CODE_HASH_LEAVE(existing); - - CODE_HASH_LEAVE(existing) = vec_size(code->chars); - vec_append(code->chars, strlen(str)+1, str); - - util_htseth(code->string_cache, str, hash, CODE_HASH_ENTER(existing)); - return CODE_HASH_LEAVE(existing); -} - -qcint_t code_alloc_field (code_t *code, size_t qcsize) -{ - qcint_t pos = (qcint_t)code->entfields; - code->entfields += qcsize; - return pos; -} - -static size_t code_size_generic(code_t *code, prog_header_t *code_header, bool lno) { - size_t size = 0; - if (lno) { - size += 4; /* LNOF */ - size += sizeof(uint32_t); /* version */ - size += sizeof(code_header->defs.length); - size += sizeof(code_header->globals.length); - size += sizeof(code_header->fields.length); - size += sizeof(code_header->statements.length); - size += sizeof(code->linenums[0]) * vec_size(code->linenums); - size += sizeof(code->columnnums[0]) * vec_size(code->columnnums); - } else { - size += sizeof(prog_header_t); - size += sizeof(prog_section_statement_t) * vec_size(code->statements); - size += sizeof(prog_section_def_t) * vec_size(code->defs); - size += sizeof(prog_section_field_t) * vec_size(code->fields); - size += sizeof(prog_section_function_t) * vec_size(code->functions); - size += sizeof(int32_t) * vec_size(code->globals); - size += 1 * vec_size(code->chars); - } - return size; -} - -#define code_size_binary(C, H) code_size_generic((C), (H), false) -#define code_size_debug(C, H) code_size_generic((C), (H), true) - -static void code_create_header(code_t *code, prog_header_t *code_header, const char *filename, const char *lnofile) { - size_t i; - - code_header->statements.offset = sizeof(prog_header_t); - code_header->statements.length = vec_size(code->statements); - code_header->defs.offset = code_header->statements.offset + (sizeof(prog_section_statement_t) * vec_size(code->statements)); - code_header->defs.length = vec_size(code->defs); - code_header->fields.offset = code_header->defs.offset + (sizeof(prog_section_def_t) * vec_size(code->defs)); - code_header->fields.length = vec_size(code->fields); - code_header->functions.offset = code_header->fields.offset + (sizeof(prog_section_field_t) * vec_size(code->fields)); - code_header->functions.length = vec_size(code->functions); - code_header->globals.offset = code_header->functions.offset + (sizeof(prog_section_function_t) * vec_size(code->functions)); - code_header->globals.length = vec_size(code->globals); - code_header->strings.offset = code_header->globals.offset + (sizeof(int32_t) * vec_size(code->globals)); - code_header->strings.length = vec_size(code->chars); - code_header->version = 6; - code_header->skip = 0; - - if (OPTS_OPTION_BOOL(OPTION_FORCECRC)) - code_header->crc16 = OPTS_OPTION_U16(OPTION_FORCED_CRC); - else - code_header->crc16 = code->crc; - code_header->entfield = code->entfields; - - if (OPTS_FLAG(DARKPLACES_STRING_TABLE_BUG)) { - /* >= + P */ - vec_push(code->chars, '\0'); /* > */ - vec_push(code->chars, '\0'); /* = */ - vec_push(code->chars, '\0'); /* P */ - } - - /* ensure all data is in LE format */ - util_swap_header(code_header); - - /* - * These are not part of the header but we ensure LE format here to save on duplicated - * code. - */ - - util_swap_statements (code->statements); - util_swap_defs_fields(code->defs); - util_swap_defs_fields(code->fields); - util_swap_functions (code->functions); - util_swap_globals (code->globals); - - if (!OPTS_OPTION_BOOL(OPTION_QUIET)) { - if (lnofile) - con_out("writing '%s' and '%s'...\n", filename, lnofile); - else - con_out("writing '%s'\n", filename); - } - - if (!OPTS_OPTION_BOOL(OPTION_QUIET) && - !OPTS_OPTION_BOOL(OPTION_PP_ONLY)) - { - char buffer[1024]; - con_out("\nOptimizations:\n"); - for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) { - if (opts_optimizationcount[i]) { - util_optimizationtostr(opts_opt_list[i].name, buffer, sizeof(buffer)); - con_out( - " %s: %u\n", - buffer, - (unsigned int)opts_optimizationcount[i] - ); - } - } - } -} - -static void code_stats(const char *filename, const char *lnofile, code_t *code, prog_header_t *code_header) { - if (OPTS_OPTION_BOOL(OPTION_QUIET) || - OPTS_OPTION_BOOL(OPTION_PP_ONLY)) - return; - - con_out("\nFile statistics:\n"); - con_out(" dat:\n"); - con_out(" name: %s\n", filename); - con_out(" size: %u (bytes)\n", code_size_binary(code, code_header)); - con_out(" crc: 0x%04X\n", code->crc); - - if (lnofile) { - con_out(" lno:\n"); - con_out(" name: %s\n", lnofile); - con_out(" size: %u (bytes)\n", code_size_debug(code, code_header)); - } - - con_out("\n"); -} - -bool code_write(code_t *code, const char *filename, const char *lnofile) { - prog_header_t code_header; - FILE *fp = NULL; - - code_create_header(code, &code_header, filename, lnofile); - - if (lnofile) { - uint32_t version = 1; - - fp = fopen(lnofile, "wb"); - if (!fp) - return false; - - util_endianswap(&version, 1, sizeof(version)); - util_endianswap(code->linenums, vec_size(code->linenums), sizeof(code->linenums[0])); - util_endianswap(code->columnnums, vec_size(code->columnnums), sizeof(code->columnnums[0])); - - if (fwrite("LNOF", 4, 1, fp) != 1 || - fwrite(&version, sizeof(version), 1, fp) != 1 || - fwrite(&code_header.defs.length, sizeof(code_header.defs.length), 1, fp) != 1 || - fwrite(&code_header.globals.length, sizeof(code_header.globals.length), 1, fp) != 1 || - fwrite(&code_header.fields.length, sizeof(code_header.fields.length), 1, fp) != 1 || - fwrite(&code_header.statements.length, sizeof(code_header.statements.length), 1, fp) != 1 || - fwrite(code->linenums, sizeof(code->linenums[0]), vec_size(code->linenums), fp) != vec_size(code->linenums) || - fwrite(code->columnnums, sizeof(code->columnnums[0]), vec_size(code->columnnums), fp) != vec_size(code->columnnums)) - { - con_err("failed to write lno file\n"); - } - - fclose(fp); - fp = NULL; - } - - fp = fopen(filename, "wb"); - if (!fp) - return false; - - if (1 != fwrite(&code_header, sizeof(prog_header_t) , 1 , fp) || - vec_size(code->statements) != fwrite(code->statements, sizeof(prog_section_statement_t), vec_size(code->statements), fp) || - vec_size(code->defs) != fwrite(code->defs, sizeof(prog_section_def_t) , vec_size(code->defs) , fp) || - vec_size(code->fields) != fwrite(code->fields, sizeof(prog_section_field_t) , vec_size(code->fields) , fp) || - vec_size(code->functions) != fwrite(code->functions, sizeof(prog_section_function_t) , vec_size(code->functions) , fp) || - vec_size(code->globals) != fwrite(code->globals, sizeof(int32_t) , vec_size(code->globals) , fp) || - vec_size(code->chars) != fwrite(code->chars, 1 , vec_size(code->chars) , fp)) - { - fclose(fp); - return false; - } - - fclose(fp); - code_stats(filename, lnofile, code, &code_header); - return true; -} - -void code_cleanup(code_t *code) { - vec_free(code->statements); - vec_free(code->linenums); - vec_free(code->columnnums); - vec_free(code->defs); - vec_free(code->fields); - vec_free(code->functions); - vec_free(code->globals); - vec_free(code->chars); - - util_htdel(code->string_cache); - - mem_d(code); -} diff --git a/code.cpp b/code.cpp new file mode 100644 index 0000000..314d11b --- /dev/null +++ b/code.cpp @@ -0,0 +1,365 @@ +#include +#include "gmqcc.h" + +/* + * We could use the old method of casting to uintptr_t then to void* + * or qcint_t; however, it's incredibly unsafe for two reasons. + * 1) The compilers aliasing optimization can legally make it unstable + * (it's undefined behaviour). + * + * 2) The cast itself depends on fresh storage (newly allocated in which + * ever function is using the cast macros), the contents of which are + * transferred in a way that the obligation to release storage is not + * propagated. + */ +typedef union { + void *enter; + qcint_t leave; +} code_hash_entry_t; + +/* Some sanity macros */ +#define CODE_HASH_ENTER(ENTRY) ((ENTRY).enter) +#define CODE_HASH_LEAVE(ENTRY) ((ENTRY).leave) + +void code_push_statement(code_t *code, prog_section_statement_t *stmt_in, lex_ctx_t ctx) +{ + prog_section_statement_t stmt = *stmt_in; + + if (OPTS_FLAG(TYPELESS_STORES)) { + switch (stmt.opcode) { + case INSTR_LOAD_S: + case INSTR_LOAD_ENT: + case INSTR_LOAD_FLD: + case INSTR_LOAD_FNC: + stmt.opcode = INSTR_LOAD_F; + break; + case INSTR_STORE_S: + case INSTR_STORE_ENT: + case INSTR_STORE_FLD: + case INSTR_STORE_FNC: + stmt.opcode = INSTR_STORE_F; + break; + case INSTR_STOREP_S: + case INSTR_STOREP_ENT: + case INSTR_STOREP_FLD: + case INSTR_STOREP_FNC: + stmt.opcode = INSTR_STOREP_F; + break; + } + } + + + if (OPTS_FLAG(SORT_OPERANDS)) { + uint16_t pair; + + switch (stmt.opcode) { + case INSTR_MUL_F: + case INSTR_MUL_V: + case INSTR_ADD_F: + case INSTR_EQ_F: + case INSTR_EQ_S: + case INSTR_EQ_E: + case INSTR_EQ_FNC: + case INSTR_NE_F: + case INSTR_NE_V: + case INSTR_NE_S: + case INSTR_NE_E: + case INSTR_NE_FNC: + case INSTR_AND: + case INSTR_OR: + case INSTR_BITAND: + case INSTR_BITOR: + if (stmt.o1.u1 < stmt.o2.u1) { + uint16_t a = stmt.o2.u1; + stmt.o1.u1 = stmt.o2.u1; + stmt.o2.u1 = a; + } + break; + + case INSTR_MUL_VF: pair = INSTR_MUL_FV; goto case_pair_gen; + case INSTR_MUL_FV: pair = INSTR_MUL_VF; goto case_pair_gen; + case INSTR_LT: pair = INSTR_GT; goto case_pair_gen; + case INSTR_GT: pair = INSTR_LT; goto case_pair_gen; + case INSTR_LE: pair = INSTR_GT; goto case_pair_gen; + case INSTR_GE: pair = INSTR_LE; + + case_pair_gen: + if (stmt.o1.u1 < stmt.o2.u1) { + uint16_t x = stmt.o1.u1; + stmt.o1.u1 = stmt.o2.u1; + stmt.o2.u1 = x; + stmt.opcode = pair; + } + break; + } + } + + vec_push(code->statements, stmt); + vec_push(code->linenums, (int)ctx.line); + vec_push(code->columnnums, (int)ctx.column); +} + +void code_pop_statement(code_t *code) +{ + vec_pop(code->statements); + vec_pop(code->linenums); + vec_pop(code->columnnums); +} + +code_t *code_init() { + static lex_ctx_t empty_ctx = {0, 0, 0}; + static prog_section_function_t empty_function = {0,0,0,0,0,0,0,{0,0,0,0,0,0,0,0}}; + static prog_section_statement_t empty_statement = {0,{0},{0},{0}}; + static prog_section_def_t empty_def = {0, 0, 0}; + + code_t *code = (code_t*)mem_a(sizeof(code_t)); + int i = 0; + + memset(code, 0, sizeof(code_t)); + code->entfields = 0; + code->string_cache = util_htnew(OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS) ? 0x100 : 1024); + + /* + * The way progs.dat is suppose to work is odd, there needs to be + * some null (empty) statements, functions, and 28 globals + */ + for(; i < 28; i++) + vec_push(code->globals, 0); + + vec_push(code->chars, '\0'); + vec_push(code->functions, empty_function); + + code_push_statement(code, &empty_statement, empty_ctx); + + vec_push(code->defs, empty_def); + vec_push(code->fields, empty_def); + + return code; +} + +void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin); + +uint32_t code_genstring(code_t *code, const char *str) { + size_t hash; + code_hash_entry_t existing; + + if (!str) + return 0; + + if (!*str) { + if (!code->string_cached_empty) { + code->string_cached_empty = vec_size(code->chars); + vec_push(code->chars, 0); + } + return code->string_cached_empty; + } + + if (OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS)) { + hash = ((unsigned char*)str)[strlen(str)-1]; + CODE_HASH_ENTER(existing) = code_util_str_htgeth(code->string_cache, str, hash); + } else { + hash = util_hthash(code->string_cache, str); + CODE_HASH_ENTER(existing) = util_htgeth(code->string_cache, str, hash); + } + + if (CODE_HASH_ENTER(existing)) + return CODE_HASH_LEAVE(existing); + + CODE_HASH_LEAVE(existing) = vec_size(code->chars); + vec_append(code->chars, strlen(str)+1, str); + + util_htseth(code->string_cache, str, hash, CODE_HASH_ENTER(existing)); + return CODE_HASH_LEAVE(existing); +} + +qcint_t code_alloc_field (code_t *code, size_t qcsize) +{ + qcint_t pos = (qcint_t)code->entfields; + code->entfields += qcsize; + return pos; +} + +static size_t code_size_generic(code_t *code, prog_header_t *code_header, bool lno) { + size_t size = 0; + if (lno) { + size += 4; /* LNOF */ + size += sizeof(uint32_t); /* version */ + size += sizeof(code_header->defs.length); + size += sizeof(code_header->globals.length); + size += sizeof(code_header->fields.length); + size += sizeof(code_header->statements.length); + size += sizeof(code->linenums[0]) * vec_size(code->linenums); + size += sizeof(code->columnnums[0]) * vec_size(code->columnnums); + } else { + size += sizeof(prog_header_t); + size += sizeof(prog_section_statement_t) * vec_size(code->statements); + size += sizeof(prog_section_def_t) * vec_size(code->defs); + size += sizeof(prog_section_field_t) * vec_size(code->fields); + size += sizeof(prog_section_function_t) * vec_size(code->functions); + size += sizeof(int32_t) * vec_size(code->globals); + size += 1 * vec_size(code->chars); + } + return size; +} + +#define code_size_binary(C, H) code_size_generic((C), (H), false) +#define code_size_debug(C, H) code_size_generic((C), (H), true) + +static void code_create_header(code_t *code, prog_header_t *code_header, const char *filename, const char *lnofile) { + size_t i; + + code_header->statements.offset = sizeof(prog_header_t); + code_header->statements.length = vec_size(code->statements); + code_header->defs.offset = code_header->statements.offset + (sizeof(prog_section_statement_t) * vec_size(code->statements)); + code_header->defs.length = vec_size(code->defs); + code_header->fields.offset = code_header->defs.offset + (sizeof(prog_section_def_t) * vec_size(code->defs)); + code_header->fields.length = vec_size(code->fields); + code_header->functions.offset = code_header->fields.offset + (sizeof(prog_section_field_t) * vec_size(code->fields)); + code_header->functions.length = vec_size(code->functions); + code_header->globals.offset = code_header->functions.offset + (sizeof(prog_section_function_t) * vec_size(code->functions)); + code_header->globals.length = vec_size(code->globals); + code_header->strings.offset = code_header->globals.offset + (sizeof(int32_t) * vec_size(code->globals)); + code_header->strings.length = vec_size(code->chars); + code_header->version = 6; + code_header->skip = 0; + + if (OPTS_OPTION_BOOL(OPTION_FORCECRC)) + code_header->crc16 = OPTS_OPTION_U16(OPTION_FORCED_CRC); + else + code_header->crc16 = code->crc; + code_header->entfield = code->entfields; + + if (OPTS_FLAG(DARKPLACES_STRING_TABLE_BUG)) { + /* >= + P */ + vec_push(code->chars, '\0'); /* > */ + vec_push(code->chars, '\0'); /* = */ + vec_push(code->chars, '\0'); /* P */ + } + + /* ensure all data is in LE format */ + util_swap_header(code_header); + + /* + * These are not part of the header but we ensure LE format here to save on duplicated + * code. + */ + + util_swap_statements (code->statements); + util_swap_defs_fields(code->defs); + util_swap_defs_fields(code->fields); + util_swap_functions (code->functions); + util_swap_globals (code->globals); + + if (!OPTS_OPTION_BOOL(OPTION_QUIET)) { + if (lnofile) + con_out("writing '%s' and '%s'...\n", filename, lnofile); + else + con_out("writing '%s'\n", filename); + } + + if (!OPTS_OPTION_BOOL(OPTION_QUIET) && + !OPTS_OPTION_BOOL(OPTION_PP_ONLY)) + { + char buffer[1024]; + con_out("\nOptimizations:\n"); + for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) { + if (opts_optimizationcount[i]) { + util_optimizationtostr(opts_opt_list[i].name, buffer, sizeof(buffer)); + con_out( + " %s: %u\n", + buffer, + (unsigned int)opts_optimizationcount[i] + ); + } + } + } +} + +static void code_stats(const char *filename, const char *lnofile, code_t *code, prog_header_t *code_header) { + if (OPTS_OPTION_BOOL(OPTION_QUIET) || + OPTS_OPTION_BOOL(OPTION_PP_ONLY)) + return; + + con_out("\nFile statistics:\n"); + con_out(" dat:\n"); + con_out(" name: %s\n", filename); + con_out(" size: %u (bytes)\n", code_size_binary(code, code_header)); + con_out(" crc: 0x%04X\n", code->crc); + + if (lnofile) { + con_out(" lno:\n"); + con_out(" name: %s\n", lnofile); + con_out(" size: %u (bytes)\n", code_size_debug(code, code_header)); + } + + con_out("\n"); +} + +bool code_write(code_t *code, const char *filename, const char *lnofile) { + prog_header_t code_header; + FILE *fp = NULL; + + code_create_header(code, &code_header, filename, lnofile); + + if (lnofile) { + uint32_t version = 1; + + fp = fopen(lnofile, "wb"); + if (!fp) + return false; + + util_endianswap(&version, 1, sizeof(version)); + util_endianswap(code->linenums, vec_size(code->linenums), sizeof(code->linenums[0])); + util_endianswap(code->columnnums, vec_size(code->columnnums), sizeof(code->columnnums[0])); + + if (fwrite("LNOF", 4, 1, fp) != 1 || + fwrite(&version, sizeof(version), 1, fp) != 1 || + fwrite(&code_header.defs.length, sizeof(code_header.defs.length), 1, fp) != 1 || + fwrite(&code_header.globals.length, sizeof(code_header.globals.length), 1, fp) != 1 || + fwrite(&code_header.fields.length, sizeof(code_header.fields.length), 1, fp) != 1 || + fwrite(&code_header.statements.length, sizeof(code_header.statements.length), 1, fp) != 1 || + fwrite(code->linenums, sizeof(code->linenums[0]), vec_size(code->linenums), fp) != vec_size(code->linenums) || + fwrite(code->columnnums, sizeof(code->columnnums[0]), vec_size(code->columnnums), fp) != vec_size(code->columnnums)) + { + con_err("failed to write lno file\n"); + } + + fclose(fp); + fp = NULL; + } + + fp = fopen(filename, "wb"); + if (!fp) + return false; + + if (1 != fwrite(&code_header, sizeof(prog_header_t) , 1 , fp) || + vec_size(code->statements) != fwrite(code->statements, sizeof(prog_section_statement_t), vec_size(code->statements), fp) || + vec_size(code->defs) != fwrite(code->defs, sizeof(prog_section_def_t) , vec_size(code->defs) , fp) || + vec_size(code->fields) != fwrite(code->fields, sizeof(prog_section_field_t) , vec_size(code->fields) , fp) || + vec_size(code->functions) != fwrite(code->functions, sizeof(prog_section_function_t) , vec_size(code->functions) , fp) || + vec_size(code->globals) != fwrite(code->globals, sizeof(int32_t) , vec_size(code->globals) , fp) || + vec_size(code->chars) != fwrite(code->chars, 1 , vec_size(code->chars) , fp)) + { + fclose(fp); + return false; + } + + fclose(fp); + code_stats(filename, lnofile, code, &code_header); + return true; +} + +void code_cleanup(code_t *code) { + vec_free(code->statements); + vec_free(code->linenums); + vec_free(code->columnnums); + vec_free(code->defs); + vec_free(code->fields); + vec_free(code->functions); + vec_free(code->globals); + vec_free(code->chars); + + util_htdel(code->string_cache); + + mem_d(code); +} diff --git a/conout.c b/conout.c deleted file mode 100644 index 9d0659b..0000000 --- a/conout.c +++ /dev/null @@ -1,226 +0,0 @@ -#include -#include "gmqcc.h" - -#define GMQCC_IS_STDOUT(X) ((X) == stdout) -#define GMQCC_IS_STDERR(X) ((X) == stderr) -#define GMQCC_IS_DEFINE(X) (GMQCC_IS_STDERR(X) || GMQCC_IS_STDOUT(X)) - -typedef struct { - FILE *handle_err; - FILE *handle_out; - int color_err; - int color_out; -} con_t; - -static con_t console; - -/* - * Enables color on output if supported. - * NOTE: The support for checking colors is NULL. On windows this will - * always work, on *nix it depends if the term has colors. - * - * NOTE: This prevents colored output to piped stdout/err via isatty - * checks. - */ -static void con_enablecolor(void) { - console.color_err = util_isatty(console.handle_err); - console.color_out = util_isatty(console.handle_out); -} - -/* - * Does a write to the handle with the format string and list of - * arguments. This colorizes for windows as well via translate - * step. - */ -static int con_write(FILE *handle, const char *fmt, va_list va) { - return vfprintf(handle, fmt, va); -} - -/********************************************************************** - * EXPOSED INTERFACE BEGINS - *********************************************************************/ - -void con_close() { - if (!GMQCC_IS_DEFINE(console.handle_err)) - fclose(console.handle_err); - if (!GMQCC_IS_DEFINE(console.handle_out)) - fclose(console.handle_out); -} - -void con_color(int state) { - if (state) - con_enablecolor(); - else { - console.color_err = 0; - console.color_out = 0; - } -} - -void con_init() { - console.handle_err = stderr; - console.handle_out = stdout; - con_enablecolor(); -} - -void con_reset() { - con_close(); - con_init(); -} - -/* - * Defaultizer because stdio.h shouldn't be used anywhere except here - * and inside file.c To prevent mis-match of wrapper-interfaces. - */ -FILE *con_default_out() { - return console.handle_out = stdout; -} - -FILE *con_default_err() { - return console.handle_err = stderr; -} - -int con_verr(const char *fmt, va_list va) { - return con_write(console.handle_err, fmt, va); -} -int con_vout(const char *fmt, va_list va) { - return con_write(console.handle_out, fmt, va); -} - -/* - * Standard stdout/stderr printf functions used generally where they need - * to be used. - */ -int con_err(const char *fmt, ...) { - va_list va; - int ln = 0; - va_start(va, fmt); - con_verr(fmt, va); - va_end(va); - return ln; -} -int con_out(const char *fmt, ...) { - va_list va; - int ln = 0; - va_start(va, fmt); - con_vout(fmt, va); - va_end (va); - return ln; -} - -/* - * Utility console message writes for lexer contexts. These will allow - * for reporting of file:line based on lexer context, These are used - * heavily in the parser/ir/ast. - */ -static void con_vprintmsg_c(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap, const char *condname) { - /* color selection table */ - static int sel[] = { - CON_WHITE, - CON_CYAN, - CON_RED - }; - - int err = !!(level == LVL_ERROR); - int color = (err) ? console.color_err : console.color_out; - int (*print) (const char *, ...) = (err) ? &con_err : &con_out; - int (*vprint)(const char *, va_list) = (err) ? &con_verr : &con_vout; - - if (color) - print("\033[0;%dm%s:%d:%d: \033[0;%dm%s: \033[0m", CON_CYAN, name, (int)line, (int)column, sel[level], msgtype); - else - print("%s:%d:%d: %s: ", name, (int)line, (int)column, msgtype); - - vprint(msg, ap); - if (condname) - print(" [%s]\n", condname); - else - print("\n"); -} - -void con_vprintmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap) { - con_vprintmsg_c(level, name, line, column, msgtype, msg, ap, NULL); -} - -void con_printmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, ...) { - va_list va; - va_start(va, msg); - con_vprintmsg(level, name, line, column, msgtype, msg, va); - va_end (va); -} - -void con_cvprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, va_list ap) { - con_vprintmsg(lvl, ctx.file, ctx.line, ctx.column, msgtype, msg, ap); -} - -void con_cprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, ...) { - va_list va; - va_start(va, msg); - con_cvprintmsg(ctx, lvl, msgtype, msg, va); - va_end (va); -} - -/* General error interface: TODO seperate as part of the compiler front-end */ -size_t compile_errors = 0; -size_t compile_warnings = 0; -size_t compile_Werrors = 0; -static lex_ctx_t first_werror; - -void compile_show_werrors() -{ - con_cprintmsg(first_werror, LVL_ERROR, "first warning", "was here"); -} - -void vcompile_error(lex_ctx_t ctx, const char *msg, va_list ap) -{ - ++compile_errors; - con_cvprintmsg(ctx, LVL_ERROR, "error", msg, ap); -} - -void compile_error(lex_ctx_t ctx, const char *msg, ...) -{ - va_list ap; - va_start(ap, msg); - vcompile_error(ctx, msg, ap); - va_end(ap); -} - -bool GMQCC_WARN vcompile_warning(lex_ctx_t ctx, int warntype, const char *fmt, va_list ap) -{ - const char *msgtype = "warning"; - int lvl = LVL_WARNING; - char warn_name[1024]; - - if (!OPTS_WARN(warntype)) - return false; - - warn_name[0] = '-'; - warn_name[1] = 'W'; - (void)util_strtononcmd(opts_warn_list[warntype].name, warn_name+2, sizeof(warn_name)-2); - - ++compile_warnings; - if (OPTS_WERROR(warntype)) { - if (!compile_Werrors) - first_werror = ctx; - ++compile_Werrors; - msgtype = "Werror"; - if (OPTS_FLAG(BAIL_ON_WERROR)) { - msgtype = "error"; - ++compile_errors; - } - lvl = LVL_ERROR; - } - - con_vprintmsg_c(lvl, ctx.file, ctx.line, ctx.column, msgtype, fmt, ap, warn_name); - - return OPTS_WERROR(warntype) && OPTS_FLAG(BAIL_ON_WERROR); -} - -bool GMQCC_WARN compile_warning(lex_ctx_t ctx, int warntype, const char *fmt, ...) -{ - bool r; - va_list ap; - va_start(ap, fmt); - r = vcompile_warning(ctx, warntype, fmt, ap); - va_end(ap); - return r; -} diff --git a/conout.cpp b/conout.cpp new file mode 100644 index 0000000..9d0659b --- /dev/null +++ b/conout.cpp @@ -0,0 +1,226 @@ +#include +#include "gmqcc.h" + +#define GMQCC_IS_STDOUT(X) ((X) == stdout) +#define GMQCC_IS_STDERR(X) ((X) == stderr) +#define GMQCC_IS_DEFINE(X) (GMQCC_IS_STDERR(X) || GMQCC_IS_STDOUT(X)) + +typedef struct { + FILE *handle_err; + FILE *handle_out; + int color_err; + int color_out; +} con_t; + +static con_t console; + +/* + * Enables color on output if supported. + * NOTE: The support for checking colors is NULL. On windows this will + * always work, on *nix it depends if the term has colors. + * + * NOTE: This prevents colored output to piped stdout/err via isatty + * checks. + */ +static void con_enablecolor(void) { + console.color_err = util_isatty(console.handle_err); + console.color_out = util_isatty(console.handle_out); +} + +/* + * Does a write to the handle with the format string and list of + * arguments. This colorizes for windows as well via translate + * step. + */ +static int con_write(FILE *handle, const char *fmt, va_list va) { + return vfprintf(handle, fmt, va); +} + +/********************************************************************** + * EXPOSED INTERFACE BEGINS + *********************************************************************/ + +void con_close() { + if (!GMQCC_IS_DEFINE(console.handle_err)) + fclose(console.handle_err); + if (!GMQCC_IS_DEFINE(console.handle_out)) + fclose(console.handle_out); +} + +void con_color(int state) { + if (state) + con_enablecolor(); + else { + console.color_err = 0; + console.color_out = 0; + } +} + +void con_init() { + console.handle_err = stderr; + console.handle_out = stdout; + con_enablecolor(); +} + +void con_reset() { + con_close(); + con_init(); +} + +/* + * Defaultizer because stdio.h shouldn't be used anywhere except here + * and inside file.c To prevent mis-match of wrapper-interfaces. + */ +FILE *con_default_out() { + return console.handle_out = stdout; +} + +FILE *con_default_err() { + return console.handle_err = stderr; +} + +int con_verr(const char *fmt, va_list va) { + return con_write(console.handle_err, fmt, va); +} +int con_vout(const char *fmt, va_list va) { + return con_write(console.handle_out, fmt, va); +} + +/* + * Standard stdout/stderr printf functions used generally where they need + * to be used. + */ +int con_err(const char *fmt, ...) { + va_list va; + int ln = 0; + va_start(va, fmt); + con_verr(fmt, va); + va_end(va); + return ln; +} +int con_out(const char *fmt, ...) { + va_list va; + int ln = 0; + va_start(va, fmt); + con_vout(fmt, va); + va_end (va); + return ln; +} + +/* + * Utility console message writes for lexer contexts. These will allow + * for reporting of file:line based on lexer context, These are used + * heavily in the parser/ir/ast. + */ +static void con_vprintmsg_c(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap, const char *condname) { + /* color selection table */ + static int sel[] = { + CON_WHITE, + CON_CYAN, + CON_RED + }; + + int err = !!(level == LVL_ERROR); + int color = (err) ? console.color_err : console.color_out; + int (*print) (const char *, ...) = (err) ? &con_err : &con_out; + int (*vprint)(const char *, va_list) = (err) ? &con_verr : &con_vout; + + if (color) + print("\033[0;%dm%s:%d:%d: \033[0;%dm%s: \033[0m", CON_CYAN, name, (int)line, (int)column, sel[level], msgtype); + else + print("%s:%d:%d: %s: ", name, (int)line, (int)column, msgtype); + + vprint(msg, ap); + if (condname) + print(" [%s]\n", condname); + else + print("\n"); +} + +void con_vprintmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap) { + con_vprintmsg_c(level, name, line, column, msgtype, msg, ap, NULL); +} + +void con_printmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, ...) { + va_list va; + va_start(va, msg); + con_vprintmsg(level, name, line, column, msgtype, msg, va); + va_end (va); +} + +void con_cvprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, va_list ap) { + con_vprintmsg(lvl, ctx.file, ctx.line, ctx.column, msgtype, msg, ap); +} + +void con_cprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, ...) { + va_list va; + va_start(va, msg); + con_cvprintmsg(ctx, lvl, msgtype, msg, va); + va_end (va); +} + +/* General error interface: TODO seperate as part of the compiler front-end */ +size_t compile_errors = 0; +size_t compile_warnings = 0; +size_t compile_Werrors = 0; +static lex_ctx_t first_werror; + +void compile_show_werrors() +{ + con_cprintmsg(first_werror, LVL_ERROR, "first warning", "was here"); +} + +void vcompile_error(lex_ctx_t ctx, const char *msg, va_list ap) +{ + ++compile_errors; + con_cvprintmsg(ctx, LVL_ERROR, "error", msg, ap); +} + +void compile_error(lex_ctx_t ctx, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + vcompile_error(ctx, msg, ap); + va_end(ap); +} + +bool GMQCC_WARN vcompile_warning(lex_ctx_t ctx, int warntype, const char *fmt, va_list ap) +{ + const char *msgtype = "warning"; + int lvl = LVL_WARNING; + char warn_name[1024]; + + if (!OPTS_WARN(warntype)) + return false; + + warn_name[0] = '-'; + warn_name[1] = 'W'; + (void)util_strtononcmd(opts_warn_list[warntype].name, warn_name+2, sizeof(warn_name)-2); + + ++compile_warnings; + if (OPTS_WERROR(warntype)) { + if (!compile_Werrors) + first_werror = ctx; + ++compile_Werrors; + msgtype = "Werror"; + if (OPTS_FLAG(BAIL_ON_WERROR)) { + msgtype = "error"; + ++compile_errors; + } + lvl = LVL_ERROR; + } + + con_vprintmsg_c(lvl, ctx.file, ctx.line, ctx.column, msgtype, fmt, ap, warn_name); + + return OPTS_WERROR(warntype) && OPTS_FLAG(BAIL_ON_WERROR); +} + +bool GMQCC_WARN compile_warning(lex_ctx_t ctx, int warntype, const char *fmt, ...) +{ + bool r; + va_list ap; + va_start(ap, fmt); + r = vcompile_warning(ctx, warntype, fmt, ap); + va_end(ap); + return r; +} diff --git a/exec.c b/exec.c deleted file mode 100644 index 8ec97e4..0000000 --- a/exec.c +++ /dev/null @@ -1,1637 +0,0 @@ -#ifndef QCVM_LOOP -#include -#include -#include -#include - -#include "gmqcc.h" - -static void loaderror(const char *fmt, ...) -{ - int err = errno; - va_list ap; - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); - printf(": %s\n", util_strerror(err)); -} - -static void qcvmerror(qc_program_t *prog, const char *fmt, ...) -{ - va_list ap; - - prog->vmerror++; - - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); - putchar('\n'); -} - -qc_program_t* prog_load(const char *filename, bool skipversion) -{ - prog_header_t header; - qc_program_t *prog; - size_t i; - FILE *file = fopen(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; - - if (fread(&header, sizeof(header), 1, file) != 1) { - loaderror("failed to read header from '%s'", filename); - fclose(file); - return NULL; - } - - util_swap_header(&header); - - if (!skipversion && header.version != 6) { - loaderror("header says this is a version %i progs, we need version 6\n", header.version); - fclose(file); - return NULL; - } - - prog = (qc_program_t*)mem_a(sizeof(qc_program_t)); - if (!prog) { - fclose(file); - fprintf(stderr, "failed to allocate program data\n"); - return NULL; - } - memset(prog, 0, sizeof(*prog)); - - prog->entityfields = header.entfield; - prog->crc16 = header.crc16; - - prog->filename = util_strdup(filename); - if (!prog->filename) { - loaderror("failed to store program name"); - goto error; - } - -#define read_data(hdrvar, progvar, reserved) \ - if (fseek(file, header.hdrvar.offset, SEEK_SET) != 0) { \ - loaderror("seek failed"); \ - goto error; \ - } \ - if (fread( \ - vec_add(prog->progvar, header.hdrvar.length + reserved), \ - sizeof(*prog->progvar), \ - header.hdrvar.length, \ - file \ - )!= header.hdrvar.length \ - ) { \ - loaderror("read failed"); \ - goto error; \ - } -#define read_data1(x) read_data(x, x, 0) -#define read_data2(x, y) read_data(x, x, y) - - read_data (statements, code, 0); - read_data1(defs); - read_data1(fields); - read_data1(functions); - read_data1(strings); - read_data2(globals, 2); /* reserve more in case a RETURN using with the global at "the end" exists */ - - util_swap_statements (prog->code); - util_swap_defs_fields(prog->defs); - util_swap_defs_fields(prog->fields); - util_swap_functions (prog->functions); - util_swap_globals (prog->globals); - - fclose(file); - - /* profile counters */ - memset(vec_add(prog->profile, vec_size(prog->code)), 0, sizeof(prog->profile[0]) * vec_size(prog->code)); - - /* Add tempstring area */ - prog->tempstring_start = vec_size(prog->strings); - prog->tempstring_at = vec_size(prog->strings); - memset(vec_add(prog->strings, 16*1024), 0, 16*1024); - - /* spawn the world entity */ - vec_push(prog->entitypool, true); - 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: - if (prog->filename) - mem_d(prog->filename); - vec_free(prog->code); - vec_free(prog->defs); - vec_free(prog->fields); - vec_free(prog->functions); - vec_free(prog->strings); - vec_free(prog->globals); - vec_free(prog->entitydata); - vec_free(prog->entitypool); - mem_d(prog); - - fclose(file); - return NULL; -} - -void prog_delete(qc_program_t *prog) -{ - if (prog->filename) mem_d(prog->filename); - vec_free(prog->code); - vec_free(prog->defs); - vec_free(prog->fields); - vec_free(prog->functions); - vec_free(prog->strings); - vec_free(prog->globals); - vec_free(prog->entitydata); - vec_free(prog->entitypool); - vec_free(prog->localstack); - vec_free(prog->stack); - vec_free(prog->profile); - mem_d(prog); -} - -/*********************************************************************** - * VM code - */ - -const char* prog_getstring(qc_program_t *prog, qcint_t str) { - /* cast for return required for C++ */ - if (str < 0 || str >= (qcint_t)vec_size(prog->strings)) - return "<<>>"; - - return prog->strings + str; -} - -prog_section_def_t* prog_entfield(qc_program_t *prog, qcint_t off) { - size_t i; - for (i = 0; i < vec_size(prog->fields); ++i) { - if (prog->fields[i].offset == off) - return (prog->fields + i); - } - return NULL; -} - -prog_section_def_t* prog_getdef(qc_program_t *prog, qcint_t off) -{ - size_t i; - for (i = 0; i < vec_size(prog->defs); ++i) { - if (prog->defs[i].offset == off) - return (prog->defs + i); - } - return NULL; -} - -qcany_t* prog_getedict(qc_program_t *prog, qcint_t e) { - if (e >= (qcint_t)vec_size(prog->entitypool)) { - prog->vmerror++; - fprintf(stderr, "Accessing out of bounds edict %i\n", (int)e); - e = 0; - } - return (qcany_t*)(prog->entitydata + (prog->entityfields * e)); -} - -static qcint_t prog_spawn_entity(qc_program_t *prog) { - char *data; - qcint_t e; - for (e = 0; e < (qcint_t)vec_size(prog->entitypool); ++e) { - if (!prog->entitypool[e]) { - data = (char*)(prog->entitydata + (prog->entityfields * e)); - memset(data, 0, prog->entityfields * sizeof(qcint_t)); - return e; - } - } - vec_push(prog->entitypool, true); - prog->entities++; - data = (char*)vec_add(prog->entitydata, prog->entityfields); - memset(data, 0, prog->entityfields * sizeof(qcint_t)); - return e; -} - -static void prog_free_entity(qc_program_t *prog, qcint_t e) { - if (!e) { - prog->vmerror++; - fprintf(stderr, "Trying to free world entity\n"); - return; - } - if (e >= (qcint_t)vec_size(prog->entitypool)) { - prog->vmerror++; - fprintf(stderr, "Trying to free out of bounds entity\n"); - return; - } - if (!prog->entitypool[e]) { - prog->vmerror++; - fprintf(stderr, "Double free on entity\n"); - return; - } - prog->entitypool[e] = false; -} - -qcint_t prog_tempstring(qc_program_t *prog, const char *str) { - size_t len = strlen(str); - size_t at = prog->tempstring_at; - - /* when we reach the end we start over */ - if (at + len >= vec_size(prog->strings)) - at = prog->tempstring_start; - - /* when it doesn't fit, reallocate */ - if (at + len >= vec_size(prog->strings)) - { - (void)vec_add(prog->strings, len+1); - memcpy(prog->strings + at, str, len+1); - return at; - } - - /* when it fits, just copy */ - memcpy(prog->strings + at, str, len+1); - prog->tempstring_at += len+1; - return at; -} - -static size_t print_escaped_string(const char *str, size_t maxlen) { - size_t len = 2; - putchar('"'); - --maxlen; /* because we're lazy and have escape sequences */ - while (*str) { - if (len >= maxlen) { - putchar('.'); - putchar('.'); - putchar('.'); - len += 3; - break; - } - switch (*str) { - case '\a': len += 2; putchar('\\'); putchar('a'); break; - case '\b': len += 2; putchar('\\'); putchar('b'); break; - case '\r': len += 2; putchar('\\'); putchar('r'); break; - case '\n': len += 2; putchar('\\'); putchar('n'); break; - case '\t': len += 2; putchar('\\'); putchar('t'); break; - case '\f': len += 2; putchar('\\'); putchar('f'); break; - case '\v': len += 2; putchar('\\'); putchar('v'); break; - case '\\': len += 2; putchar('\\'); putchar('\\'); break; - case '"': len += 2; putchar('\\'); putchar('"'); break; - default: - ++len; - putchar(*str); - break; - } - ++str; - } - putchar('"'); - return len; -} - -static void trace_print_global(qc_program_t *prog, unsigned int glob, int vtype) { - static char spaces[28+1] = " "; - prog_section_def_t *def; - qcany_t *value; - int len; - - if (!glob) { - if ((len = printf(",")) == -1) - len = 0; - - goto done; - } - - def = prog_getdef(prog, glob); - value = (qcany_t*)(&prog->globals[glob]); - - len = printf("[@%u] ", glob); - if (def) { - const char *name = prog_getstring(prog, def->name); - if (name[0] == '#') - len += printf("$"); - else - len += printf("%s ", name); - vtype = def->type & DEF_TYPEMASK; - } - - switch (vtype) { - case TYPE_VOID: - case TYPE_ENTITY: - case TYPE_FIELD: - case TYPE_FUNCTION: - case TYPE_POINTER: - len += printf("(%i),", value->_int); - break; - case TYPE_VECTOR: - len += printf("'%g %g %g',", value->vector[0], - value->vector[1], - value->vector[2]); - break; - case TYPE_STRING: - if (value->string) - len += print_escaped_string(prog_getstring(prog, value->string), sizeof(spaces)-len-5); - else - len += printf("(null)"); - len += printf(","); - /* len += printf("\"%s\",", prog_getstring(prog, value->string)); */ - break; - case TYPE_FLOAT: - default: - len += printf("%g,", value->_float); - break; - } -done: - if (len < (int)sizeof(spaces)-1) { - spaces[sizeof(spaces)-1-len] = 0; - fputs(spaces, stdout); - spaces[sizeof(spaces)-1-len] = ' '; - } -} - -static void prog_print_statement(qc_program_t *prog, prog_section_statement_t *st) { - if (st->opcode >= VINSTR_END) { - printf("\n", st->opcode); - return; - } - if ((prog->xflags & VMXF_TRACE) && vec_size(prog->function_stack)) { - size_t i; - for (i = 0; i < vec_size(prog->function_stack); ++i) - printf("->"); - printf("%s:", vec_last(prog->function_stack)); - } - printf(" <> %-12s", util_instr_str[st->opcode]); - if (st->opcode >= INSTR_IF && - st->opcode <= INSTR_IFNOT) - { - trace_print_global(prog, st->o1.u1, TYPE_FLOAT); - printf("%d\n", st->o2.s1); - } - else if (st->opcode >= INSTR_CALL0 && - st->opcode <= INSTR_CALL8) - { - trace_print_global(prog, st->o1.u1, TYPE_FUNCTION); - printf("\n"); - } - else if (st->opcode == INSTR_GOTO) - { - printf("%i\n", st->o1.s1); - } - else - { - int t[3] = { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT }; - switch (st->opcode) - { - case INSTR_MUL_FV: - t[1] = t[2] = TYPE_VECTOR; - break; - case INSTR_MUL_VF: - t[0] = t[2] = TYPE_VECTOR; - break; - case INSTR_MUL_V: - t[0] = t[1] = TYPE_VECTOR; - break; - case INSTR_ADD_V: - case INSTR_SUB_V: - case INSTR_EQ_V: - case INSTR_NE_V: - t[0] = t[1] = t[2] = TYPE_VECTOR; - break; - case INSTR_EQ_S: - case INSTR_NE_S: - t[0] = t[1] = TYPE_STRING; - break; - case INSTR_STORE_F: - case INSTR_STOREP_F: - t[2] = -1; - break; - case INSTR_STORE_V: - t[0] = t[1] = TYPE_VECTOR; t[2] = -1; - break; - case INSTR_STORE_S: - t[0] = t[1] = TYPE_STRING; t[2] = -1; - break; - case INSTR_STORE_ENT: - t[0] = t[1] = TYPE_ENTITY; t[2] = -1; - break; - case INSTR_STORE_FLD: - t[0] = t[1] = TYPE_FIELD; t[2] = -1; - break; - case INSTR_STORE_FNC: - t[0] = t[1] = TYPE_FUNCTION; t[2] = -1; - break; - case INSTR_STOREP_V: - t[0] = TYPE_VECTOR; t[1] = TYPE_ENTITY; t[2] = -1; - break; - case INSTR_STOREP_S: - t[0] = TYPE_STRING; t[1] = TYPE_ENTITY; t[2] = -1; - break; - case INSTR_STOREP_ENT: - t[0] = TYPE_ENTITY; t[1] = TYPE_ENTITY; t[2] = -1; - break; - case INSTR_STOREP_FLD: - t[0] = TYPE_FIELD; t[1] = TYPE_ENTITY; t[2] = -1; - break; - case INSTR_STOREP_FNC: - t[0] = TYPE_FUNCTION; t[1] = TYPE_ENTITY; t[2] = -1; - break; - } - if (t[0] >= 0) trace_print_global(prog, st->o1.u1, t[0]); - else printf("(none), "); - if (t[1] >= 0) trace_print_global(prog, st->o2.u1, t[1]); - else printf("(none), "); - if (t[2] >= 0) trace_print_global(prog, st->o3.u1, t[2]); - else printf("(none)"); - printf("\n"); - } -} - -static qcint_t prog_enterfunction(qc_program_t *prog, prog_section_function_t *func) { - qc_exec_stack_t st; - size_t parampos; - int32_t p; - - /* back up locals */ - st.localsp = vec_size(prog->localstack); - st.stmt = prog->statement; - st.function = func; - - if (prog->xflags & VMXF_TRACE) { - const char *str = prog_getstring(prog, func->name); - vec_push(prog->function_stack, str); - } - -#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS - if (vec_size(prog->stack)) - { - prog_section_function_t *cur; - cur = prog->stack[vec_size(prog->stack)-1].function; - if (cur) - { - qcint_t *globals = prog->globals + cur->firstlocal; - vec_append(prog->localstack, cur->locals, globals); - } - } -#else - { - qcint_t *globals = prog->globals + func->firstlocal; - vec_append(prog->localstack, func->locals, globals); - } -#endif - - /* copy parameters */ - parampos = func->firstlocal; - for (p = 0; p < func->nargs; ++p) - { - size_t s; - for (s = 0; s < func->argsize[p]; ++s) { - prog->globals[parampos] = prog->globals[OFS_PARM0 + 3*p + s]; - ++parampos; - } - } - - vec_push(prog->stack, st); - - return func->entry; -} - -static qcint_t prog_leavefunction(qc_program_t *prog) { - prog_section_function_t *prev = NULL; - size_t oldsp; - - qc_exec_stack_t st = vec_last(prog->stack); - - if (prog->xflags & VMXF_TRACE) { - if (vec_size(prog->function_stack)) - vec_pop(prog->function_stack); - } - -#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS - if (vec_size(prog->stack) > 1) { - prev = prog->stack[vec_size(prog->stack)-2].function; - oldsp = prog->stack[vec_size(prog->stack)-2].localsp; - } -#else - prev = prog->stack[vec_size(prog->stack)-1].function; - oldsp = prog->stack[vec_size(prog->stack)-1].localsp; -#endif - if (prev) { - qcint_t *globals = prog->globals + prev->firstlocal; - memcpy(globals, prog->localstack + oldsp, prev->locals * sizeof(prog->localstack[0])); - /* vec_remove(prog->localstack, oldsp, vec_size(prog->localstack)-oldsp); */ - vec_shrinkto(prog->localstack, oldsp); - } - - vec_pop(prog->stack); - - return st.stmt - 1; /* offset the ++st */ -} - -bool prog_exec(qc_program_t *prog, prog_section_function_t *func, size_t flags, long maxjumps) { - long jumpcount = 0; - size_t oldxflags = prog->xflags; - prog_section_statement_t *st; - - prog->vmerror = 0; - prog->xflags = flags; - - st = prog->code + prog_enterfunction(prog, func); - --st; - switch (flags) - { - default: - case 0: - { -#define QCVM_LOOP 1 -#define QCVM_PROFILE 0 -#define QCVM_TRACE 0 -# include __FILE__ - } - case (VMXF_TRACE): - { -#define QCVM_PROFILE 0 -#define QCVM_TRACE 1 -# include __FILE__ - } - case (VMXF_PROFILE): - { -#define QCVM_PROFILE 1 -#define QCVM_TRACE 0 -# include __FILE__ - } - case (VMXF_TRACE|VMXF_PROFILE): - { -#define QCVM_PROFILE 1 -#define QCVM_TRACE 1 -# include __FILE__ - } - }; - -cleanup: - prog->xflags = oldxflags; - vec_free(prog->localstack); - vec_free(prog->stack); - if (prog->vmerror) - return false; - return true; -} - -/*********************************************************************** - * main for when building the standalone executor - */ - -#include - -const char *type_name[TYPE_COUNT] = { - "void", - "string", - "float", - "vector", - "entity", - "field", - "function", - "pointer", - "integer", - - "variant", - - "struct", - "union", - "array", - - "nil", - "noexpr" -}; - -typedef struct { - int vtype; - const char *value; -} qcvm_parameter; - -static qcvm_parameter *main_params = NULL; - -#define CheckArgs(num) do { \ - if (prog->argc != (num)) { \ - prog->vmerror++; \ - fprintf(stderr, "ERROR: invalid number of arguments for %s: %i, expected %i\n", \ - __FUNCTION__, prog->argc, (num)); \ - return -1; \ - } \ -} while (0) - -#define GetGlobal(idx) ((qcany_t*)(prog->globals + (idx))) -#define GetArg(num) GetGlobal(OFS_PARM0 + 3*(num)) -#define Return(any) *(GetGlobal(OFS_RETURN)) = (any) - -static int qc_print(qc_program_t *prog) { - size_t i; - const char *laststr = NULL; - for (i = 0; i < (size_t)prog->argc; ++i) { - qcany_t *str = (qcany_t*)(prog->globals + OFS_PARM0 + 3*i); - laststr = prog_getstring(prog, str->string); - printf("%s", laststr); - } - if (laststr && (prog->xflags & VMXF_TRACE)) { - size_t len = strlen(laststr); - if (!len || laststr[len-1] != '\n') - printf("\n"); - } - return 0; -} - -static int qc_error(qc_program_t *prog) { - fprintf(stderr, "*** VM raised an error:\n"); - qc_print(prog); - prog->vmerror++; - return -1; -} - -static int qc_ftos(qc_program_t *prog) { - char buffer[512]; - qcany_t *num; - qcany_t str; - CheckArgs(1); - num = GetArg(0); - util_snprintf(buffer, sizeof(buffer), "%g", num->_float); - str.string = prog_tempstring(prog, buffer); - Return(str); - return 0; -} - -static int qc_stof(qc_program_t *prog) { - qcany_t *str; - qcany_t num; - CheckArgs(1); - str = GetArg(0); - num._float = (float)strtod(prog_getstring(prog, str->string), NULL); - Return(num); - return 0; -} - -static int qc_vtos(qc_program_t *prog) { - char buffer[512]; - qcany_t *num; - qcany_t str; - CheckArgs(1); - num = GetArg(0); - util_snprintf(buffer, sizeof(buffer), "'%g %g %g'", num->vector[0], num->vector[1], num->vector[2]); - str.string = prog_tempstring(prog, buffer); - Return(str); - return 0; -} - -static int qc_etos(qc_program_t *prog) { - char buffer[512]; - qcany_t *num; - qcany_t str; - CheckArgs(1); - num = GetArg(0); - util_snprintf(buffer, sizeof(buffer), "%i", num->_int); - str.string = prog_tempstring(prog, buffer); - Return(str); - return 0; -} - -static int qc_spawn(qc_program_t *prog) { - qcany_t ent; - CheckArgs(0); - ent.edict = prog_spawn_entity(prog); - Return(ent); - return (ent.edict ? 0 : -1); -} - -static int qc_kill(qc_program_t *prog) { - qcany_t *ent; - CheckArgs(1); - ent = GetArg(0); - prog_free_entity(prog, ent->edict); - return 0; -} - -static int qc_sqrt(qc_program_t *prog) { - qcany_t *num, out; - CheckArgs(1); - num = GetArg(0); - out._float = sqrt(num->_float); - Return(out); - return 0; -} - -static int qc_vlen(qc_program_t *prog) { - qcany_t *vec, len; - CheckArgs(1); - vec = GetArg(0); - len._float = sqrt(vec->vector[0] * vec->vector[0] + - vec->vector[1] * vec->vector[1] + - vec->vector[2] * vec->vector[2]); - Return(len); - return 0; -} - -static int qc_normalize(qc_program_t *prog) { - double len; - qcany_t *vec; - qcany_t out; - CheckArgs(1); - vec = GetArg(0); - len = sqrt(vec->vector[0] * vec->vector[0] + - vec->vector[1] * vec->vector[1] + - vec->vector[2] * vec->vector[2]); - if (len) - len = 1.0 / len; - else - len = 0; - out.vector[0] = len * vec->vector[0]; - out.vector[1] = len * vec->vector[1]; - out.vector[2] = len * vec->vector[2]; - Return(out); - return 0; -} - -static int qc_strcat(qc_program_t *prog) { - char *buffer; - size_t len1, len2; - qcany_t *str1, *str2; - qcany_t out; - - const char *cstr1; - const char *cstr2; - - CheckArgs(2); - str1 = GetArg(0); - str2 = GetArg(1); - cstr1 = prog_getstring(prog, str1->string); - cstr2 = prog_getstring(prog, str2->string); - len1 = strlen(cstr1); - len2 = strlen(cstr2); - buffer = (char*)mem_a(len1 + len2 + 1); - memcpy(buffer, cstr1, len1); - memcpy(buffer+len1, cstr2, len2+1); - out.string = prog_tempstring(prog, buffer); - mem_d(buffer); - Return(out); - return 0; -} - -static int qc_strcmp(qc_program_t *prog) { - qcany_t *str1, *str2; - qcany_t out; - - const char *cstr1; - const char *cstr2; - - if (prog->argc != 2 && prog->argc != 3) { - fprintf(stderr, "ERROR: invalid number of arguments for strcmp/strncmp: %i, expected 2 or 3\n", - prog->argc); - return -1; - } - - str1 = GetArg(0); - str2 = GetArg(1); - cstr1 = prog_getstring(prog, str1->string); - cstr2 = prog_getstring(prog, str2->string); - if (prog->argc == 3) - out._float = strncmp(cstr1, cstr2, GetArg(2)->_float); - else - out._float = strcmp(cstr1, cstr2); - Return(out); - return 0; -} - -static int qc_floor(qc_program_t *prog) { - qcany_t *num, out; - CheckArgs(1); - num = GetArg(0); - out._float = floor(num->_float); - Return(out); - return 0; -} - -static int qc_pow(qc_program_t *prog) { - qcany_t *base, *exp, out; - CheckArgs(2); - base = GetArg(0); - exp = GetArg(1); - out._float = powf(base->_float, exp->_float); - Return(out); - return 0; -} - -static prog_builtin_t qc_builtins[] = { - NULL, - &qc_print, /* 1 */ - &qc_ftos, /* 2 */ - &qc_spawn, /* 3 */ - &qc_kill, /* 4 */ - &qc_vtos, /* 5 */ - &qc_error, /* 6 */ - &qc_vlen, /* 7 */ - &qc_etos, /* 8 */ - &qc_stof, /* 9 */ - &qc_strcat, /* 10 */ - &qc_strcmp, /* 11 */ - &qc_normalize, /* 12 */ - &qc_sqrt, /* 13 */ - &qc_floor, /* 14 */ - &qc_pow /* 15 */ -}; - -static const char *arg0 = NULL; - -static void version(void) { - printf("GMQCC-QCVM %d.%d.%d Built %s %s\n", - GMQCC_VERSION_MAJOR, - GMQCC_VERSION_MINOR, - GMQCC_VERSION_PATCH, - __DATE__, - __TIME__ - ); -} - -static void usage(void) { - printf("usage: %s [options] [parameters] file\n", arg0); - printf("options:\n"); - printf(" -h, --help print this message\n" - " -trace trace the execution\n" - " -profile perform profiling during execution\n" - " -info print information from the prog's header\n" - " -disasm disassemble and exit\n" - " -disasm-func func disassemble and exit\n" - " -printdefs list the defs section\n" - " -printfields list the field section\n" - " -printfuns list functions information\n" - " -v be verbose\n" - " -vv be even more verbose\n"); - printf("parameters:\n"); - printf(" -vector pass a vector parameter to main()\n" - " -float pass a float parameter to main()\n" - " -string pass a string parameter to main() \n"); -} - -static void prog_main_setparams(qc_program_t *prog) { - size_t i; - qcany_t *arg; - - for (i = 0; i < vec_size(main_params); ++i) { - arg = GetGlobal(OFS_PARM0 + 3*i); - arg->vector[0] = 0; - arg->vector[1] = 0; - arg->vector[2] = 0; - switch (main_params[i].vtype) { - case TYPE_VECTOR: - (void)util_sscanf(main_params[i].value, " %f %f %f ", - &arg->vector[0], - &arg->vector[1], - &arg->vector[2]); - break; - case TYPE_FLOAT: - arg->_float = atof(main_params[i].value); - break; - case TYPE_STRING: - arg->string = prog_tempstring(prog, main_params[i].value); - break; - default: - fprintf(stderr, "error: unhandled parameter type: %i\n", main_params[i].vtype); - break; - } - } -} - -static void prog_disasm_function(qc_program_t *prog, size_t id); - -int main(int argc, char **argv) { - size_t i; - qcint_t fnmain = -1; - qc_program_t *prog; - size_t xflags = VMXF_DEFAULT; - bool opts_printfields = false; - bool opts_printdefs = false; - bool opts_printfuns = false; - bool opts_disasm = false; - bool opts_info = false; - bool noexec = false; - const char *progsfile = NULL; - const char **dis_list = NULL; - int opts_v = 0; - - arg0 = argv[0]; - - if (argc < 2) { - usage(); - exit(EXIT_FAILURE); - } - - while (argc > 1) { - if (!strcmp(argv[1], "-h") || - !strcmp(argv[1], "-help") || - !strcmp(argv[1], "--help")) - { - usage(); - exit(EXIT_SUCCESS); - } - else if (!strcmp(argv[1], "-v")) { - ++opts_v; - --argc; - ++argv; - } - else if (!strncmp(argv[1], "-vv", 3)) { - const char *av = argv[1]+1; - for (; *av; ++av) { - if (*av == 'v') - ++opts_v; - else { - usage(); - exit(EXIT_FAILURE); - } - } - --argc; - ++argv; - } - else if (!strcmp(argv[1], "-version") || - !strcmp(argv[1], "--version")) - { - version(); - exit(EXIT_SUCCESS); - } - else if (!strcmp(argv[1], "-trace")) { - --argc; - ++argv; - xflags |= VMXF_TRACE; - } - else if (!strcmp(argv[1], "-profile")) { - --argc; - ++argv; - xflags |= VMXF_PROFILE; - } - else if (!strcmp(argv[1], "-info")) { - --argc; - ++argv; - opts_info = true; - noexec = true; - } - else if (!strcmp(argv[1], "-disasm")) { - --argc; - ++argv; - opts_disasm = true; - noexec = true; - } - else if (!strcmp(argv[1], "-disasm-func")) { - --argc; - ++argv; - if (argc <= 1) { - usage(); - exit(EXIT_FAILURE); - } - vec_push(dis_list, argv[1]); - --argc; - ++argv; - noexec = true; - } - else if (!strcmp(argv[1], "-printdefs")) { - --argc; - ++argv; - opts_printdefs = true; - noexec = true; - } - else if (!strcmp(argv[1], "-printfuns")) { - --argc; - ++argv; - opts_printfuns = true; - noexec = true; - } - else if (!strcmp(argv[1], "-printfields")) { - --argc; - ++argv; - opts_printfields = true; - noexec = true; - } - else if (!strcmp(argv[1], "-vector") || - !strcmp(argv[1], "-string") || - !strcmp(argv[1], "-float") ) - { - qcvm_parameter p; - if (argv[1][1] == 'f') - p.vtype = TYPE_FLOAT; - else if (argv[1][1] == 's') - p.vtype = TYPE_STRING; - else if (argv[1][1] == 'v') - p.vtype = TYPE_VECTOR; - else - p.vtype = TYPE_VOID; - - --argc; - ++argv; - if (argc < 2) { - usage(); - exit(EXIT_FAILURE); - } - p.value = argv[1]; - - vec_push(main_params, p); - --argc; - ++argv; - } - else if (!strcmp(argv[1], "--")) { - --argc; - ++argv; - break; - } - else if (argv[1][0] != '-') { - if (progsfile) { - fprintf(stderr, "only 1 program file may be specified\n"); - usage(); - exit(EXIT_FAILURE); - } - progsfile = argv[1]; - --argc; - ++argv; - } - else - { - fprintf(stderr, "unknown parameter: %s\n", argv[1]); - usage(); - exit(EXIT_FAILURE); - } - } - - if (argc == 2 && !progsfile) { - progsfile = argv[1]; - --argc; - ++argv; - } - - if (!progsfile) { - fprintf(stderr, "must specify a program to execute\n"); - usage(); - exit(EXIT_FAILURE); - } - - prog = prog_load(progsfile, noexec); - if (!prog) { - fprintf(stderr, "failed to load program '%s'\n", progsfile); - exit(EXIT_FAILURE); - } - - prog->builtins = qc_builtins; - prog->builtins_count = GMQCC_ARRAY_COUNT(qc_builtins); - - if (opts_info) { - printf("Program's system-checksum = 0x%04x\n", (unsigned int)prog->crc16); - printf("Entity field space: %u\n", (unsigned int)prog->entityfields); - printf("Globals: %u\n", (unsigned int)vec_size(prog->globals)); - printf("Counts:\n" - " code: %lu\n" - " defs: %lu\n" - " fields: %lu\n" - " functions: %lu\n" - " strings: %lu\n", - (unsigned long)vec_size(prog->code), - (unsigned long)vec_size(prog->defs), - (unsigned long)vec_size(prog->fields), - (unsigned long)vec_size(prog->functions), - (unsigned long)vec_size(prog->strings)); - } - - if (opts_info) { - prog_delete(prog); - return 0; - } - for (i = 0; i < vec_size(dis_list); ++i) { - size_t k; - printf("Looking for `%s`\n", dis_list[i]); - for (k = 1; k < vec_size(prog->functions); ++k) { - const char *name = prog_getstring(prog, prog->functions[k].name); - if (!strcmp(name, dis_list[i])) { - prog_disasm_function(prog, k); - break; - } - } - } - if (opts_disasm) { - for (i = 1; i < vec_size(prog->functions); ++i) - prog_disasm_function(prog, i); - return 0; - } - if (opts_printdefs) { - const char *getstring = NULL; - for (i = 0; i < vec_size(prog->defs); ++i) { - printf("Global: %8s %-16s at %u%s", - type_name[prog->defs[i].type & DEF_TYPEMASK], - prog_getstring(prog, prog->defs[i].name), - (unsigned int)prog->defs[i].offset, - ((prog->defs[i].type & DEF_SAVEGLOBAL) ? " [SAVE]" : "")); - if (opts_v) { - switch (prog->defs[i].type & DEF_TYPEMASK) { - case TYPE_FLOAT: - printf(" [init: %g]", ((qcany_t*)(prog->globals + prog->defs[i].offset))->_float); - break; - case TYPE_INTEGER: - printf(" [init: %i]", (int)( ((qcany_t*)(prog->globals + prog->defs[i].offset))->_int )); - break; - case TYPE_ENTITY: - case TYPE_FUNCTION: - case TYPE_FIELD: - case TYPE_POINTER: - printf(" [init: %u]", (unsigned)( ((qcany_t*)(prog->globals + prog->defs[i].offset))->_int )); - break; - case TYPE_STRING: - getstring = prog_getstring(prog, ((qcany_t*)(prog->globals + prog->defs[i].offset))->string); - printf(" [init: `"); - print_escaped_string(getstring, strlen(getstring)); - printf("`]\n"); - break; - default: - break; - } - } - printf("\n"); - } - } - if (opts_printfields) { - for (i = 0; i < vec_size(prog->fields); ++i) { - printf("Field: %8s %-16s at %u%s\n", - type_name[prog->fields[i].type], - prog_getstring(prog, prog->fields[i].name), - (unsigned int)prog->fields[i].offset, - ((prog->fields[i].type & DEF_SAVEGLOBAL) ? " [SAVE]" : "")); - } - } - if (opts_printfuns) { - for (i = 0; i < vec_size(prog->functions); ++i) { - int32_t a; - printf("Function: %-16s taking %u parameters:(", - prog_getstring(prog, prog->functions[i].name), - (unsigned int)prog->functions[i].nargs); - for (a = 0; a < prog->functions[i].nargs; ++a) { - printf(" %i", prog->functions[i].argsize[a]); - } - if (opts_v > 1) { - int32_t start = prog->functions[i].entry; - if (start < 0) - printf(") builtin %i\n", (int)-start); - else { - size_t funsize = 0; - prog_section_statement_t *st = prog->code + start; - for (;st->opcode != INSTR_DONE; ++st) - ++funsize; - printf(") - %lu instructions", (unsigned long)funsize); - if (opts_v > 2) { - printf(" - locals: %i + %i\n", - prog->functions[i].firstlocal, - prog->functions[i].locals); - } - else - printf("\n"); - } - } - else if (opts_v) { - printf(") locals: %i + %i\n", - prog->functions[i].firstlocal, - prog->functions[i].locals); - } - else - printf(")\n"); - } - } - if (!noexec) { - for (i = 1; i < vec_size(prog->functions); ++i) { - const char *name = prog_getstring(prog, prog->functions[i].name); - if (!strcmp(name, "main")) - fnmain = (qcint_t)i; - } - if (fnmain > 0) - { - prog_main_setparams(prog); - prog_exec(prog, &prog->functions[fnmain], xflags, VM_JUMPS_DEFAULT); - } - else - fprintf(stderr, "No main function found\n"); - } - - prog_delete(prog); - return 0; -} - -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; - - if (fdef->entry < 0) { - printf("FUNCTION \"%s\" = builtin #%i\n", prog_getstring(prog, fdef->name), (int)-fdef->entry); - return; - } - else - printf("FUNCTION \"%s\"\n", prog_getstring(prog, fdef->name)); - - st = prog->code + fdef->entry; - while (st->opcode != INSTR_DONE) { - prog_print_statement(prog, st); - ++st; - } -} -#else /* !QCVM_LOOP */ -/* - * Everything from here on is not including into the compilation of the - * executor. This is simply code that is #included via #include __FILE__ - * see when QCVM_LOOP is defined, the rest of the code above do not get - * re-included. So this really just acts like one large macro, but it - * sort of isn't, which makes it nicer looking. - */ - -#define OPA ( (qcany_t*) (prog->globals + st->o1.u1) ) -#define OPB ( (qcany_t*) (prog->globals + st->o2.u1) ) -#define OPC ( (qcany_t*) (prog->globals + st->o3.u1) ) - -#define GLOBAL(x) ( (qcany_t*) (prog->globals + (x)) ) - -/* to be consistent with current darkplaces behaviour */ -#if !defined(FLOAT_IS_TRUE_FOR_INT) -# define FLOAT_IS_TRUE_FOR_INT(x) ( (x) & 0x7FFFFFFF ) -#endif - -while (prog->vmerror == 0) { - prog_section_function_t *newf; - qcany_t *ed; - qcany_t *ptr; - - ++st; - -#if QCVM_PROFILE - prog->profile[st - prog->code]++; -#endif - -#if QCVM_TRACE - prog_print_statement(prog, st); -#endif - - switch (st->opcode) - { - default: - qcvmerror(prog, "Illegal instruction in %s\n", prog->filename); - goto cleanup; - - case INSTR_DONE: - case INSTR_RETURN: - /* TODO: add instruction count to function profile count */ - GLOBAL(OFS_RETURN)->ivector[0] = OPA->ivector[0]; - GLOBAL(OFS_RETURN)->ivector[1] = OPA->ivector[1]; - GLOBAL(OFS_RETURN)->ivector[2] = OPA->ivector[2]; - - st = prog->code + prog_leavefunction(prog); - if (!vec_size(prog->stack)) - goto cleanup; - - break; - - case INSTR_MUL_F: - OPC->_float = OPA->_float * OPB->_float; - break; - case INSTR_MUL_V: - OPC->_float = OPA->vector[0]*OPB->vector[0] + - OPA->vector[1]*OPB->vector[1] + - OPA->vector[2]*OPB->vector[2]; - break; - case INSTR_MUL_FV: - { - qcfloat_t f = OPA->_float; - OPC->vector[0] = f * OPB->vector[0]; - OPC->vector[1] = f * OPB->vector[1]; - OPC->vector[2] = f * OPB->vector[2]; - break; - } - case INSTR_MUL_VF: - { - qcfloat_t f = OPB->_float; - OPC->vector[0] = f * OPA->vector[0]; - OPC->vector[1] = f * OPA->vector[1]; - OPC->vector[2] = f * OPA->vector[2]; - break; - } - case INSTR_DIV_F: - if (OPB->_float != 0.0f) - OPC->_float = OPA->_float / OPB->_float; - else - OPC->_float = 0; - break; - - case INSTR_ADD_F: - OPC->_float = OPA->_float + OPB->_float; - break; - case INSTR_ADD_V: - OPC->vector[0] = OPA->vector[0] + OPB->vector[0]; - OPC->vector[1] = OPA->vector[1] + OPB->vector[1]; - OPC->vector[2] = OPA->vector[2] + OPB->vector[2]; - break; - case INSTR_SUB_F: - OPC->_float = OPA->_float - OPB->_float; - break; - case INSTR_SUB_V: - OPC->vector[0] = OPA->vector[0] - OPB->vector[0]; - OPC->vector[1] = OPA->vector[1] - OPB->vector[1]; - OPC->vector[2] = OPA->vector[2] - OPB->vector[2]; - break; - - case INSTR_EQ_F: - OPC->_float = (OPA->_float == OPB->_float); - break; - case INSTR_EQ_V: - OPC->_float = ((OPA->vector[0] == OPB->vector[0]) && - (OPA->vector[1] == OPB->vector[1]) && - (OPA->vector[2] == OPB->vector[2]) ); - break; - case INSTR_EQ_S: - OPC->_float = !strcmp(prog_getstring(prog, OPA->string), - prog_getstring(prog, OPB->string)); - break; - case INSTR_EQ_E: - OPC->_float = (OPA->_int == OPB->_int); - break; - case INSTR_EQ_FNC: - OPC->_float = (OPA->function == OPB->function); - break; - case INSTR_NE_F: - OPC->_float = (OPA->_float != OPB->_float); - break; - case INSTR_NE_V: - OPC->_float = ((OPA->vector[0] != OPB->vector[0]) || - (OPA->vector[1] != OPB->vector[1]) || - (OPA->vector[2] != OPB->vector[2]) ); - break; - case INSTR_NE_S: - OPC->_float = !!strcmp(prog_getstring(prog, OPA->string), - prog_getstring(prog, OPB->string)); - break; - case INSTR_NE_E: - OPC->_float = (OPA->_int != OPB->_int); - break; - case INSTR_NE_FNC: - OPC->_float = (OPA->function != OPB->function); - break; - - case INSTR_LE: - OPC->_float = (OPA->_float <= OPB->_float); - break; - case INSTR_GE: - OPC->_float = (OPA->_float >= OPB->_float); - break; - case INSTR_LT: - OPC->_float = (OPA->_float < OPB->_float); - break; - case INSTR_GT: - OPC->_float = (OPA->_float > OPB->_float); - break; - - case INSTR_LOAD_F: - case INSTR_LOAD_S: - case INSTR_LOAD_FLD: - case INSTR_LOAD_ENT: - case INSTR_LOAD_FNC: - if (OPA->edict < 0 || OPA->edict >= prog->entities) { - qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename); - goto cleanup; - } - if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) { - qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)", - prog->filename, - OPB->_int); - goto cleanup; - } - ed = prog_getedict(prog, OPA->edict); - OPC->_int = ((qcany_t*)( ((qcint_t*)ed) + OPB->_int ))->_int; - break; - case INSTR_LOAD_V: - if (OPA->edict < 0 || OPA->edict >= prog->entities) { - qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename); - goto cleanup; - } - if (OPB->_int < 0 || OPB->_int + 3 > (qcint_t)prog->entityfields) - { - qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)", - prog->filename, - OPB->_int + 2); - goto cleanup; - } - ed = prog_getedict(prog, OPA->edict); - ptr = (qcany_t*)( ((qcint_t*)ed) + OPB->_int ); - OPC->ivector[0] = ptr->ivector[0]; - OPC->ivector[1] = ptr->ivector[1]; - OPC->ivector[2] = ptr->ivector[2]; - break; - - case INSTR_ADDRESS: - if (OPA->edict < 0 || OPA->edict >= prog->entities) { - qcvmerror(prog, "prog `%s` attempted to address an out of bounds entity %i", prog->filename, OPA->edict); - goto cleanup; - } - if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) - { - qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)", - prog->filename, - OPB->_int); - goto cleanup; - } - - ed = prog_getedict(prog, OPA->edict); - OPC->_int = ((qcint_t*)ed) - prog->entitydata + OPB->_int; - break; - - case INSTR_STORE_F: - case INSTR_STORE_S: - case INSTR_STORE_ENT: - case INSTR_STORE_FLD: - case INSTR_STORE_FNC: - OPB->_int = OPA->_int; - break; - case INSTR_STORE_V: - OPB->ivector[0] = OPA->ivector[0]; - OPB->ivector[1] = OPA->ivector[1]; - OPB->ivector[2] = OPA->ivector[2]; - break; - - case INSTR_STOREP_F: - case INSTR_STOREP_S: - case INSTR_STOREP_ENT: - case INSTR_STOREP_FLD: - case INSTR_STOREP_FNC: - if (OPB->_int < 0 || OPB->_int >= (qcint_t)vec_size(prog->entitydata)) { - qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int); - goto cleanup; - } - if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites) - qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n", - prog->filename, - prog_getstring(prog, prog_entfield(prog, OPB->_int)->name), - OPB->_int); - ptr = (qcany_t*)(prog->entitydata + OPB->_int); - ptr->_int = OPA->_int; - break; - case INSTR_STOREP_V: - if (OPB->_int < 0 || OPB->_int + 2 >= (qcint_t)vec_size(prog->entitydata)) { - qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int); - goto cleanup; - } - if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites) - qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n", - prog->filename, - prog_getstring(prog, prog_entfield(prog, OPB->_int)->name), - OPB->_int); - ptr = (qcany_t*)(prog->entitydata + OPB->_int); - ptr->ivector[0] = OPA->ivector[0]; - ptr->ivector[1] = OPA->ivector[1]; - ptr->ivector[2] = OPA->ivector[2]; - break; - - case INSTR_NOT_F: - OPC->_float = !FLOAT_IS_TRUE_FOR_INT(OPA->_int); - break; - case INSTR_NOT_V: - OPC->_float = !OPA->vector[0] && - !OPA->vector[1] && - !OPA->vector[2]; - break; - case INSTR_NOT_S: - OPC->_float = !OPA->string || - !*prog_getstring(prog, OPA->string); - break; - case INSTR_NOT_ENT: - OPC->_float = (OPA->edict == 0); - break; - case INSTR_NOT_FNC: - OPC->_float = !OPA->function; - break; - - case INSTR_IF: - /* this is consistent with darkplaces' behaviour */ - if(FLOAT_IS_TRUE_FOR_INT(OPA->_int)) - { - st += st->o2.s1 - 1; /* offset the s++ */ - if (++jumpcount >= maxjumps) - qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount); - } - break; - case INSTR_IFNOT: - if(!FLOAT_IS_TRUE_FOR_INT(OPA->_int)) - { - st += st->o2.s1 - 1; /* offset the s++ */ - if (++jumpcount >= maxjumps) - qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount); - } - break; - - case INSTR_CALL0: - case INSTR_CALL1: - case INSTR_CALL2: - case INSTR_CALL3: - case INSTR_CALL4: - case INSTR_CALL5: - case INSTR_CALL6: - case INSTR_CALL7: - case INSTR_CALL8: - prog->argc = st->opcode - INSTR_CALL0; - if (!OPA->function) - qcvmerror(prog, "NULL function in `%s`", prog->filename); - - if(!OPA->function || OPA->function >= (qcint_t)vec_size(prog->functions)) - { - qcvmerror(prog, "CALL outside the program in `%s`", prog->filename); - goto cleanup; - } - - newf = &prog->functions[OPA->function]; - newf->profile++; - - prog->statement = (st - prog->code) + 1; - - if (newf->entry < 0) - { - /* negative statements are built in functions */ - qcint_t builtinnumber = -newf->entry; - if (builtinnumber < (qcint_t)prog->builtins_count && prog->builtins[builtinnumber]) - prog->builtins[builtinnumber](prog); - else - qcvmerror(prog, "No such builtin #%i in %s! Try updating your gmqcc sources", - builtinnumber, prog->filename); - } - else - st = prog->code + prog_enterfunction(prog, newf) - 1; /* offset st++ */ - if (prog->vmerror) - goto cleanup; - break; - - case INSTR_STATE: - { - 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++ */ - if (++jumpcount == 10000000) - qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount); - break; - - case INSTR_AND: - OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) && - FLOAT_IS_TRUE_FOR_INT(OPB->_int); - break; - case INSTR_OR: - OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) || - FLOAT_IS_TRUE_FOR_INT(OPB->_int); - break; - - case INSTR_BITAND: - OPC->_float = ((int)OPA->_float) & ((int)OPB->_float); - break; - case INSTR_BITOR: - OPC->_float = ((int)OPA->_float) | ((int)OPB->_float); - break; - } -} - -#undef QCVM_PROFILE -#undef QCVM_TRACE -#endif /* !QCVM_LOOP */ diff --git a/exec.cpp b/exec.cpp new file mode 100644 index 0000000..8ec97e4 --- /dev/null +++ b/exec.cpp @@ -0,0 +1,1637 @@ +#ifndef QCVM_LOOP +#include +#include +#include +#include + +#include "gmqcc.h" + +static void loaderror(const char *fmt, ...) +{ + int err = errno; + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf(": %s\n", util_strerror(err)); +} + +static void qcvmerror(qc_program_t *prog, const char *fmt, ...) +{ + va_list ap; + + prog->vmerror++; + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + putchar('\n'); +} + +qc_program_t* prog_load(const char *filename, bool skipversion) +{ + prog_header_t header; + qc_program_t *prog; + size_t i; + FILE *file = fopen(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; + + if (fread(&header, sizeof(header), 1, file) != 1) { + loaderror("failed to read header from '%s'", filename); + fclose(file); + return NULL; + } + + util_swap_header(&header); + + if (!skipversion && header.version != 6) { + loaderror("header says this is a version %i progs, we need version 6\n", header.version); + fclose(file); + return NULL; + } + + prog = (qc_program_t*)mem_a(sizeof(qc_program_t)); + if (!prog) { + fclose(file); + fprintf(stderr, "failed to allocate program data\n"); + return NULL; + } + memset(prog, 0, sizeof(*prog)); + + prog->entityfields = header.entfield; + prog->crc16 = header.crc16; + + prog->filename = util_strdup(filename); + if (!prog->filename) { + loaderror("failed to store program name"); + goto error; + } + +#define read_data(hdrvar, progvar, reserved) \ + if (fseek(file, header.hdrvar.offset, SEEK_SET) != 0) { \ + loaderror("seek failed"); \ + goto error; \ + } \ + if (fread( \ + vec_add(prog->progvar, header.hdrvar.length + reserved), \ + sizeof(*prog->progvar), \ + header.hdrvar.length, \ + file \ + )!= header.hdrvar.length \ + ) { \ + loaderror("read failed"); \ + goto error; \ + } +#define read_data1(x) read_data(x, x, 0) +#define read_data2(x, y) read_data(x, x, y) + + read_data (statements, code, 0); + read_data1(defs); + read_data1(fields); + read_data1(functions); + read_data1(strings); + read_data2(globals, 2); /* reserve more in case a RETURN using with the global at "the end" exists */ + + util_swap_statements (prog->code); + util_swap_defs_fields(prog->defs); + util_swap_defs_fields(prog->fields); + util_swap_functions (prog->functions); + util_swap_globals (prog->globals); + + fclose(file); + + /* profile counters */ + memset(vec_add(prog->profile, vec_size(prog->code)), 0, sizeof(prog->profile[0]) * vec_size(prog->code)); + + /* Add tempstring area */ + prog->tempstring_start = vec_size(prog->strings); + prog->tempstring_at = vec_size(prog->strings); + memset(vec_add(prog->strings, 16*1024), 0, 16*1024); + + /* spawn the world entity */ + vec_push(prog->entitypool, true); + 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: + if (prog->filename) + mem_d(prog->filename); + vec_free(prog->code); + vec_free(prog->defs); + vec_free(prog->fields); + vec_free(prog->functions); + vec_free(prog->strings); + vec_free(prog->globals); + vec_free(prog->entitydata); + vec_free(prog->entitypool); + mem_d(prog); + + fclose(file); + return NULL; +} + +void prog_delete(qc_program_t *prog) +{ + if (prog->filename) mem_d(prog->filename); + vec_free(prog->code); + vec_free(prog->defs); + vec_free(prog->fields); + vec_free(prog->functions); + vec_free(prog->strings); + vec_free(prog->globals); + vec_free(prog->entitydata); + vec_free(prog->entitypool); + vec_free(prog->localstack); + vec_free(prog->stack); + vec_free(prog->profile); + mem_d(prog); +} + +/*********************************************************************** + * VM code + */ + +const char* prog_getstring(qc_program_t *prog, qcint_t str) { + /* cast for return required for C++ */ + if (str < 0 || str >= (qcint_t)vec_size(prog->strings)) + return "<<>>"; + + return prog->strings + str; +} + +prog_section_def_t* prog_entfield(qc_program_t *prog, qcint_t off) { + size_t i; + for (i = 0; i < vec_size(prog->fields); ++i) { + if (prog->fields[i].offset == off) + return (prog->fields + i); + } + return NULL; +} + +prog_section_def_t* prog_getdef(qc_program_t *prog, qcint_t off) +{ + size_t i; + for (i = 0; i < vec_size(prog->defs); ++i) { + if (prog->defs[i].offset == off) + return (prog->defs + i); + } + return NULL; +} + +qcany_t* prog_getedict(qc_program_t *prog, qcint_t e) { + if (e >= (qcint_t)vec_size(prog->entitypool)) { + prog->vmerror++; + fprintf(stderr, "Accessing out of bounds edict %i\n", (int)e); + e = 0; + } + return (qcany_t*)(prog->entitydata + (prog->entityfields * e)); +} + +static qcint_t prog_spawn_entity(qc_program_t *prog) { + char *data; + qcint_t e; + for (e = 0; e < (qcint_t)vec_size(prog->entitypool); ++e) { + if (!prog->entitypool[e]) { + data = (char*)(prog->entitydata + (prog->entityfields * e)); + memset(data, 0, prog->entityfields * sizeof(qcint_t)); + return e; + } + } + vec_push(prog->entitypool, true); + prog->entities++; + data = (char*)vec_add(prog->entitydata, prog->entityfields); + memset(data, 0, prog->entityfields * sizeof(qcint_t)); + return e; +} + +static void prog_free_entity(qc_program_t *prog, qcint_t e) { + if (!e) { + prog->vmerror++; + fprintf(stderr, "Trying to free world entity\n"); + return; + } + if (e >= (qcint_t)vec_size(prog->entitypool)) { + prog->vmerror++; + fprintf(stderr, "Trying to free out of bounds entity\n"); + return; + } + if (!prog->entitypool[e]) { + prog->vmerror++; + fprintf(stderr, "Double free on entity\n"); + return; + } + prog->entitypool[e] = false; +} + +qcint_t prog_tempstring(qc_program_t *prog, const char *str) { + size_t len = strlen(str); + size_t at = prog->tempstring_at; + + /* when we reach the end we start over */ + if (at + len >= vec_size(prog->strings)) + at = prog->tempstring_start; + + /* when it doesn't fit, reallocate */ + if (at + len >= vec_size(prog->strings)) + { + (void)vec_add(prog->strings, len+1); + memcpy(prog->strings + at, str, len+1); + return at; + } + + /* when it fits, just copy */ + memcpy(prog->strings + at, str, len+1); + prog->tempstring_at += len+1; + return at; +} + +static size_t print_escaped_string(const char *str, size_t maxlen) { + size_t len = 2; + putchar('"'); + --maxlen; /* because we're lazy and have escape sequences */ + while (*str) { + if (len >= maxlen) { + putchar('.'); + putchar('.'); + putchar('.'); + len += 3; + break; + } + switch (*str) { + case '\a': len += 2; putchar('\\'); putchar('a'); break; + case '\b': len += 2; putchar('\\'); putchar('b'); break; + case '\r': len += 2; putchar('\\'); putchar('r'); break; + case '\n': len += 2; putchar('\\'); putchar('n'); break; + case '\t': len += 2; putchar('\\'); putchar('t'); break; + case '\f': len += 2; putchar('\\'); putchar('f'); break; + case '\v': len += 2; putchar('\\'); putchar('v'); break; + case '\\': len += 2; putchar('\\'); putchar('\\'); break; + case '"': len += 2; putchar('\\'); putchar('"'); break; + default: + ++len; + putchar(*str); + break; + } + ++str; + } + putchar('"'); + return len; +} + +static void trace_print_global(qc_program_t *prog, unsigned int glob, int vtype) { + static char spaces[28+1] = " "; + prog_section_def_t *def; + qcany_t *value; + int len; + + if (!glob) { + if ((len = printf(",")) == -1) + len = 0; + + goto done; + } + + def = prog_getdef(prog, glob); + value = (qcany_t*)(&prog->globals[glob]); + + len = printf("[@%u] ", glob); + if (def) { + const char *name = prog_getstring(prog, def->name); + if (name[0] == '#') + len += printf("$"); + else + len += printf("%s ", name); + vtype = def->type & DEF_TYPEMASK; + } + + switch (vtype) { + case TYPE_VOID: + case TYPE_ENTITY: + case TYPE_FIELD: + case TYPE_FUNCTION: + case TYPE_POINTER: + len += printf("(%i),", value->_int); + break; + case TYPE_VECTOR: + len += printf("'%g %g %g',", value->vector[0], + value->vector[1], + value->vector[2]); + break; + case TYPE_STRING: + if (value->string) + len += print_escaped_string(prog_getstring(prog, value->string), sizeof(spaces)-len-5); + else + len += printf("(null)"); + len += printf(","); + /* len += printf("\"%s\",", prog_getstring(prog, value->string)); */ + break; + case TYPE_FLOAT: + default: + len += printf("%g,", value->_float); + break; + } +done: + if (len < (int)sizeof(spaces)-1) { + spaces[sizeof(spaces)-1-len] = 0; + fputs(spaces, stdout); + spaces[sizeof(spaces)-1-len] = ' '; + } +} + +static void prog_print_statement(qc_program_t *prog, prog_section_statement_t *st) { + if (st->opcode >= VINSTR_END) { + printf("\n", st->opcode); + return; + } + if ((prog->xflags & VMXF_TRACE) && vec_size(prog->function_stack)) { + size_t i; + for (i = 0; i < vec_size(prog->function_stack); ++i) + printf("->"); + printf("%s:", vec_last(prog->function_stack)); + } + printf(" <> %-12s", util_instr_str[st->opcode]); + if (st->opcode >= INSTR_IF && + st->opcode <= INSTR_IFNOT) + { + trace_print_global(prog, st->o1.u1, TYPE_FLOAT); + printf("%d\n", st->o2.s1); + } + else if (st->opcode >= INSTR_CALL0 && + st->opcode <= INSTR_CALL8) + { + trace_print_global(prog, st->o1.u1, TYPE_FUNCTION); + printf("\n"); + } + else if (st->opcode == INSTR_GOTO) + { + printf("%i\n", st->o1.s1); + } + else + { + int t[3] = { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT }; + switch (st->opcode) + { + case INSTR_MUL_FV: + t[1] = t[2] = TYPE_VECTOR; + break; + case INSTR_MUL_VF: + t[0] = t[2] = TYPE_VECTOR; + break; + case INSTR_MUL_V: + t[0] = t[1] = TYPE_VECTOR; + break; + case INSTR_ADD_V: + case INSTR_SUB_V: + case INSTR_EQ_V: + case INSTR_NE_V: + t[0] = t[1] = t[2] = TYPE_VECTOR; + break; + case INSTR_EQ_S: + case INSTR_NE_S: + t[0] = t[1] = TYPE_STRING; + break; + case INSTR_STORE_F: + case INSTR_STOREP_F: + t[2] = -1; + break; + case INSTR_STORE_V: + t[0] = t[1] = TYPE_VECTOR; t[2] = -1; + break; + case INSTR_STORE_S: + t[0] = t[1] = TYPE_STRING; t[2] = -1; + break; + case INSTR_STORE_ENT: + t[0] = t[1] = TYPE_ENTITY; t[2] = -1; + break; + case INSTR_STORE_FLD: + t[0] = t[1] = TYPE_FIELD; t[2] = -1; + break; + case INSTR_STORE_FNC: + t[0] = t[1] = TYPE_FUNCTION; t[2] = -1; + break; + case INSTR_STOREP_V: + t[0] = TYPE_VECTOR; t[1] = TYPE_ENTITY; t[2] = -1; + break; + case INSTR_STOREP_S: + t[0] = TYPE_STRING; t[1] = TYPE_ENTITY; t[2] = -1; + break; + case INSTR_STOREP_ENT: + t[0] = TYPE_ENTITY; t[1] = TYPE_ENTITY; t[2] = -1; + break; + case INSTR_STOREP_FLD: + t[0] = TYPE_FIELD; t[1] = TYPE_ENTITY; t[2] = -1; + break; + case INSTR_STOREP_FNC: + t[0] = TYPE_FUNCTION; t[1] = TYPE_ENTITY; t[2] = -1; + break; + } + if (t[0] >= 0) trace_print_global(prog, st->o1.u1, t[0]); + else printf("(none), "); + if (t[1] >= 0) trace_print_global(prog, st->o2.u1, t[1]); + else printf("(none), "); + if (t[2] >= 0) trace_print_global(prog, st->o3.u1, t[2]); + else printf("(none)"); + printf("\n"); + } +} + +static qcint_t prog_enterfunction(qc_program_t *prog, prog_section_function_t *func) { + qc_exec_stack_t st; + size_t parampos; + int32_t p; + + /* back up locals */ + st.localsp = vec_size(prog->localstack); + st.stmt = prog->statement; + st.function = func; + + if (prog->xflags & VMXF_TRACE) { + const char *str = prog_getstring(prog, func->name); + vec_push(prog->function_stack, str); + } + +#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS + if (vec_size(prog->stack)) + { + prog_section_function_t *cur; + cur = prog->stack[vec_size(prog->stack)-1].function; + if (cur) + { + qcint_t *globals = prog->globals + cur->firstlocal; + vec_append(prog->localstack, cur->locals, globals); + } + } +#else + { + qcint_t *globals = prog->globals + func->firstlocal; + vec_append(prog->localstack, func->locals, globals); + } +#endif + + /* copy parameters */ + parampos = func->firstlocal; + for (p = 0; p < func->nargs; ++p) + { + size_t s; + for (s = 0; s < func->argsize[p]; ++s) { + prog->globals[parampos] = prog->globals[OFS_PARM0 + 3*p + s]; + ++parampos; + } + } + + vec_push(prog->stack, st); + + return func->entry; +} + +static qcint_t prog_leavefunction(qc_program_t *prog) { + prog_section_function_t *prev = NULL; + size_t oldsp; + + qc_exec_stack_t st = vec_last(prog->stack); + + if (prog->xflags & VMXF_TRACE) { + if (vec_size(prog->function_stack)) + vec_pop(prog->function_stack); + } + +#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS + if (vec_size(prog->stack) > 1) { + prev = prog->stack[vec_size(prog->stack)-2].function; + oldsp = prog->stack[vec_size(prog->stack)-2].localsp; + } +#else + prev = prog->stack[vec_size(prog->stack)-1].function; + oldsp = prog->stack[vec_size(prog->stack)-1].localsp; +#endif + if (prev) { + qcint_t *globals = prog->globals + prev->firstlocal; + memcpy(globals, prog->localstack + oldsp, prev->locals * sizeof(prog->localstack[0])); + /* vec_remove(prog->localstack, oldsp, vec_size(prog->localstack)-oldsp); */ + vec_shrinkto(prog->localstack, oldsp); + } + + vec_pop(prog->stack); + + return st.stmt - 1; /* offset the ++st */ +} + +bool prog_exec(qc_program_t *prog, prog_section_function_t *func, size_t flags, long maxjumps) { + long jumpcount = 0; + size_t oldxflags = prog->xflags; + prog_section_statement_t *st; + + prog->vmerror = 0; + prog->xflags = flags; + + st = prog->code + prog_enterfunction(prog, func); + --st; + switch (flags) + { + default: + case 0: + { +#define QCVM_LOOP 1 +#define QCVM_PROFILE 0 +#define QCVM_TRACE 0 +# include __FILE__ + } + case (VMXF_TRACE): + { +#define QCVM_PROFILE 0 +#define QCVM_TRACE 1 +# include __FILE__ + } + case (VMXF_PROFILE): + { +#define QCVM_PROFILE 1 +#define QCVM_TRACE 0 +# include __FILE__ + } + case (VMXF_TRACE|VMXF_PROFILE): + { +#define QCVM_PROFILE 1 +#define QCVM_TRACE 1 +# include __FILE__ + } + }; + +cleanup: + prog->xflags = oldxflags; + vec_free(prog->localstack); + vec_free(prog->stack); + if (prog->vmerror) + return false; + return true; +} + +/*********************************************************************** + * main for when building the standalone executor + */ + +#include + +const char *type_name[TYPE_COUNT] = { + "void", + "string", + "float", + "vector", + "entity", + "field", + "function", + "pointer", + "integer", + + "variant", + + "struct", + "union", + "array", + + "nil", + "noexpr" +}; + +typedef struct { + int vtype; + const char *value; +} qcvm_parameter; + +static qcvm_parameter *main_params = NULL; + +#define CheckArgs(num) do { \ + if (prog->argc != (num)) { \ + prog->vmerror++; \ + fprintf(stderr, "ERROR: invalid number of arguments for %s: %i, expected %i\n", \ + __FUNCTION__, prog->argc, (num)); \ + return -1; \ + } \ +} while (0) + +#define GetGlobal(idx) ((qcany_t*)(prog->globals + (idx))) +#define GetArg(num) GetGlobal(OFS_PARM0 + 3*(num)) +#define Return(any) *(GetGlobal(OFS_RETURN)) = (any) + +static int qc_print(qc_program_t *prog) { + size_t i; + const char *laststr = NULL; + for (i = 0; i < (size_t)prog->argc; ++i) { + qcany_t *str = (qcany_t*)(prog->globals + OFS_PARM0 + 3*i); + laststr = prog_getstring(prog, str->string); + printf("%s", laststr); + } + if (laststr && (prog->xflags & VMXF_TRACE)) { + size_t len = strlen(laststr); + if (!len || laststr[len-1] != '\n') + printf("\n"); + } + return 0; +} + +static int qc_error(qc_program_t *prog) { + fprintf(stderr, "*** VM raised an error:\n"); + qc_print(prog); + prog->vmerror++; + return -1; +} + +static int qc_ftos(qc_program_t *prog) { + char buffer[512]; + qcany_t *num; + qcany_t str; + CheckArgs(1); + num = GetArg(0); + util_snprintf(buffer, sizeof(buffer), "%g", num->_float); + str.string = prog_tempstring(prog, buffer); + Return(str); + return 0; +} + +static int qc_stof(qc_program_t *prog) { + qcany_t *str; + qcany_t num; + CheckArgs(1); + str = GetArg(0); + num._float = (float)strtod(prog_getstring(prog, str->string), NULL); + Return(num); + return 0; +} + +static int qc_vtos(qc_program_t *prog) { + char buffer[512]; + qcany_t *num; + qcany_t str; + CheckArgs(1); + num = GetArg(0); + util_snprintf(buffer, sizeof(buffer), "'%g %g %g'", num->vector[0], num->vector[1], num->vector[2]); + str.string = prog_tempstring(prog, buffer); + Return(str); + return 0; +} + +static int qc_etos(qc_program_t *prog) { + char buffer[512]; + qcany_t *num; + qcany_t str; + CheckArgs(1); + num = GetArg(0); + util_snprintf(buffer, sizeof(buffer), "%i", num->_int); + str.string = prog_tempstring(prog, buffer); + Return(str); + return 0; +} + +static int qc_spawn(qc_program_t *prog) { + qcany_t ent; + CheckArgs(0); + ent.edict = prog_spawn_entity(prog); + Return(ent); + return (ent.edict ? 0 : -1); +} + +static int qc_kill(qc_program_t *prog) { + qcany_t *ent; + CheckArgs(1); + ent = GetArg(0); + prog_free_entity(prog, ent->edict); + return 0; +} + +static int qc_sqrt(qc_program_t *prog) { + qcany_t *num, out; + CheckArgs(1); + num = GetArg(0); + out._float = sqrt(num->_float); + Return(out); + return 0; +} + +static int qc_vlen(qc_program_t *prog) { + qcany_t *vec, len; + CheckArgs(1); + vec = GetArg(0); + len._float = sqrt(vec->vector[0] * vec->vector[0] + + vec->vector[1] * vec->vector[1] + + vec->vector[2] * vec->vector[2]); + Return(len); + return 0; +} + +static int qc_normalize(qc_program_t *prog) { + double len; + qcany_t *vec; + qcany_t out; + CheckArgs(1); + vec = GetArg(0); + len = sqrt(vec->vector[0] * vec->vector[0] + + vec->vector[1] * vec->vector[1] + + vec->vector[2] * vec->vector[2]); + if (len) + len = 1.0 / len; + else + len = 0; + out.vector[0] = len * vec->vector[0]; + out.vector[1] = len * vec->vector[1]; + out.vector[2] = len * vec->vector[2]; + Return(out); + return 0; +} + +static int qc_strcat(qc_program_t *prog) { + char *buffer; + size_t len1, len2; + qcany_t *str1, *str2; + qcany_t out; + + const char *cstr1; + const char *cstr2; + + CheckArgs(2); + str1 = GetArg(0); + str2 = GetArg(1); + cstr1 = prog_getstring(prog, str1->string); + cstr2 = prog_getstring(prog, str2->string); + len1 = strlen(cstr1); + len2 = strlen(cstr2); + buffer = (char*)mem_a(len1 + len2 + 1); + memcpy(buffer, cstr1, len1); + memcpy(buffer+len1, cstr2, len2+1); + out.string = prog_tempstring(prog, buffer); + mem_d(buffer); + Return(out); + return 0; +} + +static int qc_strcmp(qc_program_t *prog) { + qcany_t *str1, *str2; + qcany_t out; + + const char *cstr1; + const char *cstr2; + + if (prog->argc != 2 && prog->argc != 3) { + fprintf(stderr, "ERROR: invalid number of arguments for strcmp/strncmp: %i, expected 2 or 3\n", + prog->argc); + return -1; + } + + str1 = GetArg(0); + str2 = GetArg(1); + cstr1 = prog_getstring(prog, str1->string); + cstr2 = prog_getstring(prog, str2->string); + if (prog->argc == 3) + out._float = strncmp(cstr1, cstr2, GetArg(2)->_float); + else + out._float = strcmp(cstr1, cstr2); + Return(out); + return 0; +} + +static int qc_floor(qc_program_t *prog) { + qcany_t *num, out; + CheckArgs(1); + num = GetArg(0); + out._float = floor(num->_float); + Return(out); + return 0; +} + +static int qc_pow(qc_program_t *prog) { + qcany_t *base, *exp, out; + CheckArgs(2); + base = GetArg(0); + exp = GetArg(1); + out._float = powf(base->_float, exp->_float); + Return(out); + return 0; +} + +static prog_builtin_t qc_builtins[] = { + NULL, + &qc_print, /* 1 */ + &qc_ftos, /* 2 */ + &qc_spawn, /* 3 */ + &qc_kill, /* 4 */ + &qc_vtos, /* 5 */ + &qc_error, /* 6 */ + &qc_vlen, /* 7 */ + &qc_etos, /* 8 */ + &qc_stof, /* 9 */ + &qc_strcat, /* 10 */ + &qc_strcmp, /* 11 */ + &qc_normalize, /* 12 */ + &qc_sqrt, /* 13 */ + &qc_floor, /* 14 */ + &qc_pow /* 15 */ +}; + +static const char *arg0 = NULL; + +static void version(void) { + printf("GMQCC-QCVM %d.%d.%d Built %s %s\n", + GMQCC_VERSION_MAJOR, + GMQCC_VERSION_MINOR, + GMQCC_VERSION_PATCH, + __DATE__, + __TIME__ + ); +} + +static void usage(void) { + printf("usage: %s [options] [parameters] file\n", arg0); + printf("options:\n"); + printf(" -h, --help print this message\n" + " -trace trace the execution\n" + " -profile perform profiling during execution\n" + " -info print information from the prog's header\n" + " -disasm disassemble and exit\n" + " -disasm-func func disassemble and exit\n" + " -printdefs list the defs section\n" + " -printfields list the field section\n" + " -printfuns list functions information\n" + " -v be verbose\n" + " -vv be even more verbose\n"); + printf("parameters:\n"); + printf(" -vector pass a vector parameter to main()\n" + " -float pass a float parameter to main()\n" + " -string pass a string parameter to main() \n"); +} + +static void prog_main_setparams(qc_program_t *prog) { + size_t i; + qcany_t *arg; + + for (i = 0; i < vec_size(main_params); ++i) { + arg = GetGlobal(OFS_PARM0 + 3*i); + arg->vector[0] = 0; + arg->vector[1] = 0; + arg->vector[2] = 0; + switch (main_params[i].vtype) { + case TYPE_VECTOR: + (void)util_sscanf(main_params[i].value, " %f %f %f ", + &arg->vector[0], + &arg->vector[1], + &arg->vector[2]); + break; + case TYPE_FLOAT: + arg->_float = atof(main_params[i].value); + break; + case TYPE_STRING: + arg->string = prog_tempstring(prog, main_params[i].value); + break; + default: + fprintf(stderr, "error: unhandled parameter type: %i\n", main_params[i].vtype); + break; + } + } +} + +static void prog_disasm_function(qc_program_t *prog, size_t id); + +int main(int argc, char **argv) { + size_t i; + qcint_t fnmain = -1; + qc_program_t *prog; + size_t xflags = VMXF_DEFAULT; + bool opts_printfields = false; + bool opts_printdefs = false; + bool opts_printfuns = false; + bool opts_disasm = false; + bool opts_info = false; + bool noexec = false; + const char *progsfile = NULL; + const char **dis_list = NULL; + int opts_v = 0; + + arg0 = argv[0]; + + if (argc < 2) { + usage(); + exit(EXIT_FAILURE); + } + + while (argc > 1) { + if (!strcmp(argv[1], "-h") || + !strcmp(argv[1], "-help") || + !strcmp(argv[1], "--help")) + { + usage(); + exit(EXIT_SUCCESS); + } + else if (!strcmp(argv[1], "-v")) { + ++opts_v; + --argc; + ++argv; + } + else if (!strncmp(argv[1], "-vv", 3)) { + const char *av = argv[1]+1; + for (; *av; ++av) { + if (*av == 'v') + ++opts_v; + else { + usage(); + exit(EXIT_FAILURE); + } + } + --argc; + ++argv; + } + else if (!strcmp(argv[1], "-version") || + !strcmp(argv[1], "--version")) + { + version(); + exit(EXIT_SUCCESS); + } + else if (!strcmp(argv[1], "-trace")) { + --argc; + ++argv; + xflags |= VMXF_TRACE; + } + else if (!strcmp(argv[1], "-profile")) { + --argc; + ++argv; + xflags |= VMXF_PROFILE; + } + else if (!strcmp(argv[1], "-info")) { + --argc; + ++argv; + opts_info = true; + noexec = true; + } + else if (!strcmp(argv[1], "-disasm")) { + --argc; + ++argv; + opts_disasm = true; + noexec = true; + } + else if (!strcmp(argv[1], "-disasm-func")) { + --argc; + ++argv; + if (argc <= 1) { + usage(); + exit(EXIT_FAILURE); + } + vec_push(dis_list, argv[1]); + --argc; + ++argv; + noexec = true; + } + else if (!strcmp(argv[1], "-printdefs")) { + --argc; + ++argv; + opts_printdefs = true; + noexec = true; + } + else if (!strcmp(argv[1], "-printfuns")) { + --argc; + ++argv; + opts_printfuns = true; + noexec = true; + } + else if (!strcmp(argv[1], "-printfields")) { + --argc; + ++argv; + opts_printfields = true; + noexec = true; + } + else if (!strcmp(argv[1], "-vector") || + !strcmp(argv[1], "-string") || + !strcmp(argv[1], "-float") ) + { + qcvm_parameter p; + if (argv[1][1] == 'f') + p.vtype = TYPE_FLOAT; + else if (argv[1][1] == 's') + p.vtype = TYPE_STRING; + else if (argv[1][1] == 'v') + p.vtype = TYPE_VECTOR; + else + p.vtype = TYPE_VOID; + + --argc; + ++argv; + if (argc < 2) { + usage(); + exit(EXIT_FAILURE); + } + p.value = argv[1]; + + vec_push(main_params, p); + --argc; + ++argv; + } + else if (!strcmp(argv[1], "--")) { + --argc; + ++argv; + break; + } + else if (argv[1][0] != '-') { + if (progsfile) { + fprintf(stderr, "only 1 program file may be specified\n"); + usage(); + exit(EXIT_FAILURE); + } + progsfile = argv[1]; + --argc; + ++argv; + } + else + { + fprintf(stderr, "unknown parameter: %s\n", argv[1]); + usage(); + exit(EXIT_FAILURE); + } + } + + if (argc == 2 && !progsfile) { + progsfile = argv[1]; + --argc; + ++argv; + } + + if (!progsfile) { + fprintf(stderr, "must specify a program to execute\n"); + usage(); + exit(EXIT_FAILURE); + } + + prog = prog_load(progsfile, noexec); + if (!prog) { + fprintf(stderr, "failed to load program '%s'\n", progsfile); + exit(EXIT_FAILURE); + } + + prog->builtins = qc_builtins; + prog->builtins_count = GMQCC_ARRAY_COUNT(qc_builtins); + + if (opts_info) { + printf("Program's system-checksum = 0x%04x\n", (unsigned int)prog->crc16); + printf("Entity field space: %u\n", (unsigned int)prog->entityfields); + printf("Globals: %u\n", (unsigned int)vec_size(prog->globals)); + printf("Counts:\n" + " code: %lu\n" + " defs: %lu\n" + " fields: %lu\n" + " functions: %lu\n" + " strings: %lu\n", + (unsigned long)vec_size(prog->code), + (unsigned long)vec_size(prog->defs), + (unsigned long)vec_size(prog->fields), + (unsigned long)vec_size(prog->functions), + (unsigned long)vec_size(prog->strings)); + } + + if (opts_info) { + prog_delete(prog); + return 0; + } + for (i = 0; i < vec_size(dis_list); ++i) { + size_t k; + printf("Looking for `%s`\n", dis_list[i]); + for (k = 1; k < vec_size(prog->functions); ++k) { + const char *name = prog_getstring(prog, prog->functions[k].name); + if (!strcmp(name, dis_list[i])) { + prog_disasm_function(prog, k); + break; + } + } + } + if (opts_disasm) { + for (i = 1; i < vec_size(prog->functions); ++i) + prog_disasm_function(prog, i); + return 0; + } + if (opts_printdefs) { + const char *getstring = NULL; + for (i = 0; i < vec_size(prog->defs); ++i) { + printf("Global: %8s %-16s at %u%s", + type_name[prog->defs[i].type & DEF_TYPEMASK], + prog_getstring(prog, prog->defs[i].name), + (unsigned int)prog->defs[i].offset, + ((prog->defs[i].type & DEF_SAVEGLOBAL) ? " [SAVE]" : "")); + if (opts_v) { + switch (prog->defs[i].type & DEF_TYPEMASK) { + case TYPE_FLOAT: + printf(" [init: %g]", ((qcany_t*)(prog->globals + prog->defs[i].offset))->_float); + break; + case TYPE_INTEGER: + printf(" [init: %i]", (int)( ((qcany_t*)(prog->globals + prog->defs[i].offset))->_int )); + break; + case TYPE_ENTITY: + case TYPE_FUNCTION: + case TYPE_FIELD: + case TYPE_POINTER: + printf(" [init: %u]", (unsigned)( ((qcany_t*)(prog->globals + prog->defs[i].offset))->_int )); + break; + case TYPE_STRING: + getstring = prog_getstring(prog, ((qcany_t*)(prog->globals + prog->defs[i].offset))->string); + printf(" [init: `"); + print_escaped_string(getstring, strlen(getstring)); + printf("`]\n"); + break; + default: + break; + } + } + printf("\n"); + } + } + if (opts_printfields) { + for (i = 0; i < vec_size(prog->fields); ++i) { + printf("Field: %8s %-16s at %u%s\n", + type_name[prog->fields[i].type], + prog_getstring(prog, prog->fields[i].name), + (unsigned int)prog->fields[i].offset, + ((prog->fields[i].type & DEF_SAVEGLOBAL) ? " [SAVE]" : "")); + } + } + if (opts_printfuns) { + for (i = 0; i < vec_size(prog->functions); ++i) { + int32_t a; + printf("Function: %-16s taking %u parameters:(", + prog_getstring(prog, prog->functions[i].name), + (unsigned int)prog->functions[i].nargs); + for (a = 0; a < prog->functions[i].nargs; ++a) { + printf(" %i", prog->functions[i].argsize[a]); + } + if (opts_v > 1) { + int32_t start = prog->functions[i].entry; + if (start < 0) + printf(") builtin %i\n", (int)-start); + else { + size_t funsize = 0; + prog_section_statement_t *st = prog->code + start; + for (;st->opcode != INSTR_DONE; ++st) + ++funsize; + printf(") - %lu instructions", (unsigned long)funsize); + if (opts_v > 2) { + printf(" - locals: %i + %i\n", + prog->functions[i].firstlocal, + prog->functions[i].locals); + } + else + printf("\n"); + } + } + else if (opts_v) { + printf(") locals: %i + %i\n", + prog->functions[i].firstlocal, + prog->functions[i].locals); + } + else + printf(")\n"); + } + } + if (!noexec) { + for (i = 1; i < vec_size(prog->functions); ++i) { + const char *name = prog_getstring(prog, prog->functions[i].name); + if (!strcmp(name, "main")) + fnmain = (qcint_t)i; + } + if (fnmain > 0) + { + prog_main_setparams(prog); + prog_exec(prog, &prog->functions[fnmain], xflags, VM_JUMPS_DEFAULT); + } + else + fprintf(stderr, "No main function found\n"); + } + + prog_delete(prog); + return 0; +} + +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; + + if (fdef->entry < 0) { + printf("FUNCTION \"%s\" = builtin #%i\n", prog_getstring(prog, fdef->name), (int)-fdef->entry); + return; + } + else + printf("FUNCTION \"%s\"\n", prog_getstring(prog, fdef->name)); + + st = prog->code + fdef->entry; + while (st->opcode != INSTR_DONE) { + prog_print_statement(prog, st); + ++st; + } +} +#else /* !QCVM_LOOP */ +/* + * Everything from here on is not including into the compilation of the + * executor. This is simply code that is #included via #include __FILE__ + * see when QCVM_LOOP is defined, the rest of the code above do not get + * re-included. So this really just acts like one large macro, but it + * sort of isn't, which makes it nicer looking. + */ + +#define OPA ( (qcany_t*) (prog->globals + st->o1.u1) ) +#define OPB ( (qcany_t*) (prog->globals + st->o2.u1) ) +#define OPC ( (qcany_t*) (prog->globals + st->o3.u1) ) + +#define GLOBAL(x) ( (qcany_t*) (prog->globals + (x)) ) + +/* to be consistent with current darkplaces behaviour */ +#if !defined(FLOAT_IS_TRUE_FOR_INT) +# define FLOAT_IS_TRUE_FOR_INT(x) ( (x) & 0x7FFFFFFF ) +#endif + +while (prog->vmerror == 0) { + prog_section_function_t *newf; + qcany_t *ed; + qcany_t *ptr; + + ++st; + +#if QCVM_PROFILE + prog->profile[st - prog->code]++; +#endif + +#if QCVM_TRACE + prog_print_statement(prog, st); +#endif + + switch (st->opcode) + { + default: + qcvmerror(prog, "Illegal instruction in %s\n", prog->filename); + goto cleanup; + + case INSTR_DONE: + case INSTR_RETURN: + /* TODO: add instruction count to function profile count */ + GLOBAL(OFS_RETURN)->ivector[0] = OPA->ivector[0]; + GLOBAL(OFS_RETURN)->ivector[1] = OPA->ivector[1]; + GLOBAL(OFS_RETURN)->ivector[2] = OPA->ivector[2]; + + st = prog->code + prog_leavefunction(prog); + if (!vec_size(prog->stack)) + goto cleanup; + + break; + + case INSTR_MUL_F: + OPC->_float = OPA->_float * OPB->_float; + break; + case INSTR_MUL_V: + OPC->_float = OPA->vector[0]*OPB->vector[0] + + OPA->vector[1]*OPB->vector[1] + + OPA->vector[2]*OPB->vector[2]; + break; + case INSTR_MUL_FV: + { + qcfloat_t f = OPA->_float; + OPC->vector[0] = f * OPB->vector[0]; + OPC->vector[1] = f * OPB->vector[1]; + OPC->vector[2] = f * OPB->vector[2]; + break; + } + case INSTR_MUL_VF: + { + qcfloat_t f = OPB->_float; + OPC->vector[0] = f * OPA->vector[0]; + OPC->vector[1] = f * OPA->vector[1]; + OPC->vector[2] = f * OPA->vector[2]; + break; + } + case INSTR_DIV_F: + if (OPB->_float != 0.0f) + OPC->_float = OPA->_float / OPB->_float; + else + OPC->_float = 0; + break; + + case INSTR_ADD_F: + OPC->_float = OPA->_float + OPB->_float; + break; + case INSTR_ADD_V: + OPC->vector[0] = OPA->vector[0] + OPB->vector[0]; + OPC->vector[1] = OPA->vector[1] + OPB->vector[1]; + OPC->vector[2] = OPA->vector[2] + OPB->vector[2]; + break; + case INSTR_SUB_F: + OPC->_float = OPA->_float - OPB->_float; + break; + case INSTR_SUB_V: + OPC->vector[0] = OPA->vector[0] - OPB->vector[0]; + OPC->vector[1] = OPA->vector[1] - OPB->vector[1]; + OPC->vector[2] = OPA->vector[2] - OPB->vector[2]; + break; + + case INSTR_EQ_F: + OPC->_float = (OPA->_float == OPB->_float); + break; + case INSTR_EQ_V: + OPC->_float = ((OPA->vector[0] == OPB->vector[0]) && + (OPA->vector[1] == OPB->vector[1]) && + (OPA->vector[2] == OPB->vector[2]) ); + break; + case INSTR_EQ_S: + OPC->_float = !strcmp(prog_getstring(prog, OPA->string), + prog_getstring(prog, OPB->string)); + break; + case INSTR_EQ_E: + OPC->_float = (OPA->_int == OPB->_int); + break; + case INSTR_EQ_FNC: + OPC->_float = (OPA->function == OPB->function); + break; + case INSTR_NE_F: + OPC->_float = (OPA->_float != OPB->_float); + break; + case INSTR_NE_V: + OPC->_float = ((OPA->vector[0] != OPB->vector[0]) || + (OPA->vector[1] != OPB->vector[1]) || + (OPA->vector[2] != OPB->vector[2]) ); + break; + case INSTR_NE_S: + OPC->_float = !!strcmp(prog_getstring(prog, OPA->string), + prog_getstring(prog, OPB->string)); + break; + case INSTR_NE_E: + OPC->_float = (OPA->_int != OPB->_int); + break; + case INSTR_NE_FNC: + OPC->_float = (OPA->function != OPB->function); + break; + + case INSTR_LE: + OPC->_float = (OPA->_float <= OPB->_float); + break; + case INSTR_GE: + OPC->_float = (OPA->_float >= OPB->_float); + break; + case INSTR_LT: + OPC->_float = (OPA->_float < OPB->_float); + break; + case INSTR_GT: + OPC->_float = (OPA->_float > OPB->_float); + break; + + case INSTR_LOAD_F: + case INSTR_LOAD_S: + case INSTR_LOAD_FLD: + case INSTR_LOAD_ENT: + case INSTR_LOAD_FNC: + if (OPA->edict < 0 || OPA->edict >= prog->entities) { + qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename); + goto cleanup; + } + if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) { + qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)", + prog->filename, + OPB->_int); + goto cleanup; + } + ed = prog_getedict(prog, OPA->edict); + OPC->_int = ((qcany_t*)( ((qcint_t*)ed) + OPB->_int ))->_int; + break; + case INSTR_LOAD_V: + if (OPA->edict < 0 || OPA->edict >= prog->entities) { + qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename); + goto cleanup; + } + if (OPB->_int < 0 || OPB->_int + 3 > (qcint_t)prog->entityfields) + { + qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)", + prog->filename, + OPB->_int + 2); + goto cleanup; + } + ed = prog_getedict(prog, OPA->edict); + ptr = (qcany_t*)( ((qcint_t*)ed) + OPB->_int ); + OPC->ivector[0] = ptr->ivector[0]; + OPC->ivector[1] = ptr->ivector[1]; + OPC->ivector[2] = ptr->ivector[2]; + break; + + case INSTR_ADDRESS: + if (OPA->edict < 0 || OPA->edict >= prog->entities) { + qcvmerror(prog, "prog `%s` attempted to address an out of bounds entity %i", prog->filename, OPA->edict); + goto cleanup; + } + if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) + { + qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)", + prog->filename, + OPB->_int); + goto cleanup; + } + + ed = prog_getedict(prog, OPA->edict); + OPC->_int = ((qcint_t*)ed) - prog->entitydata + OPB->_int; + break; + + case INSTR_STORE_F: + case INSTR_STORE_S: + case INSTR_STORE_ENT: + case INSTR_STORE_FLD: + case INSTR_STORE_FNC: + OPB->_int = OPA->_int; + break; + case INSTR_STORE_V: + OPB->ivector[0] = OPA->ivector[0]; + OPB->ivector[1] = OPA->ivector[1]; + OPB->ivector[2] = OPA->ivector[2]; + break; + + case INSTR_STOREP_F: + case INSTR_STOREP_S: + case INSTR_STOREP_ENT: + case INSTR_STOREP_FLD: + case INSTR_STOREP_FNC: + if (OPB->_int < 0 || OPB->_int >= (qcint_t)vec_size(prog->entitydata)) { + qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int); + goto cleanup; + } + if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites) + qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n", + prog->filename, + prog_getstring(prog, prog_entfield(prog, OPB->_int)->name), + OPB->_int); + ptr = (qcany_t*)(prog->entitydata + OPB->_int); + ptr->_int = OPA->_int; + break; + case INSTR_STOREP_V: + if (OPB->_int < 0 || OPB->_int + 2 >= (qcint_t)vec_size(prog->entitydata)) { + qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int); + goto cleanup; + } + if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites) + qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n", + prog->filename, + prog_getstring(prog, prog_entfield(prog, OPB->_int)->name), + OPB->_int); + ptr = (qcany_t*)(prog->entitydata + OPB->_int); + ptr->ivector[0] = OPA->ivector[0]; + ptr->ivector[1] = OPA->ivector[1]; + ptr->ivector[2] = OPA->ivector[2]; + break; + + case INSTR_NOT_F: + OPC->_float = !FLOAT_IS_TRUE_FOR_INT(OPA->_int); + break; + case INSTR_NOT_V: + OPC->_float = !OPA->vector[0] && + !OPA->vector[1] && + !OPA->vector[2]; + break; + case INSTR_NOT_S: + OPC->_float = !OPA->string || + !*prog_getstring(prog, OPA->string); + break; + case INSTR_NOT_ENT: + OPC->_float = (OPA->edict == 0); + break; + case INSTR_NOT_FNC: + OPC->_float = !OPA->function; + break; + + case INSTR_IF: + /* this is consistent with darkplaces' behaviour */ + if(FLOAT_IS_TRUE_FOR_INT(OPA->_int)) + { + st += st->o2.s1 - 1; /* offset the s++ */ + if (++jumpcount >= maxjumps) + qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount); + } + break; + case INSTR_IFNOT: + if(!FLOAT_IS_TRUE_FOR_INT(OPA->_int)) + { + st += st->o2.s1 - 1; /* offset the s++ */ + if (++jumpcount >= maxjumps) + qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount); + } + break; + + case INSTR_CALL0: + case INSTR_CALL1: + case INSTR_CALL2: + case INSTR_CALL3: + case INSTR_CALL4: + case INSTR_CALL5: + case INSTR_CALL6: + case INSTR_CALL7: + case INSTR_CALL8: + prog->argc = st->opcode - INSTR_CALL0; + if (!OPA->function) + qcvmerror(prog, "NULL function in `%s`", prog->filename); + + if(!OPA->function || OPA->function >= (qcint_t)vec_size(prog->functions)) + { + qcvmerror(prog, "CALL outside the program in `%s`", prog->filename); + goto cleanup; + } + + newf = &prog->functions[OPA->function]; + newf->profile++; + + prog->statement = (st - prog->code) + 1; + + if (newf->entry < 0) + { + /* negative statements are built in functions */ + qcint_t builtinnumber = -newf->entry; + if (builtinnumber < (qcint_t)prog->builtins_count && prog->builtins[builtinnumber]) + prog->builtins[builtinnumber](prog); + else + qcvmerror(prog, "No such builtin #%i in %s! Try updating your gmqcc sources", + builtinnumber, prog->filename); + } + else + st = prog->code + prog_enterfunction(prog, newf) - 1; /* offset st++ */ + if (prog->vmerror) + goto cleanup; + break; + + case INSTR_STATE: + { + 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++ */ + if (++jumpcount == 10000000) + qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount); + break; + + case INSTR_AND: + OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) && + FLOAT_IS_TRUE_FOR_INT(OPB->_int); + break; + case INSTR_OR: + OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) || + FLOAT_IS_TRUE_FOR_INT(OPB->_int); + break; + + case INSTR_BITAND: + OPC->_float = ((int)OPA->_float) & ((int)OPB->_float); + break; + case INSTR_BITOR: + OPC->_float = ((int)OPA->_float) | ((int)OPB->_float); + break; + } +} + +#undef QCVM_PROFILE +#undef QCVM_TRACE +#endif /* !QCVM_LOOP */ diff --git a/fold.c b/fold.c deleted file mode 100644 index 64b20d9..0000000 --- a/fold.c +++ /dev/null @@ -1,1668 +0,0 @@ -#include -#include - -#include "ast.h" -#include "parser.h" - -#define FOLD_STRING_UNTRANSLATE_HTSIZE 1024 -#define FOLD_STRING_DOTRANSLATE_HTSIZE 1024 - -/* The options to use for inexact and arithmetic exceptions */ -#define FOLD_ROUNDING SFLOAT_ROUND_NEAREST_EVEN -#define FOLD_TINYNESS SFLOAT_TBEFORE - -/* - * Comparing float values is an unsafe operation when the operands to the - * comparison are floating point values that are inexact. For instance 1/3 is an - * inexact value. The FPU is meant to raise exceptions when these sorts of things - * happen, including division by zero, underflows and overflows. The C standard - * library provides us with the header to gain access to the floating- - * point environment and lets us set the rounding mode and check for these exceptions. - * The problem is the standard C library allows an implementation to leave these - * stubbed out and does not require they be implemented. Furthermore, depending - * on implementations there is no control over the FPU. This is an IEE 754 - * conforming implementation in software to compensate. - */ -typedef uint32_t sfloat_t; - -typedef union { - qcfloat_t f; - sfloat_t s; -} sfloat_cast_t; - -/* Exception flags */ -typedef enum { - SFLOAT_NOEXCEPT = 0, - SFLOAT_INVALID = 1, - SFLOAT_DIVBYZERO = 4, - SFLOAT_OVERFLOW = 8, - SFLOAT_UNDERFLOW = 16, - SFLOAT_INEXACT = 32 -} sfloat_exceptionflags_t; - -/* Rounding modes */ -typedef enum { - SFLOAT_ROUND_NEAREST_EVEN, - SFLOAT_ROUND_DOWN, - SFLOAT_ROUND_UP, - SFLOAT_ROUND_TO_ZERO -} sfloat_roundingmode_t; - -/* Underflow tininess-detection mode */ -typedef enum { - SFLOAT_TAFTER, - SFLOAT_TBEFORE -} sfloat_tdetect_t; - -typedef struct { - sfloat_roundingmode_t roundingmode; - sfloat_exceptionflags_t exceptionflags; - sfloat_tdetect_t tiny; -} sfloat_state_t; - -/* Counts the number of leading zero bits before the most-significand one bit. */ -#ifdef _MSC_VER -/* MSVC has an intrinsic for this */ - static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) { - int r = 0; - _BitScanForward(&r, x); - return r; - } -# define SFLOAT_CLZ(X, SUB) \ - (sfloat_clz((X)) - (SUB)) -#elif defined(__GNUC__) || defined(__CLANG__) -/* Clang and GCC have a builtin for this */ -# define SFLOAT_CLZ(X, SUB) \ - (__builtin_clz((X)) - (SUB)) -#else -/* Native fallback */ - static GMQCC_INLINE uint32_t sfloat_popcnt(uint32_t x) { - x -= ((x >> 1) & 0x55555555); - x = (((x >> 2) & 0x33333333) + (x & 0x33333333)); - x = (((x >> 4) + x) & 0x0F0F0F0F); - x += x >> 8; - x += x >> 16; - return x & 0x0000003F; - } - static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) { - x |= (x >> 1); - x |= (x >> 2); - x |= (x >> 4); - x |= (x >> 8); - x |= (x >> 16); - return 32 - sfloat_popcnt(x); - } -# define SFLOAT_CLZ(X, SUB) \ - (sfloat_clz((X) - (SUB))) -#endif - -/* The value of a NaN */ -#define SFLOAT_NAN 0xFFFFFFFF -/* Test if NaN */ -#define SFLOAT_ISNAN(A) \ - (0xFF000000 < (uint32_t)((A) << 1)) -/* Test if signaling NaN */ -#define SFLOAT_ISSNAN(A) \ - (((((A) >> 22) & 0x1FF) == 0x1FE) && ((A) & 0x003FFFFF)) -/* Raise exception */ -#define SFLOAT_RAISE(STATE, FLAGS) \ - ((STATE)->exceptionflags = (sfloat_exceptionflags_t)((STATE)->exceptionflags | (FLAGS))) -/* - * Shifts `A' right by the number of bits given in `COUNT'. If any non-zero bits - * are shifted off they are forced into the least significand bit of the result - * by setting it to one. As a result of this, the value of `COUNT' can be - * arbitrarily large; if `COUNT' is greater than 32, the result will be either - * zero or one, depending on whether `A' is a zero or non-zero. The result is - * stored into the value pointed by `Z'. - */ -#define SFLOAT_SHIFT(SIZE, A, COUNT, Z) \ - *(Z) = ((COUNT) == 0) \ - ? 1 \ - : (((COUNT) < (SIZE)) \ - ? ((A) >> (COUNT)) | (((A) << ((-(COUNT)) & ((SIZE) - 1))) != 0) \ - : ((A) != 0)) - -/* Extract fractional component */ -#define SFLOAT_EXTRACT_FRAC(X) \ - ((uint32_t)((X) & 0x007FFFFF)) -/* Extract exponent component */ -#define SFLOAT_EXTRACT_EXP(X) \ - ((int16_t)((X) >> 23) & 0xFF) -/* Extract sign bit */ -#define SFLOAT_EXTRACT_SIGN(X) \ - ((X) >> 31) -/* - * Normalizes the subnormal value represented by the denormalized significand - * `SA'. The normalized exponent and significand are stored at the locations - * pointed by `Z' and `SZ' respectively. - */ -#define SFLOAT_SUBNORMALIZE(SA, Z, SZ) \ - (void)(*(SZ) = (SA) << SFLOAT_CLZ((SA), 8), *(Z) = 1 - SFLOAT_CLZ((SA), 8)) -/* - * Packs the sign `SIGN', exponent `EXP' and significand `SIG' into the value - * giving the result. - * - * After the shifting into their proper positions, the fields are added together - * to form the result. This means any integer portion of `SIG' will be added - * to the exponent. Similarly, because a properly normalized significand will - * always have an integer portion equal to one, the exponent input `EXP' should - * be one less than the desired result exponent whenever the significant input - * `SIG' is a complete, normalized significand. - */ -#define SFLOAT_PACK(SIGN, EXP, SIG) \ - (sfloat_t)((((uint32_t)(SIGN)) << 31) + (((uint32_t)(EXP)) << 23) + (SIG)) - -/* - * Takes two values `a' and `b', one of which is a NaN, and returns the appropriate - * NaN result. If either `a' or `b' is a signaling NaN than an invalid exception is - * raised. - */ -static sfloat_t sfloat_propagate_nan(sfloat_state_t *state, sfloat_t a, sfloat_t b) { - bool isnan_a = SFLOAT_ISNAN(a); - bool issnan_a = SFLOAT_ISSNAN(a); - bool isnan_b = SFLOAT_ISNAN(b); - bool issnan_b = SFLOAT_ISSNAN(b); - - a |= 0x00400000; - b |= 0x00400000; - - if (issnan_a | issnan_b) - SFLOAT_RAISE(state, SFLOAT_INVALID); - if (isnan_a) - return (issnan_a & isnan_b) ? b : a; - return b; -} - -/* - * Takes an abstract value having sign `sign_z', exponent `exp_z', and significand - * `sig_z' and returns the appropriate value corresponding to the abstract input. - * - * The abstract value is simply rounded and packed into the format. If the abstract - * input cannot be represented exactly an inexact exception is raised. If the - * abstract input is too large, the overflow and inexact exceptions are both raised - * and an infinity or maximal finite value is returned. If the abstract value is - * too small, the value is rounded to a subnormal and the underflow and inexact - * exceptions are only raised if the value cannot be represented exactly with - * a subnormal. - * - * The input significand `sig_z' has it's binary point between bits 30 and 29, - * this is seven bits to the left of its usual location. The shifted significand - * must be normalized or smaller than this. If it's not normalized then the exponent - * `exp_z' must be zero; in that case, the result returned is a subnormal number - * which must not require rounding. In the more usual case where the significand - * is normalized, the exponent must be one less than the *true* exponent. - * - * The handling of underflow and overflow is otherwise in alignment with IEC/IEEE. - */ -static sfloat_t SFLOAT_PACK_round(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) { - sfloat_roundingmode_t mode = state->roundingmode; - bool even = !!(mode == SFLOAT_ROUND_NEAREST_EVEN); - unsigned char increment = 0x40; - unsigned char bits = sig_z & 0x7F; - - if (!even) { - if (mode == SFLOAT_ROUND_TO_ZERO) - increment = 0; - else { - increment = 0x7F; - if (sign_z) { - if (mode == SFLOAT_ROUND_UP) - increment = 0; - } else { - if (mode == SFLOAT_ROUND_DOWN) - increment = 0; - } - } - } - - if (0xFD <= (uint16_t)exp_z) { - if ((0xFD < exp_z) || ((exp_z == 0xFD) && ((int32_t)(sig_z + increment) < 0))) { - SFLOAT_RAISE(state, SFLOAT_OVERFLOW | SFLOAT_INEXACT); - return SFLOAT_PACK(sign_z, 0xFF, 0) - (increment == 0); - } - if (exp_z < 0) { - /* Check for underflow */ - bool tiny = (state->tiny == SFLOAT_TBEFORE) || (exp_z < -1) || (sig_z + increment < 0x80000000); - SFLOAT_SHIFT(32, sig_z, -exp_z, &sig_z); - exp_z = 0; - bits = sig_z & 0x7F; - if (tiny && bits) - SFLOAT_RAISE(state, SFLOAT_UNDERFLOW); - } - } - if (bits) - SFLOAT_RAISE(state, SFLOAT_INEXACT); - sig_z = (sig_z + increment) >> 7; - sig_z &= ~(((bits ^ 0x40) == 0) & even); - if (sig_z == 0) - exp_z = 0; - return SFLOAT_PACK(sign_z, exp_z, sig_z); -} - -/* - * Takes an abstract value having sign `sign_z', exponent `exp_z' and significand - * `sig_z' and returns the appropriate value corresponding to the abstract input. - * This function is exactly like `PACK_round' except the significand does not have - * to be normalized. - * - * Bit 31 of the significand must be zero and the exponent must be one less than - * the *true* exponent. - */ -static sfloat_t SFLOAT_PACK_normal(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) { - unsigned char c = SFLOAT_CLZ(sig_z, 1); - return SFLOAT_PACK_round(state, sign_z, exp_z - c, sig_z << c); -} - -/* - * Returns the result of adding the absolute values of `a' and `b'. The sign - * `sign_z' is ignored if the result is a NaN. - */ -static sfloat_t sfloat_add_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) { - int16_t exp_a = SFLOAT_EXTRACT_EXP(a); - int16_t exp_b = SFLOAT_EXTRACT_EXP(b); - int16_t exp_z = 0; - int16_t exp_d = exp_a - exp_b; - uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 6; - uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 6; - uint32_t sig_z = 0; - - if (0 < exp_d) { - if (exp_a == 0xFF) - return sig_a ? sfloat_propagate_nan(state, a, b) : a; - if (exp_b == 0) - --exp_d; - else - sig_b |= 0x20000000; - SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b); - exp_z = exp_a; - } else if (exp_d < 0) { - if (exp_b == 0xFF) - return sig_b ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0xFF, 0); - if (exp_a == 0) - ++exp_d; - else - sig_a |= 0x20000000; - SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a); - exp_z = exp_b; - } else { - if (exp_a == 0xFF) - return (sig_a | sig_b) ? sfloat_propagate_nan(state, a, b) : a; - if (exp_a == 0) - return SFLOAT_PACK(sign_z, 0, (sig_a + sig_b) >> 6); - sig_z = 0x40000000 + sig_a + sig_b; - exp_z = exp_a; - goto end; - } - sig_a |= 0x20000000; - sig_z = (sig_a + sig_b) << 1; - --exp_z; - if ((int32_t)sig_z < 0) { - sig_z = sig_a + sig_b; - ++exp_z; - } -end: - return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); -} - -/* - * Returns the result of subtracting the absolute values of `a' and `b'. If the - * sign `sign_z' is one, the difference is negated before being returned. The - * sign is ignored if the result is a NaN. - */ -static sfloat_t sfloat_sub_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) { - int16_t exp_a = SFLOAT_EXTRACT_EXP(a); - int16_t exp_b = SFLOAT_EXTRACT_EXP(b); - int16_t exp_z = 0; - int16_t exp_d = exp_a - exp_b; - uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 7; - uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 7; - uint32_t sig_z = 0; - - if (0 < exp_d) goto exp_greater_a; - if (exp_d < 0) goto exp_greater_b; - - if (exp_a == 0xFF) { - if (sig_a | sig_b) - return sfloat_propagate_nan(state, a, b); - SFLOAT_RAISE(state, SFLOAT_INVALID); - return SFLOAT_NAN; - } - - if (exp_a == 0) - exp_a = exp_b = 1; - - if (sig_b < sig_a) goto greater_a; - if (sig_a < sig_b) goto greater_b; - - return SFLOAT_PACK(state->roundingmode == SFLOAT_ROUND_DOWN, 0, 0); - -exp_greater_b: - if (exp_b == 0xFF) - return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z ^ 1, 0xFF, 0); - if (exp_a == 0) - ++exp_d; - else - sig_a |= 0x40000000; - SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a); - sig_b |= 0x40000000; -greater_b: - sig_z = sig_b - sig_a; - exp_z = exp_b; - sign_z ^= 1; - goto end; - -exp_greater_a: - if (exp_a == 0xFF) - return (sig_a) ? sfloat_propagate_nan(state, a, b) : a; - if (exp_b == 0) - --exp_d; - else - sig_b |= 0x40000000; - SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b); - sig_a |= 0x40000000; -greater_a: - sig_z = sig_a - sig_b; - exp_z = exp_a; - -end: - --exp_z; - return SFLOAT_PACK_normal(state, sign_z, exp_z, sig_z); -} - -static GMQCC_INLINE sfloat_t sfloat_add(sfloat_state_t *state, sfloat_t a, sfloat_t b) { - bool sign_a = SFLOAT_EXTRACT_SIGN(a); - bool sign_b = SFLOAT_EXTRACT_SIGN(b); - return (sign_a == sign_b) ? sfloat_add_impl(state, a, b, sign_a) - : sfloat_sub_impl(state, a, b, sign_a); -} - -static GMQCC_INLINE sfloat_t sfloat_sub(sfloat_state_t *state, sfloat_t a, sfloat_t b) { - bool sign_a = SFLOAT_EXTRACT_SIGN(a); - bool sign_b = SFLOAT_EXTRACT_SIGN(b); - return (sign_a == sign_b) ? sfloat_sub_impl(state, a, b, sign_a) - : sfloat_add_impl(state, a, b, sign_a); -} - -static sfloat_t sfloat_mul(sfloat_state_t *state, sfloat_t a, sfloat_t b) { - int16_t exp_a = SFLOAT_EXTRACT_EXP(a); - int16_t exp_b = SFLOAT_EXTRACT_EXP(b); - int16_t exp_z = 0; - uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a); - uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b); - uint32_t sig_z = 0; - uint64_t sig_z64 = 0; - bool sign_a = SFLOAT_EXTRACT_SIGN(a); - bool sign_b = SFLOAT_EXTRACT_SIGN(b); - bool sign_z = sign_a ^ sign_b; - - if (exp_a == 0xFF) { - if (sig_a || ((exp_b == 0xFF) && sig_b)) - return sfloat_propagate_nan(state, a, b); - if ((exp_b | sig_b) == 0) { - SFLOAT_RAISE(state, SFLOAT_INVALID); - return SFLOAT_NAN; - } - return SFLOAT_PACK(sign_z, 0xFF, 0); - } - if (exp_b == 0xFF) { - if (sig_b) - return sfloat_propagate_nan(state, a, b); - if ((exp_a | sig_a) == 0) { - SFLOAT_RAISE(state, SFLOAT_INVALID); - return SFLOAT_NAN; - } - return SFLOAT_PACK(sign_z, 0xFF, 0); - } - if (exp_a == 0) { - if (sig_a == 0) - return SFLOAT_PACK(sign_z, 0, 0); - SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a); - } - if (exp_b == 0) { - if (sig_b == 0) - return SFLOAT_PACK(sign_z, 0, 0); - SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b); - } - exp_z = exp_a + exp_b - 0x7F; - sig_a = (sig_a | 0x00800000) << 7; - sig_b = (sig_b | 0x00800000) << 8; - SFLOAT_SHIFT(64, ((uint64_t)sig_a) * sig_b, 32, &sig_z64); - sig_z = sig_z64; - if (0 <= (int32_t)(sig_z << 1)) { - sig_z <<= 1; - --exp_z; - } - return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); -} - -static sfloat_t sfloat_div(sfloat_state_t *state, sfloat_t a, sfloat_t b) { - int16_t exp_a = SFLOAT_EXTRACT_EXP(a); - int16_t exp_b = SFLOAT_EXTRACT_EXP(b); - int16_t exp_z = 0; - uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a); - uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b); - uint32_t sig_z = 0; - bool sign_a = SFLOAT_EXTRACT_SIGN(a); - bool sign_b = SFLOAT_EXTRACT_SIGN(b); - bool sign_z = sign_a ^ sign_b; - - if (exp_a == 0xFF) { - if (sig_a) - return sfloat_propagate_nan(state, a, b); - if (exp_b == 0xFF) { - if (sig_b) - return sfloat_propagate_nan(state, a, b); - SFLOAT_RAISE(state, SFLOAT_INVALID); - return SFLOAT_NAN; - } - return SFLOAT_PACK(sign_z, 0xFF, 0); - } - if (exp_b == 0xFF) - return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0, 0); - if (exp_b == 0) { - if (sig_b == 0) { - if ((exp_a | sig_a) == 0) { - SFLOAT_RAISE(state, SFLOAT_INVALID); - return SFLOAT_NAN; - } - SFLOAT_RAISE(state, SFLOAT_DIVBYZERO); - return SFLOAT_PACK(sign_z, 0xFF, 0); - } - SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b); - } - if (exp_a == 0) { - if (sig_a == 0) - return SFLOAT_PACK(sign_z, 0, 0); - SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a); - } - exp_z = exp_a - exp_b + 0x7D; - sig_a = (sig_a | 0x00800000) << 7; - sig_b = (sig_b | 0x00800000) << 8; - if (sig_b <= (sig_a + sig_a)) { - sig_a >>= 1; - ++exp_z; - } - sig_z = (((uint64_t)sig_a) << 32) / sig_b; - if ((sig_z & 0x3F) == 0) - sig_z |= ((uint64_t)sig_b * sig_z != ((uint64_t)sig_a) << 32); - return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); -} - -static sfloat_t sfloat_neg(sfloat_state_t *state, sfloat_t a) { - sfloat_cast_t neg; - neg.f = -1; - return sfloat_mul(state, a, neg.s); -} - -static GMQCC_INLINE void sfloat_check(lex_ctx_t ctx, sfloat_state_t *state, const char *vec) { - /* Exception comes from vector component */ - if (vec) { - if (state->exceptionflags & SFLOAT_DIVBYZERO) - compile_error(ctx, "division by zero in `%s' component", vec); - if (state->exceptionflags & SFLOAT_INVALID) - compile_error(ctx, "undefined (inf) in `%s' component", vec); - if (state->exceptionflags & SFLOAT_OVERFLOW) - compile_error(ctx, "arithmetic overflow in `%s' component", vec); - if (state->exceptionflags & SFLOAT_UNDERFLOW) - compile_error(ctx, "arithmetic underflow in `%s' component", vec); - return; - } - if (state->exceptionflags & SFLOAT_DIVBYZERO) - compile_error(ctx, "division by zero"); - if (state->exceptionflags & SFLOAT_INVALID) - compile_error(ctx, "undefined (inf)"); - if (state->exceptionflags & SFLOAT_OVERFLOW) - compile_error(ctx, "arithmetic overflow"); - if (state->exceptionflags & SFLOAT_UNDERFLOW) - compile_error(ctx, "arithmetic underflow"); -} - -static GMQCC_INLINE void sfloat_init(sfloat_state_t *state) { - state->exceptionflags = SFLOAT_NOEXCEPT; - state->roundingmode = FOLD_ROUNDING; - state->tiny = FOLD_TINYNESS; -} - -/* - * There is two stages to constant folding in GMQCC: there is the parse - * stage constant folding, where, with the help of the AST, operator - * usages can be constant folded. Then there is the constant folding - * in the IR for things like eliding if statements, can occur. - * - * This file is thus, split into two parts. - */ - -#define isfloat(X) (((ast_expression*)(X))->vtype == TYPE_FLOAT) -#define isvector(X) (((ast_expression*)(X))->vtype == TYPE_VECTOR) -#define isstring(X) (((ast_expression*)(X))->vtype == TYPE_STRING) -#define isarray(X) (((ast_expression*)(X))->vtype == TYPE_ARRAY) -#define isfloats(X,Y) (isfloat (X) && isfloat (Y)) - -/* - * Implementation of basic vector math for vec3_t, for trivial constant - * folding. - * - * TODO: gcc/clang hinting for autovectorization - */ -typedef enum { - VEC_COMP_X = 1 << 0, - VEC_COMP_Y = 1 << 1, - VEC_COMP_Z = 1 << 2 -} vec3_comp_t; - -typedef struct { - sfloat_cast_t x; - sfloat_cast_t y; - sfloat_cast_t z; -} vec3_soft_t; - -typedef struct { - vec3_comp_t faults; - sfloat_state_t state[3]; -} vec3_soft_state_t; - -static GMQCC_INLINE vec3_soft_t vec3_soft_convert(vec3_t vec) { - vec3_soft_t soft; - soft.x.f = vec.x; - soft.y.f = vec.y; - soft.z.f = vec.z; - return soft; -} - -static GMQCC_INLINE bool vec3_soft_exception(vec3_soft_state_t *vstate, size_t index) { - sfloat_exceptionflags_t flags = vstate->state[index].exceptionflags; - if (flags & SFLOAT_DIVBYZERO) return true; - if (flags & SFLOAT_INVALID) return true; - if (flags & SFLOAT_OVERFLOW) return true; - if (flags & SFLOAT_UNDERFLOW) return true; - return false; -} - -static GMQCC_INLINE void vec3_soft_eval(vec3_soft_state_t *state, - sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t), - vec3_t a, - vec3_t b) -{ - vec3_soft_t sa = vec3_soft_convert(a); - vec3_soft_t sb = vec3_soft_convert(b); - callback(&state->state[0], sa.x.s, sb.x.s); - if (vec3_soft_exception(state, 0)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_X); - callback(&state->state[1], sa.y.s, sb.y.s); - if (vec3_soft_exception(state, 1)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Y); - callback(&state->state[2], sa.z.s, sb.z.s); - if (vec3_soft_exception(state, 2)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Z); -} - -static GMQCC_INLINE void vec3_check_except(vec3_t a, - vec3_t b, - lex_ctx_t ctx, - sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t)) -{ - vec3_soft_state_t state; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) - return; - - sfloat_init(&state.state[0]); - sfloat_init(&state.state[1]); - sfloat_init(&state.state[2]); - - vec3_soft_eval(&state, callback, a, b); - if (state.faults & VEC_COMP_X) sfloat_check(ctx, &state.state[0], "x"); - if (state.faults & VEC_COMP_Y) sfloat_check(ctx, &state.state[1], "y"); - if (state.faults & VEC_COMP_Z) sfloat_check(ctx, &state.state[2], "z"); -} - -static GMQCC_INLINE vec3_t vec3_add(lex_ctx_t ctx, vec3_t a, vec3_t b) { - vec3_t out; - vec3_check_except(a, b, ctx, &sfloat_add); - out.x = a.x + b.x; - out.y = a.y + b.y; - out.z = a.z + b.z; - return out; -} - -static GMQCC_INLINE vec3_t vec3_sub(lex_ctx_t ctx, vec3_t a, vec3_t b) { - vec3_t out; - vec3_check_except(a, b, ctx, &sfloat_sub); - out.x = a.x - b.x; - out.y = a.y - b.y; - out.z = a.z - b.z; - return out; -} - -static GMQCC_INLINE vec3_t vec3_neg(lex_ctx_t ctx, vec3_t a) { - vec3_t out; - sfloat_cast_t v[3]; - sfloat_state_t s[3]; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) - goto end; - - v[0].f = a.x; - v[1].f = a.y; - v[2].f = a.z; - - sfloat_init(&s[0]); - sfloat_init(&s[1]); - sfloat_init(&s[2]); - - sfloat_neg(&s[0], v[0].s); - sfloat_neg(&s[1], v[1].s); - sfloat_neg(&s[2], v[2].s); - - sfloat_check(ctx, &s[0], NULL); - sfloat_check(ctx, &s[1], NULL); - sfloat_check(ctx, &s[2], NULL); - -end: - out.x = -a.x; - out.y = -a.y; - out.z = -a.z; - return out; -} - -static GMQCC_INLINE vec3_t vec3_or(vec3_t a, vec3_t b) { - vec3_t out; - out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b.x)); - out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b.y)); - out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b.z)); - return out; -} - -static GMQCC_INLINE vec3_t vec3_orvf(vec3_t a, qcfloat_t b) { - vec3_t out; - out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b)); - out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b)); - out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b)); - return out; -} - -static GMQCC_INLINE vec3_t vec3_and(vec3_t a, vec3_t b) { - vec3_t out; - out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b.x)); - out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b.y)); - out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b.z)); - return out; -} - -static GMQCC_INLINE vec3_t vec3_andvf(vec3_t a, qcfloat_t b) { - vec3_t out; - out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b)); - out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b)); - out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b)); - return out; -} - -static GMQCC_INLINE vec3_t vec3_xor(vec3_t a, vec3_t b) { - vec3_t out; - out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b.x)); - out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b.y)); - out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b.z)); - return out; -} - -static GMQCC_INLINE vec3_t vec3_xorvf(vec3_t a, qcfloat_t b) { - vec3_t out; - out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b)); - out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b)); - out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b)); - return out; -} - -static GMQCC_INLINE vec3_t vec3_not(vec3_t a) { - vec3_t out; - out.x = -1-a.x; - out.y = -1-a.y; - out.z = -1-a.z; - return out; -} - -static GMQCC_INLINE qcfloat_t vec3_mulvv(lex_ctx_t ctx, vec3_t a, vec3_t b) { - vec3_soft_t sa; - vec3_soft_t sb; - sfloat_state_t s[5]; - sfloat_t r[5]; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) - goto end; - - sa = vec3_soft_convert(a); - sb = vec3_soft_convert(b); - - sfloat_init(&s[0]); - sfloat_init(&s[1]); - sfloat_init(&s[2]); - sfloat_init(&s[3]); - sfloat_init(&s[4]); - - r[0] = sfloat_mul(&s[0], sa.x.s, sb.x.s); - r[1] = sfloat_mul(&s[1], sa.y.s, sb.y.s); - r[2] = sfloat_mul(&s[2], sa.z.s, sb.z.s); - r[3] = sfloat_add(&s[3], r[0], r[1]); - r[4] = sfloat_add(&s[4], r[3], r[2]); - - sfloat_check(ctx, &s[0], NULL); - sfloat_check(ctx, &s[1], NULL); - sfloat_check(ctx, &s[2], NULL); - sfloat_check(ctx, &s[3], NULL); - sfloat_check(ctx, &s[4], NULL); - -end: - return (a.x * b.x + a.y * b.y + a.z * b.z); -} - -static GMQCC_INLINE vec3_t vec3_mulvf(lex_ctx_t ctx, vec3_t a, qcfloat_t b) { - vec3_t out; - vec3_soft_t sa; - sfloat_cast_t sb; - sfloat_state_t s[3]; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) - goto end; - - sa = vec3_soft_convert(a); - sb.f = b; - sfloat_init(&s[0]); - sfloat_init(&s[1]); - sfloat_init(&s[2]); - - sfloat_mul(&s[0], sa.x.s, sb.s); - sfloat_mul(&s[1], sa.y.s, sb.s); - sfloat_mul(&s[2], sa.z.s, sb.s); - - sfloat_check(ctx, &s[0], "x"); - sfloat_check(ctx, &s[1], "y"); - sfloat_check(ctx, &s[2], "z"); - -end: - out.x = a.x * b; - out.y = a.y * b; - out.z = a.z * b; - return out; -} - -static GMQCC_INLINE bool vec3_cmp(vec3_t a, vec3_t b) { - return a.x == b.x && - a.y == b.y && - a.z == b.z; -} - -static GMQCC_INLINE vec3_t vec3_create(float x, float y, float z) { - vec3_t out; - out.x = x; - out.y = y; - out.z = z; - return out; -} - -static GMQCC_INLINE qcfloat_t vec3_notf(vec3_t a) { - return (!a.x && !a.y && !a.z); -} - -static GMQCC_INLINE bool vec3_pbool(vec3_t a) { - return (a.x || a.y || a.z); -} - -static GMQCC_INLINE vec3_t vec3_cross(lex_ctx_t ctx, vec3_t a, vec3_t b) { - vec3_t out; - vec3_soft_t sa; - vec3_soft_t sb; - sfloat_t r[9]; - sfloat_state_t s[9]; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) - goto end; - - sa = vec3_soft_convert(a); - sb = vec3_soft_convert(b); - - sfloat_init(&s[0]); - sfloat_init(&s[1]); - sfloat_init(&s[2]); - sfloat_init(&s[3]); - sfloat_init(&s[4]); - sfloat_init(&s[5]); - sfloat_init(&s[6]); - sfloat_init(&s[7]); - sfloat_init(&s[8]); - - r[0] = sfloat_mul(&s[0], sa.y.s, sb.z.s); - r[1] = sfloat_mul(&s[1], sa.z.s, sb.y.s); - r[2] = sfloat_mul(&s[2], sa.z.s, sb.x.s); - r[3] = sfloat_mul(&s[3], sa.x.s, sb.z.s); - r[4] = sfloat_mul(&s[4], sa.x.s, sb.y.s); - r[5] = sfloat_mul(&s[5], sa.y.s, sb.x.s); - r[6] = sfloat_sub(&s[6], r[0], r[1]); - r[7] = sfloat_sub(&s[7], r[2], r[3]); - r[8] = sfloat_sub(&s[8], r[4], r[5]); - - sfloat_check(ctx, &s[0], NULL); - sfloat_check(ctx, &s[1], NULL); - sfloat_check(ctx, &s[2], NULL); - sfloat_check(ctx, &s[3], NULL); - sfloat_check(ctx, &s[4], NULL); - sfloat_check(ctx, &s[5], NULL); - sfloat_check(ctx, &s[6], "x"); - sfloat_check(ctx, &s[7], "y"); - sfloat_check(ctx, &s[8], "z"); - -end: - out.x = a.y * b.z - a.z * b.y; - out.y = a.z * b.x - a.x * b.z; - out.z = a.x * b.y - a.y * b.x; - return out; -} - -static lex_ctx_t fold_ctx(fold_t *fold) { - lex_ctx_t ctx; - if (fold->parser->lex) - return parser_ctx(fold->parser); - - memset(&ctx, 0, sizeof(ctx)); - return ctx; -} - -static GMQCC_INLINE bool fold_immediate_true(fold_t *fold, ast_value *v) { - switch (v->expression.vtype) { - case TYPE_FLOAT: - return !!v->constval.vfloat; - case TYPE_INTEGER: - return !!v->constval.vint; - case TYPE_VECTOR: - if (OPTS_FLAG(CORRECT_LOGIC)) - return vec3_pbool(v->constval.vvec); - return !!(v->constval.vvec.x); - case TYPE_STRING: - if (!v->constval.vstring) - return false; - if (OPTS_FLAG(TRUE_EMPTY_STRINGS)) - return true; - return !!v->constval.vstring[0]; - default: - compile_error(fold_ctx(fold), "internal error: fold_immediate_true on invalid type"); - break; - } - return !!v->constval.vfunc; -} - -/* Handy macros to determine if an ast_value can be constant folded. */ -#define fold_can_1(X) \ - (ast_istype(((ast_expression*)(X)), ast_value) && (X)->hasvalue && ((X)->cvq == CV_CONST) && \ - ((ast_expression*)(X))->vtype != TYPE_FUNCTION) - -#define fold_can_2(X, Y) (fold_can_1(X) && fold_can_1(Y)) - -#define fold_immvalue_float(E) ((E)->constval.vfloat) -#define fold_immvalue_vector(E) ((E)->constval.vvec) -#define fold_immvalue_string(E) ((E)->constval.vstring) - -fold_t *fold_init(parser_t *parser) { - fold_t *fold = (fold_t*)mem_a(sizeof(fold_t)); - fold->parser = parser; - fold->imm_float = NULL; - fold->imm_vector = NULL; - fold->imm_string = NULL; - fold->imm_string_untranslate = util_htnew(FOLD_STRING_UNTRANSLATE_HTSIZE); - fold->imm_string_dotranslate = util_htnew(FOLD_STRING_DOTRANSLATE_HTSIZE); - - /* - * prime the tables with common constant values at constant - * locations. - */ - (void)fold_constgen_float (fold, 0.0f, false); - (void)fold_constgen_float (fold, 1.0f, false); - (void)fold_constgen_float (fold, -1.0f, false); - (void)fold_constgen_float (fold, 2.0f, false); - - (void)fold_constgen_vector(fold, vec3_create(0.0f, 0.0f, 0.0f)); - (void)fold_constgen_vector(fold, vec3_create(-1.0f, -1.0f, -1.0f)); - - return fold; -} - -bool fold_generate(fold_t *fold, ir_builder *ir) { - /* generate globals for immediate folded values */ - size_t i; - ast_value *cur; - - for (i = 0; i < vec_size(fold->imm_float); ++i) - if (!ast_global_codegen ((cur = fold->imm_float[i]), ir, false)) goto err; - for (i = 0; i < vec_size(fold->imm_vector); ++i) - if (!ast_global_codegen((cur = fold->imm_vector[i]), ir, false)) goto err; - for (i = 0; i < vec_size(fold->imm_string); ++i) - if (!ast_global_codegen((cur = fold->imm_string[i]), ir, false)) goto err; - - return true; - -err: - con_out("failed to generate global %s\n", cur->name); - ir_builder_delete(ir); - return false; -} - -void fold_cleanup(fold_t *fold) { - size_t i; - - for (i = 0; i < vec_size(fold->imm_float); ++i) ast_delete(fold->imm_float[i]); - for (i = 0; i < vec_size(fold->imm_vector); ++i) ast_delete(fold->imm_vector[i]); - for (i = 0; i < vec_size(fold->imm_string); ++i) ast_delete(fold->imm_string[i]); - - vec_free(fold->imm_float); - vec_free(fold->imm_vector); - vec_free(fold->imm_string); - - util_htdel(fold->imm_string_untranslate); - util_htdel(fold->imm_string_dotranslate); - - mem_d(fold); -} - -ast_expression *fold_constgen_float(fold_t *fold, qcfloat_t value, bool inexact) { - ast_value *out = NULL; - size_t i; - - for (i = 0; i < vec_size(fold->imm_float); i++) { - if (!memcmp(&fold->imm_float[i]->constval.vfloat, &value, sizeof(qcfloat_t))) - return (ast_expression*)fold->imm_float[i]; - } - - out = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_FLOAT); - out->cvq = CV_CONST; - out->hasvalue = true; - out->inexact = inexact; - out->constval.vfloat = value; - - vec_push(fold->imm_float, out); - - return (ast_expression*)out; -} - -ast_expression *fold_constgen_vector(fold_t *fold, vec3_t value) { - ast_value *out; - size_t i; - - for (i = 0; i < vec_size(fold->imm_vector); i++) { - if (vec3_cmp(fold->imm_vector[i]->constval.vvec, value)) - return (ast_expression*)fold->imm_vector[i]; - } - - out = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_VECTOR); - out->cvq = CV_CONST; - out->hasvalue = true; - out->constval.vvec = value; - - vec_push(fold->imm_vector, out); - - return (ast_expression*)out; -} - -ast_expression *fold_constgen_string(fold_t *fold, const char *str, bool translate) { - hash_table_t *table = (translate) ? fold->imm_string_untranslate : fold->imm_string_dotranslate; - ast_value *out = NULL; - size_t hash = util_hthash(table, str); - - if ((out = (ast_value*)util_htgeth(table, str, hash))) - return (ast_expression*)out; - - if (translate) { - char name[32]; - util_snprintf(name, sizeof(name), "dotranslate_%lu", (unsigned long)(fold->parser->translated++)); - out = ast_value_new(parser_ctx(fold->parser), name, TYPE_STRING); - out->expression.flags |= AST_FLAG_INCLUDE_DEF; /* def needs to be included for translatables */ - } else - out = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_STRING); - - out->cvq = CV_CONST; - out->hasvalue = true; - out->isimm = true; - out->constval.vstring = parser_strdup(str); - - vec_push(fold->imm_string, out); - util_htseth(table, str, hash, out); - - return (ast_expression*)out; -} - -typedef union { - void (*callback)(void); - sfloat_t (*binary)(sfloat_state_t *, sfloat_t, sfloat_t); - sfloat_t (*unary)(sfloat_state_t *, sfloat_t); -} float_check_callback_t; - -static bool fold_check_except_float_impl(void (*callback)(void), - fold_t *fold, - ast_value *a, - ast_value *b) -{ - float_check_callback_t call; - sfloat_state_t s; - sfloat_cast_t ca; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS) && !OPTS_WARN(WARN_INEXACT_COMPARES)) - return false; - - call.callback = callback; - sfloat_init(&s); - ca.f = fold_immvalue_float(a); - if (b) { - sfloat_cast_t cb; - cb.f = fold_immvalue_float(b); - call.binary(&s, ca.s, cb.s); - } else { - call.unary(&s, ca.s); - } - - if (s.exceptionflags == 0) - return false; - - if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) - goto inexact_possible; - - sfloat_check(fold_ctx(fold), &s, NULL); - -inexact_possible: - return s.exceptionflags & SFLOAT_INEXACT; -} - -#define fold_check_except_float(CALLBACK, FOLD, A, B) \ - fold_check_except_float_impl(((void (*)(void))(CALLBACK)), (FOLD), (A), (B)) - -static bool fold_check_inexact_float(fold_t *fold, ast_value *a, ast_value *b) { - lex_ctx_t ctx = fold_ctx(fold); - if (!OPTS_WARN(WARN_INEXACT_COMPARES)) - return false; - if (!a->inexact && !b->inexact) - return false; - return compile_warning(ctx, WARN_INEXACT_COMPARES, "inexact value in comparison"); -} - -static GMQCC_INLINE ast_expression *fold_op_mul_vec(fold_t *fold, vec3_t vec, ast_value *sel, const char *set) { - qcfloat_t x = (&vec.x)[set[0]-'x']; - qcfloat_t y = (&vec.x)[set[1]-'x']; - qcfloat_t z = (&vec.x)[set[2]-'x']; - if (!y && !z) { - ast_expression *out; - ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS]; - out = (ast_expression*)ast_member_new(fold_ctx(fold), (ast_expression*)sel, set[0]-'x', NULL); - out->node.keep = false; - ((ast_member*)out)->rvalue = true; - if (x != -1.0f) - return (ast_expression*)ast_binary_new(fold_ctx(fold), INSTR_MUL_F, fold_constgen_float(fold, x, false), out); - } - return NULL; -} - - -static GMQCC_INLINE ast_expression *fold_op_neg(fold_t *fold, ast_value *a) { - if (isfloat(a)) { - if (fold_can_1(a)) { - /* Negation can produce inexact as well */ - bool inexact = fold_check_except_float(&sfloat_neg, fold, a, NULL); - return fold_constgen_float(fold, -fold_immvalue_float(a), inexact); - } - } else if (isvector(a)) { - if (fold_can_1(a)) - return fold_constgen_vector(fold, vec3_neg(fold_ctx(fold), fold_immvalue_vector(a))); - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_not(fold_t *fold, ast_value *a) { - if (isfloat(a)) { - if (fold_can_1(a)) - return fold_constgen_float(fold, !fold_immvalue_float(a), false); - } else if (isvector(a)) { - if (fold_can_1(a)) - return fold_constgen_float(fold, vec3_notf(fold_immvalue_vector(a)), false); - } else if (isstring(a)) { - if (fold_can_1(a)) { - if (OPTS_FLAG(TRUE_EMPTY_STRINGS)) - return fold_constgen_float(fold, !fold_immvalue_string(a), false); - else - return fold_constgen_float(fold, !fold_immvalue_string(a) || !*fold_immvalue_string(a), false); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_add(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (fold_can_2(a, b)) { - bool inexact = fold_check_except_float(&sfloat_add, fold, a, b); - return fold_constgen_float(fold, fold_immvalue_float(a) + fold_immvalue_float(b), inexact); - } - } else if (isvector(a)) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_add(fold_ctx(fold), - fold_immvalue_vector(a), - fold_immvalue_vector(b))); - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_sub(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (fold_can_2(a, b)) { - bool inexact = fold_check_except_float(&sfloat_sub, fold, a, b); - return fold_constgen_float(fold, fold_immvalue_float(a) - fold_immvalue_float(b), inexact); - } - } else if (isvector(a)) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_sub(fold_ctx(fold), - fold_immvalue_vector(a), - fold_immvalue_vector(b))); - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_mul(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (isvector(b)) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(b), fold_immvalue_float(a))); - } else { - if (fold_can_2(a, b)) { - bool inexact = fold_check_except_float(&sfloat_mul, fold, a, b); - return fold_constgen_float(fold, fold_immvalue_float(a) * fold_immvalue_float(b), inexact); - } - } - } else if (isvector(a)) { - if (isfloat(b)) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(a), fold_immvalue_float(b))); - } else { - if (fold_can_2(a, b)) { - return fold_constgen_float(fold, vec3_mulvv(fold_ctx(fold), fold_immvalue_vector(a), fold_immvalue_vector(b)), false); - } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(a)) { - ast_expression *out; - if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "xyz"))) return out; - if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "yxz"))) return out; - if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "zxy"))) return out; - } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(b)) { - ast_expression *out; - if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "xyz"))) return out; - if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "yxz"))) return out; - if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "zxy"))) return out; - } - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_div(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (fold_can_2(a, b)) { - bool inexact = fold_check_except_float(&sfloat_div, fold, a, b); - return fold_constgen_float(fold, fold_immvalue_float(a) / fold_immvalue_float(b), inexact); - } else if (fold_can_1(b)) { - return (ast_expression*)ast_binary_new( - fold_ctx(fold), - INSTR_MUL_F, - (ast_expression*)a, - fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false) - ); - } - } else if (isvector(a)) { - if (fold_can_2(a, b)) { - return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(a), 1.0f / fold_immvalue_float(b))); - } else { - return (ast_expression*)ast_binary_new( - fold_ctx(fold), - INSTR_MUL_VF, - (ast_expression*)a, - (fold_can_1(b)) - ? (ast_expression*)fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false) - : (ast_expression*)ast_binary_new( - fold_ctx(fold), - INSTR_DIV_F, - (ast_expression*)fold->imm_float[1], - (ast_expression*)b - ) - ); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_mod(fold_t *fold, ast_value *a, ast_value *b) { - return (fold_can_2(a, b)) - ? fold_constgen_float(fold, fmod(fold_immvalue_float(a), fold_immvalue_float(b)), false) - : NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_bor(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) | ((qcint_t)fold_immvalue_float(b))), false); - } else { - if (isvector(b)) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_or(fold_immvalue_vector(a), fold_immvalue_vector(b))); - } else { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_orvf(fold_immvalue_vector(a), fold_immvalue_float(b))); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_band(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) & ((qcint_t)fold_immvalue_float(b))), false); - } else { - if (isvector(b)) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_and(fold_immvalue_vector(a), fold_immvalue_vector(b))); - } else { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_andvf(fold_immvalue_vector(a), fold_immvalue_float(b))); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_xor(fold_t *fold, ast_value *a, ast_value *b) { - if (isfloat(a)) { - if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) ^ ((qcint_t)fold_immvalue_float(b))), false); - } else { - if (fold_can_2(a, b)) { - if (isvector(b)) - return fold_constgen_vector(fold, vec3_xor(fold_immvalue_vector(a), fold_immvalue_vector(b))); - else - return fold_constgen_vector(fold, vec3_xorvf(fold_immvalue_vector(a), fold_immvalue_float(b))); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_lshift(fold_t *fold, ast_value *a, ast_value *b) { - if (fold_can_2(a, b) && isfloats(a, b)) - return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) * powf(2.0f, fold_immvalue_float(b))), false); - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_rshift(fold_t *fold, ast_value *a, ast_value *b) { - if (fold_can_2(a, b) && isfloats(a, b)) - return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) / powf(2.0f, fold_immvalue_float(b))), false); - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_andor(fold_t *fold, ast_value *a, ast_value *b, float expr) { - if (fold_can_2(a, b)) { - if (OPTS_FLAG(PERL_LOGIC)) { - if (expr) - return (fold_immediate_true(fold, a)) ? (ast_expression*)a : (ast_expression*)b; - else - return (fold_immediate_true(fold, a)) ? (ast_expression*)b : (ast_expression*)a; - } else { - return fold_constgen_float ( - fold, - ((expr) ? (fold_immediate_true(fold, a) || fold_immediate_true(fold, b)) - : (fold_immediate_true(fold, a) && fold_immediate_true(fold, b))) - ? 1 - : 0, - false - ); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_tern(fold_t *fold, ast_value *a, ast_value *b, ast_value *c) { - if (fold_can_1(a)) { - return fold_immediate_true(fold, a) - ? (ast_expression*)b - : (ast_expression*)c; - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_exp(fold_t *fold, ast_value *a, ast_value *b) { - if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)powf(fold_immvalue_float(a), fold_immvalue_float(b)), false); - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_lteqgt(fold_t *fold, ast_value *a, ast_value *b) { - if (fold_can_2(a,b)) { - fold_check_inexact_float(fold, a, b); - if (fold_immvalue_float(a) < fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[2]; - if (fold_immvalue_float(a) == fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[0]; - if (fold_immvalue_float(a) > fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[1]; - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_ltgt(fold_t *fold, ast_value *a, ast_value *b, bool lt) { - if (fold_can_2(a, b)) { - fold_check_inexact_float(fold, a, b); - return (lt) ? (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) < fold_immvalue_float(b))] - : (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) > fold_immvalue_float(b))]; - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_cmp(fold_t *fold, ast_value *a, ast_value *b, bool ne) { - if (fold_can_2(a, b)) { - if (isfloat(a) && isfloat(b)) { - float la = fold_immvalue_float(a); - float lb = fold_immvalue_float(b); - fold_check_inexact_float(fold, a, b); - return (ast_expression*)fold->imm_float[!(ne ? la == lb : la != lb)]; - } if (isvector(a) && isvector(b)) { - vec3_t la = fold_immvalue_vector(a); - vec3_t lb = fold_immvalue_vector(b); - return (ast_expression*)fold->imm_float[!(ne ? vec3_cmp(la, lb) : !vec3_cmp(la, lb))]; - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_bnot(fold_t *fold, ast_value *a) { - if (isfloat(a)) { - if (fold_can_1(a)) - return fold_constgen_float(fold, -1-fold_immvalue_float(a), false); - } else { - if (isvector(a)) { - if (fold_can_1(a)) - return fold_constgen_vector(fold, vec3_not(fold_immvalue_vector(a))); - } - } - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_cross(fold_t *fold, ast_value *a, ast_value *b) { - if (fold_can_2(a, b)) - return fold_constgen_vector(fold, vec3_cross(fold_ctx(fold), - fold_immvalue_vector(a), - fold_immvalue_vector(b))); - return NULL; -} - -static GMQCC_INLINE ast_expression *fold_op_length(fold_t *fold, ast_value *a) { - if (fold_can_1(a) && isstring(a)) - return fold_constgen_float(fold, strlen(fold_immvalue_string(a)), false); - if (isarray(a)) - return fold_constgen_float(fold, vec_size(a->initlist), false); - return NULL; -} - -ast_expression *fold_op(fold_t *fold, const oper_info *info, ast_expression **opexprs) { - ast_value *a = (ast_value*)opexprs[0]; - ast_value *b = (ast_value*)opexprs[1]; - ast_value *c = (ast_value*)opexprs[2]; - ast_expression *e = NULL; - - /* can a fold operation be applied to this operator usage? */ - if (!info->folds) - return NULL; - - switch(info->operands) { - case 3: if(!c) return NULL; - case 2: if(!b) return NULL; - case 1: - if(!a) { - compile_error(fold_ctx(fold), "internal error: fold_op no operands to fold\n"); - return NULL; - } - } - - /* - * we could use a boolean and default case but ironically gcc produces - * invalid broken assembly from that operation. clang/tcc get it right, - * but interestingly ignore compiling this to a jump-table when I do that, - * this happens to be the most efficent method, since you have per-level - * granularity on the pointer check happening only for the case you check - * it in. Opposed to the default method which would involve a boolean and - * pointer check after wards. - */ - #define fold_op_case(ARGS, ARGS_OPID, OP, ARGS_FOLD) \ - case opid##ARGS ARGS_OPID: \ - if ((e = fold_op_##OP ARGS_FOLD)) { \ - ++opts_optimizationcount[OPTIM_CONST_FOLD]; \ - } \ - return e - - switch(info->id) { - fold_op_case(2, ('-', 'P'), neg, (fold, a)); - fold_op_case(2, ('!', 'P'), not, (fold, a)); - fold_op_case(1, ('+'), add, (fold, a, b)); - fold_op_case(1, ('-'), sub, (fold, a, b)); - fold_op_case(1, ('*'), mul, (fold, a, b)); - fold_op_case(1, ('/'), div, (fold, a, b)); - fold_op_case(1, ('%'), mod, (fold, a, b)); - fold_op_case(1, ('|'), bor, (fold, a, b)); - fold_op_case(1, ('&'), band, (fold, a, b)); - fold_op_case(1, ('^'), xor, (fold, a, b)); - fold_op_case(1, ('<'), ltgt, (fold, a, b, true)); - fold_op_case(1, ('>'), ltgt, (fold, a, b, false)); - fold_op_case(2, ('<', '<'), lshift, (fold, a, b)); - fold_op_case(2, ('>', '>'), rshift, (fold, a, b)); - fold_op_case(2, ('|', '|'), andor, (fold, a, b, true)); - fold_op_case(2, ('&', '&'), andor, (fold, a, b, false)); - fold_op_case(2, ('?', ':'), tern, (fold, a, b, c)); - fold_op_case(2, ('*', '*'), exp, (fold, a, b)); - fold_op_case(3, ('<','=','>'), lteqgt, (fold, a, b)); - fold_op_case(2, ('!', '='), cmp, (fold, a, b, true)); - fold_op_case(2, ('=', '='), cmp, (fold, a, b, false)); - fold_op_case(2, ('~', 'P'), bnot, (fold, a)); - fold_op_case(2, ('>', '<'), cross, (fold, a, b)); - fold_op_case(3, ('l', 'e', 'n'), length, (fold, a)); - } - #undef fold_op_case - compile_error(fold_ctx(fold), "internal error: attempted to constant-fold for unsupported operator"); - return NULL; -} - -/* - * Constant folding for compiler intrinsics, similar approach to operator - * folding, primarily: individual functions for each intrinsics to fold, - * and a generic selection function. - */ -static GMQCC_INLINE ast_expression *fold_intrin_isfinite(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, isfinite(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_isinf(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, isinf(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_isnan(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, isnan(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_isnormal(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, isnormal(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_signbit(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, signbit(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intirn_acosh(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, acoshf(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_asinh(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, asinhf(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_atanh(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, (float)atanh(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_exp(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, expf(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_exp2(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, exp2f(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_expm1(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, expm1f(fold_immvalue_float(a)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_mod(fold_t *fold, ast_value *lhs, ast_value *rhs) { - return fold_constgen_float(fold, fmodf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_pow(fold_t *fold, ast_value *lhs, ast_value *rhs) { - return fold_constgen_float(fold, powf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false); -} -static GMQCC_INLINE ast_expression *fold_intrin_fabs(fold_t *fold, ast_value *a) { - return fold_constgen_float(fold, fabsf(fold_immvalue_float(a)), false); -} - - -ast_expression *fold_intrin(fold_t *fold, const char *intrin, ast_expression **arg) { - ast_expression *ret = NULL; - ast_value *a = (ast_value*)arg[0]; - ast_value *b = (ast_value*)arg[1]; - - if (!strcmp(intrin, "isfinite")) ret = fold_intrin_isfinite(fold, a); - if (!strcmp(intrin, "isinf")) ret = fold_intrin_isinf(fold, a); - if (!strcmp(intrin, "isnan")) ret = fold_intrin_isnan(fold, a); - if (!strcmp(intrin, "isnormal")) ret = fold_intrin_isnormal(fold, a); - if (!strcmp(intrin, "signbit")) ret = fold_intrin_signbit(fold, a); - if (!strcmp(intrin, "acosh")) ret = fold_intirn_acosh(fold, a); - if (!strcmp(intrin, "asinh")) ret = fold_intrin_asinh(fold, a); - if (!strcmp(intrin, "atanh")) ret = fold_intrin_atanh(fold, a); - if (!strcmp(intrin, "exp")) ret = fold_intrin_exp(fold, a); - if (!strcmp(intrin, "exp2")) ret = fold_intrin_exp2(fold, a); - if (!strcmp(intrin, "expm1")) ret = fold_intrin_expm1(fold, a); - if (!strcmp(intrin, "mod")) ret = fold_intrin_mod(fold, a, b); - if (!strcmp(intrin, "pow")) ret = fold_intrin_pow(fold, a, b); - if (!strcmp(intrin, "fabs")) ret = fold_intrin_fabs(fold, a); - - if (ret) - ++opts_optimizationcount[OPTIM_CONST_FOLD]; - - return ret; -} - -/* - * These are all the actual constant folding methods that happen in between - * the AST/IR stage of the compiler , i.e eliminating branches for const - * expressions, which is the only supported thing so far. We undefine the - * testing macros here because an ir_value is differant than an ast_value. - */ -#undef expect -#undef isfloat -#undef isstring -#undef isvector -#undef fold_immvalue_float -#undef fold_immvalue_string -#undef fold_immvalue_vector -#undef fold_can_1 -#undef fold_can_2 - -#define isfloat(X) ((X)->vtype == TYPE_FLOAT) -/*#define isstring(X) ((X)->vtype == TYPE_STRING)*/ -/*#define isvector(X) ((X)->vtype == TYPE_VECTOR)*/ -#define fold_immvalue_float(X) ((X)->constval.vfloat) -#define fold_immvalue_vector(X) ((X)->constval.vvec) -/*#define fold_immvalue_string(X) ((X)->constval.vstring)*/ -#define fold_can_1(X) ((X)->hasvalue && (X)->cvq == CV_CONST) -/*#define fold_can_2(X,Y) (fold_can_1(X) && fold_can_1(Y))*/ - -static ast_expression *fold_superfluous(ast_expression *left, ast_expression *right, int op) { - ast_expression *swapped = NULL; /* using this as bool */ - ast_value *load; - - if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right))) { - swapped = left; - left = right; - right = swapped; - } - - if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right))) - return NULL; - - switch (op) { - case INSTR_DIV_F: - if (swapped) - return NULL; - case INSTR_MUL_F: - if (fold_immvalue_float(load) == 1.0f) { - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - ast_unref(right); - return left; - } - break; - - - case INSTR_SUB_F: - if (swapped) - return NULL; - case INSTR_ADD_F: - if (fold_immvalue_float(load) == 0.0f) { - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - ast_unref(right); - return left; - } - break; - - case INSTR_MUL_V: - if (vec3_cmp(fold_immvalue_vector(load), vec3_create(1, 1, 1))) { - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - ast_unref(right); - return left; - } - break; - - case INSTR_SUB_V: - if (swapped) - return NULL; - case INSTR_ADD_V: - if (vec3_cmp(fold_immvalue_vector(load), vec3_create(0, 0, 0))) { - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - ast_unref(right); - return left; - } - break; - } - - return NULL; -} - -ast_expression *fold_binary(lex_ctx_t ctx, int op, ast_expression *left, ast_expression *right) { - ast_expression *ret = fold_superfluous(left, right, op); - if (ret) - return ret; - return (ast_expression*)ast_binary_new(ctx, op, left, right); -} - -static GMQCC_INLINE int fold_cond(ir_value *condval, ast_function *func, ast_ifthen *branch) { - if (isfloat(condval) && fold_can_1(condval) && OPTS_OPTIMIZATION(OPTIM_CONST_FOLD_DCE)) { - ast_expression_codegen *cgen; - ir_block *elide; - ir_value *dummy; - bool istrue = (fold_immvalue_float(condval) != 0.0f && branch->on_true); - bool isfalse = (fold_immvalue_float(condval) == 0.0f && branch->on_false); - ast_expression *path = (istrue) ? branch->on_true : - (isfalse) ? branch->on_false : NULL; - if (!path) { - /* - * no path to take implies that the evaluation is if(0) and there - * is no else block. so eliminate all the code. - */ - ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE]; - return true; - } - - if (!(elide = ir_function_create_block(ast_ctx(branch), func->ir_func, ast_function_label(func, ((istrue) ? "ontrue" : "onfalse"))))) - return false; - if (!(*(cgen = path->codegen))((ast_expression*)path, func, false, &dummy)) - return false; - if (!ir_block_create_jump(func->curblock, ast_ctx(branch), elide)) - return false; - /* - * now the branch has been eliminated and the correct block for the constant evaluation - * is expanded into the current block for the function. - */ - func->curblock = elide; - ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE]; - return true; - } - return -1; /* nothing done */ -} - -int fold_cond_ternary(ir_value *condval, ast_function *func, ast_ternary *branch) { - return fold_cond(condval, func, (ast_ifthen*)branch); -} - -int fold_cond_ifthen(ir_value *condval, ast_function *func, ast_ifthen *branch) { - return fold_cond(condval, func, branch); -} diff --git a/fold.cpp b/fold.cpp new file mode 100644 index 0000000..64b20d9 --- /dev/null +++ b/fold.cpp @@ -0,0 +1,1668 @@ +#include +#include + +#include "ast.h" +#include "parser.h" + +#define FOLD_STRING_UNTRANSLATE_HTSIZE 1024 +#define FOLD_STRING_DOTRANSLATE_HTSIZE 1024 + +/* The options to use for inexact and arithmetic exceptions */ +#define FOLD_ROUNDING SFLOAT_ROUND_NEAREST_EVEN +#define FOLD_TINYNESS SFLOAT_TBEFORE + +/* + * Comparing float values is an unsafe operation when the operands to the + * comparison are floating point values that are inexact. For instance 1/3 is an + * inexact value. The FPU is meant to raise exceptions when these sorts of things + * happen, including division by zero, underflows and overflows. The C standard + * library provides us with the header to gain access to the floating- + * point environment and lets us set the rounding mode and check for these exceptions. + * The problem is the standard C library allows an implementation to leave these + * stubbed out and does not require they be implemented. Furthermore, depending + * on implementations there is no control over the FPU. This is an IEE 754 + * conforming implementation in software to compensate. + */ +typedef uint32_t sfloat_t; + +typedef union { + qcfloat_t f; + sfloat_t s; +} sfloat_cast_t; + +/* Exception flags */ +typedef enum { + SFLOAT_NOEXCEPT = 0, + SFLOAT_INVALID = 1, + SFLOAT_DIVBYZERO = 4, + SFLOAT_OVERFLOW = 8, + SFLOAT_UNDERFLOW = 16, + SFLOAT_INEXACT = 32 +} sfloat_exceptionflags_t; + +/* Rounding modes */ +typedef enum { + SFLOAT_ROUND_NEAREST_EVEN, + SFLOAT_ROUND_DOWN, + SFLOAT_ROUND_UP, + SFLOAT_ROUND_TO_ZERO +} sfloat_roundingmode_t; + +/* Underflow tininess-detection mode */ +typedef enum { + SFLOAT_TAFTER, + SFLOAT_TBEFORE +} sfloat_tdetect_t; + +typedef struct { + sfloat_roundingmode_t roundingmode; + sfloat_exceptionflags_t exceptionflags; + sfloat_tdetect_t tiny; +} sfloat_state_t; + +/* Counts the number of leading zero bits before the most-significand one bit. */ +#ifdef _MSC_VER +/* MSVC has an intrinsic for this */ + static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) { + int r = 0; + _BitScanForward(&r, x); + return r; + } +# define SFLOAT_CLZ(X, SUB) \ + (sfloat_clz((X)) - (SUB)) +#elif defined(__GNUC__) || defined(__CLANG__) +/* Clang and GCC have a builtin for this */ +# define SFLOAT_CLZ(X, SUB) \ + (__builtin_clz((X)) - (SUB)) +#else +/* Native fallback */ + static GMQCC_INLINE uint32_t sfloat_popcnt(uint32_t x) { + x -= ((x >> 1) & 0x55555555); + x = (((x >> 2) & 0x33333333) + (x & 0x33333333)); + x = (((x >> 4) + x) & 0x0F0F0F0F); + x += x >> 8; + x += x >> 16; + return x & 0x0000003F; + } + static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) { + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + return 32 - sfloat_popcnt(x); + } +# define SFLOAT_CLZ(X, SUB) \ + (sfloat_clz((X) - (SUB))) +#endif + +/* The value of a NaN */ +#define SFLOAT_NAN 0xFFFFFFFF +/* Test if NaN */ +#define SFLOAT_ISNAN(A) \ + (0xFF000000 < (uint32_t)((A) << 1)) +/* Test if signaling NaN */ +#define SFLOAT_ISSNAN(A) \ + (((((A) >> 22) & 0x1FF) == 0x1FE) && ((A) & 0x003FFFFF)) +/* Raise exception */ +#define SFLOAT_RAISE(STATE, FLAGS) \ + ((STATE)->exceptionflags = (sfloat_exceptionflags_t)((STATE)->exceptionflags | (FLAGS))) +/* + * Shifts `A' right by the number of bits given in `COUNT'. If any non-zero bits + * are shifted off they are forced into the least significand bit of the result + * by setting it to one. As a result of this, the value of `COUNT' can be + * arbitrarily large; if `COUNT' is greater than 32, the result will be either + * zero or one, depending on whether `A' is a zero or non-zero. The result is + * stored into the value pointed by `Z'. + */ +#define SFLOAT_SHIFT(SIZE, A, COUNT, Z) \ + *(Z) = ((COUNT) == 0) \ + ? 1 \ + : (((COUNT) < (SIZE)) \ + ? ((A) >> (COUNT)) | (((A) << ((-(COUNT)) & ((SIZE) - 1))) != 0) \ + : ((A) != 0)) + +/* Extract fractional component */ +#define SFLOAT_EXTRACT_FRAC(X) \ + ((uint32_t)((X) & 0x007FFFFF)) +/* Extract exponent component */ +#define SFLOAT_EXTRACT_EXP(X) \ + ((int16_t)((X) >> 23) & 0xFF) +/* Extract sign bit */ +#define SFLOAT_EXTRACT_SIGN(X) \ + ((X) >> 31) +/* + * Normalizes the subnormal value represented by the denormalized significand + * `SA'. The normalized exponent and significand are stored at the locations + * pointed by `Z' and `SZ' respectively. + */ +#define SFLOAT_SUBNORMALIZE(SA, Z, SZ) \ + (void)(*(SZ) = (SA) << SFLOAT_CLZ((SA), 8), *(Z) = 1 - SFLOAT_CLZ((SA), 8)) +/* + * Packs the sign `SIGN', exponent `EXP' and significand `SIG' into the value + * giving the result. + * + * After the shifting into their proper positions, the fields are added together + * to form the result. This means any integer portion of `SIG' will be added + * to the exponent. Similarly, because a properly normalized significand will + * always have an integer portion equal to one, the exponent input `EXP' should + * be one less than the desired result exponent whenever the significant input + * `SIG' is a complete, normalized significand. + */ +#define SFLOAT_PACK(SIGN, EXP, SIG) \ + (sfloat_t)((((uint32_t)(SIGN)) << 31) + (((uint32_t)(EXP)) << 23) + (SIG)) + +/* + * Takes two values `a' and `b', one of which is a NaN, and returns the appropriate + * NaN result. If either `a' or `b' is a signaling NaN than an invalid exception is + * raised. + */ +static sfloat_t sfloat_propagate_nan(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + bool isnan_a = SFLOAT_ISNAN(a); + bool issnan_a = SFLOAT_ISSNAN(a); + bool isnan_b = SFLOAT_ISNAN(b); + bool issnan_b = SFLOAT_ISSNAN(b); + + a |= 0x00400000; + b |= 0x00400000; + + if (issnan_a | issnan_b) + SFLOAT_RAISE(state, SFLOAT_INVALID); + if (isnan_a) + return (issnan_a & isnan_b) ? b : a; + return b; +} + +/* + * Takes an abstract value having sign `sign_z', exponent `exp_z', and significand + * `sig_z' and returns the appropriate value corresponding to the abstract input. + * + * The abstract value is simply rounded and packed into the format. If the abstract + * input cannot be represented exactly an inexact exception is raised. If the + * abstract input is too large, the overflow and inexact exceptions are both raised + * and an infinity or maximal finite value is returned. If the abstract value is + * too small, the value is rounded to a subnormal and the underflow and inexact + * exceptions are only raised if the value cannot be represented exactly with + * a subnormal. + * + * The input significand `sig_z' has it's binary point between bits 30 and 29, + * this is seven bits to the left of its usual location. The shifted significand + * must be normalized or smaller than this. If it's not normalized then the exponent + * `exp_z' must be zero; in that case, the result returned is a subnormal number + * which must not require rounding. In the more usual case where the significand + * is normalized, the exponent must be one less than the *true* exponent. + * + * The handling of underflow and overflow is otherwise in alignment with IEC/IEEE. + */ +static sfloat_t SFLOAT_PACK_round(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) { + sfloat_roundingmode_t mode = state->roundingmode; + bool even = !!(mode == SFLOAT_ROUND_NEAREST_EVEN); + unsigned char increment = 0x40; + unsigned char bits = sig_z & 0x7F; + + if (!even) { + if (mode == SFLOAT_ROUND_TO_ZERO) + increment = 0; + else { + increment = 0x7F; + if (sign_z) { + if (mode == SFLOAT_ROUND_UP) + increment = 0; + } else { + if (mode == SFLOAT_ROUND_DOWN) + increment = 0; + } + } + } + + if (0xFD <= (uint16_t)exp_z) { + if ((0xFD < exp_z) || ((exp_z == 0xFD) && ((int32_t)(sig_z + increment) < 0))) { + SFLOAT_RAISE(state, SFLOAT_OVERFLOW | SFLOAT_INEXACT); + return SFLOAT_PACK(sign_z, 0xFF, 0) - (increment == 0); + } + if (exp_z < 0) { + /* Check for underflow */ + bool tiny = (state->tiny == SFLOAT_TBEFORE) || (exp_z < -1) || (sig_z + increment < 0x80000000); + SFLOAT_SHIFT(32, sig_z, -exp_z, &sig_z); + exp_z = 0; + bits = sig_z & 0x7F; + if (tiny && bits) + SFLOAT_RAISE(state, SFLOAT_UNDERFLOW); + } + } + if (bits) + SFLOAT_RAISE(state, SFLOAT_INEXACT); + sig_z = (sig_z + increment) >> 7; + sig_z &= ~(((bits ^ 0x40) == 0) & even); + if (sig_z == 0) + exp_z = 0; + return SFLOAT_PACK(sign_z, exp_z, sig_z); +} + +/* + * Takes an abstract value having sign `sign_z', exponent `exp_z' and significand + * `sig_z' and returns the appropriate value corresponding to the abstract input. + * This function is exactly like `PACK_round' except the significand does not have + * to be normalized. + * + * Bit 31 of the significand must be zero and the exponent must be one less than + * the *true* exponent. + */ +static sfloat_t SFLOAT_PACK_normal(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) { + unsigned char c = SFLOAT_CLZ(sig_z, 1); + return SFLOAT_PACK_round(state, sign_z, exp_z - c, sig_z << c); +} + +/* + * Returns the result of adding the absolute values of `a' and `b'. The sign + * `sign_z' is ignored if the result is a NaN. + */ +static sfloat_t sfloat_add_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) { + int16_t exp_a = SFLOAT_EXTRACT_EXP(a); + int16_t exp_b = SFLOAT_EXTRACT_EXP(b); + int16_t exp_z = 0; + int16_t exp_d = exp_a - exp_b; + uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 6; + uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 6; + uint32_t sig_z = 0; + + if (0 < exp_d) { + if (exp_a == 0xFF) + return sig_a ? sfloat_propagate_nan(state, a, b) : a; + if (exp_b == 0) + --exp_d; + else + sig_b |= 0x20000000; + SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b); + exp_z = exp_a; + } else if (exp_d < 0) { + if (exp_b == 0xFF) + return sig_b ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0xFF, 0); + if (exp_a == 0) + ++exp_d; + else + sig_a |= 0x20000000; + SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a); + exp_z = exp_b; + } else { + if (exp_a == 0xFF) + return (sig_a | sig_b) ? sfloat_propagate_nan(state, a, b) : a; + if (exp_a == 0) + return SFLOAT_PACK(sign_z, 0, (sig_a + sig_b) >> 6); + sig_z = 0x40000000 + sig_a + sig_b; + exp_z = exp_a; + goto end; + } + sig_a |= 0x20000000; + sig_z = (sig_a + sig_b) << 1; + --exp_z; + if ((int32_t)sig_z < 0) { + sig_z = sig_a + sig_b; + ++exp_z; + } +end: + return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); +} + +/* + * Returns the result of subtracting the absolute values of `a' and `b'. If the + * sign `sign_z' is one, the difference is negated before being returned. The + * sign is ignored if the result is a NaN. + */ +static sfloat_t sfloat_sub_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) { + int16_t exp_a = SFLOAT_EXTRACT_EXP(a); + int16_t exp_b = SFLOAT_EXTRACT_EXP(b); + int16_t exp_z = 0; + int16_t exp_d = exp_a - exp_b; + uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 7; + uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 7; + uint32_t sig_z = 0; + + if (0 < exp_d) goto exp_greater_a; + if (exp_d < 0) goto exp_greater_b; + + if (exp_a == 0xFF) { + if (sig_a | sig_b) + return sfloat_propagate_nan(state, a, b); + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + + if (exp_a == 0) + exp_a = exp_b = 1; + + if (sig_b < sig_a) goto greater_a; + if (sig_a < sig_b) goto greater_b; + + return SFLOAT_PACK(state->roundingmode == SFLOAT_ROUND_DOWN, 0, 0); + +exp_greater_b: + if (exp_b == 0xFF) + return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z ^ 1, 0xFF, 0); + if (exp_a == 0) + ++exp_d; + else + sig_a |= 0x40000000; + SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a); + sig_b |= 0x40000000; +greater_b: + sig_z = sig_b - sig_a; + exp_z = exp_b; + sign_z ^= 1; + goto end; + +exp_greater_a: + if (exp_a == 0xFF) + return (sig_a) ? sfloat_propagate_nan(state, a, b) : a; + if (exp_b == 0) + --exp_d; + else + sig_b |= 0x40000000; + SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b); + sig_a |= 0x40000000; +greater_a: + sig_z = sig_a - sig_b; + exp_z = exp_a; + +end: + --exp_z; + return SFLOAT_PACK_normal(state, sign_z, exp_z, sig_z); +} + +static GMQCC_INLINE sfloat_t sfloat_add(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + bool sign_a = SFLOAT_EXTRACT_SIGN(a); + bool sign_b = SFLOAT_EXTRACT_SIGN(b); + return (sign_a == sign_b) ? sfloat_add_impl(state, a, b, sign_a) + : sfloat_sub_impl(state, a, b, sign_a); +} + +static GMQCC_INLINE sfloat_t sfloat_sub(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + bool sign_a = SFLOAT_EXTRACT_SIGN(a); + bool sign_b = SFLOAT_EXTRACT_SIGN(b); + return (sign_a == sign_b) ? sfloat_sub_impl(state, a, b, sign_a) + : sfloat_add_impl(state, a, b, sign_a); +} + +static sfloat_t sfloat_mul(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + int16_t exp_a = SFLOAT_EXTRACT_EXP(a); + int16_t exp_b = SFLOAT_EXTRACT_EXP(b); + int16_t exp_z = 0; + uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a); + uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b); + uint32_t sig_z = 0; + uint64_t sig_z64 = 0; + bool sign_a = SFLOAT_EXTRACT_SIGN(a); + bool sign_b = SFLOAT_EXTRACT_SIGN(b); + bool sign_z = sign_a ^ sign_b; + + if (exp_a == 0xFF) { + if (sig_a || ((exp_b == 0xFF) && sig_b)) + return sfloat_propagate_nan(state, a, b); + if ((exp_b | sig_b) == 0) { + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + return SFLOAT_PACK(sign_z, 0xFF, 0); + } + if (exp_b == 0xFF) { + if (sig_b) + return sfloat_propagate_nan(state, a, b); + if ((exp_a | sig_a) == 0) { + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + return SFLOAT_PACK(sign_z, 0xFF, 0); + } + if (exp_a == 0) { + if (sig_a == 0) + return SFLOAT_PACK(sign_z, 0, 0); + SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a); + } + if (exp_b == 0) { + if (sig_b == 0) + return SFLOAT_PACK(sign_z, 0, 0); + SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b); + } + exp_z = exp_a + exp_b - 0x7F; + sig_a = (sig_a | 0x00800000) << 7; + sig_b = (sig_b | 0x00800000) << 8; + SFLOAT_SHIFT(64, ((uint64_t)sig_a) * sig_b, 32, &sig_z64); + sig_z = sig_z64; + if (0 <= (int32_t)(sig_z << 1)) { + sig_z <<= 1; + --exp_z; + } + return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); +} + +static sfloat_t sfloat_div(sfloat_state_t *state, sfloat_t a, sfloat_t b) { + int16_t exp_a = SFLOAT_EXTRACT_EXP(a); + int16_t exp_b = SFLOAT_EXTRACT_EXP(b); + int16_t exp_z = 0; + uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a); + uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b); + uint32_t sig_z = 0; + bool sign_a = SFLOAT_EXTRACT_SIGN(a); + bool sign_b = SFLOAT_EXTRACT_SIGN(b); + bool sign_z = sign_a ^ sign_b; + + if (exp_a == 0xFF) { + if (sig_a) + return sfloat_propagate_nan(state, a, b); + if (exp_b == 0xFF) { + if (sig_b) + return sfloat_propagate_nan(state, a, b); + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + return SFLOAT_PACK(sign_z, 0xFF, 0); + } + if (exp_b == 0xFF) + return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0, 0); + if (exp_b == 0) { + if (sig_b == 0) { + if ((exp_a | sig_a) == 0) { + SFLOAT_RAISE(state, SFLOAT_INVALID); + return SFLOAT_NAN; + } + SFLOAT_RAISE(state, SFLOAT_DIVBYZERO); + return SFLOAT_PACK(sign_z, 0xFF, 0); + } + SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b); + } + if (exp_a == 0) { + if (sig_a == 0) + return SFLOAT_PACK(sign_z, 0, 0); + SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a); + } + exp_z = exp_a - exp_b + 0x7D; + sig_a = (sig_a | 0x00800000) << 7; + sig_b = (sig_b | 0x00800000) << 8; + if (sig_b <= (sig_a + sig_a)) { + sig_a >>= 1; + ++exp_z; + } + sig_z = (((uint64_t)sig_a) << 32) / sig_b; + if ((sig_z & 0x3F) == 0) + sig_z |= ((uint64_t)sig_b * sig_z != ((uint64_t)sig_a) << 32); + return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z); +} + +static sfloat_t sfloat_neg(sfloat_state_t *state, sfloat_t a) { + sfloat_cast_t neg; + neg.f = -1; + return sfloat_mul(state, a, neg.s); +} + +static GMQCC_INLINE void sfloat_check(lex_ctx_t ctx, sfloat_state_t *state, const char *vec) { + /* Exception comes from vector component */ + if (vec) { + if (state->exceptionflags & SFLOAT_DIVBYZERO) + compile_error(ctx, "division by zero in `%s' component", vec); + if (state->exceptionflags & SFLOAT_INVALID) + compile_error(ctx, "undefined (inf) in `%s' component", vec); + if (state->exceptionflags & SFLOAT_OVERFLOW) + compile_error(ctx, "arithmetic overflow in `%s' component", vec); + if (state->exceptionflags & SFLOAT_UNDERFLOW) + compile_error(ctx, "arithmetic underflow in `%s' component", vec); + return; + } + if (state->exceptionflags & SFLOAT_DIVBYZERO) + compile_error(ctx, "division by zero"); + if (state->exceptionflags & SFLOAT_INVALID) + compile_error(ctx, "undefined (inf)"); + if (state->exceptionflags & SFLOAT_OVERFLOW) + compile_error(ctx, "arithmetic overflow"); + if (state->exceptionflags & SFLOAT_UNDERFLOW) + compile_error(ctx, "arithmetic underflow"); +} + +static GMQCC_INLINE void sfloat_init(sfloat_state_t *state) { + state->exceptionflags = SFLOAT_NOEXCEPT; + state->roundingmode = FOLD_ROUNDING; + state->tiny = FOLD_TINYNESS; +} + +/* + * There is two stages to constant folding in GMQCC: there is the parse + * stage constant folding, where, with the help of the AST, operator + * usages can be constant folded. Then there is the constant folding + * in the IR for things like eliding if statements, can occur. + * + * This file is thus, split into two parts. + */ + +#define isfloat(X) (((ast_expression*)(X))->vtype == TYPE_FLOAT) +#define isvector(X) (((ast_expression*)(X))->vtype == TYPE_VECTOR) +#define isstring(X) (((ast_expression*)(X))->vtype == TYPE_STRING) +#define isarray(X) (((ast_expression*)(X))->vtype == TYPE_ARRAY) +#define isfloats(X,Y) (isfloat (X) && isfloat (Y)) + +/* + * Implementation of basic vector math for vec3_t, for trivial constant + * folding. + * + * TODO: gcc/clang hinting for autovectorization + */ +typedef enum { + VEC_COMP_X = 1 << 0, + VEC_COMP_Y = 1 << 1, + VEC_COMP_Z = 1 << 2 +} vec3_comp_t; + +typedef struct { + sfloat_cast_t x; + sfloat_cast_t y; + sfloat_cast_t z; +} vec3_soft_t; + +typedef struct { + vec3_comp_t faults; + sfloat_state_t state[3]; +} vec3_soft_state_t; + +static GMQCC_INLINE vec3_soft_t vec3_soft_convert(vec3_t vec) { + vec3_soft_t soft; + soft.x.f = vec.x; + soft.y.f = vec.y; + soft.z.f = vec.z; + return soft; +} + +static GMQCC_INLINE bool vec3_soft_exception(vec3_soft_state_t *vstate, size_t index) { + sfloat_exceptionflags_t flags = vstate->state[index].exceptionflags; + if (flags & SFLOAT_DIVBYZERO) return true; + if (flags & SFLOAT_INVALID) return true; + if (flags & SFLOAT_OVERFLOW) return true; + if (flags & SFLOAT_UNDERFLOW) return true; + return false; +} + +static GMQCC_INLINE void vec3_soft_eval(vec3_soft_state_t *state, + sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t), + vec3_t a, + vec3_t b) +{ + vec3_soft_t sa = vec3_soft_convert(a); + vec3_soft_t sb = vec3_soft_convert(b); + callback(&state->state[0], sa.x.s, sb.x.s); + if (vec3_soft_exception(state, 0)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_X); + callback(&state->state[1], sa.y.s, sb.y.s); + if (vec3_soft_exception(state, 1)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Y); + callback(&state->state[2], sa.z.s, sb.z.s); + if (vec3_soft_exception(state, 2)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Z); +} + +static GMQCC_INLINE void vec3_check_except(vec3_t a, + vec3_t b, + lex_ctx_t ctx, + sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t)) +{ + vec3_soft_state_t state; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) + return; + + sfloat_init(&state.state[0]); + sfloat_init(&state.state[1]); + sfloat_init(&state.state[2]); + + vec3_soft_eval(&state, callback, a, b); + if (state.faults & VEC_COMP_X) sfloat_check(ctx, &state.state[0], "x"); + if (state.faults & VEC_COMP_Y) sfloat_check(ctx, &state.state[1], "y"); + if (state.faults & VEC_COMP_Z) sfloat_check(ctx, &state.state[2], "z"); +} + +static GMQCC_INLINE vec3_t vec3_add(lex_ctx_t ctx, vec3_t a, vec3_t b) { + vec3_t out; + vec3_check_except(a, b, ctx, &sfloat_add); + out.x = a.x + b.x; + out.y = a.y + b.y; + out.z = a.z + b.z; + return out; +} + +static GMQCC_INLINE vec3_t vec3_sub(lex_ctx_t ctx, vec3_t a, vec3_t b) { + vec3_t out; + vec3_check_except(a, b, ctx, &sfloat_sub); + out.x = a.x - b.x; + out.y = a.y - b.y; + out.z = a.z - b.z; + return out; +} + +static GMQCC_INLINE vec3_t vec3_neg(lex_ctx_t ctx, vec3_t a) { + vec3_t out; + sfloat_cast_t v[3]; + sfloat_state_t s[3]; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) + goto end; + + v[0].f = a.x; + v[1].f = a.y; + v[2].f = a.z; + + sfloat_init(&s[0]); + sfloat_init(&s[1]); + sfloat_init(&s[2]); + + sfloat_neg(&s[0], v[0].s); + sfloat_neg(&s[1], v[1].s); + sfloat_neg(&s[2], v[2].s); + + sfloat_check(ctx, &s[0], NULL); + sfloat_check(ctx, &s[1], NULL); + sfloat_check(ctx, &s[2], NULL); + +end: + out.x = -a.x; + out.y = -a.y; + out.z = -a.z; + return out; +} + +static GMQCC_INLINE vec3_t vec3_or(vec3_t a, vec3_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b.x)); + out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b.y)); + out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b.z)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_orvf(vec3_t a, qcfloat_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b)); + out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b)); + out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_and(vec3_t a, vec3_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b.x)); + out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b.y)); + out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b.z)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_andvf(vec3_t a, qcfloat_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b)); + out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b)); + out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_xor(vec3_t a, vec3_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b.x)); + out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b.y)); + out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b.z)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_xorvf(vec3_t a, qcfloat_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b)); + out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b)); + out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_not(vec3_t a) { + vec3_t out; + out.x = -1-a.x; + out.y = -1-a.y; + out.z = -1-a.z; + return out; +} + +static GMQCC_INLINE qcfloat_t vec3_mulvv(lex_ctx_t ctx, vec3_t a, vec3_t b) { + vec3_soft_t sa; + vec3_soft_t sb; + sfloat_state_t s[5]; + sfloat_t r[5]; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) + goto end; + + sa = vec3_soft_convert(a); + sb = vec3_soft_convert(b); + + sfloat_init(&s[0]); + sfloat_init(&s[1]); + sfloat_init(&s[2]); + sfloat_init(&s[3]); + sfloat_init(&s[4]); + + r[0] = sfloat_mul(&s[0], sa.x.s, sb.x.s); + r[1] = sfloat_mul(&s[1], sa.y.s, sb.y.s); + r[2] = sfloat_mul(&s[2], sa.z.s, sb.z.s); + r[3] = sfloat_add(&s[3], r[0], r[1]); + r[4] = sfloat_add(&s[4], r[3], r[2]); + + sfloat_check(ctx, &s[0], NULL); + sfloat_check(ctx, &s[1], NULL); + sfloat_check(ctx, &s[2], NULL); + sfloat_check(ctx, &s[3], NULL); + sfloat_check(ctx, &s[4], NULL); + +end: + return (a.x * b.x + a.y * b.y + a.z * b.z); +} + +static GMQCC_INLINE vec3_t vec3_mulvf(lex_ctx_t ctx, vec3_t a, qcfloat_t b) { + vec3_t out; + vec3_soft_t sa; + sfloat_cast_t sb; + sfloat_state_t s[3]; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) + goto end; + + sa = vec3_soft_convert(a); + sb.f = b; + sfloat_init(&s[0]); + sfloat_init(&s[1]); + sfloat_init(&s[2]); + + sfloat_mul(&s[0], sa.x.s, sb.s); + sfloat_mul(&s[1], sa.y.s, sb.s); + sfloat_mul(&s[2], sa.z.s, sb.s); + + sfloat_check(ctx, &s[0], "x"); + sfloat_check(ctx, &s[1], "y"); + sfloat_check(ctx, &s[2], "z"); + +end: + out.x = a.x * b; + out.y = a.y * b; + out.z = a.z * b; + return out; +} + +static GMQCC_INLINE bool vec3_cmp(vec3_t a, vec3_t b) { + return a.x == b.x && + a.y == b.y && + a.z == b.z; +} + +static GMQCC_INLINE vec3_t vec3_create(float x, float y, float z) { + vec3_t out; + out.x = x; + out.y = y; + out.z = z; + return out; +} + +static GMQCC_INLINE qcfloat_t vec3_notf(vec3_t a) { + return (!a.x && !a.y && !a.z); +} + +static GMQCC_INLINE bool vec3_pbool(vec3_t a) { + return (a.x || a.y || a.z); +} + +static GMQCC_INLINE vec3_t vec3_cross(lex_ctx_t ctx, vec3_t a, vec3_t b) { + vec3_t out; + vec3_soft_t sa; + vec3_soft_t sb; + sfloat_t r[9]; + sfloat_state_t s[9]; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) + goto end; + + sa = vec3_soft_convert(a); + sb = vec3_soft_convert(b); + + sfloat_init(&s[0]); + sfloat_init(&s[1]); + sfloat_init(&s[2]); + sfloat_init(&s[3]); + sfloat_init(&s[4]); + sfloat_init(&s[5]); + sfloat_init(&s[6]); + sfloat_init(&s[7]); + sfloat_init(&s[8]); + + r[0] = sfloat_mul(&s[0], sa.y.s, sb.z.s); + r[1] = sfloat_mul(&s[1], sa.z.s, sb.y.s); + r[2] = sfloat_mul(&s[2], sa.z.s, sb.x.s); + r[3] = sfloat_mul(&s[3], sa.x.s, sb.z.s); + r[4] = sfloat_mul(&s[4], sa.x.s, sb.y.s); + r[5] = sfloat_mul(&s[5], sa.y.s, sb.x.s); + r[6] = sfloat_sub(&s[6], r[0], r[1]); + r[7] = sfloat_sub(&s[7], r[2], r[3]); + r[8] = sfloat_sub(&s[8], r[4], r[5]); + + sfloat_check(ctx, &s[0], NULL); + sfloat_check(ctx, &s[1], NULL); + sfloat_check(ctx, &s[2], NULL); + sfloat_check(ctx, &s[3], NULL); + sfloat_check(ctx, &s[4], NULL); + sfloat_check(ctx, &s[5], NULL); + sfloat_check(ctx, &s[6], "x"); + sfloat_check(ctx, &s[7], "y"); + sfloat_check(ctx, &s[8], "z"); + +end: + out.x = a.y * b.z - a.z * b.y; + out.y = a.z * b.x - a.x * b.z; + out.z = a.x * b.y - a.y * b.x; + return out; +} + +static lex_ctx_t fold_ctx(fold_t *fold) { + lex_ctx_t ctx; + if (fold->parser->lex) + return parser_ctx(fold->parser); + + memset(&ctx, 0, sizeof(ctx)); + return ctx; +} + +static GMQCC_INLINE bool fold_immediate_true(fold_t *fold, ast_value *v) { + switch (v->expression.vtype) { + case TYPE_FLOAT: + return !!v->constval.vfloat; + case TYPE_INTEGER: + return !!v->constval.vint; + case TYPE_VECTOR: + if (OPTS_FLAG(CORRECT_LOGIC)) + return vec3_pbool(v->constval.vvec); + return !!(v->constval.vvec.x); + case TYPE_STRING: + if (!v->constval.vstring) + return false; + if (OPTS_FLAG(TRUE_EMPTY_STRINGS)) + return true; + return !!v->constval.vstring[0]; + default: + compile_error(fold_ctx(fold), "internal error: fold_immediate_true on invalid type"); + break; + } + return !!v->constval.vfunc; +} + +/* Handy macros to determine if an ast_value can be constant folded. */ +#define fold_can_1(X) \ + (ast_istype(((ast_expression*)(X)), ast_value) && (X)->hasvalue && ((X)->cvq == CV_CONST) && \ + ((ast_expression*)(X))->vtype != TYPE_FUNCTION) + +#define fold_can_2(X, Y) (fold_can_1(X) && fold_can_1(Y)) + +#define fold_immvalue_float(E) ((E)->constval.vfloat) +#define fold_immvalue_vector(E) ((E)->constval.vvec) +#define fold_immvalue_string(E) ((E)->constval.vstring) + +fold_t *fold_init(parser_t *parser) { + fold_t *fold = (fold_t*)mem_a(sizeof(fold_t)); + fold->parser = parser; + fold->imm_float = NULL; + fold->imm_vector = NULL; + fold->imm_string = NULL; + fold->imm_string_untranslate = util_htnew(FOLD_STRING_UNTRANSLATE_HTSIZE); + fold->imm_string_dotranslate = util_htnew(FOLD_STRING_DOTRANSLATE_HTSIZE); + + /* + * prime the tables with common constant values at constant + * locations. + */ + (void)fold_constgen_float (fold, 0.0f, false); + (void)fold_constgen_float (fold, 1.0f, false); + (void)fold_constgen_float (fold, -1.0f, false); + (void)fold_constgen_float (fold, 2.0f, false); + + (void)fold_constgen_vector(fold, vec3_create(0.0f, 0.0f, 0.0f)); + (void)fold_constgen_vector(fold, vec3_create(-1.0f, -1.0f, -1.0f)); + + return fold; +} + +bool fold_generate(fold_t *fold, ir_builder *ir) { + /* generate globals for immediate folded values */ + size_t i; + ast_value *cur; + + for (i = 0; i < vec_size(fold->imm_float); ++i) + if (!ast_global_codegen ((cur = fold->imm_float[i]), ir, false)) goto err; + for (i = 0; i < vec_size(fold->imm_vector); ++i) + if (!ast_global_codegen((cur = fold->imm_vector[i]), ir, false)) goto err; + for (i = 0; i < vec_size(fold->imm_string); ++i) + if (!ast_global_codegen((cur = fold->imm_string[i]), ir, false)) goto err; + + return true; + +err: + con_out("failed to generate global %s\n", cur->name); + ir_builder_delete(ir); + return false; +} + +void fold_cleanup(fold_t *fold) { + size_t i; + + for (i = 0; i < vec_size(fold->imm_float); ++i) ast_delete(fold->imm_float[i]); + for (i = 0; i < vec_size(fold->imm_vector); ++i) ast_delete(fold->imm_vector[i]); + for (i = 0; i < vec_size(fold->imm_string); ++i) ast_delete(fold->imm_string[i]); + + vec_free(fold->imm_float); + vec_free(fold->imm_vector); + vec_free(fold->imm_string); + + util_htdel(fold->imm_string_untranslate); + util_htdel(fold->imm_string_dotranslate); + + mem_d(fold); +} + +ast_expression *fold_constgen_float(fold_t *fold, qcfloat_t value, bool inexact) { + ast_value *out = NULL; + size_t i; + + for (i = 0; i < vec_size(fold->imm_float); i++) { + if (!memcmp(&fold->imm_float[i]->constval.vfloat, &value, sizeof(qcfloat_t))) + return (ast_expression*)fold->imm_float[i]; + } + + out = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_FLOAT); + out->cvq = CV_CONST; + out->hasvalue = true; + out->inexact = inexact; + out->constval.vfloat = value; + + vec_push(fold->imm_float, out); + + return (ast_expression*)out; +} + +ast_expression *fold_constgen_vector(fold_t *fold, vec3_t value) { + ast_value *out; + size_t i; + + for (i = 0; i < vec_size(fold->imm_vector); i++) { + if (vec3_cmp(fold->imm_vector[i]->constval.vvec, value)) + return (ast_expression*)fold->imm_vector[i]; + } + + out = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_VECTOR); + out->cvq = CV_CONST; + out->hasvalue = true; + out->constval.vvec = value; + + vec_push(fold->imm_vector, out); + + return (ast_expression*)out; +} + +ast_expression *fold_constgen_string(fold_t *fold, const char *str, bool translate) { + hash_table_t *table = (translate) ? fold->imm_string_untranslate : fold->imm_string_dotranslate; + ast_value *out = NULL; + size_t hash = util_hthash(table, str); + + if ((out = (ast_value*)util_htgeth(table, str, hash))) + return (ast_expression*)out; + + if (translate) { + char name[32]; + util_snprintf(name, sizeof(name), "dotranslate_%lu", (unsigned long)(fold->parser->translated++)); + out = ast_value_new(parser_ctx(fold->parser), name, TYPE_STRING); + out->expression.flags |= AST_FLAG_INCLUDE_DEF; /* def needs to be included for translatables */ + } else + out = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_STRING); + + out->cvq = CV_CONST; + out->hasvalue = true; + out->isimm = true; + out->constval.vstring = parser_strdup(str); + + vec_push(fold->imm_string, out); + util_htseth(table, str, hash, out); + + return (ast_expression*)out; +} + +typedef union { + void (*callback)(void); + sfloat_t (*binary)(sfloat_state_t *, sfloat_t, sfloat_t); + sfloat_t (*unary)(sfloat_state_t *, sfloat_t); +} float_check_callback_t; + +static bool fold_check_except_float_impl(void (*callback)(void), + fold_t *fold, + ast_value *a, + ast_value *b) +{ + float_check_callback_t call; + sfloat_state_t s; + sfloat_cast_t ca; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS) && !OPTS_WARN(WARN_INEXACT_COMPARES)) + return false; + + call.callback = callback; + sfloat_init(&s); + ca.f = fold_immvalue_float(a); + if (b) { + sfloat_cast_t cb; + cb.f = fold_immvalue_float(b); + call.binary(&s, ca.s, cb.s); + } else { + call.unary(&s, ca.s); + } + + if (s.exceptionflags == 0) + return false; + + if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS)) + goto inexact_possible; + + sfloat_check(fold_ctx(fold), &s, NULL); + +inexact_possible: + return s.exceptionflags & SFLOAT_INEXACT; +} + +#define fold_check_except_float(CALLBACK, FOLD, A, B) \ + fold_check_except_float_impl(((void (*)(void))(CALLBACK)), (FOLD), (A), (B)) + +static bool fold_check_inexact_float(fold_t *fold, ast_value *a, ast_value *b) { + lex_ctx_t ctx = fold_ctx(fold); + if (!OPTS_WARN(WARN_INEXACT_COMPARES)) + return false; + if (!a->inexact && !b->inexact) + return false; + return compile_warning(ctx, WARN_INEXACT_COMPARES, "inexact value in comparison"); +} + +static GMQCC_INLINE ast_expression *fold_op_mul_vec(fold_t *fold, vec3_t vec, ast_value *sel, const char *set) { + qcfloat_t x = (&vec.x)[set[0]-'x']; + qcfloat_t y = (&vec.x)[set[1]-'x']; + qcfloat_t z = (&vec.x)[set[2]-'x']; + if (!y && !z) { + ast_expression *out; + ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS]; + out = (ast_expression*)ast_member_new(fold_ctx(fold), (ast_expression*)sel, set[0]-'x', NULL); + out->node.keep = false; + ((ast_member*)out)->rvalue = true; + if (x != -1.0f) + return (ast_expression*)ast_binary_new(fold_ctx(fold), INSTR_MUL_F, fold_constgen_float(fold, x, false), out); + } + return NULL; +} + + +static GMQCC_INLINE ast_expression *fold_op_neg(fold_t *fold, ast_value *a) { + if (isfloat(a)) { + if (fold_can_1(a)) { + /* Negation can produce inexact as well */ + bool inexact = fold_check_except_float(&sfloat_neg, fold, a, NULL); + return fold_constgen_float(fold, -fold_immvalue_float(a), inexact); + } + } else if (isvector(a)) { + if (fold_can_1(a)) + return fold_constgen_vector(fold, vec3_neg(fold_ctx(fold), fold_immvalue_vector(a))); + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_not(fold_t *fold, ast_value *a) { + if (isfloat(a)) { + if (fold_can_1(a)) + return fold_constgen_float(fold, !fold_immvalue_float(a), false); + } else if (isvector(a)) { + if (fold_can_1(a)) + return fold_constgen_float(fold, vec3_notf(fold_immvalue_vector(a)), false); + } else if (isstring(a)) { + if (fold_can_1(a)) { + if (OPTS_FLAG(TRUE_EMPTY_STRINGS)) + return fold_constgen_float(fold, !fold_immvalue_string(a), false); + else + return fold_constgen_float(fold, !fold_immvalue_string(a) || !*fold_immvalue_string(a), false); + } + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_add(fold_t *fold, ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (fold_can_2(a, b)) { + bool inexact = fold_check_except_float(&sfloat_add, fold, a, b); + return fold_constgen_float(fold, fold_immvalue_float(a) + fold_immvalue_float(b), inexact); + } + } else if (isvector(a)) { + if (fold_can_2(a, b)) + return fold_constgen_vector(fold, vec3_add(fold_ctx(fold), + fold_immvalue_vector(a), + fold_immvalue_vector(b))); + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_sub(fold_t *fold, ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (fold_can_2(a, b)) { + bool inexact = fold_check_except_float(&sfloat_sub, fold, a, b); + return fold_constgen_float(fold, fold_immvalue_float(a) - fold_immvalue_float(b), inexact); + } + } else if (isvector(a)) { + if (fold_can_2(a, b)) + return fold_constgen_vector(fold, vec3_sub(fold_ctx(fold), + fold_immvalue_vector(a), + fold_immvalue_vector(b))); + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_mul(fold_t *fold, ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (isvector(b)) { + if (fold_can_2(a, b)) + return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(b), fold_immvalue_float(a))); + } else { + if (fold_can_2(a, b)) { + bool inexact = fold_check_except_float(&sfloat_mul, fold, a, b); + return fold_constgen_float(fold, fold_immvalue_float(a) * fold_immvalue_float(b), inexact); + } + } + } else if (isvector(a)) { + if (isfloat(b)) { + if (fold_can_2(a, b)) + return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(a), fold_immvalue_float(b))); + } else { + if (fold_can_2(a, b)) { + return fold_constgen_float(fold, vec3_mulvv(fold_ctx(fold), fold_immvalue_vector(a), fold_immvalue_vector(b)), false); + } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(a)) { + ast_expression *out; + if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "xyz"))) return out; + if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "yxz"))) return out; + if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "zxy"))) return out; + } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(b)) { + ast_expression *out; + if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "xyz"))) return out; + if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "yxz"))) return out; + if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "zxy"))) return out; + } + } + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_div(fold_t *fold, ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (fold_can_2(a, b)) { + bool inexact = fold_check_except_float(&sfloat_div, fold, a, b); + return fold_constgen_float(fold, fold_immvalue_float(a) / fold_immvalue_float(b), inexact); + } else if (fold_can_1(b)) { + return (ast_expression*)ast_binary_new( + fold_ctx(fold), + INSTR_MUL_F, + (ast_expression*)a, + fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false) + ); + } + } else if (isvector(a)) { + if (fold_can_2(a, b)) { + return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(a), 1.0f / fold_immvalue_float(b))); + } else { + return (ast_expression*)ast_binary_new( + fold_ctx(fold), + INSTR_MUL_VF, + (ast_expression*)a, + (fold_can_1(b)) + ? (ast_expression*)fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false) + : (ast_expression*)ast_binary_new( + fold_ctx(fold), + INSTR_DIV_F, + (ast_expression*)fold->imm_float[1], + (ast_expression*)b + ) + ); + } + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_mod(fold_t *fold, ast_value *a, ast_value *b) { + return (fold_can_2(a, b)) + ? fold_constgen_float(fold, fmod(fold_immvalue_float(a), fold_immvalue_float(b)), false) + : NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_bor(fold_t *fold, ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (fold_can_2(a, b)) + return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) | ((qcint_t)fold_immvalue_float(b))), false); + } else { + if (isvector(b)) { + if (fold_can_2(a, b)) + return fold_constgen_vector(fold, vec3_or(fold_immvalue_vector(a), fold_immvalue_vector(b))); + } else { + if (fold_can_2(a, b)) + return fold_constgen_vector(fold, vec3_orvf(fold_immvalue_vector(a), fold_immvalue_float(b))); + } + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_band(fold_t *fold, ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (fold_can_2(a, b)) + return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) & ((qcint_t)fold_immvalue_float(b))), false); + } else { + if (isvector(b)) { + if (fold_can_2(a, b)) + return fold_constgen_vector(fold, vec3_and(fold_immvalue_vector(a), fold_immvalue_vector(b))); + } else { + if (fold_can_2(a, b)) + return fold_constgen_vector(fold, vec3_andvf(fold_immvalue_vector(a), fold_immvalue_float(b))); + } + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_xor(fold_t *fold, ast_value *a, ast_value *b) { + if (isfloat(a)) { + if (fold_can_2(a, b)) + return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) ^ ((qcint_t)fold_immvalue_float(b))), false); + } else { + if (fold_can_2(a, b)) { + if (isvector(b)) + return fold_constgen_vector(fold, vec3_xor(fold_immvalue_vector(a), fold_immvalue_vector(b))); + else + return fold_constgen_vector(fold, vec3_xorvf(fold_immvalue_vector(a), fold_immvalue_float(b))); + } + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_lshift(fold_t *fold, ast_value *a, ast_value *b) { + if (fold_can_2(a, b) && isfloats(a, b)) + return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) * powf(2.0f, fold_immvalue_float(b))), false); + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_rshift(fold_t *fold, ast_value *a, ast_value *b) { + if (fold_can_2(a, b) && isfloats(a, b)) + return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) / powf(2.0f, fold_immvalue_float(b))), false); + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_andor(fold_t *fold, ast_value *a, ast_value *b, float expr) { + if (fold_can_2(a, b)) { + if (OPTS_FLAG(PERL_LOGIC)) { + if (expr) + return (fold_immediate_true(fold, a)) ? (ast_expression*)a : (ast_expression*)b; + else + return (fold_immediate_true(fold, a)) ? (ast_expression*)b : (ast_expression*)a; + } else { + return fold_constgen_float ( + fold, + ((expr) ? (fold_immediate_true(fold, a) || fold_immediate_true(fold, b)) + : (fold_immediate_true(fold, a) && fold_immediate_true(fold, b))) + ? 1 + : 0, + false + ); + } + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_tern(fold_t *fold, ast_value *a, ast_value *b, ast_value *c) { + if (fold_can_1(a)) { + return fold_immediate_true(fold, a) + ? (ast_expression*)b + : (ast_expression*)c; + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_exp(fold_t *fold, ast_value *a, ast_value *b) { + if (fold_can_2(a, b)) + return fold_constgen_float(fold, (qcfloat_t)powf(fold_immvalue_float(a), fold_immvalue_float(b)), false); + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_lteqgt(fold_t *fold, ast_value *a, ast_value *b) { + if (fold_can_2(a,b)) { + fold_check_inexact_float(fold, a, b); + if (fold_immvalue_float(a) < fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[2]; + if (fold_immvalue_float(a) == fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[0]; + if (fold_immvalue_float(a) > fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[1]; + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_ltgt(fold_t *fold, ast_value *a, ast_value *b, bool lt) { + if (fold_can_2(a, b)) { + fold_check_inexact_float(fold, a, b); + return (lt) ? (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) < fold_immvalue_float(b))] + : (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) > fold_immvalue_float(b))]; + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_cmp(fold_t *fold, ast_value *a, ast_value *b, bool ne) { + if (fold_can_2(a, b)) { + if (isfloat(a) && isfloat(b)) { + float la = fold_immvalue_float(a); + float lb = fold_immvalue_float(b); + fold_check_inexact_float(fold, a, b); + return (ast_expression*)fold->imm_float[!(ne ? la == lb : la != lb)]; + } if (isvector(a) && isvector(b)) { + vec3_t la = fold_immvalue_vector(a); + vec3_t lb = fold_immvalue_vector(b); + return (ast_expression*)fold->imm_float[!(ne ? vec3_cmp(la, lb) : !vec3_cmp(la, lb))]; + } + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_bnot(fold_t *fold, ast_value *a) { + if (isfloat(a)) { + if (fold_can_1(a)) + return fold_constgen_float(fold, -1-fold_immvalue_float(a), false); + } else { + if (isvector(a)) { + if (fold_can_1(a)) + return fold_constgen_vector(fold, vec3_not(fold_immvalue_vector(a))); + } + } + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_cross(fold_t *fold, ast_value *a, ast_value *b) { + if (fold_can_2(a, b)) + return fold_constgen_vector(fold, vec3_cross(fold_ctx(fold), + fold_immvalue_vector(a), + fold_immvalue_vector(b))); + return NULL; +} + +static GMQCC_INLINE ast_expression *fold_op_length(fold_t *fold, ast_value *a) { + if (fold_can_1(a) && isstring(a)) + return fold_constgen_float(fold, strlen(fold_immvalue_string(a)), false); + if (isarray(a)) + return fold_constgen_float(fold, vec_size(a->initlist), false); + return NULL; +} + +ast_expression *fold_op(fold_t *fold, const oper_info *info, ast_expression **opexprs) { + ast_value *a = (ast_value*)opexprs[0]; + ast_value *b = (ast_value*)opexprs[1]; + ast_value *c = (ast_value*)opexprs[2]; + ast_expression *e = NULL; + + /* can a fold operation be applied to this operator usage? */ + if (!info->folds) + return NULL; + + switch(info->operands) { + case 3: if(!c) return NULL; + case 2: if(!b) return NULL; + case 1: + if(!a) { + compile_error(fold_ctx(fold), "internal error: fold_op no operands to fold\n"); + return NULL; + } + } + + /* + * we could use a boolean and default case but ironically gcc produces + * invalid broken assembly from that operation. clang/tcc get it right, + * but interestingly ignore compiling this to a jump-table when I do that, + * this happens to be the most efficent method, since you have per-level + * granularity on the pointer check happening only for the case you check + * it in. Opposed to the default method which would involve a boolean and + * pointer check after wards. + */ + #define fold_op_case(ARGS, ARGS_OPID, OP, ARGS_FOLD) \ + case opid##ARGS ARGS_OPID: \ + if ((e = fold_op_##OP ARGS_FOLD)) { \ + ++opts_optimizationcount[OPTIM_CONST_FOLD]; \ + } \ + return e + + switch(info->id) { + fold_op_case(2, ('-', 'P'), neg, (fold, a)); + fold_op_case(2, ('!', 'P'), not, (fold, a)); + fold_op_case(1, ('+'), add, (fold, a, b)); + fold_op_case(1, ('-'), sub, (fold, a, b)); + fold_op_case(1, ('*'), mul, (fold, a, b)); + fold_op_case(1, ('/'), div, (fold, a, b)); + fold_op_case(1, ('%'), mod, (fold, a, b)); + fold_op_case(1, ('|'), bor, (fold, a, b)); + fold_op_case(1, ('&'), band, (fold, a, b)); + fold_op_case(1, ('^'), xor, (fold, a, b)); + fold_op_case(1, ('<'), ltgt, (fold, a, b, true)); + fold_op_case(1, ('>'), ltgt, (fold, a, b, false)); + fold_op_case(2, ('<', '<'), lshift, (fold, a, b)); + fold_op_case(2, ('>', '>'), rshift, (fold, a, b)); + fold_op_case(2, ('|', '|'), andor, (fold, a, b, true)); + fold_op_case(2, ('&', '&'), andor, (fold, a, b, false)); + fold_op_case(2, ('?', ':'), tern, (fold, a, b, c)); + fold_op_case(2, ('*', '*'), exp, (fold, a, b)); + fold_op_case(3, ('<','=','>'), lteqgt, (fold, a, b)); + fold_op_case(2, ('!', '='), cmp, (fold, a, b, true)); + fold_op_case(2, ('=', '='), cmp, (fold, a, b, false)); + fold_op_case(2, ('~', 'P'), bnot, (fold, a)); + fold_op_case(2, ('>', '<'), cross, (fold, a, b)); + fold_op_case(3, ('l', 'e', 'n'), length, (fold, a)); + } + #undef fold_op_case + compile_error(fold_ctx(fold), "internal error: attempted to constant-fold for unsupported operator"); + return NULL; +} + +/* + * Constant folding for compiler intrinsics, similar approach to operator + * folding, primarily: individual functions for each intrinsics to fold, + * and a generic selection function. + */ +static GMQCC_INLINE ast_expression *fold_intrin_isfinite(fold_t *fold, ast_value *a) { + return fold_constgen_float(fold, isfinite(fold_immvalue_float(a)), false); +} +static GMQCC_INLINE ast_expression *fold_intrin_isinf(fold_t *fold, ast_value *a) { + return fold_constgen_float(fold, isinf(fold_immvalue_float(a)), false); +} +static GMQCC_INLINE ast_expression *fold_intrin_isnan(fold_t *fold, ast_value *a) { + return fold_constgen_float(fold, isnan(fold_immvalue_float(a)), false); +} +static GMQCC_INLINE ast_expression *fold_intrin_isnormal(fold_t *fold, ast_value *a) { + return fold_constgen_float(fold, isnormal(fold_immvalue_float(a)), false); +} +static GMQCC_INLINE ast_expression *fold_intrin_signbit(fold_t *fold, ast_value *a) { + return fold_constgen_float(fold, signbit(fold_immvalue_float(a)), false); +} +static GMQCC_INLINE ast_expression *fold_intirn_acosh(fold_t *fold, ast_value *a) { + return fold_constgen_float(fold, acoshf(fold_immvalue_float(a)), false); +} +static GMQCC_INLINE ast_expression *fold_intrin_asinh(fold_t *fold, ast_value *a) { + return fold_constgen_float(fold, asinhf(fold_immvalue_float(a)), false); +} +static GMQCC_INLINE ast_expression *fold_intrin_atanh(fold_t *fold, ast_value *a) { + return fold_constgen_float(fold, (float)atanh(fold_immvalue_float(a)), false); +} +static GMQCC_INLINE ast_expression *fold_intrin_exp(fold_t *fold, ast_value *a) { + return fold_constgen_float(fold, expf(fold_immvalue_float(a)), false); +} +static GMQCC_INLINE ast_expression *fold_intrin_exp2(fold_t *fold, ast_value *a) { + return fold_constgen_float(fold, exp2f(fold_immvalue_float(a)), false); +} +static GMQCC_INLINE ast_expression *fold_intrin_expm1(fold_t *fold, ast_value *a) { + return fold_constgen_float(fold, expm1f(fold_immvalue_float(a)), false); +} +static GMQCC_INLINE ast_expression *fold_intrin_mod(fold_t *fold, ast_value *lhs, ast_value *rhs) { + return fold_constgen_float(fold, fmodf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false); +} +static GMQCC_INLINE ast_expression *fold_intrin_pow(fold_t *fold, ast_value *lhs, ast_value *rhs) { + return fold_constgen_float(fold, powf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false); +} +static GMQCC_INLINE ast_expression *fold_intrin_fabs(fold_t *fold, ast_value *a) { + return fold_constgen_float(fold, fabsf(fold_immvalue_float(a)), false); +} + + +ast_expression *fold_intrin(fold_t *fold, const char *intrin, ast_expression **arg) { + ast_expression *ret = NULL; + ast_value *a = (ast_value*)arg[0]; + ast_value *b = (ast_value*)arg[1]; + + if (!strcmp(intrin, "isfinite")) ret = fold_intrin_isfinite(fold, a); + if (!strcmp(intrin, "isinf")) ret = fold_intrin_isinf(fold, a); + if (!strcmp(intrin, "isnan")) ret = fold_intrin_isnan(fold, a); + if (!strcmp(intrin, "isnormal")) ret = fold_intrin_isnormal(fold, a); + if (!strcmp(intrin, "signbit")) ret = fold_intrin_signbit(fold, a); + if (!strcmp(intrin, "acosh")) ret = fold_intirn_acosh(fold, a); + if (!strcmp(intrin, "asinh")) ret = fold_intrin_asinh(fold, a); + if (!strcmp(intrin, "atanh")) ret = fold_intrin_atanh(fold, a); + if (!strcmp(intrin, "exp")) ret = fold_intrin_exp(fold, a); + if (!strcmp(intrin, "exp2")) ret = fold_intrin_exp2(fold, a); + if (!strcmp(intrin, "expm1")) ret = fold_intrin_expm1(fold, a); + if (!strcmp(intrin, "mod")) ret = fold_intrin_mod(fold, a, b); + if (!strcmp(intrin, "pow")) ret = fold_intrin_pow(fold, a, b); + if (!strcmp(intrin, "fabs")) ret = fold_intrin_fabs(fold, a); + + if (ret) + ++opts_optimizationcount[OPTIM_CONST_FOLD]; + + return ret; +} + +/* + * These are all the actual constant folding methods that happen in between + * the AST/IR stage of the compiler , i.e eliminating branches for const + * expressions, which is the only supported thing so far. We undefine the + * testing macros here because an ir_value is differant than an ast_value. + */ +#undef expect +#undef isfloat +#undef isstring +#undef isvector +#undef fold_immvalue_float +#undef fold_immvalue_string +#undef fold_immvalue_vector +#undef fold_can_1 +#undef fold_can_2 + +#define isfloat(X) ((X)->vtype == TYPE_FLOAT) +/*#define isstring(X) ((X)->vtype == TYPE_STRING)*/ +/*#define isvector(X) ((X)->vtype == TYPE_VECTOR)*/ +#define fold_immvalue_float(X) ((X)->constval.vfloat) +#define fold_immvalue_vector(X) ((X)->constval.vvec) +/*#define fold_immvalue_string(X) ((X)->constval.vstring)*/ +#define fold_can_1(X) ((X)->hasvalue && (X)->cvq == CV_CONST) +/*#define fold_can_2(X,Y) (fold_can_1(X) && fold_can_1(Y))*/ + +static ast_expression *fold_superfluous(ast_expression *left, ast_expression *right, int op) { + ast_expression *swapped = NULL; /* using this as bool */ + ast_value *load; + + if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right))) { + swapped = left; + left = right; + right = swapped; + } + + if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right))) + return NULL; + + switch (op) { + case INSTR_DIV_F: + if (swapped) + return NULL; + case INSTR_MUL_F: + if (fold_immvalue_float(load) == 1.0f) { + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + ast_unref(right); + return left; + } + break; + + + case INSTR_SUB_F: + if (swapped) + return NULL; + case INSTR_ADD_F: + if (fold_immvalue_float(load) == 0.0f) { + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + ast_unref(right); + return left; + } + break; + + case INSTR_MUL_V: + if (vec3_cmp(fold_immvalue_vector(load), vec3_create(1, 1, 1))) { + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + ast_unref(right); + return left; + } + break; + + case INSTR_SUB_V: + if (swapped) + return NULL; + case INSTR_ADD_V: + if (vec3_cmp(fold_immvalue_vector(load), vec3_create(0, 0, 0))) { + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + ast_unref(right); + return left; + } + break; + } + + return NULL; +} + +ast_expression *fold_binary(lex_ctx_t ctx, int op, ast_expression *left, ast_expression *right) { + ast_expression *ret = fold_superfluous(left, right, op); + if (ret) + return ret; + return (ast_expression*)ast_binary_new(ctx, op, left, right); +} + +static GMQCC_INLINE int fold_cond(ir_value *condval, ast_function *func, ast_ifthen *branch) { + if (isfloat(condval) && fold_can_1(condval) && OPTS_OPTIMIZATION(OPTIM_CONST_FOLD_DCE)) { + ast_expression_codegen *cgen; + ir_block *elide; + ir_value *dummy; + bool istrue = (fold_immvalue_float(condval) != 0.0f && branch->on_true); + bool isfalse = (fold_immvalue_float(condval) == 0.0f && branch->on_false); + ast_expression *path = (istrue) ? branch->on_true : + (isfalse) ? branch->on_false : NULL; + if (!path) { + /* + * no path to take implies that the evaluation is if(0) and there + * is no else block. so eliminate all the code. + */ + ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE]; + return true; + } + + if (!(elide = ir_function_create_block(ast_ctx(branch), func->ir_func, ast_function_label(func, ((istrue) ? "ontrue" : "onfalse"))))) + return false; + if (!(*(cgen = path->codegen))((ast_expression*)path, func, false, &dummy)) + return false; + if (!ir_block_create_jump(func->curblock, ast_ctx(branch), elide)) + return false; + /* + * now the branch has been eliminated and the correct block for the constant evaluation + * is expanded into the current block for the function. + */ + func->curblock = elide; + ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE]; + return true; + } + return -1; /* nothing done */ +} + +int fold_cond_ternary(ir_value *condval, ast_function *func, ast_ternary *branch) { + return fold_cond(condval, func, (ast_ifthen*)branch); +} + +int fold_cond_ifthen(ir_value *condval, ast_function *func, ast_ifthen *branch) { + return fold_cond(condval, func, branch); +} diff --git a/ftepp.c b/ftepp.c deleted file mode 100644 index 1c80d9e..0000000 --- a/ftepp.c +++ /dev/null @@ -1,1954 +0,0 @@ -#include -#include -#include - -#include "gmqcc.h" -#include "lexer.h" - -#define HT_MACROS 1024 - -typedef struct { - bool on; - bool was_on; - bool had_else; -} ppcondition; - -typedef struct { - int token; - char *value; - /* a copy from the lexer */ - union { - vec3_t v; - int i; - double f; - int t; /* type */ - } constval; -} pptoken; - -typedef struct { - lex_ctx_t ctx; - - char *name; - char **params; - /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */ - bool has_params; - bool variadic; - - pptoken **output; -} ppmacro; - -typedef struct ftepp_s { - lex_file *lex; - int token; - unsigned int errors; - - bool output_on; - ppcondition *conditions; - /*ppmacro **macros;*/ - ht macros; /* hashtable */ - char *output_string; - - char *itemname; - char *includename; - bool in_macro; - - uint32_t predef_countval; - uint32_t predef_randval; -} ftepp_t; - -/* __DATE__ */ -static char *ftepp_predef_date(ftepp_t *context) { - const struct tm *itime = NULL; - char *value = (char*)mem_a(82); - time_t rtime; - - (void)context; - - time (&rtime); - itime = util_localtime(&rtime); - strftime(value, 82, "\"%b %d %Y\"", itime); - - return value; -} - -/* __TIME__ */ -static char *ftepp_predef_time(ftepp_t *context) { - const struct tm *itime = NULL; - char *value = (char*)mem_a(82); - time_t rtime; - - (void)context; - - time (&rtime); - itime = util_localtime(&rtime); - strftime(value, 82, "\"%X\"", itime); - - return value; -} - -/* __LINE__ */ -static char *ftepp_predef_line(ftepp_t *context) { - char *value; - - util_asprintf(&value, "%d", (int)context->lex->line); - return value; -} -/* __FILE__ */ -static char *ftepp_predef_file(ftepp_t *context) { - size_t length = strlen(context->lex->name) + 3; /* two quotes and a terminator */ - char *value = (char*)mem_a(length); - - util_snprintf(value, length, "\"%s\"", context->lex->name); - return value; -} -/* __COUNTER_LAST__ */ -static char *ftepp_predef_counterlast(ftepp_t *context) { - char *value; - util_asprintf(&value, "%u", context->predef_countval); - return value; -} -/* __COUNTER__ */ -static char *ftepp_predef_counter(ftepp_t *context) { - char *value; - - context->predef_countval ++; - util_asprintf(&value, "%u", context->predef_countval); - - return value; -} -/* __RANDOM__ */ -static char *ftepp_predef_random(ftepp_t *context) { - char *value; - - context->predef_randval = (util_rand() % 0xFF) + 1; - util_asprintf(&value, "%u", context->predef_randval); - return value; -} -/* __RANDOM_LAST__ */ -static char *ftepp_predef_randomlast(ftepp_t *context) { - char *value; - - util_asprintf(&value, "%u", context->predef_randval); - return value; -} -/* __TIMESTAMP__ */ -static char *ftepp_predef_timestamp(ftepp_t *context) { - struct stat finfo; - const char *find; - char *value; - size_t size; - - if (stat(context->lex->name, &finfo)) - return util_strdup("\"\""); - - find = util_ctime(&finfo.st_mtime); - value = (char*)mem_a(strlen(find) + 1); - memcpy(&value[1], find, (size = strlen(find)) - 1); - - value[0] = '"'; - value[size] = '"'; - - return value; -} - -typedef struct { - const char *name; - char *(*func)(ftepp_t *); -} ftepp_predef_t; - -static const ftepp_predef_t ftepp_predefs[] = { - { "__LINE__", &ftepp_predef_line }, - { "__FILE__", &ftepp_predef_file }, - { "__COUNTER__", &ftepp_predef_counter }, - { "__COUNTER_LAST__", &ftepp_predef_counterlast }, - { "__RANDOM__", &ftepp_predef_random }, - { "__RANDOM_LAST__", &ftepp_predef_randomlast }, - { "__DATE__", &ftepp_predef_date }, - { "__TIME__", &ftepp_predef_time }, - { "__TIME_STAMP__", &ftepp_predef_timestamp } -}; - -static GMQCC_INLINE size_t ftepp_predef_index(const char *name) { - /* no hashtable here, we simply check for one to exist the naive way */ - size_t i; - for(i = 1; i < GMQCC_ARRAY_COUNT(ftepp_predefs) + 1; i++) - if (!strcmp(ftepp_predefs[i-1].name, name)) - return i; - return 0; -} - -bool ftepp_predef_exists(const char *name); -bool ftepp_predef_exists(const char *name) { - return ftepp_predef_index(name) != 0; -} - -/* singleton because we're allowed */ -static GMQCC_INLINE char *(*ftepp_predef(const char *name))(ftepp_t *context) { - size_t i = ftepp_predef_index(name); - return (i != 0) ? ftepp_predefs[i-1].func : NULL; -} - -#define ftepp_tokval(f) ((f)->lex->tok.value) -#define ftepp_ctx(f) ((f)->lex->tok.ctx) - -static void ftepp_errorat(ftepp_t *ftepp, lex_ctx_t ctx, const char *fmt, ...) -{ - va_list ap; - - ftepp->errors++; - - va_start(ap, fmt); - con_cvprintmsg(ctx, LVL_ERROR, "error", fmt, ap); - va_end(ap); -} - -static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...) -{ - va_list ap; - - ftepp->errors++; - - va_start(ap, fmt); - con_cvprintmsg(ftepp->lex->tok.ctx, LVL_ERROR, "error", fmt, ap); - va_end(ap); -} - -static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...) -{ - bool r; - va_list ap; - - va_start(ap, fmt); - r = vcompile_warning(ftepp->lex->tok.ctx, warntype, fmt, ap); - va_end(ap); - return r; -} - -static pptoken *pptoken_make(ftepp_t *ftepp) -{ - pptoken *token = (pptoken*)mem_a(sizeof(pptoken)); - token->token = ftepp->token; - token->value = util_strdup(ftepp_tokval(ftepp)); - memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval)); - return token; -} - -static GMQCC_INLINE void pptoken_delete(pptoken *self) -{ - mem_d(self->value); - mem_d(self); -} - -static ppmacro *ppmacro_new(lex_ctx_t ctx, const char *name) -{ - ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro)); - - (void)ctx; - memset(macro, 0, sizeof(*macro)); - macro->name = util_strdup(name); - return macro; -} - -static void ppmacro_delete(ppmacro *self) -{ - size_t i; - for (i = 0; i < vec_size(self->params); ++i) - mem_d(self->params[i]); - vec_free(self->params); - for (i = 0; i < vec_size(self->output); ++i) - pptoken_delete(self->output[i]); - vec_free(self->output); - mem_d(self->name); - mem_d(self); -} - -static ftepp_t* ftepp_new(void) -{ - ftepp_t *ftepp; - - ftepp = (ftepp_t*)mem_a(sizeof(*ftepp)); - memset(ftepp, 0, sizeof(*ftepp)); - - ftepp->macros = util_htnew(HT_MACROS); - ftepp->output_on = true; - ftepp->predef_countval = 0; - ftepp->predef_randval = 0; - - return ftepp; -} - -static GMQCC_INLINE void ftepp_flush_do(ftepp_t *self) -{ - vec_free(self->output_string); -} - -static void ftepp_delete(ftepp_t *self) -{ - ftepp_flush_do(self); - if (self->itemname) - mem_d(self->itemname); - if (self->includename) - vec_free(self->includename); - - util_htrem(self->macros, (void (*)(void*))&ppmacro_delete); - - vec_free(self->conditions); - if (self->lex) - lex_close(self->lex); - mem_d(self); -} - -static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond) -{ - if (ignore_cond || ftepp->output_on) - { - size_t len; - char *data; - len = strlen(str); - data = vec_add(ftepp->output_string, len); - memcpy(data, str, len); - } -} - -static GMQCC_INLINE void ftepp_update_output_condition(ftepp_t *ftepp) -{ - size_t i; - ftepp->output_on = true; - for (i = 0; i < vec_size(ftepp->conditions); ++i) - ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on; -} - -static GMQCC_INLINE ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name) -{ - return (ppmacro*)util_htget(ftepp->macros, name); -} - -static GMQCC_INLINE void ftepp_macro_delete(ftepp_t *ftepp, const char *name) -{ - util_htrm(ftepp->macros, name, (void (*)(void*))&ppmacro_delete); -} - -static GMQCC_INLINE int ftepp_next(ftepp_t *ftepp) -{ - return (ftepp->token = lex_do(ftepp->lex)); -} - -/* Important: this does not skip newlines! */ -static bool ftepp_skipspace(ftepp_t *ftepp) -{ - if (ftepp->token != TOKEN_WHITE) - return true; - while (ftepp_next(ftepp) == TOKEN_WHITE) {} - if (ftepp->token >= TOKEN_EOF) { - ftepp_error(ftepp, "unexpected end of preprocessor directive"); - return false; - } - return true; -} - -/* this one skips EOLs as well */ -static bool ftepp_skipallwhite(ftepp_t *ftepp) -{ - if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL) - return true; - do { - ftepp_next(ftepp); - } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL); - if (ftepp->token >= TOKEN_EOF) { - ftepp_error(ftepp, "unexpected end of preprocessor directive"); - return false; - } - return true; -} - -/** - * The huge macro parsing code... - */ -static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro) -{ - do { - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token == ')') - break; - switch (ftepp->token) { - case TOKEN_IDENT: - case TOKEN_TYPENAME: - case TOKEN_KEYWORD: - vec_push(macro->params, util_strdup(ftepp_tokval(ftepp))); - break; - case TOKEN_DOTS: - macro->variadic = true; - break; - default: - ftepp_error(ftepp, "unexpected token in parameter list"); - return false; - } - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (macro->variadic && ftepp->token != ')') { - ftepp_error(ftepp, "cannot have parameters after the variadic parameters"); - return false; - } - } while (ftepp->token == ','); - - if (ftepp->token != ')') { - ftepp_error(ftepp, "expected closing paren after macro parameter list"); - return false; - } - ftepp_next(ftepp); - /* skipspace happens in ftepp_define */ - return true; -} - -static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro) -{ - pptoken *ptok; - while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) { - bool subscript = false; - size_t index = 0; - if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_ARGS__")) { - subscript = !!(ftepp_next(ftepp) == '#'); - - if (subscript && ftepp_next(ftepp) != '#') { - ftepp_error(ftepp, "expected `##` in __VA_ARGS__ for subscripting"); - return false; - } else if (subscript) { - if (ftepp_next(ftepp) == '[') { - if (ftepp_next(ftepp) != TOKEN_INTCONST) { - ftepp_error(ftepp, "expected index for __VA_ARGS__ subscript"); - return false; - } - - index = (int)strtol(ftepp_tokval(ftepp), NULL, 10); - - if (ftepp_next(ftepp) != ']') { - ftepp_error(ftepp, "expected `]` in __VA_ARGS__ subscript"); - return false; - } - - /* - * mark it as an array to be handled later as such and not - * as traditional __VA_ARGS__ - */ - ftepp->token = TOKEN_VA_ARGS_ARRAY; - ptok = pptoken_make(ftepp); - ptok->constval.i = index; - vec_push(macro->output, ptok); - ftepp_next(ftepp); - } else { - ftepp_error(ftepp, "expected `[` for subscripting of __VA_ARGS__"); - return false; - } - } else { - int old = ftepp->token; - ftepp->token = TOKEN_VA_ARGS; - ptok = pptoken_make(ftepp); - vec_push(macro->output, ptok); - ftepp->token = old; - } - } - else if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_COUNT__")) { - ftepp->token = TOKEN_VA_COUNT; - ptok = pptoken_make(ftepp); - vec_push(macro->output, ptok); - ftepp_next(ftepp); - } else { - ptok = pptoken_make(ftepp); - vec_push(macro->output, ptok); - ftepp_next(ftepp); - } - } - /* recursive expansion can cause EOFs here */ - if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { - ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file"); - return false; - } - return true; -} - -static const char *ftepp_math_constants[][2] = { - { "M_E", "2.7182818284590452354" }, /* e */ - { "M_LOG2E", "1.4426950408889634074" }, /* log_2 e */ - { "M_LOG10E", "0.43429448190325182765" }, /* log_10 e */ - { "M_LN2", "0.69314718055994530942" }, /* log_e 2 */ - { "M_LN10", "2.30258509299404568402" }, /* log_e 10 */ - { "M_PI", "3.14159265358979323846" }, /* pi */ - { "M_PI_2", "1.57079632679489661923" }, /* pi/2 */ - { "M_PI_4", "0.78539816339744830962" }, /* pi/4 */ - { "M_1_PI", "0.31830988618379067154" }, /* 1/pi */ - { "M_2_PI", "0.63661977236758134308" }, /* 2/pi */ - { "M_2_SQRTPI", "1.12837916709551257390" }, /* 2/sqrt(pi) */ - { "M_SQRT2", "1.41421356237309504880" }, /* sqrt(2) */ - { "M_SQRT1_2", "0.70710678118654752440" }, /* 1/sqrt(2) */ - { "M_TAU", "6.28318530717958647692" } /* pi*2 */ -}; - -static bool ftepp_define(ftepp_t *ftepp) -{ - ppmacro *macro = NULL; - size_t l = ftepp_ctx(ftepp).line; - size_t i; - bool mathconstant = false; - - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - - switch (ftepp->token) { - case TOKEN_IDENT: - case TOKEN_TYPENAME: - case TOKEN_KEYWORD: - if (OPTS_FLAG(FTEPP_MATHDEFS)) { - for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++) { - if (!strcmp(ftepp_math_constants[i][0], ftepp_tokval(ftepp))) { - mathconstant = true; - break; - } - } - } - - macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); - - if (OPTS_FLAG(FTEPP_MATHDEFS)) { - /* user defined ones take precedence */ - if (macro && mathconstant) { - ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); - macro = NULL; - } - } - - if (macro && ftepp->output_on) { - if (ftepp_warn(ftepp, WARN_CPP, "redefining `%s`", ftepp_tokval(ftepp))) - return false; - ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); - } - macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp)); - break; - default: - ftepp_error(ftepp, "expected macro name"); - return false; - } - - (void)ftepp_next(ftepp); - - if (ftepp->token == '(') { - macro->has_params = true; - if (!ftepp_define_params(ftepp, macro)) { - ppmacro_delete(macro); - return false; - } - } - - if (!ftepp_skipspace(ftepp)) { - ppmacro_delete(macro); - return false; - } - - if (!ftepp_define_body(ftepp, macro)) { - ppmacro_delete(macro); - return false; - } - - if (ftepp->output_on) - util_htset(ftepp->macros, macro->name, (void*)macro); - else { - ppmacro_delete(macro); - } - - for (; l < ftepp_ctx(ftepp).line; ++l) - ftepp_out(ftepp, "\n", true); - return true; -} - -/** - * When a macro is used we have to handle parameters as well - * as special-concatenation via ## or stringification via # - * - * Note: parenthesis can nest, so FOO((a),b) is valid, but only - * this kind of parens. Curly braces or [] don't count towards the - * paren-level. - */ -typedef struct { - pptoken **tokens; -} macroparam; - -static void macroparam_clean(macroparam *self) -{ - size_t i; - for (i = 0; i < vec_size(self->tokens); ++i) - pptoken_delete(self->tokens[i]); - vec_free(self->tokens); -} - -/* need to leave the last token up */ -static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params) -{ - macroparam *params = NULL; - pptoken *ptok; - macroparam mp; - size_t parens = 0; - size_t i; - - if (!ftepp_skipallwhite(ftepp)) - return false; - while (ftepp->token != ')') { - mp.tokens = NULL; - if (!ftepp_skipallwhite(ftepp)) - return false; - while (parens || ftepp->token != ',') { - if (ftepp->token == '(') - ++parens; - else if (ftepp->token == ')') { - if (!parens) - break; - --parens; - } - ptok = pptoken_make(ftepp); - vec_push(mp.tokens, ptok); - if (ftepp_next(ftepp) >= TOKEN_EOF) { - ftepp_error(ftepp, "unexpected end of file in macro call"); - goto on_error; - } - } - vec_push(params, mp); - mp.tokens = NULL; - if (ftepp->token == ')') - break; - if (ftepp->token != ',') { - ftepp_error(ftepp, "expected closing paren or comma in macro call"); - goto on_error; - } - if (ftepp_next(ftepp) >= TOKEN_EOF) { - ftepp_error(ftepp, "unexpected end of file in macro call"); - goto on_error; - } - } - *out_params = params; - return true; - -on_error: - if (mp.tokens) - macroparam_clean(&mp); - for (i = 0; i < vec_size(params); ++i) - macroparam_clean(¶ms[i]); - vec_free(params); - return false; -} - -static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx) -{ - size_t i; - for (i = 0; i < vec_size(macro->params); ++i) { - if (!strcmp(macro->params[i], name)) { - *idx = i; - return true; - } - } - return false; -} - -static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token) -{ - char chs[2]; - const char *ch; - chs[1] = 0; - switch (token->token) { - case TOKEN_STRINGCONST: - ch = token->value; - while (*ch) { - /* in preprocessor mode strings already are string, - * so we don't get actual newline bytes here. - * Still need to escape backslashes and quotes. - */ - switch (*ch) { - case '\\': ftepp_out(ftepp, "\\\\", false); break; - case '"': ftepp_out(ftepp, "\\\"", false); break; - default: - chs[0] = *ch; - ftepp_out(ftepp, chs, false); - break; - } - ++ch; - } - break; - /*case TOKEN_WHITE: - ftepp_out(ftepp, " ", false); - break;*/ - case TOKEN_EOL: - ftepp_out(ftepp, "\\n", false); - break; - default: - ftepp_out(ftepp, token->value, false); - break; - } -} - -static void ftepp_stringify(ftepp_t *ftepp, macroparam *param) -{ - size_t i; - ftepp_out(ftepp, "\"", false); - for (i = 0; i < vec_size(param->tokens); ++i) - ftepp_stringify_token(ftepp, param->tokens[i]); - ftepp_out(ftepp, "\"", false); -} - -static void ftepp_recursion_header(ftepp_t *ftepp) -{ - ftepp_out(ftepp, "\n#pragma push(line)\n", false); -} - -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; - pptoken *out; - for (i = 0; i < vec_size(param->tokens); ++i) { - out = param->tokens[i]; - if (out->token == TOKEN_EOL) - ftepp_out(ftepp, "\n", 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); - } - } -} - -static bool ftepp_preprocess(ftepp_t *ftepp); -static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline) -{ - char *buffer = NULL; - char *old_string = ftepp->output_string; - char *inner_string; - lex_file *old_lexer = ftepp->lex; - size_t vararg_start = vec_size(macro->params); - bool retval = true; - bool has_newlines; - size_t varargs; - - size_t o, pi; - lex_file *inlex; - - bool old_inmacro; - bool strip = false; - - int nextok; - - if (vararg_start < vec_size(params)) - varargs = vec_size(params) - vararg_start; - else - varargs = 0; - - /* really ... */ - if (!vec_size(macro->output)) - return true; - - ftepp->output_string = NULL; - for (o = 0; o < vec_size(macro->output); ++o) { - pptoken *out = macro->output[o]; - switch (out->token) { - case TOKEN_VA_ARGS: - if (!macro->variadic) { - ftepp_error(ftepp, "internal preprocessor error: TOKEN_VA_ARGS in non-variadic macro"); - vec_free(old_string); - return false; - } - if (!varargs) - break; - - pi = 0; - ftepp_param_out(ftepp, ¶ms[pi + vararg_start]); - for (++pi; pi < varargs; ++pi) { - ftepp_out(ftepp, ", ", false); - ftepp_param_out(ftepp, ¶ms[pi + vararg_start]); - } - break; - - case TOKEN_VA_ARGS_ARRAY: - if ((size_t)out->constval.i >= varargs) { - ftepp_error(ftepp, "subscript of `[%u]` is out of bounds for `__VA_ARGS__`", out->constval.i); - vec_free(old_string); - return false; - } - - ftepp_param_out(ftepp, ¶ms[out->constval.i + vararg_start]); - break; - - case TOKEN_VA_COUNT: - util_asprintf(&buffer, "%d", varargs); - ftepp_out(ftepp, buffer, false); - mem_d(buffer); - break; - - case TOKEN_IDENT: - case TOKEN_TYPENAME: - case TOKEN_KEYWORD: - if (!macro_params_find(macro, out->value, &pi)) { - ftepp_out(ftepp, out->value, false); - break; - } else - ftepp_param_out(ftepp, ¶ms[pi]); - break; - case '#': - if (o + 1 < vec_size(macro->output)) { - nextok = macro->output[o+1]->token; - if (nextok == '#') { - /* raw concatenation */ - ++o; - strip = true; - break; - } - if ( (nextok == TOKEN_IDENT || - nextok == TOKEN_KEYWORD || - nextok == TOKEN_TYPENAME) && - macro_params_find(macro, macro->output[o+1]->value, &pi)) - { - ++o; - - ftepp_stringify(ftepp, ¶ms[pi]); - break; - } - } - ftepp_out(ftepp, "#", false); - break; - case TOKEN_EOL: - ftepp_out(ftepp, "\n", false); - break; - default: - buffer = out->value; - #define buffer_stripable(X) ((X) == ' ' || (X) == '\t') - if (vec_size(macro->output) > o + 1 && macro->output[o+1]->token == '#' && buffer_stripable(*buffer)) - buffer++; - if (strip) { - while (buffer_stripable(*buffer)) buffer++; - strip = false; - } - ftepp_out(ftepp, buffer, false); - break; - } - } - vec_push(ftepp->output_string, 0); - /* Now run the preprocessor recursively on this string buffer */ - /* - printf("__________\n%s\n=========\n", ftepp->output_string); - */ - inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name); - if (!inlex) { - ftepp_error(ftepp, "internal error: failed to instantiate lexer"); - retval = false; - goto cleanup; - } - - inlex->line = ftepp->lex->line; - inlex->sline = ftepp->lex->sline; - ftepp->lex = inlex; - - old_inmacro = ftepp->in_macro; - ftepp->in_macro = true; - ftepp->output_string = NULL; - if (!ftepp_preprocess(ftepp)) { - ftepp->in_macro = old_inmacro; - vec_free(ftepp->lex->open_string); - vec_free(ftepp->output_string); - lex_close(ftepp->lex); - retval = false; - goto cleanup; - } - ftepp->in_macro = old_inmacro; - vec_free(ftepp->lex->open_string); - lex_close(ftepp->lex); - - inner_string = ftepp->output_string; - ftepp->output_string = old_string; - - has_newlines = (strchr(inner_string, '\n') != NULL); - - if (has_newlines && !old_inmacro) - ftepp_recursion_header(ftepp); - - vec_append(ftepp->output_string, vec_size(inner_string), inner_string); - vec_free(inner_string); - - if (has_newlines && !old_inmacro) - ftepp_recursion_footer(ftepp); - - if (resetline && !ftepp->in_macro) { - char lineno[128]; - util_snprintf(lineno, 128, "\n#pragma line(%lu)\n", (unsigned long)(old_lexer->sline)); - ftepp_out(ftepp, lineno, false); - } - - old_string = ftepp->output_string; -cleanup: - ftepp->lex = old_lexer; - ftepp->output_string = old_string; - return retval; -} - -static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro) -{ - size_t o; - macroparam *params = NULL; - bool retval = true; - size_t paramline; - - if (!macro->has_params) { - if (!ftepp_macro_expand(ftepp, macro, NULL, false)) - return false; - ftepp_next(ftepp); - return true; - } - ftepp_next(ftepp); - - if (!ftepp_skipallwhite(ftepp)) - return false; - - if (ftepp->token != '(') { - ftepp_error(ftepp, "expected macro parameters in parenthesis"); - return false; - } - - ftepp_next(ftepp); - paramline = ftepp->lex->sline; - if (!ftepp_macro_call_params(ftepp, ¶ms)) - return false; - - if ( vec_size(params) < vec_size(macro->params) || - (vec_size(params) > vec_size(macro->params) && !macro->variadic) ) - { - ftepp_error(ftepp, "macro %s expects%s %u paramteters, %u provided", macro->name, - (macro->variadic ? " at least" : ""), - (unsigned int)vec_size(macro->params), - (unsigned int)vec_size(params)); - retval = false; - goto cleanup; - } - - if (!ftepp_macro_expand(ftepp, macro, params, (paramline != ftepp->lex->sline))) - retval = false; - ftepp_next(ftepp); - -cleanup: - for (o = 0; o < vec_size(params); ++o) - macroparam_clean(¶ms[o]); - vec_free(params); - return retval; -} - -/** - * #if - the FTEQCC way: - * defined(FOO) => true if FOO was #defined regardless of parameters or contents - * => True if the number is not 0 - * ! => True if the factor yields false - * !! => ERROR on 2 or more unary nots - * => becomes the macro's FIRST token regardless of parameters - * && => True if both expressions are true - * || => True if either expression is true - * => False - * => False (remember for macros the rule applies instead) - * Unary + and - are weird and wrong in fteqcc so we don't allow them - * parenthesis in expressions are allowed - * parameter lists on macros are errors - * No mathematical calculations are executed - */ -static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out); -static bool ftepp_if_op(ftepp_t *ftepp) -{ - ftepp->lex->flags.noops = false; - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - ftepp->lex->flags.noops = true; - return true; -} -static bool ftepp_if_value(ftepp_t *ftepp, bool *out, double *value_out) -{ - ppmacro *macro; - bool wasnot = false; - bool wasneg = false; - - if (!ftepp_skipspace(ftepp)) - return false; - - while (ftepp->token == '!') { - wasnot = true; - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - } - - if (ftepp->token == TOKEN_OPERATOR && !strcmp(ftepp_tokval(ftepp), "-")) - { - wasneg = true; - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - } - - switch (ftepp->token) { - case TOKEN_IDENT: - case TOKEN_TYPENAME: - case TOKEN_KEYWORD: - if (!strcmp(ftepp_tokval(ftepp), "defined")) { - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token != '(') { - ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis"); - return false; - } - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token != TOKEN_IDENT && - ftepp->token != TOKEN_TYPENAME && - ftepp->token != TOKEN_KEYWORD) - { - ftepp_error(ftepp, "defined() used on an unexpected token type"); - return false; - } - macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); - *out = !!macro; - ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token != ')') { - ftepp_error(ftepp, "expected closing paren"); - return false; - } - break; - } - - macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); - if (!macro || !vec_size(macro->output)) { - *out = false; - *value_out = 0; - } else { - /* This does not expand recursively! */ - switch (macro->output[0]->token) { - case TOKEN_INTCONST: - *value_out = macro->output[0]->constval.i; - *out = !!(macro->output[0]->constval.i); - break; - case TOKEN_FLOATCONST: - *value_out = macro->output[0]->constval.f; - *out = !!(macro->output[0]->constval.f); - break; - default: - *out = false; - break; - } - } - break; - case TOKEN_STRINGCONST: - *value_out = 0; - *out = false; - break; - case TOKEN_INTCONST: - *value_out = ftepp->lex->tok.constval.i; - *out = !!(ftepp->lex->tok.constval.i); - break; - case TOKEN_FLOATCONST: - *value_out = ftepp->lex->tok.constval.f; - *out = !!(ftepp->lex->tok.constval.f); - break; - - case '(': - ftepp_next(ftepp); - if (!ftepp_if_expr(ftepp, out, value_out)) - return false; - if (ftepp->token != ')') { - ftepp_error(ftepp, "expected closing paren in #if expression"); - return false; - } - break; - - default: - ftepp_error(ftepp, "junk in #if: `%s` ...", ftepp_tokval(ftepp)); - if (OPTS_OPTION_BOOL(OPTION_DEBUG)) - ftepp_error(ftepp, "internal: token %i\n", ftepp->token); - return false; - } - if (wasneg) - *value_out = -*value_out; - if (wasnot) { - *out = !*out; - *value_out = (*out ? 1 : 0); - } - return true; -} - -/* -static bool ftepp_if_nextvalue(ftepp_t *ftepp, bool *out, double *value_out) -{ - if (!ftepp_next(ftepp)) - return false; - return ftepp_if_value(ftepp, out, value_out); -} -*/ - -static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out) -{ - if (!ftepp_if_value(ftepp, out, value_out)) - return false; - - if (!ftepp_if_op(ftepp)) - return false; - - if (ftepp->token == ')' || ftepp->token != TOKEN_OPERATOR) - return true; - - /* FTEQCC is all right-associative and no precedence here */ - if (!strcmp(ftepp_tokval(ftepp), "&&") || - !strcmp(ftepp_tokval(ftepp), "||")) - { - bool next = false; - char opc = ftepp_tokval(ftepp)[0]; - double nextvalue; - - (void)nextvalue; - if (!ftepp_next(ftepp)) - return false; - if (!ftepp_if_expr(ftepp, &next, &nextvalue)) - return false; - - if (opc == '&') - *out = *out && next; - else - *out = *out || next; - - *value_out = (*out ? 1 : 0); - return true; - } - else if (!strcmp(ftepp_tokval(ftepp), "==") || - !strcmp(ftepp_tokval(ftepp), "!=") || - !strcmp(ftepp_tokval(ftepp), ">=") || - !strcmp(ftepp_tokval(ftepp), "<=") || - !strcmp(ftepp_tokval(ftepp), ">") || - !strcmp(ftepp_tokval(ftepp), "<")) - { - bool next = false; - const char opc0 = ftepp_tokval(ftepp)[0]; - const char opc1 = ftepp_tokval(ftepp)[1]; - double other; - - if (!ftepp_next(ftepp)) - return false; - if (!ftepp_if_expr(ftepp, &next, &other)) - return false; - - if (opc0 == '=') - *out = (*value_out == other); - else if (opc0 == '!') - *out = (*value_out != other); - else if (opc0 == '>') { - if (opc1 == '=') *out = (*value_out >= other); - else *out = (*value_out > other); - } - else if (opc0 == '<') { - if (opc1 == '=') *out = (*value_out <= other); - else *out = (*value_out < other); - } - *value_out = (*out ? 1 : 0); - - return true; - } - else { - ftepp_error(ftepp, "junk after #if"); - return false; - } -} - -static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond) -{ - bool result = false; - double dummy = 0; - - memset(cond, 0, sizeof(*cond)); - (void)ftepp_next(ftepp); - - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token == TOKEN_EOL) { - ftepp_error(ftepp, "expected expression for #if-directive"); - return false; - } - - if (!ftepp_if_expr(ftepp, &result, &dummy)) - return false; - - cond->on = result; - return true; -} - -/** - * ifdef is rather simple - */ -static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond) -{ - ppmacro *macro; - memset(cond, 0, sizeof(*cond)); - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - - switch (ftepp->token) { - case TOKEN_IDENT: - case TOKEN_TYPENAME: - case TOKEN_KEYWORD: - macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); - break; - default: - ftepp_error(ftepp, "expected macro name"); - return false; - } - - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - /* relaxing this condition - if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { - ftepp_error(ftepp, "stray tokens after #ifdef"); - return false; - } - */ - cond->on = !!macro; - return true; -} - -/** - * undef is also simple - */ -static bool ftepp_undef(ftepp_t *ftepp) -{ - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - - if (ftepp->output_on) { - switch (ftepp->token) { - case TOKEN_IDENT: - case TOKEN_TYPENAME: - case TOKEN_KEYWORD: - ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); - break; - default: - ftepp_error(ftepp, "expected macro name"); - return false; - } - } - - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - /* relaxing this condition - if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { - ftepp_error(ftepp, "stray tokens after #ifdef"); - return false; - } - */ - return true; -} - -/* Special unescape-string function which skips a leading quote - * and stops at a quote, not just at \0 - */ -static void unescape(const char *str, char *out) { - ++str; - while (*str && *str != '"') { - if (*str == '\\') { - ++str; - switch (*str) { - case '\\': *out++ = *str; break; - case '"': *out++ = *str; break; - case 'a': *out++ = '\a'; break; - case 'b': *out++ = '\b'; break; - case 'r': *out++ = '\r'; break; - case 'n': *out++ = '\n'; break; - case 't': *out++ = '\t'; break; - case 'f': *out++ = '\f'; break; - case 'v': *out++ = '\v'; break; - default: - *out++ = '\\'; - *out++ = *str; - break; - } - ++str; - continue; - } - - *out++ = *str++; - } - *out = 0; -} - -static char *ftepp_include_find_path(const char *file, const char *pathfile) -{ - FILE *fp; - char *filename = NULL; - const char *last_slash; - size_t len; - - if (!pathfile) - return NULL; - - last_slash = strrchr(pathfile, '/'); - - if (last_slash) { - len = last_slash - pathfile; - memcpy(vec_add(filename, len), pathfile, len); - vec_push(filename, '/'); - } - - len = strlen(file); - memcpy(vec_add(filename, len+1), file, len); - vec_last(filename) = 0; - - fp = fopen(filename, "rb"); - if (fp) { - fclose(fp); - return filename; - } - vec_free(filename); - return NULL; -} - -static char *ftepp_include_find(ftepp_t *ftepp, const char *file) -{ - char *filename = NULL; - - filename = ftepp_include_find_path(file, ftepp->includename); - if (!filename) - filename = ftepp_include_find_path(file, ftepp->itemname); - return filename; -} - -static bool ftepp_directive_warning(ftepp_t *ftepp) { - char *message = NULL; - - if (!ftepp_skipspace(ftepp)) - return false; - - /* handle the odd non string constant case so it works like C */ - if (ftepp->token != TOKEN_STRINGCONST) { - bool store = false; - vec_append(message, 8, "#warning"); - ftepp_next(ftepp); - while (ftepp->token != TOKEN_EOL) { - vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp)); - ftepp_next(ftepp); - } - vec_push(message, '\0'); - if (ftepp->output_on) - store = ftepp_warn(ftepp, WARN_CPP, message); - else - store = false; - vec_free(message); - return store; - } - - if (!ftepp->output_on) - return false; - - unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp)); - return ftepp_warn(ftepp, WARN_CPP, "#warning %s", ftepp_tokval(ftepp)); -} - -static void ftepp_directive_error(ftepp_t *ftepp) { - char *message = NULL; - - if (!ftepp_skipspace(ftepp)) - return; - - /* handle the odd non string constant case so it works like C */ - if (ftepp->token != TOKEN_STRINGCONST) { - vec_append(message, 6, "#error"); - ftepp_next(ftepp); - while (ftepp->token != TOKEN_EOL) { - vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp)); - ftepp_next(ftepp); - } - vec_push(message, '\0'); - if (ftepp->output_on) - ftepp_error(ftepp, message); - vec_free(message); - return; - } - - if (!ftepp->output_on) - return; - - unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp)); - ftepp_error(ftepp, "#error %s", ftepp_tokval(ftepp)); -} - -static void ftepp_directive_message(ftepp_t *ftepp) { - char *message = NULL; - - if (!ftepp_skipspace(ftepp)) - return; - - /* handle the odd non string constant case so it works like C */ - if (ftepp->token != TOKEN_STRINGCONST) { - vec_append(message, 8, "#message"); - ftepp_next(ftepp); - while (ftepp->token != TOKEN_EOL) { - vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp)); - ftepp_next(ftepp); - } - vec_push(message, '\0'); - if (ftepp->output_on) - con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", message); - vec_free(message); - return; - } - - if (!ftepp->output_on) - return; - - unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp)); - con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", ftepp_tokval(ftepp)); -} - -/** - * Include a file. - * FIXME: do we need/want a -I option? - * FIXME: what about when dealing with files in subdirectories coming from a progs.src? - */ -static bool ftepp_include(ftepp_t *ftepp) -{ - lex_file *old_lexer = ftepp->lex; - lex_file *inlex; - lex_ctx_t ctx; - char lineno[128]; - char *filename; - char *parsename = NULL; - char *old_includename; - - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - - if (ftepp->token != TOKEN_STRINGCONST) { - 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) { - (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); - ftepp_out(ftepp, "\n#pragma file(", false); - ftepp_out(ftepp, parsename, false); - ftepp_out(ftepp, ")\n#pragma line(1)\n", false); - - filename = ftepp_include_find(ftepp, parsename); - if (!filename) { - 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); - vec_free(filename); - return false; - } - ftepp->lex = inlex; - old_includename = ftepp->includename; - ftepp->includename = filename; - if (!ftepp_preprocess(ftepp)) { - vec_free(ftepp->includename); - ftepp->includename = old_includename; - lex_close(ftepp->lex); - ftepp->lex = old_lexer; - return false; - } - vec_free(ftepp->includename); - ftepp->includename = old_includename; - lex_close(ftepp->lex); - ftepp->lex = old_lexer; - - ftepp_out(ftepp, "\n#pragma file(", false); - ftepp_out(ftepp, ctx.file, false); - util_snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1)); - ftepp_out(ftepp, lineno, false); - - /* skip the line */ - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token != TOKEN_EOL) { - ftepp_error(ftepp, "stray tokens after #include"); - return false; - } - (void)ftepp_next(ftepp); - - return true; -} - -/* Basic structure handlers */ -static bool ftepp_else_allowed(ftepp_t *ftepp) -{ - if (!vec_size(ftepp->conditions)) { - ftepp_error(ftepp, "#else without #if"); - return false; - } - if (vec_last(ftepp->conditions).had_else) { - ftepp_error(ftepp, "multiple #else for a single #if"); - return false; - } - return true; -} - -static GMQCC_INLINE void ftepp_inmacro(ftepp_t *ftepp, const char *hash) { - if (ftepp->in_macro) - (void)!ftepp_warn(ftepp, WARN_DIRECTIVE_INMACRO, "`#%s` directive in macro", hash); -} - -static bool ftepp_hash(ftepp_t *ftepp) -{ - ppcondition cond; - ppcondition *pc; - - lex_ctx_t ctx = ftepp_ctx(ftepp); - - if (!ftepp_skipspace(ftepp)) - return false; - - switch (ftepp->token) { - case TOKEN_KEYWORD: - case TOKEN_IDENT: - case TOKEN_TYPENAME: - if (!strcmp(ftepp_tokval(ftepp), "define")) { - ftepp_inmacro(ftepp, "define"); - return ftepp_define(ftepp); - } - else if (!strcmp(ftepp_tokval(ftepp), "undef")) { - ftepp_inmacro(ftepp, "undef"); - return ftepp_undef(ftepp); - } - else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) { - ftepp_inmacro(ftepp, "ifdef"); - if (!ftepp_ifdef(ftepp, &cond)) - return false; - cond.was_on = cond.on; - vec_push(ftepp->conditions, cond); - ftepp->output_on = ftepp->output_on && cond.on; - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) { - ftepp_inmacro(ftepp, "ifndef"); - if (!ftepp_ifdef(ftepp, &cond)) - return false; - cond.on = !cond.on; - cond.was_on = cond.on; - vec_push(ftepp->conditions, cond); - ftepp->output_on = ftepp->output_on && cond.on; - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) { - ftepp_inmacro(ftepp, "elifdef"); - if (!ftepp_else_allowed(ftepp)) - return false; - if (!ftepp_ifdef(ftepp, &cond)) - return false; - pc = &vec_last(ftepp->conditions); - pc->on = !pc->was_on && cond.on; - pc->was_on = pc->was_on || pc->on; - ftepp_update_output_condition(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) { - ftepp_inmacro(ftepp, "elifndef"); - if (!ftepp_else_allowed(ftepp)) - return false; - if (!ftepp_ifdef(ftepp, &cond)) - return false; - cond.on = !cond.on; - pc = &vec_last(ftepp->conditions); - pc->on = !pc->was_on && cond.on; - pc->was_on = pc->was_on || pc->on; - ftepp_update_output_condition(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "elif")) { - ftepp_inmacro(ftepp, "elif"); - if (!ftepp_else_allowed(ftepp)) - return false; - if (!ftepp_if(ftepp, &cond)) - return false; - pc = &vec_last(ftepp->conditions); - pc->on = !pc->was_on && cond.on; - pc->was_on = pc->was_on || pc->on; - ftepp_update_output_condition(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "if")) { - ftepp_inmacro(ftepp, "if"); - if (!ftepp_if(ftepp, &cond)) - return false; - cond.was_on = cond.on; - vec_push(ftepp->conditions, cond); - ftepp->output_on = ftepp->output_on && cond.on; - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "else")) { - ftepp_inmacro(ftepp, "else"); - if (!ftepp_else_allowed(ftepp)) - return false; - pc = &vec_last(ftepp->conditions); - pc->on = !pc->was_on; - pc->had_else = true; - ftepp_next(ftepp); - ftepp_update_output_condition(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "endif")) { - ftepp_inmacro(ftepp, "endif"); - if (!vec_size(ftepp->conditions)) { - ftepp_error(ftepp, "#endif without #if"); - return false; - } - vec_pop(ftepp->conditions); - ftepp_next(ftepp); - ftepp_update_output_condition(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "include")) { - ftepp_inmacro(ftepp, "include"); - return ftepp_include(ftepp); - } - else if (!strcmp(ftepp_tokval(ftepp), "pragma")) { - ftepp_out(ftepp, "#", false); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "warning")) { - ftepp_directive_warning(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "error")) { - ftepp_directive_error(ftepp); - break; - } - else if (!strcmp(ftepp_tokval(ftepp), "message")) { - ftepp_directive_message(ftepp); - break; - } - else { - if (ftepp->output_on) { - ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp)); - return false; - } else { - ftepp_next(ftepp); - break; - } - } - /* break; never reached */ - default: - ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp)); - return false; - case TOKEN_EOL: - ftepp_errorat(ftepp, ctx, "empty preprocessor directive"); - return false; - case TOKEN_EOF: - ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp)); - return false; - - /* Builtins! Don't forget the builtins! */ - case TOKEN_INTCONST: - case TOKEN_FLOATCONST: - ftepp_out(ftepp, "#", false); - return true; - } - if (!ftepp_skipspace(ftepp)) - return false; - return true; -} - -static bool ftepp_preprocess(ftepp_t *ftepp) -{ - ppmacro *macro; - bool newline = true; - - /* predef stuff */ - char *expand = NULL; - - ftepp->lex->flags.preprocessing = true; - ftepp->lex->flags.mergelines = false; - ftepp->lex->flags.noops = true; - - ftepp_next(ftepp); - do - { - if (ftepp->token >= TOKEN_EOF) - break; - switch (ftepp->token) { - case TOKEN_KEYWORD: - case TOKEN_IDENT: - case TOKEN_TYPENAME: - /* is it a predef? */ - if (OPTS_FLAG(FTEPP_PREDEFS)) { - char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp)); - if (predef) { - expand = predef(ftepp); - ftepp_out (ftepp, expand, false); - ftepp_next(ftepp); - - mem_d(expand); - break; - } - } - - if (ftepp->output_on) - macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); - else - macro = NULL; - - if (!macro) { - ftepp_out(ftepp, ftepp_tokval(ftepp), false); - ftepp_next(ftepp); - break; - } - if (!ftepp_macro_call(ftepp, macro)) - ftepp->token = TOKEN_ERROR; - break; - case '#': - if (!newline) { - ftepp_out(ftepp, ftepp_tokval(ftepp), false); - ftepp_next(ftepp); - break; - } - ftepp->lex->flags.mergelines = true; - if (ftepp_next(ftepp) >= TOKEN_EOF) { - ftepp_error(ftepp, "error in preprocessor directive"); - ftepp->token = TOKEN_ERROR; - break; - } - if (!ftepp_hash(ftepp)) - ftepp->token = TOKEN_ERROR; - ftepp->lex->flags.mergelines = false; - break; - case TOKEN_EOL: - newline = true; - ftepp_out(ftepp, "\n", true); - ftepp_next(ftepp); - break; - case TOKEN_WHITE: - /* same as default but don't set newline=false */ - ftepp_out(ftepp, ftepp_tokval(ftepp), true); - ftepp_next(ftepp); - break; - default: - newline = false; - ftepp_out(ftepp, ftepp_tokval(ftepp), false); - ftepp_next(ftepp); - break; - } - } while (!ftepp->errors && ftepp->token < TOKEN_EOF); - - /* force a 0 at the end but don't count it as added to the output */ - vec_push(ftepp->output_string, 0); - vec_shrinkby(ftepp->output_string, 1); - - return (ftepp->token == TOKEN_EOF); -} - -/* Like in parser.c - files keep the previous state so we have one global - * preprocessor. Except here we will want to warn about dangling #ifs. - */ -static bool ftepp_preprocess_done(ftepp_t *ftepp) -{ - bool retval = true; - if (vec_size(ftepp->conditions)) { - if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?")) - retval = false; - } - lex_close(ftepp->lex); - ftepp->lex = NULL; - if (ftepp->itemname) { - mem_d(ftepp->itemname); - ftepp->itemname = NULL; - } - return retval; -} - -bool ftepp_preprocess_file(ftepp_t *ftepp, const char *filename) -{ - ftepp->lex = lex_open(filename); - ftepp->itemname = util_strdup(filename); - if (!ftepp->lex) { - con_out("failed to open file \"%s\"\n", filename); - return false; - } - if (!ftepp_preprocess(ftepp)) - return false; - return ftepp_preprocess_done(ftepp); -} - -bool ftepp_preprocess_string(ftepp_t *ftepp, const char *name, const char *str) -{ - ftepp->lex = lex_open_string(str, strlen(str), name); - ftepp->itemname = util_strdup(name); - if (!ftepp->lex) { - con_out("failed to create lexer for string \"%s\"\n", name); - return false; - } - if (!ftepp_preprocess(ftepp)) - return false; - return ftepp_preprocess_done(ftepp); -} - - -void ftepp_add_macro(ftepp_t *ftepp, const char *name, const char *value) { - char *create = NULL; - - /* use saner path for empty macros */ - if (!value) { - ftepp_add_define(ftepp, "__builtin__", name); - return; - } - - vec_append(create, 8, "#define "); - vec_append(create, strlen(name), name); - vec_push (create, ' '); - vec_append(create, strlen(value), value); - vec_push (create, 0); - - ftepp_preprocess_string(ftepp, "__builtin__", create); - vec_free (create); -} - -ftepp_t *ftepp_create() -{ - ftepp_t *ftepp; - char minor[32]; - char major[32]; - size_t i; - - ftepp = ftepp_new(); - if (!ftepp) - return NULL; - - memset(minor, 0, sizeof(minor)); - memset(major, 0, sizeof(major)); - - /* set the right macro based on the selected standard */ - ftepp_add_define(ftepp, NULL, "GMQCC"); - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) { - ftepp_add_define(ftepp, NULL, "__STD_FTEQCC__"); - /* 1.00 */ - major[0] = '"'; - major[1] = '1'; - major[2] = '"'; - - minor[0] = '"'; - minor[1] = '0'; - minor[2] = '"'; - } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) { - ftepp_add_define(ftepp, NULL, "__STD_GMQCC__"); - util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR); - util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR); - } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCCX) { - ftepp_add_define(ftepp, NULL, "__STD_QCCX__"); - util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR); - util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR); - } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { - ftepp_add_define(ftepp, NULL, "__STD_QCC__"); - /* 1.0 */ - major[0] = '"'; - major[1] = '1'; - major[2] = '"'; - - minor[0] = '"'; - minor[1] = '0'; - minor[2] = '"'; - } - - ftepp_add_macro(ftepp, "__STD_VERSION_MINOR__", minor); - ftepp_add_macro(ftepp, "__STD_VERSION_MAJOR__", major); - - /* - * We're going to just make __NULL__ nil, which works for 60% of the - * cases of __NULL_ for fteqcc. - */ - ftepp_add_macro(ftepp, "__NULL__", "nil"); - - /* add all the math constants if they can be */ - if (OPTS_FLAG(FTEPP_MATHDEFS)) { - for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++) - if (!ftepp_macro_find(ftepp, ftepp_math_constants[i][0])) - ftepp_add_macro(ftepp, ftepp_math_constants[i][0], ftepp_math_constants[i][1]); - } - - return ftepp; -} - -void ftepp_add_define(ftepp_t *ftepp, const char *source, const char *name) -{ - ppmacro *macro; - lex_ctx_t ctx = { "__builtin__", 0, 0 }; - ctx.file = source; - macro = ppmacro_new(ctx, name); - /*vec_push(ftepp->macros, macro);*/ - util_htset(ftepp->macros, name, macro); -} - -const char *ftepp_get(ftepp_t *ftepp) -{ - return ftepp->output_string; -} - -void ftepp_flush(ftepp_t *ftepp) -{ - ftepp_flush_do(ftepp); -} - -void ftepp_finish(ftepp_t *ftepp) -{ - if (!ftepp) - return; - ftepp_delete(ftepp); -} diff --git a/ftepp.cpp b/ftepp.cpp new file mode 100644 index 0000000..1c80d9e --- /dev/null +++ b/ftepp.cpp @@ -0,0 +1,1954 @@ +#include +#include +#include + +#include "gmqcc.h" +#include "lexer.h" + +#define HT_MACROS 1024 + +typedef struct { + bool on; + bool was_on; + bool had_else; +} ppcondition; + +typedef struct { + int token; + char *value; + /* a copy from the lexer */ + union { + vec3_t v; + int i; + double f; + int t; /* type */ + } constval; +} pptoken; + +typedef struct { + lex_ctx_t ctx; + + char *name; + char **params; + /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */ + bool has_params; + bool variadic; + + pptoken **output; +} ppmacro; + +typedef struct ftepp_s { + lex_file *lex; + int token; + unsigned int errors; + + bool output_on; + ppcondition *conditions; + /*ppmacro **macros;*/ + ht macros; /* hashtable */ + char *output_string; + + char *itemname; + char *includename; + bool in_macro; + + uint32_t predef_countval; + uint32_t predef_randval; +} ftepp_t; + +/* __DATE__ */ +static char *ftepp_predef_date(ftepp_t *context) { + const struct tm *itime = NULL; + char *value = (char*)mem_a(82); + time_t rtime; + + (void)context; + + time (&rtime); + itime = util_localtime(&rtime); + strftime(value, 82, "\"%b %d %Y\"", itime); + + return value; +} + +/* __TIME__ */ +static char *ftepp_predef_time(ftepp_t *context) { + const struct tm *itime = NULL; + char *value = (char*)mem_a(82); + time_t rtime; + + (void)context; + + time (&rtime); + itime = util_localtime(&rtime); + strftime(value, 82, "\"%X\"", itime); + + return value; +} + +/* __LINE__ */ +static char *ftepp_predef_line(ftepp_t *context) { + char *value; + + util_asprintf(&value, "%d", (int)context->lex->line); + return value; +} +/* __FILE__ */ +static char *ftepp_predef_file(ftepp_t *context) { + size_t length = strlen(context->lex->name) + 3; /* two quotes and a terminator */ + char *value = (char*)mem_a(length); + + util_snprintf(value, length, "\"%s\"", context->lex->name); + return value; +} +/* __COUNTER_LAST__ */ +static char *ftepp_predef_counterlast(ftepp_t *context) { + char *value; + util_asprintf(&value, "%u", context->predef_countval); + return value; +} +/* __COUNTER__ */ +static char *ftepp_predef_counter(ftepp_t *context) { + char *value; + + context->predef_countval ++; + util_asprintf(&value, "%u", context->predef_countval); + + return value; +} +/* __RANDOM__ */ +static char *ftepp_predef_random(ftepp_t *context) { + char *value; + + context->predef_randval = (util_rand() % 0xFF) + 1; + util_asprintf(&value, "%u", context->predef_randval); + return value; +} +/* __RANDOM_LAST__ */ +static char *ftepp_predef_randomlast(ftepp_t *context) { + char *value; + + util_asprintf(&value, "%u", context->predef_randval); + return value; +} +/* __TIMESTAMP__ */ +static char *ftepp_predef_timestamp(ftepp_t *context) { + struct stat finfo; + const char *find; + char *value; + size_t size; + + if (stat(context->lex->name, &finfo)) + return util_strdup("\"\""); + + find = util_ctime(&finfo.st_mtime); + value = (char*)mem_a(strlen(find) + 1); + memcpy(&value[1], find, (size = strlen(find)) - 1); + + value[0] = '"'; + value[size] = '"'; + + return value; +} + +typedef struct { + const char *name; + char *(*func)(ftepp_t *); +} ftepp_predef_t; + +static const ftepp_predef_t ftepp_predefs[] = { + { "__LINE__", &ftepp_predef_line }, + { "__FILE__", &ftepp_predef_file }, + { "__COUNTER__", &ftepp_predef_counter }, + { "__COUNTER_LAST__", &ftepp_predef_counterlast }, + { "__RANDOM__", &ftepp_predef_random }, + { "__RANDOM_LAST__", &ftepp_predef_randomlast }, + { "__DATE__", &ftepp_predef_date }, + { "__TIME__", &ftepp_predef_time }, + { "__TIME_STAMP__", &ftepp_predef_timestamp } +}; + +static GMQCC_INLINE size_t ftepp_predef_index(const char *name) { + /* no hashtable here, we simply check for one to exist the naive way */ + size_t i; + for(i = 1; i < GMQCC_ARRAY_COUNT(ftepp_predefs) + 1; i++) + if (!strcmp(ftepp_predefs[i-1].name, name)) + return i; + return 0; +} + +bool ftepp_predef_exists(const char *name); +bool ftepp_predef_exists(const char *name) { + return ftepp_predef_index(name) != 0; +} + +/* singleton because we're allowed */ +static GMQCC_INLINE char *(*ftepp_predef(const char *name))(ftepp_t *context) { + size_t i = ftepp_predef_index(name); + return (i != 0) ? ftepp_predefs[i-1].func : NULL; +} + +#define ftepp_tokval(f) ((f)->lex->tok.value) +#define ftepp_ctx(f) ((f)->lex->tok.ctx) + +static void ftepp_errorat(ftepp_t *ftepp, lex_ctx_t ctx, const char *fmt, ...) +{ + va_list ap; + + ftepp->errors++; + + va_start(ap, fmt); + con_cvprintmsg(ctx, LVL_ERROR, "error", fmt, ap); + va_end(ap); +} + +static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...) +{ + va_list ap; + + ftepp->errors++; + + va_start(ap, fmt); + con_cvprintmsg(ftepp->lex->tok.ctx, LVL_ERROR, "error", fmt, ap); + va_end(ap); +} + +static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...) +{ + bool r; + va_list ap; + + va_start(ap, fmt); + r = vcompile_warning(ftepp->lex->tok.ctx, warntype, fmt, ap); + va_end(ap); + return r; +} + +static pptoken *pptoken_make(ftepp_t *ftepp) +{ + pptoken *token = (pptoken*)mem_a(sizeof(pptoken)); + token->token = ftepp->token; + token->value = util_strdup(ftepp_tokval(ftepp)); + memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval)); + return token; +} + +static GMQCC_INLINE void pptoken_delete(pptoken *self) +{ + mem_d(self->value); + mem_d(self); +} + +static ppmacro *ppmacro_new(lex_ctx_t ctx, const char *name) +{ + ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro)); + + (void)ctx; + memset(macro, 0, sizeof(*macro)); + macro->name = util_strdup(name); + return macro; +} + +static void ppmacro_delete(ppmacro *self) +{ + size_t i; + for (i = 0; i < vec_size(self->params); ++i) + mem_d(self->params[i]); + vec_free(self->params); + for (i = 0; i < vec_size(self->output); ++i) + pptoken_delete(self->output[i]); + vec_free(self->output); + mem_d(self->name); + mem_d(self); +} + +static ftepp_t* ftepp_new(void) +{ + ftepp_t *ftepp; + + ftepp = (ftepp_t*)mem_a(sizeof(*ftepp)); + memset(ftepp, 0, sizeof(*ftepp)); + + ftepp->macros = util_htnew(HT_MACROS); + ftepp->output_on = true; + ftepp->predef_countval = 0; + ftepp->predef_randval = 0; + + return ftepp; +} + +static GMQCC_INLINE void ftepp_flush_do(ftepp_t *self) +{ + vec_free(self->output_string); +} + +static void ftepp_delete(ftepp_t *self) +{ + ftepp_flush_do(self); + if (self->itemname) + mem_d(self->itemname); + if (self->includename) + vec_free(self->includename); + + util_htrem(self->macros, (void (*)(void*))&ppmacro_delete); + + vec_free(self->conditions); + if (self->lex) + lex_close(self->lex); + mem_d(self); +} + +static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond) +{ + if (ignore_cond || ftepp->output_on) + { + size_t len; + char *data; + len = strlen(str); + data = vec_add(ftepp->output_string, len); + memcpy(data, str, len); + } +} + +static GMQCC_INLINE void ftepp_update_output_condition(ftepp_t *ftepp) +{ + size_t i; + ftepp->output_on = true; + for (i = 0; i < vec_size(ftepp->conditions); ++i) + ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on; +} + +static GMQCC_INLINE ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name) +{ + return (ppmacro*)util_htget(ftepp->macros, name); +} + +static GMQCC_INLINE void ftepp_macro_delete(ftepp_t *ftepp, const char *name) +{ + util_htrm(ftepp->macros, name, (void (*)(void*))&ppmacro_delete); +} + +static GMQCC_INLINE int ftepp_next(ftepp_t *ftepp) +{ + return (ftepp->token = lex_do(ftepp->lex)); +} + +/* Important: this does not skip newlines! */ +static bool ftepp_skipspace(ftepp_t *ftepp) +{ + if (ftepp->token != TOKEN_WHITE) + return true; + while (ftepp_next(ftepp) == TOKEN_WHITE) {} + if (ftepp->token >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected end of preprocessor directive"); + return false; + } + return true; +} + +/* this one skips EOLs as well */ +static bool ftepp_skipallwhite(ftepp_t *ftepp) +{ + if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL) + return true; + do { + ftepp_next(ftepp); + } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL); + if (ftepp->token >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected end of preprocessor directive"); + return false; + } + return true; +} + +/** + * The huge macro parsing code... + */ +static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro) +{ + do { + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token == ')') + break; + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + vec_push(macro->params, util_strdup(ftepp_tokval(ftepp))); + break; + case TOKEN_DOTS: + macro->variadic = true; + break; + default: + ftepp_error(ftepp, "unexpected token in parameter list"); + return false; + } + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (macro->variadic && ftepp->token != ')') { + ftepp_error(ftepp, "cannot have parameters after the variadic parameters"); + return false; + } + } while (ftepp->token == ','); + + if (ftepp->token != ')') { + ftepp_error(ftepp, "expected closing paren after macro parameter list"); + return false; + } + ftepp_next(ftepp); + /* skipspace happens in ftepp_define */ + return true; +} + +static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro) +{ + pptoken *ptok; + while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) { + bool subscript = false; + size_t index = 0; + if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_ARGS__")) { + subscript = !!(ftepp_next(ftepp) == '#'); + + if (subscript && ftepp_next(ftepp) != '#') { + ftepp_error(ftepp, "expected `##` in __VA_ARGS__ for subscripting"); + return false; + } else if (subscript) { + if (ftepp_next(ftepp) == '[') { + if (ftepp_next(ftepp) != TOKEN_INTCONST) { + ftepp_error(ftepp, "expected index for __VA_ARGS__ subscript"); + return false; + } + + index = (int)strtol(ftepp_tokval(ftepp), NULL, 10); + + if (ftepp_next(ftepp) != ']') { + ftepp_error(ftepp, "expected `]` in __VA_ARGS__ subscript"); + return false; + } + + /* + * mark it as an array to be handled later as such and not + * as traditional __VA_ARGS__ + */ + ftepp->token = TOKEN_VA_ARGS_ARRAY; + ptok = pptoken_make(ftepp); + ptok->constval.i = index; + vec_push(macro->output, ptok); + ftepp_next(ftepp); + } else { + ftepp_error(ftepp, "expected `[` for subscripting of __VA_ARGS__"); + return false; + } + } else { + int old = ftepp->token; + ftepp->token = TOKEN_VA_ARGS; + ptok = pptoken_make(ftepp); + vec_push(macro->output, ptok); + ftepp->token = old; + } + } + else if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_COUNT__")) { + ftepp->token = TOKEN_VA_COUNT; + ptok = pptoken_make(ftepp); + vec_push(macro->output, ptok); + ftepp_next(ftepp); + } else { + ptok = pptoken_make(ftepp); + vec_push(macro->output, ptok); + ftepp_next(ftepp); + } + } + /* recursive expansion can cause EOFs here */ + if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { + ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file"); + return false; + } + return true; +} + +static const char *ftepp_math_constants[][2] = { + { "M_E", "2.7182818284590452354" }, /* e */ + { "M_LOG2E", "1.4426950408889634074" }, /* log_2 e */ + { "M_LOG10E", "0.43429448190325182765" }, /* log_10 e */ + { "M_LN2", "0.69314718055994530942" }, /* log_e 2 */ + { "M_LN10", "2.30258509299404568402" }, /* log_e 10 */ + { "M_PI", "3.14159265358979323846" }, /* pi */ + { "M_PI_2", "1.57079632679489661923" }, /* pi/2 */ + { "M_PI_4", "0.78539816339744830962" }, /* pi/4 */ + { "M_1_PI", "0.31830988618379067154" }, /* 1/pi */ + { "M_2_PI", "0.63661977236758134308" }, /* 2/pi */ + { "M_2_SQRTPI", "1.12837916709551257390" }, /* 2/sqrt(pi) */ + { "M_SQRT2", "1.41421356237309504880" }, /* sqrt(2) */ + { "M_SQRT1_2", "0.70710678118654752440" }, /* 1/sqrt(2) */ + { "M_TAU", "6.28318530717958647692" } /* pi*2 */ +}; + +static bool ftepp_define(ftepp_t *ftepp) +{ + ppmacro *macro = NULL; + size_t l = ftepp_ctx(ftepp).line; + size_t i; + bool mathconstant = false; + + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + if (OPTS_FLAG(FTEPP_MATHDEFS)) { + for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++) { + if (!strcmp(ftepp_math_constants[i][0], ftepp_tokval(ftepp))) { + mathconstant = true; + break; + } + } + } + + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + + if (OPTS_FLAG(FTEPP_MATHDEFS)) { + /* user defined ones take precedence */ + if (macro && mathconstant) { + ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); + macro = NULL; + } + } + + if (macro && ftepp->output_on) { + if (ftepp_warn(ftepp, WARN_CPP, "redefining `%s`", ftepp_tokval(ftepp))) + return false; + ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); + } + macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp)); + break; + default: + ftepp_error(ftepp, "expected macro name"); + return false; + } + + (void)ftepp_next(ftepp); + + if (ftepp->token == '(') { + macro->has_params = true; + if (!ftepp_define_params(ftepp, macro)) { + ppmacro_delete(macro); + return false; + } + } + + if (!ftepp_skipspace(ftepp)) { + ppmacro_delete(macro); + return false; + } + + if (!ftepp_define_body(ftepp, macro)) { + ppmacro_delete(macro); + return false; + } + + if (ftepp->output_on) + util_htset(ftepp->macros, macro->name, (void*)macro); + else { + ppmacro_delete(macro); + } + + for (; l < ftepp_ctx(ftepp).line; ++l) + ftepp_out(ftepp, "\n", true); + return true; +} + +/** + * When a macro is used we have to handle parameters as well + * as special-concatenation via ## or stringification via # + * + * Note: parenthesis can nest, so FOO((a),b) is valid, but only + * this kind of parens. Curly braces or [] don't count towards the + * paren-level. + */ +typedef struct { + pptoken **tokens; +} macroparam; + +static void macroparam_clean(macroparam *self) +{ + size_t i; + for (i = 0; i < vec_size(self->tokens); ++i) + pptoken_delete(self->tokens[i]); + vec_free(self->tokens); +} + +/* need to leave the last token up */ +static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params) +{ + macroparam *params = NULL; + pptoken *ptok; + macroparam mp; + size_t parens = 0; + size_t i; + + if (!ftepp_skipallwhite(ftepp)) + return false; + while (ftepp->token != ')') { + mp.tokens = NULL; + if (!ftepp_skipallwhite(ftepp)) + return false; + while (parens || ftepp->token != ',') { + if (ftepp->token == '(') + ++parens; + else if (ftepp->token == ')') { + if (!parens) + break; + --parens; + } + ptok = pptoken_make(ftepp); + vec_push(mp.tokens, ptok); + if (ftepp_next(ftepp) >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected end of file in macro call"); + goto on_error; + } + } + vec_push(params, mp); + mp.tokens = NULL; + if (ftepp->token == ')') + break; + if (ftepp->token != ',') { + ftepp_error(ftepp, "expected closing paren or comma in macro call"); + goto on_error; + } + if (ftepp_next(ftepp) >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected end of file in macro call"); + goto on_error; + } + } + *out_params = params; + return true; + +on_error: + if (mp.tokens) + macroparam_clean(&mp); + for (i = 0; i < vec_size(params); ++i) + macroparam_clean(¶ms[i]); + vec_free(params); + return false; +} + +static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx) +{ + size_t i; + for (i = 0; i < vec_size(macro->params); ++i) { + if (!strcmp(macro->params[i], name)) { + *idx = i; + return true; + } + } + return false; +} + +static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token) +{ + char chs[2]; + const char *ch; + chs[1] = 0; + switch (token->token) { + case TOKEN_STRINGCONST: + ch = token->value; + while (*ch) { + /* in preprocessor mode strings already are string, + * so we don't get actual newline bytes here. + * Still need to escape backslashes and quotes. + */ + switch (*ch) { + case '\\': ftepp_out(ftepp, "\\\\", false); break; + case '"': ftepp_out(ftepp, "\\\"", false); break; + default: + chs[0] = *ch; + ftepp_out(ftepp, chs, false); + break; + } + ++ch; + } + break; + /*case TOKEN_WHITE: + ftepp_out(ftepp, " ", false); + break;*/ + case TOKEN_EOL: + ftepp_out(ftepp, "\\n", false); + break; + default: + ftepp_out(ftepp, token->value, false); + break; + } +} + +static void ftepp_stringify(ftepp_t *ftepp, macroparam *param) +{ + size_t i; + ftepp_out(ftepp, "\"", false); + for (i = 0; i < vec_size(param->tokens); ++i) + ftepp_stringify_token(ftepp, param->tokens[i]); + ftepp_out(ftepp, "\"", false); +} + +static void ftepp_recursion_header(ftepp_t *ftepp) +{ + ftepp_out(ftepp, "\n#pragma push(line)\n", false); +} + +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; + pptoken *out; + for (i = 0; i < vec_size(param->tokens); ++i) { + out = param->tokens[i]; + if (out->token == TOKEN_EOL) + ftepp_out(ftepp, "\n", 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); + } + } +} + +static bool ftepp_preprocess(ftepp_t *ftepp); +static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline) +{ + char *buffer = NULL; + char *old_string = ftepp->output_string; + char *inner_string; + lex_file *old_lexer = ftepp->lex; + size_t vararg_start = vec_size(macro->params); + bool retval = true; + bool has_newlines; + size_t varargs; + + size_t o, pi; + lex_file *inlex; + + bool old_inmacro; + bool strip = false; + + int nextok; + + if (vararg_start < vec_size(params)) + varargs = vec_size(params) - vararg_start; + else + varargs = 0; + + /* really ... */ + if (!vec_size(macro->output)) + return true; + + ftepp->output_string = NULL; + for (o = 0; o < vec_size(macro->output); ++o) { + pptoken *out = macro->output[o]; + switch (out->token) { + case TOKEN_VA_ARGS: + if (!macro->variadic) { + ftepp_error(ftepp, "internal preprocessor error: TOKEN_VA_ARGS in non-variadic macro"); + vec_free(old_string); + return false; + } + if (!varargs) + break; + + pi = 0; + ftepp_param_out(ftepp, ¶ms[pi + vararg_start]); + for (++pi; pi < varargs; ++pi) { + ftepp_out(ftepp, ", ", false); + ftepp_param_out(ftepp, ¶ms[pi + vararg_start]); + } + break; + + case TOKEN_VA_ARGS_ARRAY: + if ((size_t)out->constval.i >= varargs) { + ftepp_error(ftepp, "subscript of `[%u]` is out of bounds for `__VA_ARGS__`", out->constval.i); + vec_free(old_string); + return false; + } + + ftepp_param_out(ftepp, ¶ms[out->constval.i + vararg_start]); + break; + + case TOKEN_VA_COUNT: + util_asprintf(&buffer, "%d", varargs); + ftepp_out(ftepp, buffer, false); + mem_d(buffer); + break; + + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + if (!macro_params_find(macro, out->value, &pi)) { + ftepp_out(ftepp, out->value, false); + break; + } else + ftepp_param_out(ftepp, ¶ms[pi]); + break; + case '#': + if (o + 1 < vec_size(macro->output)) { + nextok = macro->output[o+1]->token; + if (nextok == '#') { + /* raw concatenation */ + ++o; + strip = true; + break; + } + if ( (nextok == TOKEN_IDENT || + nextok == TOKEN_KEYWORD || + nextok == TOKEN_TYPENAME) && + macro_params_find(macro, macro->output[o+1]->value, &pi)) + { + ++o; + + ftepp_stringify(ftepp, ¶ms[pi]); + break; + } + } + ftepp_out(ftepp, "#", false); + break; + case TOKEN_EOL: + ftepp_out(ftepp, "\n", false); + break; + default: + buffer = out->value; + #define buffer_stripable(X) ((X) == ' ' || (X) == '\t') + if (vec_size(macro->output) > o + 1 && macro->output[o+1]->token == '#' && buffer_stripable(*buffer)) + buffer++; + if (strip) { + while (buffer_stripable(*buffer)) buffer++; + strip = false; + } + ftepp_out(ftepp, buffer, false); + break; + } + } + vec_push(ftepp->output_string, 0); + /* Now run the preprocessor recursively on this string buffer */ + /* + printf("__________\n%s\n=========\n", ftepp->output_string); + */ + inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name); + if (!inlex) { + ftepp_error(ftepp, "internal error: failed to instantiate lexer"); + retval = false; + goto cleanup; + } + + inlex->line = ftepp->lex->line; + inlex->sline = ftepp->lex->sline; + ftepp->lex = inlex; + + old_inmacro = ftepp->in_macro; + ftepp->in_macro = true; + ftepp->output_string = NULL; + if (!ftepp_preprocess(ftepp)) { + ftepp->in_macro = old_inmacro; + vec_free(ftepp->lex->open_string); + vec_free(ftepp->output_string); + lex_close(ftepp->lex); + retval = false; + goto cleanup; + } + ftepp->in_macro = old_inmacro; + vec_free(ftepp->lex->open_string); + lex_close(ftepp->lex); + + inner_string = ftepp->output_string; + ftepp->output_string = old_string; + + has_newlines = (strchr(inner_string, '\n') != NULL); + + if (has_newlines && !old_inmacro) + ftepp_recursion_header(ftepp); + + vec_append(ftepp->output_string, vec_size(inner_string), inner_string); + vec_free(inner_string); + + if (has_newlines && !old_inmacro) + ftepp_recursion_footer(ftepp); + + if (resetline && !ftepp->in_macro) { + char lineno[128]; + util_snprintf(lineno, 128, "\n#pragma line(%lu)\n", (unsigned long)(old_lexer->sline)); + ftepp_out(ftepp, lineno, false); + } + + old_string = ftepp->output_string; +cleanup: + ftepp->lex = old_lexer; + ftepp->output_string = old_string; + return retval; +} + +static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro) +{ + size_t o; + macroparam *params = NULL; + bool retval = true; + size_t paramline; + + if (!macro->has_params) { + if (!ftepp_macro_expand(ftepp, macro, NULL, false)) + return false; + ftepp_next(ftepp); + return true; + } + ftepp_next(ftepp); + + if (!ftepp_skipallwhite(ftepp)) + return false; + + if (ftepp->token != '(') { + ftepp_error(ftepp, "expected macro parameters in parenthesis"); + return false; + } + + ftepp_next(ftepp); + paramline = ftepp->lex->sline; + if (!ftepp_macro_call_params(ftepp, ¶ms)) + return false; + + if ( vec_size(params) < vec_size(macro->params) || + (vec_size(params) > vec_size(macro->params) && !macro->variadic) ) + { + ftepp_error(ftepp, "macro %s expects%s %u paramteters, %u provided", macro->name, + (macro->variadic ? " at least" : ""), + (unsigned int)vec_size(macro->params), + (unsigned int)vec_size(params)); + retval = false; + goto cleanup; + } + + if (!ftepp_macro_expand(ftepp, macro, params, (paramline != ftepp->lex->sline))) + retval = false; + ftepp_next(ftepp); + +cleanup: + for (o = 0; o < vec_size(params); ++o) + macroparam_clean(¶ms[o]); + vec_free(params); + return retval; +} + +/** + * #if - the FTEQCC way: + * defined(FOO) => true if FOO was #defined regardless of parameters or contents + * => True if the number is not 0 + * ! => True if the factor yields false + * !! => ERROR on 2 or more unary nots + * => becomes the macro's FIRST token regardless of parameters + * && => True if both expressions are true + * || => True if either expression is true + * => False + * => False (remember for macros the rule applies instead) + * Unary + and - are weird and wrong in fteqcc so we don't allow them + * parenthesis in expressions are allowed + * parameter lists on macros are errors + * No mathematical calculations are executed + */ +static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out); +static bool ftepp_if_op(ftepp_t *ftepp) +{ + ftepp->lex->flags.noops = false; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + ftepp->lex->flags.noops = true; + return true; +} +static bool ftepp_if_value(ftepp_t *ftepp, bool *out, double *value_out) +{ + ppmacro *macro; + bool wasnot = false; + bool wasneg = false; + + if (!ftepp_skipspace(ftepp)) + return false; + + while (ftepp->token == '!') { + wasnot = true; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + } + + if (ftepp->token == TOKEN_OPERATOR && !strcmp(ftepp_tokval(ftepp), "-")) + { + wasneg = true; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + } + + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + if (!strcmp(ftepp_tokval(ftepp), "defined")) { + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != '(') { + ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis"); + return false; + } + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != TOKEN_IDENT && + ftepp->token != TOKEN_TYPENAME && + ftepp->token != TOKEN_KEYWORD) + { + ftepp_error(ftepp, "defined() used on an unexpected token type"); + return false; + } + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + *out = !!macro; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != ')') { + ftepp_error(ftepp, "expected closing paren"); + return false; + } + break; + } + + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + if (!macro || !vec_size(macro->output)) { + *out = false; + *value_out = 0; + } else { + /* This does not expand recursively! */ + switch (macro->output[0]->token) { + case TOKEN_INTCONST: + *value_out = macro->output[0]->constval.i; + *out = !!(macro->output[0]->constval.i); + break; + case TOKEN_FLOATCONST: + *value_out = macro->output[0]->constval.f; + *out = !!(macro->output[0]->constval.f); + break; + default: + *out = false; + break; + } + } + break; + case TOKEN_STRINGCONST: + *value_out = 0; + *out = false; + break; + case TOKEN_INTCONST: + *value_out = ftepp->lex->tok.constval.i; + *out = !!(ftepp->lex->tok.constval.i); + break; + case TOKEN_FLOATCONST: + *value_out = ftepp->lex->tok.constval.f; + *out = !!(ftepp->lex->tok.constval.f); + break; + + case '(': + ftepp_next(ftepp); + if (!ftepp_if_expr(ftepp, out, value_out)) + return false; + if (ftepp->token != ')') { + ftepp_error(ftepp, "expected closing paren in #if expression"); + return false; + } + break; + + default: + ftepp_error(ftepp, "junk in #if: `%s` ...", ftepp_tokval(ftepp)); + if (OPTS_OPTION_BOOL(OPTION_DEBUG)) + ftepp_error(ftepp, "internal: token %i\n", ftepp->token); + return false; + } + if (wasneg) + *value_out = -*value_out; + if (wasnot) { + *out = !*out; + *value_out = (*out ? 1 : 0); + } + return true; +} + +/* +static bool ftepp_if_nextvalue(ftepp_t *ftepp, bool *out, double *value_out) +{ + if (!ftepp_next(ftepp)) + return false; + return ftepp_if_value(ftepp, out, value_out); +} +*/ + +static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out) +{ + if (!ftepp_if_value(ftepp, out, value_out)) + return false; + + if (!ftepp_if_op(ftepp)) + return false; + + if (ftepp->token == ')' || ftepp->token != TOKEN_OPERATOR) + return true; + + /* FTEQCC is all right-associative and no precedence here */ + if (!strcmp(ftepp_tokval(ftepp), "&&") || + !strcmp(ftepp_tokval(ftepp), "||")) + { + bool next = false; + char opc = ftepp_tokval(ftepp)[0]; + double nextvalue; + + (void)nextvalue; + if (!ftepp_next(ftepp)) + return false; + if (!ftepp_if_expr(ftepp, &next, &nextvalue)) + return false; + + if (opc == '&') + *out = *out && next; + else + *out = *out || next; + + *value_out = (*out ? 1 : 0); + return true; + } + else if (!strcmp(ftepp_tokval(ftepp), "==") || + !strcmp(ftepp_tokval(ftepp), "!=") || + !strcmp(ftepp_tokval(ftepp), ">=") || + !strcmp(ftepp_tokval(ftepp), "<=") || + !strcmp(ftepp_tokval(ftepp), ">") || + !strcmp(ftepp_tokval(ftepp), "<")) + { + bool next = false; + const char opc0 = ftepp_tokval(ftepp)[0]; + const char opc1 = ftepp_tokval(ftepp)[1]; + double other; + + if (!ftepp_next(ftepp)) + return false; + if (!ftepp_if_expr(ftepp, &next, &other)) + return false; + + if (opc0 == '=') + *out = (*value_out == other); + else if (opc0 == '!') + *out = (*value_out != other); + else if (opc0 == '>') { + if (opc1 == '=') *out = (*value_out >= other); + else *out = (*value_out > other); + } + else if (opc0 == '<') { + if (opc1 == '=') *out = (*value_out <= other); + else *out = (*value_out < other); + } + *value_out = (*out ? 1 : 0); + + return true; + } + else { + ftepp_error(ftepp, "junk after #if"); + return false; + } +} + +static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond) +{ + bool result = false; + double dummy = 0; + + memset(cond, 0, sizeof(*cond)); + (void)ftepp_next(ftepp); + + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token == TOKEN_EOL) { + ftepp_error(ftepp, "expected expression for #if-directive"); + return false; + } + + if (!ftepp_if_expr(ftepp, &result, &dummy)) + return false; + + cond->on = result; + return true; +} + +/** + * ifdef is rather simple + */ +static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond) +{ + ppmacro *macro; + memset(cond, 0, sizeof(*cond)); + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + break; + default: + ftepp_error(ftepp, "expected macro name"); + return false; + } + + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + /* relaxing this condition + if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { + ftepp_error(ftepp, "stray tokens after #ifdef"); + return false; + } + */ + cond->on = !!macro; + return true; +} + +/** + * undef is also simple + */ +static bool ftepp_undef(ftepp_t *ftepp) +{ + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + if (ftepp->output_on) { + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); + break; + default: + ftepp_error(ftepp, "expected macro name"); + return false; + } + } + + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + /* relaxing this condition + if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { + ftepp_error(ftepp, "stray tokens after #ifdef"); + return false; + } + */ + return true; +} + +/* Special unescape-string function which skips a leading quote + * and stops at a quote, not just at \0 + */ +static void unescape(const char *str, char *out) { + ++str; + while (*str && *str != '"') { + if (*str == '\\') { + ++str; + switch (*str) { + case '\\': *out++ = *str; break; + case '"': *out++ = *str; break; + case 'a': *out++ = '\a'; break; + case 'b': *out++ = '\b'; break; + case 'r': *out++ = '\r'; break; + case 'n': *out++ = '\n'; break; + case 't': *out++ = '\t'; break; + case 'f': *out++ = '\f'; break; + case 'v': *out++ = '\v'; break; + default: + *out++ = '\\'; + *out++ = *str; + break; + } + ++str; + continue; + } + + *out++ = *str++; + } + *out = 0; +} + +static char *ftepp_include_find_path(const char *file, const char *pathfile) +{ + FILE *fp; + char *filename = NULL; + const char *last_slash; + size_t len; + + if (!pathfile) + return NULL; + + last_slash = strrchr(pathfile, '/'); + + if (last_slash) { + len = last_slash - pathfile; + memcpy(vec_add(filename, len), pathfile, len); + vec_push(filename, '/'); + } + + len = strlen(file); + memcpy(vec_add(filename, len+1), file, len); + vec_last(filename) = 0; + + fp = fopen(filename, "rb"); + if (fp) { + fclose(fp); + return filename; + } + vec_free(filename); + return NULL; +} + +static char *ftepp_include_find(ftepp_t *ftepp, const char *file) +{ + char *filename = NULL; + + filename = ftepp_include_find_path(file, ftepp->includename); + if (!filename) + filename = ftepp_include_find_path(file, ftepp->itemname); + return filename; +} + +static bool ftepp_directive_warning(ftepp_t *ftepp) { + char *message = NULL; + + if (!ftepp_skipspace(ftepp)) + return false; + + /* handle the odd non string constant case so it works like C */ + if (ftepp->token != TOKEN_STRINGCONST) { + bool store = false; + vec_append(message, 8, "#warning"); + ftepp_next(ftepp); + while (ftepp->token != TOKEN_EOL) { + vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp)); + ftepp_next(ftepp); + } + vec_push(message, '\0'); + if (ftepp->output_on) + store = ftepp_warn(ftepp, WARN_CPP, message); + else + store = false; + vec_free(message); + return store; + } + + if (!ftepp->output_on) + return false; + + unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp)); + return ftepp_warn(ftepp, WARN_CPP, "#warning %s", ftepp_tokval(ftepp)); +} + +static void ftepp_directive_error(ftepp_t *ftepp) { + char *message = NULL; + + if (!ftepp_skipspace(ftepp)) + return; + + /* handle the odd non string constant case so it works like C */ + if (ftepp->token != TOKEN_STRINGCONST) { + vec_append(message, 6, "#error"); + ftepp_next(ftepp); + while (ftepp->token != TOKEN_EOL) { + vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp)); + ftepp_next(ftepp); + } + vec_push(message, '\0'); + if (ftepp->output_on) + ftepp_error(ftepp, message); + vec_free(message); + return; + } + + if (!ftepp->output_on) + return; + + unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp)); + ftepp_error(ftepp, "#error %s", ftepp_tokval(ftepp)); +} + +static void ftepp_directive_message(ftepp_t *ftepp) { + char *message = NULL; + + if (!ftepp_skipspace(ftepp)) + return; + + /* handle the odd non string constant case so it works like C */ + if (ftepp->token != TOKEN_STRINGCONST) { + vec_append(message, 8, "#message"); + ftepp_next(ftepp); + while (ftepp->token != TOKEN_EOL) { + vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp)); + ftepp_next(ftepp); + } + vec_push(message, '\0'); + if (ftepp->output_on) + con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", message); + vec_free(message); + return; + } + + if (!ftepp->output_on) + return; + + unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp)); + con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", ftepp_tokval(ftepp)); +} + +/** + * Include a file. + * FIXME: do we need/want a -I option? + * FIXME: what about when dealing with files in subdirectories coming from a progs.src? + */ +static bool ftepp_include(ftepp_t *ftepp) +{ + lex_file *old_lexer = ftepp->lex; + lex_file *inlex; + lex_ctx_t ctx; + char lineno[128]; + char *filename; + char *parsename = NULL; + char *old_includename; + + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + if (ftepp->token != TOKEN_STRINGCONST) { + 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) { + (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); + ftepp_out(ftepp, "\n#pragma file(", false); + ftepp_out(ftepp, parsename, false); + ftepp_out(ftepp, ")\n#pragma line(1)\n", false); + + filename = ftepp_include_find(ftepp, parsename); + if (!filename) { + 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); + vec_free(filename); + return false; + } + ftepp->lex = inlex; + old_includename = ftepp->includename; + ftepp->includename = filename; + if (!ftepp_preprocess(ftepp)) { + vec_free(ftepp->includename); + ftepp->includename = old_includename; + lex_close(ftepp->lex); + ftepp->lex = old_lexer; + return false; + } + vec_free(ftepp->includename); + ftepp->includename = old_includename; + lex_close(ftepp->lex); + ftepp->lex = old_lexer; + + ftepp_out(ftepp, "\n#pragma file(", false); + ftepp_out(ftepp, ctx.file, false); + util_snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1)); + ftepp_out(ftepp, lineno, false); + + /* skip the line */ + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != TOKEN_EOL) { + ftepp_error(ftepp, "stray tokens after #include"); + return false; + } + (void)ftepp_next(ftepp); + + return true; +} + +/* Basic structure handlers */ +static bool ftepp_else_allowed(ftepp_t *ftepp) +{ + if (!vec_size(ftepp->conditions)) { + ftepp_error(ftepp, "#else without #if"); + return false; + } + if (vec_last(ftepp->conditions).had_else) { + ftepp_error(ftepp, "multiple #else for a single #if"); + return false; + } + return true; +} + +static GMQCC_INLINE void ftepp_inmacro(ftepp_t *ftepp, const char *hash) { + if (ftepp->in_macro) + (void)!ftepp_warn(ftepp, WARN_DIRECTIVE_INMACRO, "`#%s` directive in macro", hash); +} + +static bool ftepp_hash(ftepp_t *ftepp) +{ + ppcondition cond; + ppcondition *pc; + + lex_ctx_t ctx = ftepp_ctx(ftepp); + + if (!ftepp_skipspace(ftepp)) + return false; + + switch (ftepp->token) { + case TOKEN_KEYWORD: + case TOKEN_IDENT: + case TOKEN_TYPENAME: + if (!strcmp(ftepp_tokval(ftepp), "define")) { + ftepp_inmacro(ftepp, "define"); + return ftepp_define(ftepp); + } + else if (!strcmp(ftepp_tokval(ftepp), "undef")) { + ftepp_inmacro(ftepp, "undef"); + return ftepp_undef(ftepp); + } + else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) { + ftepp_inmacro(ftepp, "ifdef"); + if (!ftepp_ifdef(ftepp, &cond)) + return false; + cond.was_on = cond.on; + vec_push(ftepp->conditions, cond); + ftepp->output_on = ftepp->output_on && cond.on; + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) { + ftepp_inmacro(ftepp, "ifndef"); + if (!ftepp_ifdef(ftepp, &cond)) + return false; + cond.on = !cond.on; + cond.was_on = cond.on; + vec_push(ftepp->conditions, cond); + ftepp->output_on = ftepp->output_on && cond.on; + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) { + ftepp_inmacro(ftepp, "elifdef"); + if (!ftepp_else_allowed(ftepp)) + return false; + if (!ftepp_ifdef(ftepp, &cond)) + return false; + pc = &vec_last(ftepp->conditions); + pc->on = !pc->was_on && cond.on; + pc->was_on = pc->was_on || pc->on; + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) { + ftepp_inmacro(ftepp, "elifndef"); + if (!ftepp_else_allowed(ftepp)) + return false; + if (!ftepp_ifdef(ftepp, &cond)) + return false; + cond.on = !cond.on; + pc = &vec_last(ftepp->conditions); + pc->on = !pc->was_on && cond.on; + pc->was_on = pc->was_on || pc->on; + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "elif")) { + ftepp_inmacro(ftepp, "elif"); + if (!ftepp_else_allowed(ftepp)) + return false; + if (!ftepp_if(ftepp, &cond)) + return false; + pc = &vec_last(ftepp->conditions); + pc->on = !pc->was_on && cond.on; + pc->was_on = pc->was_on || pc->on; + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "if")) { + ftepp_inmacro(ftepp, "if"); + if (!ftepp_if(ftepp, &cond)) + return false; + cond.was_on = cond.on; + vec_push(ftepp->conditions, cond); + ftepp->output_on = ftepp->output_on && cond.on; + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "else")) { + ftepp_inmacro(ftepp, "else"); + if (!ftepp_else_allowed(ftepp)) + return false; + pc = &vec_last(ftepp->conditions); + pc->on = !pc->was_on; + pc->had_else = true; + ftepp_next(ftepp); + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "endif")) { + ftepp_inmacro(ftepp, "endif"); + if (!vec_size(ftepp->conditions)) { + ftepp_error(ftepp, "#endif without #if"); + return false; + } + vec_pop(ftepp->conditions); + ftepp_next(ftepp); + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "include")) { + ftepp_inmacro(ftepp, "include"); + return ftepp_include(ftepp); + } + else if (!strcmp(ftepp_tokval(ftepp), "pragma")) { + ftepp_out(ftepp, "#", false); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "warning")) { + ftepp_directive_warning(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "error")) { + ftepp_directive_error(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "message")) { + ftepp_directive_message(ftepp); + break; + } + else { + if (ftepp->output_on) { + ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp)); + return false; + } else { + ftepp_next(ftepp); + break; + } + } + /* break; never reached */ + default: + ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp)); + return false; + case TOKEN_EOL: + ftepp_errorat(ftepp, ctx, "empty preprocessor directive"); + return false; + case TOKEN_EOF: + ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp)); + return false; + + /* Builtins! Don't forget the builtins! */ + case TOKEN_INTCONST: + case TOKEN_FLOATCONST: + ftepp_out(ftepp, "#", false); + return true; + } + if (!ftepp_skipspace(ftepp)) + return false; + return true; +} + +static bool ftepp_preprocess(ftepp_t *ftepp) +{ + ppmacro *macro; + bool newline = true; + + /* predef stuff */ + char *expand = NULL; + + ftepp->lex->flags.preprocessing = true; + ftepp->lex->flags.mergelines = false; + ftepp->lex->flags.noops = true; + + ftepp_next(ftepp); + do + { + if (ftepp->token >= TOKEN_EOF) + break; + switch (ftepp->token) { + case TOKEN_KEYWORD: + case TOKEN_IDENT: + case TOKEN_TYPENAME: + /* is it a predef? */ + if (OPTS_FLAG(FTEPP_PREDEFS)) { + char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp)); + if (predef) { + expand = predef(ftepp); + ftepp_out (ftepp, expand, false); + ftepp_next(ftepp); + + mem_d(expand); + break; + } + } + + if (ftepp->output_on) + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + else + macro = NULL; + + if (!macro) { + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_next(ftepp); + break; + } + if (!ftepp_macro_call(ftepp, macro)) + ftepp->token = TOKEN_ERROR; + break; + case '#': + if (!newline) { + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_next(ftepp); + break; + } + ftepp->lex->flags.mergelines = true; + if (ftepp_next(ftepp) >= TOKEN_EOF) { + ftepp_error(ftepp, "error in preprocessor directive"); + ftepp->token = TOKEN_ERROR; + break; + } + if (!ftepp_hash(ftepp)) + ftepp->token = TOKEN_ERROR; + ftepp->lex->flags.mergelines = false; + break; + case TOKEN_EOL: + newline = true; + ftepp_out(ftepp, "\n", true); + ftepp_next(ftepp); + break; + case TOKEN_WHITE: + /* same as default but don't set newline=false */ + ftepp_out(ftepp, ftepp_tokval(ftepp), true); + ftepp_next(ftepp); + break; + default: + newline = false; + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_next(ftepp); + break; + } + } while (!ftepp->errors && ftepp->token < TOKEN_EOF); + + /* force a 0 at the end but don't count it as added to the output */ + vec_push(ftepp->output_string, 0); + vec_shrinkby(ftepp->output_string, 1); + + return (ftepp->token == TOKEN_EOF); +} + +/* Like in parser.c - files keep the previous state so we have one global + * preprocessor. Except here we will want to warn about dangling #ifs. + */ +static bool ftepp_preprocess_done(ftepp_t *ftepp) +{ + bool retval = true; + if (vec_size(ftepp->conditions)) { + if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?")) + retval = false; + } + lex_close(ftepp->lex); + ftepp->lex = NULL; + if (ftepp->itemname) { + mem_d(ftepp->itemname); + ftepp->itemname = NULL; + } + return retval; +} + +bool ftepp_preprocess_file(ftepp_t *ftepp, const char *filename) +{ + ftepp->lex = lex_open(filename); + ftepp->itemname = util_strdup(filename); + if (!ftepp->lex) { + con_out("failed to open file \"%s\"\n", filename); + return false; + } + if (!ftepp_preprocess(ftepp)) + return false; + return ftepp_preprocess_done(ftepp); +} + +bool ftepp_preprocess_string(ftepp_t *ftepp, const char *name, const char *str) +{ + ftepp->lex = lex_open_string(str, strlen(str), name); + ftepp->itemname = util_strdup(name); + if (!ftepp->lex) { + con_out("failed to create lexer for string \"%s\"\n", name); + return false; + } + if (!ftepp_preprocess(ftepp)) + return false; + return ftepp_preprocess_done(ftepp); +} + + +void ftepp_add_macro(ftepp_t *ftepp, const char *name, const char *value) { + char *create = NULL; + + /* use saner path for empty macros */ + if (!value) { + ftepp_add_define(ftepp, "__builtin__", name); + return; + } + + vec_append(create, 8, "#define "); + vec_append(create, strlen(name), name); + vec_push (create, ' '); + vec_append(create, strlen(value), value); + vec_push (create, 0); + + ftepp_preprocess_string(ftepp, "__builtin__", create); + vec_free (create); +} + +ftepp_t *ftepp_create() +{ + ftepp_t *ftepp; + char minor[32]; + char major[32]; + size_t i; + + ftepp = ftepp_new(); + if (!ftepp) + return NULL; + + memset(minor, 0, sizeof(minor)); + memset(major, 0, sizeof(major)); + + /* set the right macro based on the selected standard */ + ftepp_add_define(ftepp, NULL, "GMQCC"); + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) { + ftepp_add_define(ftepp, NULL, "__STD_FTEQCC__"); + /* 1.00 */ + major[0] = '"'; + major[1] = '1'; + major[2] = '"'; + + minor[0] = '"'; + minor[1] = '0'; + minor[2] = '"'; + } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) { + ftepp_add_define(ftepp, NULL, "__STD_GMQCC__"); + util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR); + util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR); + } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCCX) { + ftepp_add_define(ftepp, NULL, "__STD_QCCX__"); + util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR); + util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR); + } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { + ftepp_add_define(ftepp, NULL, "__STD_QCC__"); + /* 1.0 */ + major[0] = '"'; + major[1] = '1'; + major[2] = '"'; + + minor[0] = '"'; + minor[1] = '0'; + minor[2] = '"'; + } + + ftepp_add_macro(ftepp, "__STD_VERSION_MINOR__", minor); + ftepp_add_macro(ftepp, "__STD_VERSION_MAJOR__", major); + + /* + * We're going to just make __NULL__ nil, which works for 60% of the + * cases of __NULL_ for fteqcc. + */ + ftepp_add_macro(ftepp, "__NULL__", "nil"); + + /* add all the math constants if they can be */ + if (OPTS_FLAG(FTEPP_MATHDEFS)) { + for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++) + if (!ftepp_macro_find(ftepp, ftepp_math_constants[i][0])) + ftepp_add_macro(ftepp, ftepp_math_constants[i][0], ftepp_math_constants[i][1]); + } + + return ftepp; +} + +void ftepp_add_define(ftepp_t *ftepp, const char *source, const char *name) +{ + ppmacro *macro; + lex_ctx_t ctx = { "__builtin__", 0, 0 }; + ctx.file = source; + macro = ppmacro_new(ctx, name); + /*vec_push(ftepp->macros, macro);*/ + util_htset(ftepp->macros, name, macro); +} + +const char *ftepp_get(ftepp_t *ftepp) +{ + return ftepp->output_string; +} + +void ftepp_flush(ftepp_t *ftepp) +{ + ftepp_flush_do(ftepp); +} + +void ftepp_finish(ftepp_t *ftepp) +{ + if (!ftepp) + return; + ftepp_delete(ftepp); +} diff --git a/intrin.c b/intrin.c deleted file mode 100644 index 9ef0207..0000000 --- a/intrin.c +++ /dev/null @@ -1,2082 +0,0 @@ -#include -#include "parser.h" - -#define intrin_ctx(I) parser_ctx((I)->parser) - -static GMQCC_INLINE ast_function *intrin_value(intrin_t *intrin, ast_value **out, const char *name, qcint_t vtype) { - ast_value *value = NULL; - ast_function *func = NULL; - char buffer[1024]; - char stype [1024]; - - util_snprintf(buffer, sizeof(buffer), "__builtin_%s", name); - util_snprintf(stype, sizeof(stype), "<%s>", type_name[vtype]); - - value = ast_value_new(intrin_ctx(intrin), buffer, TYPE_FUNCTION); - value->intrinsic = true; - value->expression.next = (ast_expression*)ast_value_new(intrin_ctx(intrin), stype, vtype); - func = ast_function_new(intrin_ctx(intrin), buffer, value); - value->expression.flags |= AST_FLAG_ERASEABLE; - - *out = value; - return func; -} - -static GMQCC_INLINE void intrin_reg(intrin_t *intrin, ast_value *const value, ast_function *const func) { - vec_push(intrin->parser->functions, func); - vec_push(intrin->parser->globals, (ast_expression*)value); -} - -#define QC_POW_EPSILON 0.00001f - -/* - * since some intrinsics depend on each other there is the possibility - * that an intrinsic will fail to get a 'depended' function that a - * builtin needs, causing some dependency in the chain to have a NULL - * function. This will cause a segmentation fault at code generation, - * even though an error was raised. To contiue to allow it (instead - * of stopping compilation right away). We need to return from the - * parser, before compilation stops after all the collected errors. - */ -static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from); -static ast_expression *intrin_nullfunc(intrin_t *intrin) { - ast_value *value = NULL; - ast_function *func = intrin_value(intrin, &value, NULL, TYPE_VOID); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_isfinite(intrin_t *intrin) { - /* - * float isfinite(float x) { - * return !(isnan(x) || isinf(x)); - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_function *func = intrin_value(intrin, &value, "isfinite", TYPE_FLOAT); - ast_call *callisnan = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "isnan", "isfinite")); - ast_call *callisinf = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "isinf", "isfinite")); - ast_block *block = ast_block_new(intrin_ctx(intrin)); - - /* float x; */ - vec_push(value->expression.params, x); - - /* = isnan(x); */ - vec_push(callisnan->params, (ast_expression*)x); - - /* = isinf(x); */ - vec_push(callisinf->params, (ast_expression*)x); - - /* return (! || ); */ - vec_push(block->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_unary_new( - intrin_ctx(intrin), - INSTR_NOT_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_OR, - (ast_expression*)callisnan, - (ast_expression*)callisinf - ) - ) - ) - ); - - vec_push(func->blocks, block); - intrin_reg(intrin, value, func); - - return (ast_expression*)value;; -} - -static ast_expression *intrin_isinf(intrin_t *intrin) { - /* - * float isinf(float x) { - * return (x != 0.0) && (x + x == x); - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "isinf", TYPE_FLOAT); - - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_AND, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_NE_F, - (ast_expression*)x, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_EQ_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)x, - (ast_expression*)x - ), - (ast_expression*)x - ) - ) - ) - ); - - vec_push(value->expression.params, x); - vec_push(func->blocks, body); - - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_isnan(intrin_t *intrin) { - /* - * float isnan(float x) { - * float local; - * local = x; - * - * return (x != local); - * } - */ - ast_value *value = NULL; - ast_value *arg1 = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_value *local = ast_value_new(intrin_ctx(intrin), "local", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "isnan", TYPE_FLOAT); - - vec_push(body->locals, local); - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)local, - (ast_expression*)arg1 - ) - ); - - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_NE_F, - (ast_expression*)arg1, - (ast_expression*)local - ) - ) - ); - - vec_push(value->expression.params, arg1); - vec_push(func->blocks, body); - - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_isnormal(intrin_t *intrin) { - /* - * float isnormal(float x) { - * return isfinite(x); - * } - */ - ast_value *value = NULL; - ast_call *callisfinite = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "isfinite", "isnormal")); - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "isnormal", TYPE_FLOAT); - - vec_push(value->expression.params, x); - vec_push(callisfinite->params, (ast_expression*)x); - - /* return */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)callisfinite - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_signbit(intrin_t *intrin) { - /* - * float signbit(float x) { - * return (x < 0); - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "signbit", TYPE_FLOAT); - - vec_push(value->expression.params, x); - - /* return (x < 0); */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_ternary_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LT, - (ast_expression*)x, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)intrin->fold->imm_float[0] - ) - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_acosh(intrin_t *intrin) { - /* - * float acosh(float x) { - * return log(x + sqrt((x * x) - 1)); - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_call *calllog = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "acosh")); - ast_call *callsqrt = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "acosh")); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "acosh", TYPE_FLOAT); - - vec_push(value->expression.params, x); - - /* = sqrt((x * x) - 1); */ - vec_push(callsqrt->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_SUB_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)x, - (ast_expression*)x - ), - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - /* = log(x + ); */ - vec_push(calllog->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)x, - (ast_expression*)callsqrt - ) - ); - - /* return ; */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)calllog - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_asinh(intrin_t *intrin) { - /* - * float asinh(float x) { - * return log(x + sqrt((x * x) + 1)); - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_call *calllog = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "asinh")); - ast_call *callsqrt = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "asinh")); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "asinh", TYPE_FLOAT); - - vec_push(value->expression.params, x); - - /* = sqrt((x * x) + 1); */ - vec_push(callsqrt->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)x, - (ast_expression*)x - ), - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - /* = log(x + ); */ - vec_push(calllog->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)x, - (ast_expression*)callsqrt - ) - ); - - /* return ; */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)calllog - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_atanh(intrin_t *intrin) { - /* - * float atanh(float x) { - * return 0.5 * log((1 + x) / (1 - x)) - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_call *calllog = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "atanh")); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "atanh", TYPE_FLOAT); - - vec_push(value->expression.params, x); - - /* = log((1 + x) / (1 - x)); */ - vec_push(calllog->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)x - ), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_SUB_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)x - ) - ) - ); - - /* return 0.5 * ; */ - vec_push(body->exprs, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)fold_constgen_float(intrin->fold, 0.5, false), - (ast_expression*)calllog - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_exp(intrin_t *intrin) { - /* - * float exp(float x) { - * float sum = 1.0; - * float acc = 1.0; - * float i; - * for (i = 1; i < 200; ++i) - * sum += (acc *= x / i); - * - * return sum; - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_value *sum = ast_value_new(intrin_ctx(intrin), "sum", TYPE_FLOAT); - ast_value *acc = ast_value_new(intrin_ctx(intrin), "acc", TYPE_FLOAT); - ast_value *i = ast_value_new(intrin_ctx(intrin), "i", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "exp", TYPE_FLOAT); - - vec_push(value->expression.params, x); - vec_push(body->locals, sum); - vec_push(body->locals, acc); - vec_push(body->locals, i); - - /* sum = 1.0; */ - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)sum, - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - /* acc = 1.0; */ - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)acc, - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - /* - * for (i = 1; i < 200; ++i) - * sum += (acc *= x / i); - */ - vec_push(body->exprs, - (ast_expression*)ast_loop_new( - intrin_ctx(intrin), - /* i = 1; */ - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)i, - (ast_expression*)intrin->fold->imm_float[1] - ), - /* i < 200; */ - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LT, - (ast_expression*)i, - (ast_expression*)fold_constgen_float(intrin->fold, 200.0f, false) - ), - false, - NULL, - false, - /* ++i; */ - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_ADD_F, - (ast_expression*)i, - (ast_expression*)intrin->fold->imm_float[1] - ), - /* sum += (acc *= (x / i)) */ - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_ADD_F, - (ast_expression*)sum, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)acc, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)x, - (ast_expression*)i - ) - ) - ) - ) - ); - - /* return sum; */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)sum - ) - ); - - vec_push(func->blocks, body); - - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_exp2(intrin_t *intrin) { - /* - * float exp2(float x) { - * return pow(2, x); - * } - */ - ast_value *value = NULL; - ast_call *callpow = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "pow", "exp2")); - ast_value *arg1 = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "exp2", TYPE_FLOAT); - - vec_push(value->expression.params, arg1); - - vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]); - vec_push(callpow->params, (ast_expression*)arg1); - - /* return */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)callpow - ) - ); - - vec_push(func->blocks, body); - - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_expm1(intrin_t *intrin) { - /* - * float expm1(float x) { - * return exp(x) - 1; - * } - */ - ast_value *value = NULL; - ast_call *callexp = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "exp", "expm1")); - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "expm1", TYPE_FLOAT); - - vec_push(value->expression.params, x); - - /* = exp(x); */ - vec_push(callexp->params, (ast_expression*)x); - - /* return - 1; */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_SUB_F, - (ast_expression*)callexp, - (ast_expression*)intrin->fold->imm_float[1] - ) - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_pow(intrin_t *intrin) { - /* - * - * float pow(float base, float exp) { - * float result; - * float low; - * float high; - * float mid; - * float square; - * float accumulate; - * - * if (exp == 0.0) - * return 1; - * if (exp == 1.0) - * return base; - * if (exp < 0) - * return 1.0 / pow(base, -exp); - * if (exp >= 1) { - * result = pow(base, exp / 2); - * return result * result; - * } - * - * low = 0.0f; - * high = 1.0f; - * square = sqrt(base); - * accumulate = square; - * mid = high / 2.0f - * - * while (fabs(mid - exp) > QC_POW_EPSILON) { - * square = sqrt(square); - * if (mid < exp) { - * low = mid; - * accumulate *= square; - * } else { - * high = mid; - * accumulate *= (1.0f / square); - * } - * mid = (low + high) / 2; - * } - * return accumulate; - * } - */ - ast_value *value = NULL; - ast_function *func = intrin_value(intrin, &value, "pow", TYPE_FLOAT); - - /* prepare some calls for later */ - ast_call *callpow1 = ast_call_new(intrin_ctx(intrin), (ast_expression*)value); /* for pow(base, -exp) */ - ast_call *callpow2 = ast_call_new(intrin_ctx(intrin), (ast_expression*)value); /* for pow(vase, exp / 2) */ - ast_call *callsqrt1 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(base) */ - ast_call *callsqrt2 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(square) */ - ast_call *callfabs = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "fabs", "pow")); /* for fabs(mid - exp) */ - - /* prepare some blocks for later */ - ast_block *expgt1 = ast_block_new(intrin_ctx(intrin)); - ast_block *midltexp = ast_block_new(intrin_ctx(intrin)); - ast_block *midltexpelse = ast_block_new(intrin_ctx(intrin)); - ast_block *whileblock = ast_block_new(intrin_ctx(intrin)); - - /* float pow(float base, float exp) */ - ast_value *base = ast_value_new(intrin_ctx(intrin), "base", TYPE_FLOAT); - ast_value *exp = ast_value_new(intrin_ctx(intrin), "exp", TYPE_FLOAT); - /* { */ - ast_block *body = ast_block_new(intrin_ctx(intrin)); - - /* - * float result; - * float low; - * float high; - * float square; - * float accumulate; - * float mid; - */ - ast_value *result = ast_value_new(intrin_ctx(intrin), "result", TYPE_FLOAT); - ast_value *low = ast_value_new(intrin_ctx(intrin), "low", TYPE_FLOAT); - ast_value *high = ast_value_new(intrin_ctx(intrin), "high", TYPE_FLOAT); - ast_value *square = ast_value_new(intrin_ctx(intrin), "square", TYPE_FLOAT); - ast_value *accumulate = ast_value_new(intrin_ctx(intrin), "accumulate", TYPE_FLOAT); - ast_value *mid = ast_value_new(intrin_ctx(intrin), "mid", TYPE_FLOAT); - vec_push(body->locals, result); - vec_push(body->locals, low); - vec_push(body->locals, high); - vec_push(body->locals, square); - vec_push(body->locals, accumulate); - vec_push(body->locals, mid); - - vec_push(value->expression.params, base); - vec_push(value->expression.params, exp); - - /* - * if (exp == 0.0) - * return 1; - */ - vec_push(body->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_EQ_F, - (ast_expression*)exp, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)intrin->fold->imm_float[1] - ), - NULL - ) - ); - - /* - * if (exp == 1.0) - * return base; - */ - vec_push(body->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_EQ_F, - (ast_expression*)exp, - (ast_expression*)intrin->fold->imm_float[1] - ), - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)base - ), - NULL - ) - ); - - /* = pow(base, -exp) */ - vec_push(callpow1->params, (ast_expression*)base); - vec_push(callpow1->params, - (ast_expression*)ast_unary_new( - intrin_ctx(intrin), - VINSTR_NEG_F, - (ast_expression*)exp - ) - ); - - /* - * if (exp < 0) - * return 1.0 / ; - */ - vec_push(body->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LT, - (ast_expression*)exp, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)callpow1 - ) - ), - NULL - ) - ); - - /* = pow(base, exp / 2) */ - vec_push(callpow2->params, (ast_expression*)base); - vec_push(callpow2->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)exp, - (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ - ) - ); - - /* - * = { - * result = ; - * return result * result; - * } - */ - vec_push(expgt1->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)result, - (ast_expression*)callpow2 - ) - ); - vec_push(expgt1->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)result, - (ast_expression*)result - ) - ) - ); - - /* - * if (exp >= 1) { - * - * } - */ - vec_push(body->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_GE, - (ast_expression*)exp, - (ast_expression*)intrin->fold->imm_float[1] - ), - (ast_expression*)expgt1, - NULL - ) - ); - - /* - * = sqrt(base) - */ - vec_push(callsqrt1->params, (ast_expression*)base); - - /* - * low = 0.0f; - * high = 1.0f; - * square = sqrt(base); - * accumulate = square; - * mid = high / 2.0f; - */ - vec_push(body->exprs, - (ast_expression*)ast_store_new(intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)low, - (ast_expression*)intrin->fold->imm_float[0] - ) - ); - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)high, - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)square, - (ast_expression*)callsqrt1 - ) - ); - - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)accumulate, - (ast_expression*)square - ) - ); - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)mid, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)high, - (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ - ) - ) - ); - - /* - * = { - * low = mid; - * accumulate *= square; - * } - */ - vec_push(midltexp->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)low, - (ast_expression*)mid - ) - ); - vec_push(midltexp->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)accumulate, - (ast_expression*)square - ) - ); - - /* - * = { - * high = mid; - * accumulate *= (1.0 / square); - * } - */ - vec_push(midltexpelse->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)high, - (ast_expression*)mid - ) - ); - vec_push(midltexpelse->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)accumulate, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)square - ) - ) - ); - - /* - * = sqrt(square) - */ - vec_push(callsqrt2->params, (ast_expression*)square); - - /* - * = { - * square = ; - * if (mid < exp) - * ; - * else - * ; - * - * mid = (low + high) / 2; - * } - */ - vec_push(whileblock->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)square, - (ast_expression*)callsqrt2 - ) - ); - vec_push(whileblock->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LT, - (ast_expression*)mid, - (ast_expression*)exp - ), - (ast_expression*)midltexp, - (ast_expression*)midltexpelse - ) - ); - vec_push(whileblock->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)mid, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)low, - (ast_expression*)high - ), - (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ - ) - ) - ); - - /* - * = fabs(mid - exp) - */ - vec_push(callfabs->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_SUB_F, - (ast_expression*)mid, - (ast_expression*)exp - ) - ); - - /* - * while ( > epsilon) - * - */ - vec_push(body->exprs, - (ast_expression*)ast_loop_new( - intrin_ctx(intrin), - /* init */ - NULL, - /* pre condition */ - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_GT, - (ast_expression*)callfabs, - (ast_expression*)fold_constgen_float(intrin->fold, QC_POW_EPSILON, false) - ), - /* pre not */ - false, - /* post condition */ - NULL, - /* post not */ - false, - /* increment expression */ - NULL, - /* code block */ - (ast_expression*)whileblock - ) - ); - - /* return accumulate */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)accumulate - ) - ); - - /* } */ - vec_push(func->blocks, body); - - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_mod(intrin_t *intrin) { - /* - * float mod(float a, float b) { - * float div = a / b; - * float sign = (div < 0.0f) ? -1 : 1; - * return a - b * sign * floor(sign * div); - * } - */ - ast_value *value = NULL; - ast_call *call = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", "mod")); - ast_value *a = ast_value_new(intrin_ctx(intrin), "a", TYPE_FLOAT); - ast_value *b = ast_value_new(intrin_ctx(intrin), "b", TYPE_FLOAT); - ast_value *div = ast_value_new(intrin_ctx(intrin), "div", TYPE_FLOAT); - ast_value *sign = ast_value_new(intrin_ctx(intrin), "sign", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "mod", TYPE_FLOAT); - - vec_push(value->expression.params, a); - vec_push(value->expression.params, b); - - vec_push(body->locals, div); - vec_push(body->locals, sign); - - /* div = a / b; */ - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)div, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)a, - (ast_expression*)b - ) - ) - ); - - /* sign = (div < 0.0f) ? -1 : 1; */ - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)sign, - (ast_expression*)ast_ternary_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LT, - (ast_expression*)div, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)intrin->fold->imm_float[2], - (ast_expression*)intrin->fold->imm_float[1] - ) - ) - ); - - /* floor(sign * div) */ - vec_push(call->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)sign, - (ast_expression*)div - ) - ); - - /* return a - b * sign * */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_SUB_F, - (ast_expression*)a, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)b, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)sign, - (ast_expression*)call - ) - ) - ) - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_fabs(intrin_t *intrin) { - /* - * float fabs(float x) { - * return x < 0 ? -x : x; - * } - */ - ast_value *value = NULL; - ast_value *arg1 = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "fabs", TYPE_FLOAT); - - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_ternary_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LE, - (ast_expression*)arg1, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)ast_unary_new( - intrin_ctx(intrin), - VINSTR_NEG_F, - (ast_expression*)arg1 - ), - (ast_expression*)arg1 - ) - ) - ); - - vec_push(value->expression.params, arg1); - vec_push(func->blocks, body); - - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_epsilon(intrin_t *intrin) { - /* - * float epsilon(void) { - * float eps = 1.0f; - * do { eps /= 2.0f; } while ((1.0f + (eps / 2.0f)) != 1.0f); - * return eps; - * } - */ - ast_value *value = NULL; - ast_value *eps = ast_value_new(intrin_ctx(intrin), "eps", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, "epsilon", TYPE_FLOAT); - - vec_push(body->locals, eps); - - /* eps = 1.0f; */ - vec_push(body->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)eps, - (ast_expression*)intrin->fold->imm_float[0] - ) - ); - - vec_push(body->exprs, - (ast_expression*)ast_loop_new( - intrin_ctx(intrin), - NULL, - NULL, - false, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_NE_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)eps, - (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ - ) - ), - (ast_expression*)intrin->fold->imm_float[1] - ), - false, - NULL, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_DIV_F, - (ast_expression*)eps, - (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ - ) - ) - ); - - /* return eps; */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)eps - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_nan(intrin_t *intrin) { - /* - * float nan(void) { - * float x = 0.0f; - * return x / x; - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_function *func = intrin_value(intrin, &value, "nan", TYPE_FLOAT); - ast_block *block = ast_block_new(intrin_ctx(intrin)); - - vec_push(block->locals, x); - - vec_push(block->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)x, - (ast_expression*)intrin->fold->imm_float[0] - ) - ); - - vec_push(block->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)x, - (ast_expression*)x - ) - ) - ); - - vec_push(func->blocks, block); - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_inf(intrin_t *intrin) { - /* - * float inf(void) { - * float x = 1.0f; - * float y = 0.0f; - * return x / y; - * } - */ - ast_value *value = NULL; - ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_value *y = ast_value_new(intrin_ctx(intrin), "y", TYPE_FLOAT); - ast_function *func = intrin_value(intrin, &value, "inf", TYPE_FLOAT); - ast_block *block = ast_block_new(intrin_ctx(intrin)); - size_t i; - - vec_push(block->locals, x); - vec_push(block->locals, y); - - /* to keep code size down */ - for (i = 0; i <= 1; i++) { - vec_push(block->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((i == 0) ? x : y), - (ast_expression*)intrin->fold->imm_float[i] - ) - ); - } - - vec_push(block->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)x, - (ast_expression*)y - ) - ) - ); - - vec_push(func->blocks, block); - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_ln(intrin_t *intrin) { - /* - * float log(float power, float base) { - * float whole; - * float nth - * float sign = 1.0f; - * float eps = epsilon(); - * - * if (power <= 1.0f || bbase <= 1.0) { - * if (power <= 0.0f || base <= 0.0f) - * return nan(); - * - * if (power < 1.0f) { - * power = 1.0f / power; - * sign *= -1.0f; - * } - * - * if (base < 1.0f) { - * sign *= -1.0f; - * base = 1.0f / base; - * } - * } - * - * float A_i = 1; - * float B_i = 0; - * float A_iminus1 = 0; - * float B_iminus1 = 1; - * - * for (;;) { - * whole = power; - * nth = 0.0f; - * - * while (whole >= base) { - * float base2 = base; - * float n2 = 1.0f; - * float newbase2 = base2 * base2; - * - * while (whole >= newbase2) { - * base2 = newbase2; - * n2 *= 2; - * newbase2 *= newbase2; - * } - * - * whole /= base2; - * nth += n2; - * } - * - * float b_iplus1 = n; - * float A_iplus1 = b_iplus1 * A_i + A_iminus1; - * float B_iplus1 = b_iplus1 * B_i + B_iminus1; - * - * A_iminus1 = A_i; - * B_iminus1 = B_i; - * A_i = A_iplus1; - * B_i = B_iplus1; - * - * if (whole <= 1.0f + eps) - * break; - * - * power = base; - * bower = whole; - * } - * return sign * A_i / B_i; - * } - */ - - ast_value *value = NULL; - ast_value *power = ast_value_new(intrin_ctx(intrin), "power", TYPE_FLOAT); - ast_value *base = ast_value_new(intrin_ctx(intrin), "base", TYPE_FLOAT); - ast_value *whole = ast_value_new(intrin_ctx(intrin), "whole", TYPE_FLOAT); - ast_value *nth = ast_value_new(intrin_ctx(intrin), "nth", TYPE_FLOAT); - ast_value *sign = ast_value_new(intrin_ctx(intrin), "sign", TYPE_FLOAT); - ast_value *A_i = ast_value_new(intrin_ctx(intrin), "A_i", TYPE_FLOAT); - ast_value *B_i = ast_value_new(intrin_ctx(intrin), "B_i", TYPE_FLOAT); - ast_value *A_iminus1 = ast_value_new(intrin_ctx(intrin), "A_iminus1", TYPE_FLOAT); - ast_value *B_iminus1 = ast_value_new(intrin_ctx(intrin), "B_iminus1", TYPE_FLOAT); - ast_value *b_iplus1 = ast_value_new(intrin_ctx(intrin), "b_iplus1", TYPE_FLOAT); - ast_value *A_iplus1 = ast_value_new(intrin_ctx(intrin), "A_iplus1", TYPE_FLOAT); - ast_value *B_iplus1 = ast_value_new(intrin_ctx(intrin), "B_iplus1", TYPE_FLOAT); - ast_value *eps = ast_value_new(intrin_ctx(intrin), "eps", TYPE_FLOAT); - ast_value *base2 = ast_value_new(intrin_ctx(intrin), "base2", TYPE_FLOAT); - ast_value *n2 = ast_value_new(intrin_ctx(intrin), "n2", TYPE_FLOAT); - ast_value *newbase2 = ast_value_new(intrin_ctx(intrin), "newbase2", TYPE_FLOAT); - ast_block *block = ast_block_new(intrin_ctx(intrin)); - ast_block *plt1orblt1 = ast_block_new(intrin_ctx(intrin)); /* (power <= 1.0f || base <= 1.0f) */ - ast_block *plt1 = ast_block_new(intrin_ctx(intrin)); /* (power < 1.0f) */ - ast_block *blt1 = ast_block_new(intrin_ctx(intrin)); /* (base < 1.0f) */ - ast_block *forloop = ast_block_new(intrin_ctx(intrin)); /* for(;;) */ - ast_block *whileloop = ast_block_new(intrin_ctx(intrin)); /* while (whole >= base) */ - ast_block *nestwhile = ast_block_new(intrin_ctx(intrin)); /* while (whole >= newbase2) */ - ast_function *func = intrin_value(intrin, &value, "ln", TYPE_FLOAT); - size_t i; - - vec_push(value->expression.params, power); - vec_push(value->expression.params, base); - - vec_push(block->locals, whole); - vec_push(block->locals, nth); - vec_push(block->locals, sign); - vec_push(block->locals, eps); - vec_push(block->locals, A_i); - vec_push(block->locals, B_i); - vec_push(block->locals, A_iminus1); - vec_push(block->locals, B_iminus1); - - /* sign = 1.0f; */ - vec_push(block->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)sign, - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - /* eps = __builtin_epsilon(); */ - vec_push(block->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)eps, - (ast_expression*)ast_call_new( - intrin_ctx(intrin), - intrin_func_self(intrin, "__builtin_epsilon", "ln") - ) - ) - ); - - /* - * A_i = 1; - * B_i = 0; - * A_iminus1 = 0; - * B_iminus1 = 1; - */ - for (i = 0; i <= 1; i++) { - int j; - for (j = 1; j >= 0; j--) { - vec_push(block->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((j) ? ((i) ? B_iminus1 : A_i) - : ((i) ? A_iminus1 : B_i)), - (ast_expression*)intrin->fold->imm_float[j] - ) - ); - } - } - - /* - * = { - * power = 1.0f / power; - * sign *= -1.0f; - * } - * = { - * base = 1.0f / base; - * sign *= -1.0f; - * } - */ - for (i = 0; i <= 1; i++) { - vec_push(((i) ? blt1 : plt1)->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((i) ? base : power), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)((i) ? base : power) - ) - ) - ); - vec_push(plt1->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)sign, - (ast_expression*)intrin->fold->imm_float[2] - ) - ); - } - - /* - * = { - * if (power <= 0.0 || base <= 0.0f) - * return __builtin_nan(); - * if (power < 1.0f) - * - * if (base < 1.0f) - * - * } - */ - vec_push(plt1orblt1->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_OR, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LE, - (ast_expression*)power, - (ast_expression*)intrin->fold->imm_float[0] - ), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LE, - (ast_expression*)base, - (ast_expression*)intrin->fold->imm_float[0] - ) - ), - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_call_new( - intrin_ctx(intrin), - intrin_func_self(intrin, "__builtin_nan", "ln") - ) - ), - NULL - ) - ); - - for (i = 0; i <= 1; i++) { - vec_push(plt1orblt1->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LT, - (ast_expression*)((i) ? base : power), - (ast_expression*)intrin->fold->imm_float[1] - ), - (ast_expression*)((i) ? blt1 : plt1), - NULL - ) - ); - } - - vec_push(block->exprs, (ast_expression*)plt1orblt1); - - - /* whole = power; */ - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)whole, - (ast_expression*)power - ) - ); - - /* nth = 0.0f; */ - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)nth, - (ast_expression*)intrin->fold->imm_float[0] - ) - ); - - /* base2 = base; */ - vec_push(whileloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)base2, - (ast_expression*)base - ) - ); - - /* n2 = 1.0f; */ - vec_push(whileloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)n2, - (ast_expression*)intrin->fold->imm_float[1] - ) - ); - - /* newbase2 = base2 * base2; */ - vec_push(whileloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)newbase2, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)base2, - (ast_expression*)base2 - ) - ) - ); - - /* while loop locals */ - vec_push(whileloop->locals, base2); - vec_push(whileloop->locals, n2); - vec_push(whileloop->locals, newbase2); - - /* base2 = newbase2; */ - vec_push(nestwhile->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)base2, - (ast_expression*)newbase2 - ) - ); - - /* n2 *= 2; */ - vec_push(nestwhile->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)n2, - (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ - ) - ); - - /* newbase2 *= newbase2; */ - vec_push(nestwhile->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_MUL_F, - (ast_expression*)newbase2, - (ast_expression*)newbase2 - ) - ); - - /* while (whole >= newbase2) */ - vec_push(whileloop->exprs, - (ast_expression*)ast_loop_new( - intrin_ctx(intrin), - NULL, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_GE, - (ast_expression*)whole, - (ast_expression*)newbase2 - ), - false, - NULL, - false, - NULL, - (ast_expression*)nestwhile - ) - ); - - /* whole /= base2; */ - vec_push(whileloop->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_DIV_F, - (ast_expression*)whole, - (ast_expression*)base2 - ) - ); - - /* nth += n2; */ - vec_push(whileloop->exprs, - (ast_expression*)ast_binstore_new( - intrin_ctx(intrin), - INSTR_STORE_F, - INSTR_ADD_F, - (ast_expression*)nth, - (ast_expression*)n2 - ) - ); - - /* while (whole >= base) */ - vec_push(forloop->exprs, - (ast_expression*)ast_loop_new( - intrin_ctx(intrin), - NULL, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_GE, - (ast_expression*)whole, - (ast_expression*)base - ), - false, - NULL, - false, - NULL, - (ast_expression*)whileloop - ) - ); - - vec_push(forloop->locals, b_iplus1); - vec_push(forloop->locals, A_iplus1); - vec_push(forloop->locals, B_iplus1); - - /* b_iplus1 = nth; */ - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)b_iplus1, - (ast_expression*)nth - ) - ); - - /* - * A_iplus1 = b_iplus1 * A_i + A_iminus1; - * B_iplus1 = b_iplus1 * B_i + B_iminus1; - */ - for (i = 0; i <= 1; i++) { - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((i) ? B_iplus1 : A_iplus1), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)b_iplus1, - (ast_expression*) ((i) ? B_i : A_i) - ), - (ast_expression*)((i) ? B_iminus1 : A_iminus1) - ) - ) - ); - } - - /* - * A_iminus1 = A_i; - * B_iminus1 = B_i; - */ - for (i = 0; i <= 1; i++) { - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((i) ? B_iminus1 : A_iminus1), - (ast_expression*)((i) ? B_i : A_i) - ) - ); - } - - /* - * A_i = A_iplus1; - * B_i = B_iplus1; - */ - for (i = 0; i <= 1; i++) { - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((i) ? B_i : A_i), - (ast_expression*)((i) ? B_iplus1 : A_iplus1) - ) - ); - } - - /* - * if (whole <= 1.0f + eps) - * break; - */ - vec_push(forloop->exprs, - (ast_expression*)ast_ifthen_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_LE, - (ast_expression*)whole, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_ADD_F, - (ast_expression*)intrin->fold->imm_float[1], - (ast_expression*)eps - ) - ), - (ast_expression*)ast_breakcont_new( - intrin_ctx(intrin), - false, - 0 - ), - NULL - ) - ); - - /* - * power = base; - * base = whole; - */ - for (i = 0; i <= 1; i++) { - vec_push(forloop->exprs, - (ast_expression*)ast_store_new( - intrin_ctx(intrin), - INSTR_STORE_F, - (ast_expression*)((i) ? base : power), - (ast_expression*)((i) ? whole : base) - ) - ); - } - - /* add the for loop block */ - vec_push(block->exprs, - (ast_expression*)ast_loop_new( - intrin_ctx(intrin), - NULL, - /* for(; 1; ) ?? (can this be NULL too?) */ - (ast_expression*)intrin->fold->imm_float[1], - false, - NULL, - false, - NULL, - (ast_expression*)forloop - ) - ); - - /* return sign * A_i / B_il */ - vec_push(block->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_MUL_F, - (ast_expression*)sign, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - INSTR_DIV_F, - (ast_expression*)A_i, - (ast_expression*)B_i - ) - ) - ) - ); - - vec_push(func->blocks, block); - intrin_reg(intrin, value, func); - - return (ast_expression*)value; -} - -static ast_expression *intrin_log_variant(intrin_t *intrin, const char *name, float base) { - ast_value *value = NULL; - ast_call *callln = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "__builtin_ln", name)); - ast_value *arg1 = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, name, TYPE_FLOAT); - - 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, false)); - - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)callln - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_log(intrin_t *intrin) { - return intrin_log_variant(intrin, "log", 2.7182818284590452354); -} -static ast_expression *intrin_log10(intrin_t *intrin) { - return intrin_log_variant(intrin, "log10", 10); -} -static ast_expression *intrin_log2(intrin_t *intrin) { - return intrin_log_variant(intrin, "log2", 2); -} -static ast_expression *intrin_logb(intrin_t *intrin) { - /* FLT_RADIX == 2 for now */ - return intrin_log_variant(intrin, "log2", 2); -} - -static ast_expression *intrin_shift_variant(intrin_t *intrin, const char *name, size_t instr) { - /* - * float [shift] (float a, float b) { - * return floor(a [instr] pow(2, b)); - */ - ast_value *value = NULL; - ast_call *callpow = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "pow", name)); - ast_call *callfloor = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", name)); - ast_value *a = ast_value_new(intrin_ctx(intrin), "a", TYPE_FLOAT); - ast_value *b = ast_value_new(intrin_ctx(intrin), "b", TYPE_FLOAT); - ast_block *body = ast_block_new(intrin_ctx(intrin)); - ast_function *func = intrin_value(intrin, &value, name, TYPE_FLOAT); - - vec_push(value->expression.params, a); - vec_push(value->expression.params, b); - - /* = pow(2, b) */ - vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]); - vec_push(callpow->params, (ast_expression*)b); - - /* = floor(a [instr] ) */ - vec_push( - callfloor->params, - (ast_expression*)ast_binary_new( - intrin_ctx(intrin), - instr, - (ast_expression*)a, - (ast_expression*)callpow - ) - ); - - /* return */ - vec_push(body->exprs, - (ast_expression*)ast_return_new( - intrin_ctx(intrin), - (ast_expression*)callfloor - ) - ); - - vec_push(func->blocks, body); - intrin_reg(intrin, value, func); - return (ast_expression*)value; -} - -static ast_expression *intrin_lshift(intrin_t *intrin) { - return intrin_shift_variant(intrin, "lshift", INSTR_MUL_F); -} - -static ast_expression *intrin_rshift(intrin_t *intrin) { - return intrin_shift_variant(intrin, "rshift", INSTR_DIV_F); -} - -/* - * TODO: make static (and handle ast_type_string) here for the builtin - * instead of in SYA parse close. - */ -ast_expression *intrin_debug_typestring(intrin_t *intrin) { - (void)intrin; - return (ast_expression*)0x1; -} - -static const intrin_func_t intrinsics[] = { - {&intrin_isfinite, "__builtin_isfinite", "isfinite", 1}, - {&intrin_isinf, "__builtin_isinf", "isinf", 1}, - {&intrin_isnan, "__builtin_isnan", "isnan", 1}, - {&intrin_isnormal, "__builtin_isnormal", "isnormal", 1}, - {&intrin_signbit, "__builtin_signbit", "signbit", 1}, - {&intrin_acosh, "__builtin_acosh", "acosh", 1}, - {&intrin_asinh, "__builtin_asinh", "asinh", 1}, - {&intrin_atanh, "__builtin_atanh", "atanh", 1}, - {&intrin_exp, "__builtin_exp", "exp", 1}, - {&intrin_exp2, "__builtin_exp2", "exp2", 1}, - {&intrin_expm1, "__builtin_expm1", "expm1", 1}, - {&intrin_mod, "__builtin_mod", "mod", 2}, - {&intrin_pow, "__builtin_pow", "pow", 2}, - {&intrin_fabs, "__builtin_fabs", "fabs", 1}, - {&intrin_log, "__builtin_log", "log", 1}, - {&intrin_log10, "__builtin_log10", "log10", 1}, - {&intrin_log2, "__builtin_log2", "log2", 1}, - {&intrin_logb, "__builtin_logb", "logb", 1}, - {&intrin_lshift, "__builtin_lshift", "", 2}, - {&intrin_rshift, "__builtin_rshift", "", 2}, - {&intrin_epsilon, "__builtin_epsilon", "", 0}, - {&intrin_nan, "__builtin_nan", "", 0}, - {&intrin_inf, "__builtin_inf", "", 0}, - {&intrin_ln, "__builtin_ln", "", 2}, - {&intrin_debug_typestring, "__builtin_debug_typestring", "", 0}, - {&intrin_nullfunc, "#nullfunc", "", 0} -}; - -static void intrin_error(intrin_t *intrin, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - vcompile_error(intrin->parser->lex->tok.ctx, fmt, ap); - va_end(ap); -} - -/* exposed */ -intrin_t *intrin_init(parser_t *parser) { - intrin_t *intrin = (intrin_t*)mem_a(sizeof(intrin_t)); - size_t i; - - intrin->parser = parser; - intrin->fold = parser->fold; - intrin->intrinsics = NULL; - intrin->generated = NULL; - - vec_append(intrin->intrinsics, GMQCC_ARRAY_COUNT(intrinsics), intrinsics); - - /* populate with null pointers for tracking generation */ - for (i = 0; i < GMQCC_ARRAY_COUNT(intrinsics); i++) - vec_push(intrin->generated, NULL); - - return intrin; -} - -void intrin_cleanup(intrin_t *intrin) { - vec_free(intrin->intrinsics); - vec_free(intrin->generated); - mem_d(intrin); -} - -ast_expression *intrin_fold(intrin_t *intrin, ast_value *value, ast_expression **exprs) { - size_t i; - if (!value || !value->name) - return NULL; - for (i = 0; i < vec_size(intrin->intrinsics); i++) - if (!strcmp(value->name, intrin->intrinsics[i].name)) - return (vec_size(exprs) != intrin->intrinsics[i].args) - ? NULL - : fold_intrin(intrin->fold, value->name + 10, exprs); - return NULL; -} - -static GMQCC_INLINE ast_expression *intrin_func_try(intrin_t *intrin, size_t offset, const char *compare) { - size_t i; - for (i = 0; i < vec_size(intrin->intrinsics); i++) { - if (strcmp(*(char **)((char *)&intrin->intrinsics[i] + offset), compare)) - continue; - if (intrin->generated[i]) - return intrin->generated[i]; - return intrin->generated[i] = intrin->intrinsics[i].intrin(intrin); - } - return NULL; -} - -static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from) { - size_t i; - ast_expression *find; - - /* try current first */ - if ((find = parser_find_global(intrin->parser, name)) && ((ast_value*)find)->expression.vtype == TYPE_FUNCTION) - for (i = 0; i < vec_size(intrin->parser->functions); ++i) - if (((ast_value*)find)->name && !strcmp(intrin->parser->functions[i]->name, ((ast_value*)find)->name) && intrin->parser->functions[i]->builtin < 0) - return find; - /* try name second */ - if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, name), name))) - return find; - /* try alias third */ - if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, alias), name))) - return find; - - if (from) { - intrin_error(intrin, "need function `%s', compiler depends on it for `__builtin_%s'", name, from); - return intrin_func_self(intrin, "#nullfunc", NULL); - } - return NULL; -} - -ast_expression *intrin_func(intrin_t *intrin, const char *name) { - return intrin_func_self(intrin, name, NULL); -} diff --git a/intrin.cpp b/intrin.cpp new file mode 100644 index 0000000..9ef0207 --- /dev/null +++ b/intrin.cpp @@ -0,0 +1,2082 @@ +#include +#include "parser.h" + +#define intrin_ctx(I) parser_ctx((I)->parser) + +static GMQCC_INLINE ast_function *intrin_value(intrin_t *intrin, ast_value **out, const char *name, qcint_t vtype) { + ast_value *value = NULL; + ast_function *func = NULL; + char buffer[1024]; + char stype [1024]; + + util_snprintf(buffer, sizeof(buffer), "__builtin_%s", name); + util_snprintf(stype, sizeof(stype), "<%s>", type_name[vtype]); + + value = ast_value_new(intrin_ctx(intrin), buffer, TYPE_FUNCTION); + value->intrinsic = true; + value->expression.next = (ast_expression*)ast_value_new(intrin_ctx(intrin), stype, vtype); + func = ast_function_new(intrin_ctx(intrin), buffer, value); + value->expression.flags |= AST_FLAG_ERASEABLE; + + *out = value; + return func; +} + +static GMQCC_INLINE void intrin_reg(intrin_t *intrin, ast_value *const value, ast_function *const func) { + vec_push(intrin->parser->functions, func); + vec_push(intrin->parser->globals, (ast_expression*)value); +} + +#define QC_POW_EPSILON 0.00001f + +/* + * since some intrinsics depend on each other there is the possibility + * that an intrinsic will fail to get a 'depended' function that a + * builtin needs, causing some dependency in the chain to have a NULL + * function. This will cause a segmentation fault at code generation, + * even though an error was raised. To contiue to allow it (instead + * of stopping compilation right away). We need to return from the + * parser, before compilation stops after all the collected errors. + */ +static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from); +static ast_expression *intrin_nullfunc(intrin_t *intrin) { + ast_value *value = NULL; + ast_function *func = intrin_value(intrin, &value, NULL, TYPE_VOID); + intrin_reg(intrin, value, func); + return (ast_expression*)value; +} + +static ast_expression *intrin_isfinite(intrin_t *intrin) { + /* + * float isfinite(float x) { + * return !(isnan(x) || isinf(x)); + * } + */ + ast_value *value = NULL; + ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_function *func = intrin_value(intrin, &value, "isfinite", TYPE_FLOAT); + ast_call *callisnan = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "isnan", "isfinite")); + ast_call *callisinf = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "isinf", "isfinite")); + ast_block *block = ast_block_new(intrin_ctx(intrin)); + + /* float x; */ + vec_push(value->expression.params, x); + + /* = isnan(x); */ + vec_push(callisnan->params, (ast_expression*)x); + + /* = isinf(x); */ + vec_push(callisinf->params, (ast_expression*)x); + + /* return (! || ); */ + vec_push(block->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)ast_unary_new( + intrin_ctx(intrin), + INSTR_NOT_F, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_OR, + (ast_expression*)callisnan, + (ast_expression*)callisinf + ) + ) + ) + ); + + vec_push(func->blocks, block); + intrin_reg(intrin, value, func); + + return (ast_expression*)value;; +} + +static ast_expression *intrin_isinf(intrin_t *intrin) { + /* + * float isinf(float x) { + * return (x != 0.0) && (x + x == x); + * } + */ + ast_value *value = NULL; + ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, "isinf", TYPE_FLOAT); + + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_AND, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_NE_F, + (ast_expression*)x, + (ast_expression*)intrin->fold->imm_float[0] + ), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_EQ_F, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_ADD_F, + (ast_expression*)x, + (ast_expression*)x + ), + (ast_expression*)x + ) + ) + ) + ); + + vec_push(value->expression.params, x); + vec_push(func->blocks, body); + + intrin_reg(intrin, value, func); + + return (ast_expression*)value; +} + +static ast_expression *intrin_isnan(intrin_t *intrin) { + /* + * float isnan(float x) { + * float local; + * local = x; + * + * return (x != local); + * } + */ + ast_value *value = NULL; + ast_value *arg1 = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_value *local = ast_value_new(intrin_ctx(intrin), "local", TYPE_FLOAT); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, "isnan", TYPE_FLOAT); + + vec_push(body->locals, local); + vec_push(body->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)local, + (ast_expression*)arg1 + ) + ); + + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_NE_F, + (ast_expression*)arg1, + (ast_expression*)local + ) + ) + ); + + vec_push(value->expression.params, arg1); + vec_push(func->blocks, body); + + intrin_reg(intrin, value, func); + + return (ast_expression*)value; +} + +static ast_expression *intrin_isnormal(intrin_t *intrin) { + /* + * float isnormal(float x) { + * return isfinite(x); + * } + */ + ast_value *value = NULL; + ast_call *callisfinite = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "isfinite", "isnormal")); + ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, "isnormal", TYPE_FLOAT); + + vec_push(value->expression.params, x); + vec_push(callisfinite->params, (ast_expression*)x); + + /* return */ + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)callisfinite + ) + ); + + vec_push(func->blocks, body); + intrin_reg(intrin, value, func); + return (ast_expression*)value; +} + +static ast_expression *intrin_signbit(intrin_t *intrin) { + /* + * float signbit(float x) { + * return (x < 0); + * } + */ + ast_value *value = NULL; + ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, "signbit", TYPE_FLOAT); + + vec_push(value->expression.params, x); + + /* return (x < 0); */ + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)ast_ternary_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_LT, + (ast_expression*)x, + (ast_expression*)intrin->fold->imm_float[0] + ), + (ast_expression*)intrin->fold->imm_float[1], + (ast_expression*)intrin->fold->imm_float[0] + ) + ) + ); + + vec_push(func->blocks, body); + intrin_reg(intrin, value, func); + return (ast_expression*)value; +} + +static ast_expression *intrin_acosh(intrin_t *intrin) { + /* + * float acosh(float x) { + * return log(x + sqrt((x * x) - 1)); + * } + */ + ast_value *value = NULL; + ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_call *calllog = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "acosh")); + ast_call *callsqrt = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "acosh")); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, "acosh", TYPE_FLOAT); + + vec_push(value->expression.params, x); + + /* = sqrt((x * x) - 1); */ + vec_push(callsqrt->params, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_SUB_F, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_MUL_F, + (ast_expression*)x, + (ast_expression*)x + ), + (ast_expression*)intrin->fold->imm_float[1] + ) + ); + + /* = log(x + ); */ + vec_push(calllog->params, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_ADD_F, + (ast_expression*)x, + (ast_expression*)callsqrt + ) + ); + + /* return ; */ + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)calllog + ) + ); + + vec_push(func->blocks, body); + intrin_reg(intrin, value, func); + return (ast_expression*)value; +} + +static ast_expression *intrin_asinh(intrin_t *intrin) { + /* + * float asinh(float x) { + * return log(x + sqrt((x * x) + 1)); + * } + */ + ast_value *value = NULL; + ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_call *calllog = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "asinh")); + ast_call *callsqrt = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "asinh")); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, "asinh", TYPE_FLOAT); + + vec_push(value->expression.params, x); + + /* = sqrt((x * x) + 1); */ + vec_push(callsqrt->params, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_ADD_F, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_MUL_F, + (ast_expression*)x, + (ast_expression*)x + ), + (ast_expression*)intrin->fold->imm_float[1] + ) + ); + + /* = log(x + ); */ + vec_push(calllog->params, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_ADD_F, + (ast_expression*)x, + (ast_expression*)callsqrt + ) + ); + + /* return ; */ + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)calllog + ) + ); + + vec_push(func->blocks, body); + intrin_reg(intrin, value, func); + return (ast_expression*)value; +} + +static ast_expression *intrin_atanh(intrin_t *intrin) { + /* + * float atanh(float x) { + * return 0.5 * log((1 + x) / (1 - x)) + * } + */ + ast_value *value = NULL; + ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_call *calllog = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "atanh")); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, "atanh", TYPE_FLOAT); + + vec_push(value->expression.params, x); + + /* = log((1 + x) / (1 - x)); */ + vec_push(calllog->params, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_DIV_F, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_ADD_F, + (ast_expression*)intrin->fold->imm_float[1], + (ast_expression*)x + ), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_SUB_F, + (ast_expression*)intrin->fold->imm_float[1], + (ast_expression*)x + ) + ) + ); + + /* return 0.5 * ; */ + vec_push(body->exprs, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_MUL_F, + (ast_expression*)fold_constgen_float(intrin->fold, 0.5, false), + (ast_expression*)calllog + ) + ); + + vec_push(func->blocks, body); + intrin_reg(intrin, value, func); + return (ast_expression*)value; +} + +static ast_expression *intrin_exp(intrin_t *intrin) { + /* + * float exp(float x) { + * float sum = 1.0; + * float acc = 1.0; + * float i; + * for (i = 1; i < 200; ++i) + * sum += (acc *= x / i); + * + * return sum; + * } + */ + ast_value *value = NULL; + ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_value *sum = ast_value_new(intrin_ctx(intrin), "sum", TYPE_FLOAT); + ast_value *acc = ast_value_new(intrin_ctx(intrin), "acc", TYPE_FLOAT); + ast_value *i = ast_value_new(intrin_ctx(intrin), "i", TYPE_FLOAT); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, "exp", TYPE_FLOAT); + + vec_push(value->expression.params, x); + vec_push(body->locals, sum); + vec_push(body->locals, acc); + vec_push(body->locals, i); + + /* sum = 1.0; */ + vec_push(body->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)sum, + (ast_expression*)intrin->fold->imm_float[1] + ) + ); + + /* acc = 1.0; */ + vec_push(body->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)acc, + (ast_expression*)intrin->fold->imm_float[1] + ) + ); + + /* + * for (i = 1; i < 200; ++i) + * sum += (acc *= x / i); + */ + vec_push(body->exprs, + (ast_expression*)ast_loop_new( + intrin_ctx(intrin), + /* i = 1; */ + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)i, + (ast_expression*)intrin->fold->imm_float[1] + ), + /* i < 200; */ + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_LT, + (ast_expression*)i, + (ast_expression*)fold_constgen_float(intrin->fold, 200.0f, false) + ), + false, + NULL, + false, + /* ++i; */ + (ast_expression*)ast_binstore_new( + intrin_ctx(intrin), + INSTR_STORE_F, + INSTR_ADD_F, + (ast_expression*)i, + (ast_expression*)intrin->fold->imm_float[1] + ), + /* sum += (acc *= (x / i)) */ + (ast_expression*)ast_binstore_new( + intrin_ctx(intrin), + INSTR_STORE_F, + INSTR_ADD_F, + (ast_expression*)sum, + (ast_expression*)ast_binstore_new( + intrin_ctx(intrin), + INSTR_STORE_F, + INSTR_MUL_F, + (ast_expression*)acc, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_DIV_F, + (ast_expression*)x, + (ast_expression*)i + ) + ) + ) + ) + ); + + /* return sum; */ + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)sum + ) + ); + + vec_push(func->blocks, body); + + intrin_reg(intrin, value, func); + return (ast_expression*)value; +} + +static ast_expression *intrin_exp2(intrin_t *intrin) { + /* + * float exp2(float x) { + * return pow(2, x); + * } + */ + ast_value *value = NULL; + ast_call *callpow = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "pow", "exp2")); + ast_value *arg1 = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, "exp2", TYPE_FLOAT); + + vec_push(value->expression.params, arg1); + + vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]); + vec_push(callpow->params, (ast_expression*)arg1); + + /* return */ + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)callpow + ) + ); + + vec_push(func->blocks, body); + + intrin_reg(intrin, value, func); + return (ast_expression*)value; +} + +static ast_expression *intrin_expm1(intrin_t *intrin) { + /* + * float expm1(float x) { + * return exp(x) - 1; + * } + */ + ast_value *value = NULL; + ast_call *callexp = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "exp", "expm1")); + ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, "expm1", TYPE_FLOAT); + + vec_push(value->expression.params, x); + + /* = exp(x); */ + vec_push(callexp->params, (ast_expression*)x); + + /* return - 1; */ + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_SUB_F, + (ast_expression*)callexp, + (ast_expression*)intrin->fold->imm_float[1] + ) + ) + ); + + vec_push(func->blocks, body); + intrin_reg(intrin, value, func); + return (ast_expression*)value; +} + +static ast_expression *intrin_pow(intrin_t *intrin) { + /* + * + * float pow(float base, float exp) { + * float result; + * float low; + * float high; + * float mid; + * float square; + * float accumulate; + * + * if (exp == 0.0) + * return 1; + * if (exp == 1.0) + * return base; + * if (exp < 0) + * return 1.0 / pow(base, -exp); + * if (exp >= 1) { + * result = pow(base, exp / 2); + * return result * result; + * } + * + * low = 0.0f; + * high = 1.0f; + * square = sqrt(base); + * accumulate = square; + * mid = high / 2.0f + * + * while (fabs(mid - exp) > QC_POW_EPSILON) { + * square = sqrt(square); + * if (mid < exp) { + * low = mid; + * accumulate *= square; + * } else { + * high = mid; + * accumulate *= (1.0f / square); + * } + * mid = (low + high) / 2; + * } + * return accumulate; + * } + */ + ast_value *value = NULL; + ast_function *func = intrin_value(intrin, &value, "pow", TYPE_FLOAT); + + /* prepare some calls for later */ + ast_call *callpow1 = ast_call_new(intrin_ctx(intrin), (ast_expression*)value); /* for pow(base, -exp) */ + ast_call *callpow2 = ast_call_new(intrin_ctx(intrin), (ast_expression*)value); /* for pow(vase, exp / 2) */ + ast_call *callsqrt1 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(base) */ + ast_call *callsqrt2 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(square) */ + ast_call *callfabs = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "fabs", "pow")); /* for fabs(mid - exp) */ + + /* prepare some blocks for later */ + ast_block *expgt1 = ast_block_new(intrin_ctx(intrin)); + ast_block *midltexp = ast_block_new(intrin_ctx(intrin)); + ast_block *midltexpelse = ast_block_new(intrin_ctx(intrin)); + ast_block *whileblock = ast_block_new(intrin_ctx(intrin)); + + /* float pow(float base, float exp) */ + ast_value *base = ast_value_new(intrin_ctx(intrin), "base", TYPE_FLOAT); + ast_value *exp = ast_value_new(intrin_ctx(intrin), "exp", TYPE_FLOAT); + /* { */ + ast_block *body = ast_block_new(intrin_ctx(intrin)); + + /* + * float result; + * float low; + * float high; + * float square; + * float accumulate; + * float mid; + */ + ast_value *result = ast_value_new(intrin_ctx(intrin), "result", TYPE_FLOAT); + ast_value *low = ast_value_new(intrin_ctx(intrin), "low", TYPE_FLOAT); + ast_value *high = ast_value_new(intrin_ctx(intrin), "high", TYPE_FLOAT); + ast_value *square = ast_value_new(intrin_ctx(intrin), "square", TYPE_FLOAT); + ast_value *accumulate = ast_value_new(intrin_ctx(intrin), "accumulate", TYPE_FLOAT); + ast_value *mid = ast_value_new(intrin_ctx(intrin), "mid", TYPE_FLOAT); + vec_push(body->locals, result); + vec_push(body->locals, low); + vec_push(body->locals, high); + vec_push(body->locals, square); + vec_push(body->locals, accumulate); + vec_push(body->locals, mid); + + vec_push(value->expression.params, base); + vec_push(value->expression.params, exp); + + /* + * if (exp == 0.0) + * return 1; + */ + vec_push(body->exprs, + (ast_expression*)ast_ifthen_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_EQ_F, + (ast_expression*)exp, + (ast_expression*)intrin->fold->imm_float[0] + ), + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)intrin->fold->imm_float[1] + ), + NULL + ) + ); + + /* + * if (exp == 1.0) + * return base; + */ + vec_push(body->exprs, + (ast_expression*)ast_ifthen_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_EQ_F, + (ast_expression*)exp, + (ast_expression*)intrin->fold->imm_float[1] + ), + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)base + ), + NULL + ) + ); + + /* = pow(base, -exp) */ + vec_push(callpow1->params, (ast_expression*)base); + vec_push(callpow1->params, + (ast_expression*)ast_unary_new( + intrin_ctx(intrin), + VINSTR_NEG_F, + (ast_expression*)exp + ) + ); + + /* + * if (exp < 0) + * return 1.0 / ; + */ + vec_push(body->exprs, + (ast_expression*)ast_ifthen_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_LT, + (ast_expression*)exp, + (ast_expression*)intrin->fold->imm_float[0] + ), + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_DIV_F, + (ast_expression*)intrin->fold->imm_float[1], + (ast_expression*)callpow1 + ) + ), + NULL + ) + ); + + /* = pow(base, exp / 2) */ + vec_push(callpow2->params, (ast_expression*)base); + vec_push(callpow2->params, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_DIV_F, + (ast_expression*)exp, + (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ + ) + ); + + /* + * = { + * result = ; + * return result * result; + * } + */ + vec_push(expgt1->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)result, + (ast_expression*)callpow2 + ) + ); + vec_push(expgt1->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_MUL_F, + (ast_expression*)result, + (ast_expression*)result + ) + ) + ); + + /* + * if (exp >= 1) { + * + * } + */ + vec_push(body->exprs, + (ast_expression*)ast_ifthen_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_GE, + (ast_expression*)exp, + (ast_expression*)intrin->fold->imm_float[1] + ), + (ast_expression*)expgt1, + NULL + ) + ); + + /* + * = sqrt(base) + */ + vec_push(callsqrt1->params, (ast_expression*)base); + + /* + * low = 0.0f; + * high = 1.0f; + * square = sqrt(base); + * accumulate = square; + * mid = high / 2.0f; + */ + vec_push(body->exprs, + (ast_expression*)ast_store_new(intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)low, + (ast_expression*)intrin->fold->imm_float[0] + ) + ); + vec_push(body->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)high, + (ast_expression*)intrin->fold->imm_float[1] + ) + ); + + vec_push(body->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)square, + (ast_expression*)callsqrt1 + ) + ); + + vec_push(body->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)accumulate, + (ast_expression*)square + ) + ); + vec_push(body->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)mid, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_DIV_F, + (ast_expression*)high, + (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ + ) + ) + ); + + /* + * = { + * low = mid; + * accumulate *= square; + * } + */ + vec_push(midltexp->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)low, + (ast_expression*)mid + ) + ); + vec_push(midltexp->exprs, + (ast_expression*)ast_binstore_new( + intrin_ctx(intrin), + INSTR_STORE_F, + INSTR_MUL_F, + (ast_expression*)accumulate, + (ast_expression*)square + ) + ); + + /* + * = { + * high = mid; + * accumulate *= (1.0 / square); + * } + */ + vec_push(midltexpelse->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)high, + (ast_expression*)mid + ) + ); + vec_push(midltexpelse->exprs, + (ast_expression*)ast_binstore_new( + intrin_ctx(intrin), + INSTR_STORE_F, + INSTR_MUL_F, + (ast_expression*)accumulate, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_DIV_F, + (ast_expression*)intrin->fold->imm_float[1], + (ast_expression*)square + ) + ) + ); + + /* + * = sqrt(square) + */ + vec_push(callsqrt2->params, (ast_expression*)square); + + /* + * = { + * square = ; + * if (mid < exp) + * ; + * else + * ; + * + * mid = (low + high) / 2; + * } + */ + vec_push(whileblock->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)square, + (ast_expression*)callsqrt2 + ) + ); + vec_push(whileblock->exprs, + (ast_expression*)ast_ifthen_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_LT, + (ast_expression*)mid, + (ast_expression*)exp + ), + (ast_expression*)midltexp, + (ast_expression*)midltexpelse + ) + ); + vec_push(whileblock->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)mid, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_DIV_F, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_ADD_F, + (ast_expression*)low, + (ast_expression*)high + ), + (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ + ) + ) + ); + + /* + * = fabs(mid - exp) + */ + vec_push(callfabs->params, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_SUB_F, + (ast_expression*)mid, + (ast_expression*)exp + ) + ); + + /* + * while ( > epsilon) + * + */ + vec_push(body->exprs, + (ast_expression*)ast_loop_new( + intrin_ctx(intrin), + /* init */ + NULL, + /* pre condition */ + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_GT, + (ast_expression*)callfabs, + (ast_expression*)fold_constgen_float(intrin->fold, QC_POW_EPSILON, false) + ), + /* pre not */ + false, + /* post condition */ + NULL, + /* post not */ + false, + /* increment expression */ + NULL, + /* code block */ + (ast_expression*)whileblock + ) + ); + + /* return accumulate */ + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)accumulate + ) + ); + + /* } */ + vec_push(func->blocks, body); + + intrin_reg(intrin, value, func); + return (ast_expression*)value; +} + +static ast_expression *intrin_mod(intrin_t *intrin) { + /* + * float mod(float a, float b) { + * float div = a / b; + * float sign = (div < 0.0f) ? -1 : 1; + * return a - b * sign * floor(sign * div); + * } + */ + ast_value *value = NULL; + ast_call *call = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", "mod")); + ast_value *a = ast_value_new(intrin_ctx(intrin), "a", TYPE_FLOAT); + ast_value *b = ast_value_new(intrin_ctx(intrin), "b", TYPE_FLOAT); + ast_value *div = ast_value_new(intrin_ctx(intrin), "div", TYPE_FLOAT); + ast_value *sign = ast_value_new(intrin_ctx(intrin), "sign", TYPE_FLOAT); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, "mod", TYPE_FLOAT); + + vec_push(value->expression.params, a); + vec_push(value->expression.params, b); + + vec_push(body->locals, div); + vec_push(body->locals, sign); + + /* div = a / b; */ + vec_push(body->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)div, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_DIV_F, + (ast_expression*)a, + (ast_expression*)b + ) + ) + ); + + /* sign = (div < 0.0f) ? -1 : 1; */ + vec_push(body->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)sign, + (ast_expression*)ast_ternary_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_LT, + (ast_expression*)div, + (ast_expression*)intrin->fold->imm_float[0] + ), + (ast_expression*)intrin->fold->imm_float[2], + (ast_expression*)intrin->fold->imm_float[1] + ) + ) + ); + + /* floor(sign * div) */ + vec_push(call->params, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_MUL_F, + (ast_expression*)sign, + (ast_expression*)div + ) + ); + + /* return a - b * sign * */ + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_SUB_F, + (ast_expression*)a, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_MUL_F, + (ast_expression*)b, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_MUL_F, + (ast_expression*)sign, + (ast_expression*)call + ) + ) + ) + ) + ); + + vec_push(func->blocks, body); + intrin_reg(intrin, value, func); + + return (ast_expression*)value; +} + +static ast_expression *intrin_fabs(intrin_t *intrin) { + /* + * float fabs(float x) { + * return x < 0 ? -x : x; + * } + */ + ast_value *value = NULL; + ast_value *arg1 = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, "fabs", TYPE_FLOAT); + + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)ast_ternary_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_LE, + (ast_expression*)arg1, + (ast_expression*)intrin->fold->imm_float[0] + ), + (ast_expression*)ast_unary_new( + intrin_ctx(intrin), + VINSTR_NEG_F, + (ast_expression*)arg1 + ), + (ast_expression*)arg1 + ) + ) + ); + + vec_push(value->expression.params, arg1); + vec_push(func->blocks, body); + + intrin_reg(intrin, value, func); + + return (ast_expression*)value; +} + +static ast_expression *intrin_epsilon(intrin_t *intrin) { + /* + * float epsilon(void) { + * float eps = 1.0f; + * do { eps /= 2.0f; } while ((1.0f + (eps / 2.0f)) != 1.0f); + * return eps; + * } + */ + ast_value *value = NULL; + ast_value *eps = ast_value_new(intrin_ctx(intrin), "eps", TYPE_FLOAT); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, "epsilon", TYPE_FLOAT); + + vec_push(body->locals, eps); + + /* eps = 1.0f; */ + vec_push(body->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)eps, + (ast_expression*)intrin->fold->imm_float[0] + ) + ); + + vec_push(body->exprs, + (ast_expression*)ast_loop_new( + intrin_ctx(intrin), + NULL, + NULL, + false, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_NE_F, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_ADD_F, + (ast_expression*)intrin->fold->imm_float[1], + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_MUL_F, + (ast_expression*)eps, + (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ + ) + ), + (ast_expression*)intrin->fold->imm_float[1] + ), + false, + NULL, + (ast_expression*)ast_binstore_new( + intrin_ctx(intrin), + INSTR_STORE_F, + INSTR_DIV_F, + (ast_expression*)eps, + (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ + ) + ) + ); + + /* return eps; */ + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)eps + ) + ); + + vec_push(func->blocks, body); + intrin_reg(intrin, value, func); + + return (ast_expression*)value; +} + +static ast_expression *intrin_nan(intrin_t *intrin) { + /* + * float nan(void) { + * float x = 0.0f; + * return x / x; + * } + */ + ast_value *value = NULL; + ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_function *func = intrin_value(intrin, &value, "nan", TYPE_FLOAT); + ast_block *block = ast_block_new(intrin_ctx(intrin)); + + vec_push(block->locals, x); + + vec_push(block->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)x, + (ast_expression*)intrin->fold->imm_float[0] + ) + ); + + vec_push(block->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_DIV_F, + (ast_expression*)x, + (ast_expression*)x + ) + ) + ); + + vec_push(func->blocks, block); + intrin_reg(intrin, value, func); + + return (ast_expression*)value; +} + +static ast_expression *intrin_inf(intrin_t *intrin) { + /* + * float inf(void) { + * float x = 1.0f; + * float y = 0.0f; + * return x / y; + * } + */ + ast_value *value = NULL; + ast_value *x = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_value *y = ast_value_new(intrin_ctx(intrin), "y", TYPE_FLOAT); + ast_function *func = intrin_value(intrin, &value, "inf", TYPE_FLOAT); + ast_block *block = ast_block_new(intrin_ctx(intrin)); + size_t i; + + vec_push(block->locals, x); + vec_push(block->locals, y); + + /* to keep code size down */ + for (i = 0; i <= 1; i++) { + vec_push(block->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)((i == 0) ? x : y), + (ast_expression*)intrin->fold->imm_float[i] + ) + ); + } + + vec_push(block->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_DIV_F, + (ast_expression*)x, + (ast_expression*)y + ) + ) + ); + + vec_push(func->blocks, block); + intrin_reg(intrin, value, func); + + return (ast_expression*)value; +} + +static ast_expression *intrin_ln(intrin_t *intrin) { + /* + * float log(float power, float base) { + * float whole; + * float nth + * float sign = 1.0f; + * float eps = epsilon(); + * + * if (power <= 1.0f || bbase <= 1.0) { + * if (power <= 0.0f || base <= 0.0f) + * return nan(); + * + * if (power < 1.0f) { + * power = 1.0f / power; + * sign *= -1.0f; + * } + * + * if (base < 1.0f) { + * sign *= -1.0f; + * base = 1.0f / base; + * } + * } + * + * float A_i = 1; + * float B_i = 0; + * float A_iminus1 = 0; + * float B_iminus1 = 1; + * + * for (;;) { + * whole = power; + * nth = 0.0f; + * + * while (whole >= base) { + * float base2 = base; + * float n2 = 1.0f; + * float newbase2 = base2 * base2; + * + * while (whole >= newbase2) { + * base2 = newbase2; + * n2 *= 2; + * newbase2 *= newbase2; + * } + * + * whole /= base2; + * nth += n2; + * } + * + * float b_iplus1 = n; + * float A_iplus1 = b_iplus1 * A_i + A_iminus1; + * float B_iplus1 = b_iplus1 * B_i + B_iminus1; + * + * A_iminus1 = A_i; + * B_iminus1 = B_i; + * A_i = A_iplus1; + * B_i = B_iplus1; + * + * if (whole <= 1.0f + eps) + * break; + * + * power = base; + * bower = whole; + * } + * return sign * A_i / B_i; + * } + */ + + ast_value *value = NULL; + ast_value *power = ast_value_new(intrin_ctx(intrin), "power", TYPE_FLOAT); + ast_value *base = ast_value_new(intrin_ctx(intrin), "base", TYPE_FLOAT); + ast_value *whole = ast_value_new(intrin_ctx(intrin), "whole", TYPE_FLOAT); + ast_value *nth = ast_value_new(intrin_ctx(intrin), "nth", TYPE_FLOAT); + ast_value *sign = ast_value_new(intrin_ctx(intrin), "sign", TYPE_FLOAT); + ast_value *A_i = ast_value_new(intrin_ctx(intrin), "A_i", TYPE_FLOAT); + ast_value *B_i = ast_value_new(intrin_ctx(intrin), "B_i", TYPE_FLOAT); + ast_value *A_iminus1 = ast_value_new(intrin_ctx(intrin), "A_iminus1", TYPE_FLOAT); + ast_value *B_iminus1 = ast_value_new(intrin_ctx(intrin), "B_iminus1", TYPE_FLOAT); + ast_value *b_iplus1 = ast_value_new(intrin_ctx(intrin), "b_iplus1", TYPE_FLOAT); + ast_value *A_iplus1 = ast_value_new(intrin_ctx(intrin), "A_iplus1", TYPE_FLOAT); + ast_value *B_iplus1 = ast_value_new(intrin_ctx(intrin), "B_iplus1", TYPE_FLOAT); + ast_value *eps = ast_value_new(intrin_ctx(intrin), "eps", TYPE_FLOAT); + ast_value *base2 = ast_value_new(intrin_ctx(intrin), "base2", TYPE_FLOAT); + ast_value *n2 = ast_value_new(intrin_ctx(intrin), "n2", TYPE_FLOAT); + ast_value *newbase2 = ast_value_new(intrin_ctx(intrin), "newbase2", TYPE_FLOAT); + ast_block *block = ast_block_new(intrin_ctx(intrin)); + ast_block *plt1orblt1 = ast_block_new(intrin_ctx(intrin)); /* (power <= 1.0f || base <= 1.0f) */ + ast_block *plt1 = ast_block_new(intrin_ctx(intrin)); /* (power < 1.0f) */ + ast_block *blt1 = ast_block_new(intrin_ctx(intrin)); /* (base < 1.0f) */ + ast_block *forloop = ast_block_new(intrin_ctx(intrin)); /* for(;;) */ + ast_block *whileloop = ast_block_new(intrin_ctx(intrin)); /* while (whole >= base) */ + ast_block *nestwhile = ast_block_new(intrin_ctx(intrin)); /* while (whole >= newbase2) */ + ast_function *func = intrin_value(intrin, &value, "ln", TYPE_FLOAT); + size_t i; + + vec_push(value->expression.params, power); + vec_push(value->expression.params, base); + + vec_push(block->locals, whole); + vec_push(block->locals, nth); + vec_push(block->locals, sign); + vec_push(block->locals, eps); + vec_push(block->locals, A_i); + vec_push(block->locals, B_i); + vec_push(block->locals, A_iminus1); + vec_push(block->locals, B_iminus1); + + /* sign = 1.0f; */ + vec_push(block->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)sign, + (ast_expression*)intrin->fold->imm_float[1] + ) + ); + + /* eps = __builtin_epsilon(); */ + vec_push(block->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)eps, + (ast_expression*)ast_call_new( + intrin_ctx(intrin), + intrin_func_self(intrin, "__builtin_epsilon", "ln") + ) + ) + ); + + /* + * A_i = 1; + * B_i = 0; + * A_iminus1 = 0; + * B_iminus1 = 1; + */ + for (i = 0; i <= 1; i++) { + int j; + for (j = 1; j >= 0; j--) { + vec_push(block->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)((j) ? ((i) ? B_iminus1 : A_i) + : ((i) ? A_iminus1 : B_i)), + (ast_expression*)intrin->fold->imm_float[j] + ) + ); + } + } + + /* + * = { + * power = 1.0f / power; + * sign *= -1.0f; + * } + * = { + * base = 1.0f / base; + * sign *= -1.0f; + * } + */ + for (i = 0; i <= 1; i++) { + vec_push(((i) ? blt1 : plt1)->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)((i) ? base : power), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_DIV_F, + (ast_expression*)intrin->fold->imm_float[1], + (ast_expression*)((i) ? base : power) + ) + ) + ); + vec_push(plt1->exprs, + (ast_expression*)ast_binstore_new( + intrin_ctx(intrin), + INSTR_STORE_F, + INSTR_MUL_F, + (ast_expression*)sign, + (ast_expression*)intrin->fold->imm_float[2] + ) + ); + } + + /* + * = { + * if (power <= 0.0 || base <= 0.0f) + * return __builtin_nan(); + * if (power < 1.0f) + * + * if (base < 1.0f) + * + * } + */ + vec_push(plt1orblt1->exprs, + (ast_expression*)ast_ifthen_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_OR, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_LE, + (ast_expression*)power, + (ast_expression*)intrin->fold->imm_float[0] + ), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_LE, + (ast_expression*)base, + (ast_expression*)intrin->fold->imm_float[0] + ) + ), + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)ast_call_new( + intrin_ctx(intrin), + intrin_func_self(intrin, "__builtin_nan", "ln") + ) + ), + NULL + ) + ); + + for (i = 0; i <= 1; i++) { + vec_push(plt1orblt1->exprs, + (ast_expression*)ast_ifthen_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_LT, + (ast_expression*)((i) ? base : power), + (ast_expression*)intrin->fold->imm_float[1] + ), + (ast_expression*)((i) ? blt1 : plt1), + NULL + ) + ); + } + + vec_push(block->exprs, (ast_expression*)plt1orblt1); + + + /* whole = power; */ + vec_push(forloop->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)whole, + (ast_expression*)power + ) + ); + + /* nth = 0.0f; */ + vec_push(forloop->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)nth, + (ast_expression*)intrin->fold->imm_float[0] + ) + ); + + /* base2 = base; */ + vec_push(whileloop->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)base2, + (ast_expression*)base + ) + ); + + /* n2 = 1.0f; */ + vec_push(whileloop->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)n2, + (ast_expression*)intrin->fold->imm_float[1] + ) + ); + + /* newbase2 = base2 * base2; */ + vec_push(whileloop->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)newbase2, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_MUL_F, + (ast_expression*)base2, + (ast_expression*)base2 + ) + ) + ); + + /* while loop locals */ + vec_push(whileloop->locals, base2); + vec_push(whileloop->locals, n2); + vec_push(whileloop->locals, newbase2); + + /* base2 = newbase2; */ + vec_push(nestwhile->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)base2, + (ast_expression*)newbase2 + ) + ); + + /* n2 *= 2; */ + vec_push(nestwhile->exprs, + (ast_expression*)ast_binstore_new( + intrin_ctx(intrin), + INSTR_STORE_F, + INSTR_MUL_F, + (ast_expression*)n2, + (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */ + ) + ); + + /* newbase2 *= newbase2; */ + vec_push(nestwhile->exprs, + (ast_expression*)ast_binstore_new( + intrin_ctx(intrin), + INSTR_STORE_F, + INSTR_MUL_F, + (ast_expression*)newbase2, + (ast_expression*)newbase2 + ) + ); + + /* while (whole >= newbase2) */ + vec_push(whileloop->exprs, + (ast_expression*)ast_loop_new( + intrin_ctx(intrin), + NULL, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_GE, + (ast_expression*)whole, + (ast_expression*)newbase2 + ), + false, + NULL, + false, + NULL, + (ast_expression*)nestwhile + ) + ); + + /* whole /= base2; */ + vec_push(whileloop->exprs, + (ast_expression*)ast_binstore_new( + intrin_ctx(intrin), + INSTR_STORE_F, + INSTR_DIV_F, + (ast_expression*)whole, + (ast_expression*)base2 + ) + ); + + /* nth += n2; */ + vec_push(whileloop->exprs, + (ast_expression*)ast_binstore_new( + intrin_ctx(intrin), + INSTR_STORE_F, + INSTR_ADD_F, + (ast_expression*)nth, + (ast_expression*)n2 + ) + ); + + /* while (whole >= base) */ + vec_push(forloop->exprs, + (ast_expression*)ast_loop_new( + intrin_ctx(intrin), + NULL, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_GE, + (ast_expression*)whole, + (ast_expression*)base + ), + false, + NULL, + false, + NULL, + (ast_expression*)whileloop + ) + ); + + vec_push(forloop->locals, b_iplus1); + vec_push(forloop->locals, A_iplus1); + vec_push(forloop->locals, B_iplus1); + + /* b_iplus1 = nth; */ + vec_push(forloop->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)b_iplus1, + (ast_expression*)nth + ) + ); + + /* + * A_iplus1 = b_iplus1 * A_i + A_iminus1; + * B_iplus1 = b_iplus1 * B_i + B_iminus1; + */ + for (i = 0; i <= 1; i++) { + vec_push(forloop->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)((i) ? B_iplus1 : A_iplus1), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_ADD_F, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_MUL_F, + (ast_expression*)b_iplus1, + (ast_expression*) ((i) ? B_i : A_i) + ), + (ast_expression*)((i) ? B_iminus1 : A_iminus1) + ) + ) + ); + } + + /* + * A_iminus1 = A_i; + * B_iminus1 = B_i; + */ + for (i = 0; i <= 1; i++) { + vec_push(forloop->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)((i) ? B_iminus1 : A_iminus1), + (ast_expression*)((i) ? B_i : A_i) + ) + ); + } + + /* + * A_i = A_iplus1; + * B_i = B_iplus1; + */ + for (i = 0; i <= 1; i++) { + vec_push(forloop->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)((i) ? B_i : A_i), + (ast_expression*)((i) ? B_iplus1 : A_iplus1) + ) + ); + } + + /* + * if (whole <= 1.0f + eps) + * break; + */ + vec_push(forloop->exprs, + (ast_expression*)ast_ifthen_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_LE, + (ast_expression*)whole, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_ADD_F, + (ast_expression*)intrin->fold->imm_float[1], + (ast_expression*)eps + ) + ), + (ast_expression*)ast_breakcont_new( + intrin_ctx(intrin), + false, + 0 + ), + NULL + ) + ); + + /* + * power = base; + * base = whole; + */ + for (i = 0; i <= 1; i++) { + vec_push(forloop->exprs, + (ast_expression*)ast_store_new( + intrin_ctx(intrin), + INSTR_STORE_F, + (ast_expression*)((i) ? base : power), + (ast_expression*)((i) ? whole : base) + ) + ); + } + + /* add the for loop block */ + vec_push(block->exprs, + (ast_expression*)ast_loop_new( + intrin_ctx(intrin), + NULL, + /* for(; 1; ) ?? (can this be NULL too?) */ + (ast_expression*)intrin->fold->imm_float[1], + false, + NULL, + false, + NULL, + (ast_expression*)forloop + ) + ); + + /* return sign * A_i / B_il */ + vec_push(block->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_MUL_F, + (ast_expression*)sign, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + INSTR_DIV_F, + (ast_expression*)A_i, + (ast_expression*)B_i + ) + ) + ) + ); + + vec_push(func->blocks, block); + intrin_reg(intrin, value, func); + + return (ast_expression*)value; +} + +static ast_expression *intrin_log_variant(intrin_t *intrin, const char *name, float base) { + ast_value *value = NULL; + ast_call *callln = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "__builtin_ln", name)); + ast_value *arg1 = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, name, TYPE_FLOAT); + + 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, false)); + + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)callln + ) + ); + + vec_push(func->blocks, body); + intrin_reg(intrin, value, func); + return (ast_expression*)value; +} + +static ast_expression *intrin_log(intrin_t *intrin) { + return intrin_log_variant(intrin, "log", 2.7182818284590452354); +} +static ast_expression *intrin_log10(intrin_t *intrin) { + return intrin_log_variant(intrin, "log10", 10); +} +static ast_expression *intrin_log2(intrin_t *intrin) { + return intrin_log_variant(intrin, "log2", 2); +} +static ast_expression *intrin_logb(intrin_t *intrin) { + /* FLT_RADIX == 2 for now */ + return intrin_log_variant(intrin, "log2", 2); +} + +static ast_expression *intrin_shift_variant(intrin_t *intrin, const char *name, size_t instr) { + /* + * float [shift] (float a, float b) { + * return floor(a [instr] pow(2, b)); + */ + ast_value *value = NULL; + ast_call *callpow = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "pow", name)); + ast_call *callfloor = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", name)); + ast_value *a = ast_value_new(intrin_ctx(intrin), "a", TYPE_FLOAT); + ast_value *b = ast_value_new(intrin_ctx(intrin), "b", TYPE_FLOAT); + ast_block *body = ast_block_new(intrin_ctx(intrin)); + ast_function *func = intrin_value(intrin, &value, name, TYPE_FLOAT); + + vec_push(value->expression.params, a); + vec_push(value->expression.params, b); + + /* = pow(2, b) */ + vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]); + vec_push(callpow->params, (ast_expression*)b); + + /* = floor(a [instr] ) */ + vec_push( + callfloor->params, + (ast_expression*)ast_binary_new( + intrin_ctx(intrin), + instr, + (ast_expression*)a, + (ast_expression*)callpow + ) + ); + + /* return */ + vec_push(body->exprs, + (ast_expression*)ast_return_new( + intrin_ctx(intrin), + (ast_expression*)callfloor + ) + ); + + vec_push(func->blocks, body); + intrin_reg(intrin, value, func); + return (ast_expression*)value; +} + +static ast_expression *intrin_lshift(intrin_t *intrin) { + return intrin_shift_variant(intrin, "lshift", INSTR_MUL_F); +} + +static ast_expression *intrin_rshift(intrin_t *intrin) { + return intrin_shift_variant(intrin, "rshift", INSTR_DIV_F); +} + +/* + * TODO: make static (and handle ast_type_string) here for the builtin + * instead of in SYA parse close. + */ +ast_expression *intrin_debug_typestring(intrin_t *intrin) { + (void)intrin; + return (ast_expression*)0x1; +} + +static const intrin_func_t intrinsics[] = { + {&intrin_isfinite, "__builtin_isfinite", "isfinite", 1}, + {&intrin_isinf, "__builtin_isinf", "isinf", 1}, + {&intrin_isnan, "__builtin_isnan", "isnan", 1}, + {&intrin_isnormal, "__builtin_isnormal", "isnormal", 1}, + {&intrin_signbit, "__builtin_signbit", "signbit", 1}, + {&intrin_acosh, "__builtin_acosh", "acosh", 1}, + {&intrin_asinh, "__builtin_asinh", "asinh", 1}, + {&intrin_atanh, "__builtin_atanh", "atanh", 1}, + {&intrin_exp, "__builtin_exp", "exp", 1}, + {&intrin_exp2, "__builtin_exp2", "exp2", 1}, + {&intrin_expm1, "__builtin_expm1", "expm1", 1}, + {&intrin_mod, "__builtin_mod", "mod", 2}, + {&intrin_pow, "__builtin_pow", "pow", 2}, + {&intrin_fabs, "__builtin_fabs", "fabs", 1}, + {&intrin_log, "__builtin_log", "log", 1}, + {&intrin_log10, "__builtin_log10", "log10", 1}, + {&intrin_log2, "__builtin_log2", "log2", 1}, + {&intrin_logb, "__builtin_logb", "logb", 1}, + {&intrin_lshift, "__builtin_lshift", "", 2}, + {&intrin_rshift, "__builtin_rshift", "", 2}, + {&intrin_epsilon, "__builtin_epsilon", "", 0}, + {&intrin_nan, "__builtin_nan", "", 0}, + {&intrin_inf, "__builtin_inf", "", 0}, + {&intrin_ln, "__builtin_ln", "", 2}, + {&intrin_debug_typestring, "__builtin_debug_typestring", "", 0}, + {&intrin_nullfunc, "#nullfunc", "", 0} +}; + +static void intrin_error(intrin_t *intrin, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vcompile_error(intrin->parser->lex->tok.ctx, fmt, ap); + va_end(ap); +} + +/* exposed */ +intrin_t *intrin_init(parser_t *parser) { + intrin_t *intrin = (intrin_t*)mem_a(sizeof(intrin_t)); + size_t i; + + intrin->parser = parser; + intrin->fold = parser->fold; + intrin->intrinsics = NULL; + intrin->generated = NULL; + + vec_append(intrin->intrinsics, GMQCC_ARRAY_COUNT(intrinsics), intrinsics); + + /* populate with null pointers for tracking generation */ + for (i = 0; i < GMQCC_ARRAY_COUNT(intrinsics); i++) + vec_push(intrin->generated, NULL); + + return intrin; +} + +void intrin_cleanup(intrin_t *intrin) { + vec_free(intrin->intrinsics); + vec_free(intrin->generated); + mem_d(intrin); +} + +ast_expression *intrin_fold(intrin_t *intrin, ast_value *value, ast_expression **exprs) { + size_t i; + if (!value || !value->name) + return NULL; + for (i = 0; i < vec_size(intrin->intrinsics); i++) + if (!strcmp(value->name, intrin->intrinsics[i].name)) + return (vec_size(exprs) != intrin->intrinsics[i].args) + ? NULL + : fold_intrin(intrin->fold, value->name + 10, exprs); + return NULL; +} + +static GMQCC_INLINE ast_expression *intrin_func_try(intrin_t *intrin, size_t offset, const char *compare) { + size_t i; + for (i = 0; i < vec_size(intrin->intrinsics); i++) { + if (strcmp(*(char **)((char *)&intrin->intrinsics[i] + offset), compare)) + continue; + if (intrin->generated[i]) + return intrin->generated[i]; + return intrin->generated[i] = intrin->intrinsics[i].intrin(intrin); + } + return NULL; +} + +static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from) { + size_t i; + ast_expression *find; + + /* try current first */ + if ((find = parser_find_global(intrin->parser, name)) && ((ast_value*)find)->expression.vtype == TYPE_FUNCTION) + for (i = 0; i < vec_size(intrin->parser->functions); ++i) + if (((ast_value*)find)->name && !strcmp(intrin->parser->functions[i]->name, ((ast_value*)find)->name) && intrin->parser->functions[i]->builtin < 0) + return find; + /* try name second */ + if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, name), name))) + return find; + /* try alias third */ + if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, alias), name))) + return find; + + if (from) { + intrin_error(intrin, "need function `%s', compiler depends on it for `__builtin_%s'", name, from); + return intrin_func_self(intrin, "#nullfunc", NULL); + } + return NULL; +} + +ast_expression *intrin_func(intrin_t *intrin, const char *name) { + return intrin_func_self(intrin, name, NULL); +} diff --git a/ir.c b/ir.c deleted file mode 100644 index 8c3d234..0000000 --- a/ir.c +++ /dev/null @@ -1,4441 +0,0 @@ -#include -#include - -#include "gmqcc.h" -#include "ir.h" - -/*********************************************************************** - * Type sizes used at multiple points in the IR codegen - */ - -const char *type_name[TYPE_COUNT] = { - "void", - "string", - "float", - "vector", - "entity", - "field", - "function", - "pointer", - "integer", - "variant", - "struct", - "union", - "array", - - "nil", - "" -}; - -static size_t type_sizeof_[TYPE_COUNT] = { - 1, /* TYPE_VOID */ - 1, /* TYPE_STRING */ - 1, /* TYPE_FLOAT */ - 3, /* TYPE_VECTOR */ - 1, /* TYPE_ENTITY */ - 1, /* TYPE_FIELD */ - 1, /* TYPE_FUNCTION */ - 1, /* TYPE_POINTER */ - 1, /* TYPE_INTEGER */ - 3, /* TYPE_VARIANT */ - 0, /* TYPE_STRUCT */ - 0, /* TYPE_UNION */ - 0, /* TYPE_ARRAY */ - 0, /* TYPE_NIL */ - 0, /* TYPE_NOESPR */ -}; - -const uint16_t type_store_instr[TYPE_COUNT] = { - INSTR_STORE_F, /* should use I when having integer support */ - INSTR_STORE_S, - INSTR_STORE_F, - INSTR_STORE_V, - INSTR_STORE_ENT, - INSTR_STORE_FLD, - INSTR_STORE_FNC, - INSTR_STORE_ENT, /* should use I */ -#if 0 - INSTR_STORE_I, /* integer type */ -#else - INSTR_STORE_F, -#endif - - INSTR_STORE_V, /* variant, should never be accessed */ - - VINSTR_END, /* struct */ - VINSTR_END, /* union */ - VINSTR_END, /* array */ - VINSTR_END, /* nil */ - VINSTR_END, /* noexpr */ -}; - -const uint16_t field_store_instr[TYPE_COUNT] = { - INSTR_STORE_FLD, - INSTR_STORE_FLD, - INSTR_STORE_FLD, - INSTR_STORE_V, - INSTR_STORE_FLD, - INSTR_STORE_FLD, - INSTR_STORE_FLD, - INSTR_STORE_FLD, -#if 0 - INSTR_STORE_FLD, /* integer type */ -#else - INSTR_STORE_FLD, -#endif - - INSTR_STORE_V, /* variant, should never be accessed */ - - VINSTR_END, /* struct */ - VINSTR_END, /* union */ - VINSTR_END, /* array */ - VINSTR_END, /* nil */ - VINSTR_END, /* noexpr */ -}; - -const uint16_t type_storep_instr[TYPE_COUNT] = { - INSTR_STOREP_F, /* should use I when having integer support */ - INSTR_STOREP_S, - INSTR_STOREP_F, - INSTR_STOREP_V, - INSTR_STOREP_ENT, - INSTR_STOREP_FLD, - INSTR_STOREP_FNC, - INSTR_STOREP_ENT, /* should use I */ -#if 0 - INSTR_STOREP_ENT, /* integer type */ -#else - INSTR_STOREP_F, -#endif - - INSTR_STOREP_V, /* variant, should never be accessed */ - - VINSTR_END, /* struct */ - VINSTR_END, /* union */ - VINSTR_END, /* array */ - VINSTR_END, /* nil */ - VINSTR_END, /* noexpr */ -}; - -const uint16_t type_eq_instr[TYPE_COUNT] = { - INSTR_EQ_F, /* should use I when having integer support */ - INSTR_EQ_S, - INSTR_EQ_F, - INSTR_EQ_V, - INSTR_EQ_E, - INSTR_EQ_E, /* FLD has no comparison */ - INSTR_EQ_FNC, - INSTR_EQ_E, /* should use I */ -#if 0 - INSTR_EQ_I, -#else - INSTR_EQ_F, -#endif - - INSTR_EQ_V, /* variant, should never be accessed */ - - VINSTR_END, /* struct */ - VINSTR_END, /* union */ - VINSTR_END, /* array */ - VINSTR_END, /* nil */ - VINSTR_END, /* noexpr */ -}; - -const uint16_t type_ne_instr[TYPE_COUNT] = { - INSTR_NE_F, /* should use I when having integer support */ - INSTR_NE_S, - INSTR_NE_F, - INSTR_NE_V, - INSTR_NE_E, - INSTR_NE_E, /* FLD has no comparison */ - INSTR_NE_FNC, - INSTR_NE_E, /* should use I */ -#if 0 - INSTR_NE_I, -#else - INSTR_NE_F, -#endif - - INSTR_NE_V, /* variant, should never be accessed */ - - VINSTR_END, /* struct */ - VINSTR_END, /* union */ - VINSTR_END, /* array */ - VINSTR_END, /* nil */ - VINSTR_END, /* noexpr */ -}; - -const uint16_t type_not_instr[TYPE_COUNT] = { - INSTR_NOT_F, /* should use I when having integer support */ - VINSTR_END, /* not to be used, depends on string related -f flags */ - INSTR_NOT_F, - INSTR_NOT_V, - INSTR_NOT_ENT, - INSTR_NOT_ENT, - INSTR_NOT_FNC, - INSTR_NOT_ENT, /* should use I */ -#if 0 - INSTR_NOT_I, /* integer type */ -#else - INSTR_NOT_F, -#endif - - INSTR_NOT_V, /* variant, should never be accessed */ - - VINSTR_END, /* struct */ - VINSTR_END, /* union */ - VINSTR_END, /* array */ - VINSTR_END, /* nil */ - VINSTR_END, /* noexpr */ -}; - -/* protos */ -static ir_value* ir_value_var(const char *name, int st, int vtype); -static bool ir_value_set_name(ir_value*, const char *name); -static void ir_value_dump(ir_value*, int (*oprintf)(const char*,...)); - -static ir_value* ir_gen_extparam_proto(ir_builder *ir); -static void ir_gen_extparam (ir_builder *ir); - -static bool ir_builder_set_name(ir_builder *self, const char *name); - -static ir_function* ir_function_new(struct ir_builder_s *owner, int returntype); -static bool ir_function_set_name(ir_function*, const char *name); -static void ir_function_delete(ir_function*); -static void ir_function_dump(ir_function*, char *ind, int (*oprintf)(const char*,...)); - -static ir_value* ir_block_create_general_instr(ir_block *self, lex_ctx_t, const char *label, - int op, ir_value *a, ir_value *b, int outype); -static void ir_block_delete(ir_block*); -static ir_block* ir_block_new(struct ir_function_s *owner, const char *label); -static bool GMQCC_WARN ir_block_create_store(ir_block*, lex_ctx_t, ir_value *target, ir_value *what); -static bool ir_block_set_label(ir_block*, const char *label); -static void ir_block_dump(ir_block*, char *ind, int (*oprintf)(const char*,...)); - -static bool ir_instr_op(ir_instr*, int op, ir_value *value, bool writing); -static void ir_instr_delete(ir_instr*); -static void ir_instr_dump(ir_instr* in, char *ind, int (*oprintf)(const char*,...)); -/* error functions */ - -static void irerror(lex_ctx_t ctx, const char *msg, ...) -{ - va_list ap; - va_start(ap, msg); - con_cvprintmsg(ctx, LVL_ERROR, "internal error", msg, ap); - va_end(ap); -} - -static bool GMQCC_WARN irwarning(lex_ctx_t ctx, int warntype, const char *fmt, ...) -{ - bool r; - va_list ap; - va_start(ap, fmt); - r = vcompile_warning(ctx, warntype, fmt, ap); - va_end(ap); - return r; -} - -/*********************************************************************** - * Vector utility functions - */ - -static bool GMQCC_WARN vec_ir_value_find(ir_value **vec, const ir_value *what, size_t *idx) -{ - size_t i; - size_t len = vec_size(vec); - for (i = 0; i < len; ++i) { - if (vec[i] == what) { - if (idx) *idx = i; - return true; - } - } - return false; -} - -static bool GMQCC_WARN vec_ir_block_find(ir_block **vec, ir_block *what, size_t *idx) -{ - size_t i; - size_t len = vec_size(vec); - for (i = 0; i < len; ++i) { - if (vec[i] == what) { - if (idx) *idx = i; - return true; - } - } - return false; -} - -static bool GMQCC_WARN vec_ir_instr_find(ir_instr **vec, ir_instr *what, size_t *idx) -{ - size_t i; - size_t len = vec_size(vec); - for (i = 0; i < len; ++i) { - if (vec[i] == what) { - if (idx) *idx = i; - return true; - } - } - return false; -} - -/*********************************************************************** - * IR Builder - */ - -static void ir_block_delete_quick(ir_block* self); -static void ir_instr_delete_quick(ir_instr *self); -static void ir_function_delete_quick(ir_function *self); - -ir_builder* ir_builder_new(const char *modulename) -{ - ir_builder* self; - size_t i; - - self = (ir_builder*)mem_a(sizeof(*self)); - if (!self) - return NULL; - - self->functions = NULL; - self->globals = NULL; - self->fields = NULL; - self->filenames = NULL; - self->filestrings = NULL; - self->htglobals = util_htnew(IR_HT_SIZE); - self->htfields = util_htnew(IR_HT_SIZE); - self->htfunctions = util_htnew(IR_HT_SIZE); - - self->extparams = NULL; - self->extparam_protos = NULL; - - self->first_common_globaltemp = 0; - self->max_globaltemps = 0; - self->first_common_local = 0; - self->max_locals = 0; - - self->str_immediate = 0; - self->name = NULL; - if (!ir_builder_set_name(self, modulename)) { - mem_d(self); - return NULL; - } - - self->nil = ir_value_var("nil", store_value, TYPE_NIL); - self->nil->cvq = CV_CONST; - - for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) { - /* we write to them, but they're not supposed to be used outside the IR, so - * let's not allow the generation of ir_instrs which use these. - * So it's a constant noexpr. - */ - self->vinstr_temp[i] = ir_value_var("vinstr_temp", store_value, TYPE_NOEXPR); - self->vinstr_temp[i]->cvq = CV_CONST; - } - - self->reserved_va_count = NULL; - self->coverage_func = NULL; - - self->code = code_init(); - - return self; -} - -void ir_builder_delete(ir_builder* self) -{ - size_t i; - util_htdel(self->htglobals); - util_htdel(self->htfields); - util_htdel(self->htfunctions); - mem_d((void*)self->name); - for (i = 0; i != vec_size(self->functions); ++i) { - ir_function_delete_quick(self->functions[i]); - } - vec_free(self->functions); - for (i = 0; i != vec_size(self->extparams); ++i) { - ir_value_delete(self->extparams[i]); - } - vec_free(self->extparams); - vec_free(self->extparam_protos); - for (i = 0; i != vec_size(self->globals); ++i) { - ir_value_delete(self->globals[i]); - } - vec_free(self->globals); - for (i = 0; i != vec_size(self->fields); ++i) { - ir_value_delete(self->fields[i]); - } - ir_value_delete(self->nil); - for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) { - ir_value_delete(self->vinstr_temp[i]); - } - vec_free(self->fields); - vec_free(self->filenames); - vec_free(self->filestrings); - - code_cleanup(self->code); - mem_d(self); -} - -bool ir_builder_set_name(ir_builder *self, const char *name) -{ - if (self->name) - mem_d((void*)self->name); - self->name = util_strdup(name); - return !!self->name; -} - -static ir_function* ir_builder_get_function(ir_builder *self, const char *name) -{ - return (ir_function*)util_htget(self->htfunctions, name); -} - -ir_function* ir_builder_create_function(ir_builder *self, const char *name, int outtype) -{ - ir_function *fn = ir_builder_get_function(self, name); - if (fn) { - return NULL; - } - - fn = ir_function_new(self, outtype); - if (!ir_function_set_name(fn, name)) - { - ir_function_delete(fn); - return NULL; - } - vec_push(self->functions, fn); - util_htset(self->htfunctions, name, fn); - - fn->value = ir_builder_create_global(self, fn->name, TYPE_FUNCTION); - if (!fn->value) { - ir_function_delete(fn); - return NULL; - } - - fn->value->hasvalue = true; - fn->value->outtype = outtype; - fn->value->constval.vfunc = fn; - fn->value->context = fn->context; - - return fn; -} - -static ir_value* ir_builder_get_global(ir_builder *self, const char *name) -{ - return (ir_value*)util_htget(self->htglobals, name); -} - -ir_value* ir_builder_create_global(ir_builder *self, const char *name, int vtype) -{ - ir_value *ve; - - if (name[0] != '#') - { - ve = ir_builder_get_global(self, name); - if (ve) { - return NULL; - } - } - - ve = ir_value_var(name, store_global, vtype); - vec_push(self->globals, ve); - util_htset(self->htglobals, name, ve); - return ve; -} - -ir_value* ir_builder_get_va_count(ir_builder *self) -{ - if (self->reserved_va_count) - return self->reserved_va_count; - return (self->reserved_va_count = ir_builder_create_global(self, "reserved:va_count", TYPE_FLOAT)); -} - -static ir_value* ir_builder_get_field(ir_builder *self, const char *name) -{ - return (ir_value*)util_htget(self->htfields, name); -} - - -ir_value* ir_builder_create_field(ir_builder *self, const char *name, int vtype) -{ - ir_value *ve = ir_builder_get_field(self, name); - if (ve) { - return NULL; - } - - ve = ir_value_var(name, store_global, TYPE_FIELD); - ve->fieldtype = vtype; - vec_push(self->fields, ve); - util_htset(self->htfields, name, ve); - return ve; -} - -/*********************************************************************** - *IR Function - */ - -static bool ir_function_naive_phi(ir_function*); -static void ir_function_enumerate(ir_function*); -static bool ir_function_calculate_liferanges(ir_function*); -static bool ir_function_allocate_locals(ir_function*); - -ir_function* ir_function_new(ir_builder* owner, int outtype) -{ - ir_function *self; - self = (ir_function*)mem_a(sizeof(*self)); - - if (!self) - return NULL; - - memset(self, 0, sizeof(*self)); - - self->name = NULL; - if (!ir_function_set_name(self, "<@unnamed>")) { - mem_d(self); - return NULL; - } - self->flags = 0; - - self->owner = owner; - self->context.file = "<@no context>"; - self->context.line = 0; - self->outtype = outtype; - self->value = NULL; - self->builtin = 0; - - self->params = NULL; - self->blocks = NULL; - self->values = NULL; - self->locals = NULL; - - self->max_varargs = 0; - - self->code_function_def = -1; - self->allocated_locals = 0; - self->globaltemps = 0; - - self->run_id = 0; - return self; -} - -bool ir_function_set_name(ir_function *self, const char *name) -{ - if (self->name) - mem_d((void*)self->name); - self->name = util_strdup(name); - return !!self->name; -} - -static void ir_function_delete_quick(ir_function *self) -{ - size_t i; - mem_d((void*)self->name); - - for (i = 0; i != vec_size(self->blocks); ++i) - ir_block_delete_quick(self->blocks[i]); - vec_free(self->blocks); - - vec_free(self->params); - - for (i = 0; i != vec_size(self->values); ++i) - ir_value_delete(self->values[i]); - vec_free(self->values); - - for (i = 0; i != vec_size(self->locals); ++i) - ir_value_delete(self->locals[i]); - vec_free(self->locals); - - /* self->value is deleted by the builder */ - - mem_d(self); -} - -void ir_function_delete(ir_function *self) -{ - size_t i; - mem_d((void*)self->name); - - for (i = 0; i != vec_size(self->blocks); ++i) - ir_block_delete(self->blocks[i]); - vec_free(self->blocks); - - vec_free(self->params); - - for (i = 0; i != vec_size(self->values); ++i) - ir_value_delete(self->values[i]); - vec_free(self->values); - - for (i = 0; i != vec_size(self->locals); ++i) - ir_value_delete(self->locals[i]); - vec_free(self->locals); - - /* self->value is deleted by the builder */ - - mem_d(self); -} - -static void ir_function_collect_value(ir_function *self, ir_value *v) -{ - vec_push(self->values, v); -} - -ir_block* ir_function_create_block(lex_ctx_t ctx, ir_function *self, const char *label) -{ - ir_block* bn = ir_block_new(self, label); - bn->context = ctx; - vec_push(self->blocks, bn); - - if ((self->flags & IR_FLAG_BLOCK_COVERAGE) && self->owner->coverage_func) - (void)ir_block_create_call(bn, ctx, NULL, self->owner->coverage_func, false); - - return bn; -} - -static bool instr_is_operation(uint16_t op) -{ - return ( (op >= INSTR_MUL_F && op <= INSTR_GT) || - (op >= INSTR_LOAD_F && op <= INSTR_LOAD_FNC) || - (op == INSTR_ADDRESS) || - (op >= INSTR_NOT_F && op <= INSTR_NOT_FNC) || - (op >= INSTR_AND && op <= INSTR_BITOR) || - (op >= INSTR_CALL0 && op <= INSTR_CALL8) || - (op >= VINSTR_BITAND_V && op <= VINSTR_NEG_V) ); -} - -static bool ir_function_pass_peephole(ir_function *self) -{ - size_t b; - - for (b = 0; b < vec_size(self->blocks); ++b) { - size_t i; - ir_block *block = self->blocks[b]; - - for (i = 0; i < vec_size(block->instr); ++i) { - ir_instr *inst; - inst = block->instr[i]; - - if (i >= 1 && - (inst->opcode >= INSTR_STORE_F && - inst->opcode <= INSTR_STORE_FNC)) - { - ir_instr *store; - ir_instr *oper; - ir_value *value; - - store = inst; - - oper = block->instr[i-1]; - if (!instr_is_operation(oper->opcode)) - continue; - - /* Don't change semantics of MUL_VF in engines where these may not alias. */ - if (OPTS_FLAG(LEGACY_VECTOR_MATHS)) { - if (oper->opcode == INSTR_MUL_VF && oper->_ops[2]->memberof == oper->_ops[1]) - continue; - if (oper->opcode == INSTR_MUL_FV && oper->_ops[1]->memberof == oper->_ops[2]) - continue; - } - - value = oper->_ops[0]; - - /* only do it for SSA values */ - if (value->store != store_value) - continue; - - /* don't optimize out the temp if it's used later again */ - if (vec_size(value->reads) != 1) - continue; - - /* The very next store must use this value */ - if (value->reads[0] != store) - continue; - - /* And of course the store must _read_ from it, so it's in - * OP 1 */ - if (store->_ops[1] != value) - continue; - - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - (void)!ir_instr_op(oper, 0, store->_ops[0], true); - - vec_remove(block->instr, i, 1); - ir_instr_delete(store); - } - else if (inst->opcode == VINSTR_COND) - { - /* COND on a value resulting from a NOT could - * remove the NOT and swap its operands - */ - while (true) { - ir_block *tmp; - size_t inotid; - ir_instr *inot; - ir_value *value; - value = inst->_ops[0]; - - if (value->store != store_value || - vec_size(value->reads) != 1 || - value->reads[0] != inst) - { - break; - } - - inot = value->writes[0]; - if (inot->_ops[0] != value || - inot->opcode < INSTR_NOT_F || - inot->opcode > INSTR_NOT_FNC || - inot->opcode == INSTR_NOT_V || /* can't do these */ - inot->opcode == INSTR_NOT_S) - { - break; - } - - /* count */ - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - /* change operand */ - (void)!ir_instr_op(inst, 0, inot->_ops[1], false); - /* remove NOT */ - tmp = inot->owner; - for (inotid = 0; inotid < vec_size(tmp->instr); ++inotid) { - if (tmp->instr[inotid] == inot) - break; - } - if (inotid >= vec_size(tmp->instr)) { - compile_error(inst->context, "sanity-check failed: failed to find instruction to optimize out"); - return false; - } - vec_remove(tmp->instr, inotid, 1); - ir_instr_delete(inot); - /* swap ontrue/onfalse */ - tmp = inst->bops[0]; - inst->bops[0] = inst->bops[1]; - inst->bops[1] = tmp; - } - continue; - } - } - } - - return true; -} - -static bool ir_function_pass_tailrecursion(ir_function *self) -{ - size_t b, p; - - for (b = 0; b < vec_size(self->blocks); ++b) { - ir_value *funcval; - ir_instr *ret, *call, *store = NULL; - ir_block *block = self->blocks[b]; - - if (!block->final || vec_size(block->instr) < 2) - continue; - - ret = block->instr[vec_size(block->instr)-1]; - if (ret->opcode != INSTR_DONE && ret->opcode != INSTR_RETURN) - continue; - - call = block->instr[vec_size(block->instr)-2]; - if (call->opcode >= INSTR_STORE_F && call->opcode <= INSTR_STORE_FNC) { - /* account for the unoptimized - * CALL - * STORE %return, %tmp - * RETURN %tmp - * version - */ - if (vec_size(block->instr) < 3) - continue; - - store = call; - call = block->instr[vec_size(block->instr)-3]; - } - - if (call->opcode < INSTR_CALL0 || call->opcode > INSTR_CALL8) - continue; - - if (store) { - /* optimize out the STORE */ - if (ret->_ops[0] && - ret->_ops[0] == store->_ops[0] && - store->_ops[1] == call->_ops[0]) - { - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - call->_ops[0] = store->_ops[0]; - vec_remove(block->instr, vec_size(block->instr) - 2, 1); - ir_instr_delete(store); - } - else - continue; - } - - if (!call->_ops[0]) - continue; - - funcval = call->_ops[1]; - if (!funcval) - continue; - if (funcval->vtype != TYPE_FUNCTION || funcval->constval.vfunc != self) - continue; - - /* now we have a CALL and a RET, check if it's a tailcall */ - if (ret->_ops[0] && call->_ops[0] != ret->_ops[0]) - continue; - - ++opts_optimizationcount[OPTIM_TAIL_RECURSION]; - vec_shrinkby(block->instr, 2); - - block->final = false; /* open it back up */ - - /* emite parameter-stores */ - for (p = 0; p < vec_size(call->params); ++p) { - /* assert(call->params_count <= self->locals_count); */ - if (!ir_block_create_store(block, call->context, self->locals[p], call->params[p])) { - irerror(call->context, "failed to create tailcall store instruction for parameter %i", (int)p); - return false; - } - } - if (!ir_block_create_jump(block, call->context, self->blocks[0])) { - irerror(call->context, "failed to create tailcall jump"); - return false; - } - - ir_instr_delete(call); - ir_instr_delete(ret); - } - - return true; -} - -bool ir_function_finalize(ir_function *self) -{ - size_t i; - - if (self->builtin) - return true; - - if (OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) { - if (!ir_function_pass_peephole(self)) { - irerror(self->context, "generic optimization pass broke something in `%s`", self->name); - return false; - } - } - - if (OPTS_OPTIMIZATION(OPTIM_TAIL_RECURSION)) { - if (!ir_function_pass_tailrecursion(self)) { - irerror(self->context, "tail-recursion optimization pass broke something in `%s`", self->name); - return false; - } - } - - if (!ir_function_naive_phi(self)) { - irerror(self->context, "internal error: ir_function_naive_phi failed"); - return false; - } - - for (i = 0; i < vec_size(self->locals); ++i) { - ir_value *v = self->locals[i]; - if (v->vtype == TYPE_VECTOR || - (v->vtype == TYPE_FIELD && v->outtype == TYPE_VECTOR)) - { - ir_value_vector_member(v, 0); - ir_value_vector_member(v, 1); - ir_value_vector_member(v, 2); - } - } - for (i = 0; i < vec_size(self->values); ++i) { - ir_value *v = self->values[i]; - if (v->vtype == TYPE_VECTOR || - (v->vtype == TYPE_FIELD && v->outtype == TYPE_VECTOR)) - { - ir_value_vector_member(v, 0); - ir_value_vector_member(v, 1); - ir_value_vector_member(v, 2); - } - } - - ir_function_enumerate(self); - - if (!ir_function_calculate_liferanges(self)) - return false; - if (!ir_function_allocate_locals(self)) - return false; - return true; -} - -ir_value* ir_function_create_local(ir_function *self, const char *name, int vtype, bool param) -{ - ir_value *ve; - - if (param && - vec_size(self->locals) && - self->locals[vec_size(self->locals)-1]->store != store_param) { - irerror(self->context, "cannot add parameters after adding locals"); - return NULL; - } - - ve = ir_value_var(name, (param ? store_param : store_local), vtype); - if (param) - ve->locked = true; - vec_push(self->locals, ve); - return ve; -} - -/*********************************************************************** - *IR Block - */ - -ir_block* ir_block_new(ir_function* owner, const char *name) -{ - ir_block *self; - self = (ir_block*)mem_a(sizeof(*self)); - if (!self) - return NULL; - - memset(self, 0, sizeof(*self)); - - self->label = NULL; - if (name && !ir_block_set_label(self, name)) { - mem_d(self); - return NULL; - } - self->owner = owner; - self->context.file = "<@no context>"; - self->context.line = 0; - self->final = false; - - self->instr = NULL; - self->entries = NULL; - self->exits = NULL; - - self->eid = 0; - self->is_return = false; - - self->living = NULL; - - self->generated = false; - - return self; -} - -static void ir_block_delete_quick(ir_block* self) -{ - size_t i; - if (self->label) mem_d(self->label); - for (i = 0; i != vec_size(self->instr); ++i) - ir_instr_delete_quick(self->instr[i]); - vec_free(self->instr); - vec_free(self->entries); - vec_free(self->exits); - vec_free(self->living); - mem_d(self); -} - -void ir_block_delete(ir_block* self) -{ - size_t i; - if (self->label) mem_d(self->label); - for (i = 0; i != vec_size(self->instr); ++i) - ir_instr_delete(self->instr[i]); - vec_free(self->instr); - vec_free(self->entries); - vec_free(self->exits); - vec_free(self->living); - mem_d(self); -} - -bool ir_block_set_label(ir_block *self, const char *name) -{ - if (self->label) - mem_d((void*)self->label); - self->label = util_strdup(name); - return !!self->label; -} - -/*********************************************************************** - *IR Instructions - */ - -static ir_instr* ir_instr_new(lex_ctx_t ctx, ir_block* owner, int op) -{ - ir_instr *self; - self = (ir_instr*)mem_a(sizeof(*self)); - if (!self) - return NULL; - - self->owner = owner; - self->context = ctx; - self->opcode = op; - self->_ops[0] = NULL; - self->_ops[1] = NULL; - self->_ops[2] = NULL; - self->bops[0] = NULL; - self->bops[1] = NULL; - - self->phi = NULL; - self->params = NULL; - - self->eid = 0; - - self->likely = true; - return self; -} - -static void ir_instr_delete_quick(ir_instr *self) -{ - vec_free(self->phi); - vec_free(self->params); - mem_d(self); -} - -static void ir_instr_delete(ir_instr *self) -{ - size_t i; - /* The following calls can only delete from - * vectors, we still want to delete this instruction - * so ignore the return value. Since with the warn_unused_result attribute - * gcc doesn't care about an explicit: (void)foo(); to ignore the result, - * I have to improvise here and use if(foo()); - */ - for (i = 0; i < vec_size(self->phi); ++i) { - size_t idx; - if (vec_ir_instr_find(self->phi[i].value->writes, self, &idx)) - vec_remove(self->phi[i].value->writes, idx, 1); - if (vec_ir_instr_find(self->phi[i].value->reads, self, &idx)) - vec_remove(self->phi[i].value->reads, idx, 1); - } - vec_free(self->phi); - for (i = 0; i < vec_size(self->params); ++i) { - size_t idx; - if (vec_ir_instr_find(self->params[i]->writes, self, &idx)) - vec_remove(self->params[i]->writes, idx, 1); - if (vec_ir_instr_find(self->params[i]->reads, self, &idx)) - vec_remove(self->params[i]->reads, idx, 1); - } - vec_free(self->params); - (void)!ir_instr_op(self, 0, NULL, false); - (void)!ir_instr_op(self, 1, NULL, false); - (void)!ir_instr_op(self, 2, NULL, false); - mem_d(self); -} - -static bool ir_instr_op(ir_instr *self, int op, ir_value *v, bool writing) -{ - if (v && v->vtype == TYPE_NOEXPR) { - irerror(self->context, "tried to use a NOEXPR value"); - return false; - } - - if (self->_ops[op]) { - size_t idx; - if (writing && vec_ir_instr_find(self->_ops[op]->writes, self, &idx)) - vec_remove(self->_ops[op]->writes, idx, 1); - else if (vec_ir_instr_find(self->_ops[op]->reads, self, &idx)) - vec_remove(self->_ops[op]->reads, idx, 1); - } - if (v) { - if (writing) - vec_push(v->writes, self); - else - vec_push(v->reads, self); - } - self->_ops[op] = v; - return true; -} - -/*********************************************************************** - *IR Value - */ - -static void ir_value_code_setaddr(ir_value *self, int32_t gaddr) -{ - self->code.globaladdr = gaddr; - if (self->members[0]) self->members[0]->code.globaladdr = gaddr; - if (self->members[1]) self->members[1]->code.globaladdr = gaddr; - if (self->members[2]) self->members[2]->code.globaladdr = gaddr; -} - -static int32_t ir_value_code_addr(const ir_value *self) -{ - if (self->store == store_return) - return OFS_RETURN + self->code.addroffset; - return self->code.globaladdr + self->code.addroffset; -} - -ir_value* ir_value_var(const char *name, int storetype, int vtype) -{ - ir_value *self; - self = (ir_value*)mem_a(sizeof(*self)); - self->vtype = vtype; - self->fieldtype = TYPE_VOID; - self->outtype = TYPE_VOID; - self->store = storetype; - self->flags = 0; - - self->reads = NULL; - self->writes = NULL; - - self->cvq = CV_NONE; - self->hasvalue = false; - self->context.file = "<@no context>"; - self->context.line = 0; - self->name = NULL; - if (name && !ir_value_set_name(self, name)) { - irerror(self->context, "out of memory"); - mem_d(self); - return NULL; - } - - memset(&self->constval, 0, sizeof(self->constval)); - memset(&self->code, 0, sizeof(self->code)); - - self->members[0] = NULL; - self->members[1] = NULL; - self->members[2] = NULL; - self->memberof = NULL; - - self->unique_life = false; - self->locked = false; - self->callparam = false; - - self->life = NULL; - 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; - size_t len; - ir_value *m; - if (member >= 3) - return NULL; - - if (self->members[member]) - return self->members[member]; - - if (self->name) { - len = strlen(self->name); - name = (char*)mem_a(len + 3); - memcpy(name, self->name, len); - name[len+0] = '_'; - name[len+1] = 'x' + member; - name[len+2] = '\0'; - } - else - name = NULL; - - if (self->vtype == TYPE_VECTOR) - { - m = ir_value_var(name, self->store, TYPE_FLOAT); - if (name) - mem_d(name); - if (!m) - return NULL; - m->context = self->context; - - self->members[member] = m; - m->code.addroffset = member; - } - else if (self->vtype == TYPE_FIELD) - { - if (self->fieldtype != TYPE_VECTOR) - return NULL; - m = ir_value_var(name, self->store, TYPE_FIELD); - if (name) - mem_d(name); - if (!m) - return NULL; - m->fieldtype = TYPE_FLOAT; - m->context = self->context; - - self->members[member] = m; - m->code.addroffset = member; - } - else - { - irerror(self->context, "invalid member access on %s", self->name); - return NULL; - } - - m->memberof = self; - return m; -} - -static GMQCC_INLINE size_t ir_value_sizeof(const ir_value *self) -{ - if (self->vtype == TYPE_FIELD && self->fieldtype == TYPE_VECTOR) - return type_sizeof_[TYPE_VECTOR]; - return type_sizeof_[self->vtype]; -} - -static ir_value* ir_value_out(ir_function *owner, const char *name, int storetype, int vtype) -{ - ir_value *v = ir_value_var(name, storetype, vtype); - if (!v) - return NULL; - ir_function_collect_value(owner, v); - return v; -} - -void ir_value_delete(ir_value* self) -{ - size_t i; - if (self->name) - mem_d((void*)self->name); - if (self->hasvalue) - { - if (self->vtype == TYPE_STRING) - mem_d((void*)self->constval.vstring); - } - 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); - vec_free(self->life); - mem_d(self); -} - -bool ir_value_set_name(ir_value *self, const char *name) -{ - if (self->name) - mem_d((void*)self->name); - self->name = util_strdup(name); - return !!self->name; -} - -bool ir_value_set_float(ir_value *self, float f) -{ - if (self->vtype != TYPE_FLOAT) - return false; - self->constval.vfloat = f; - self->hasvalue = true; - return true; -} - -bool ir_value_set_func(ir_value *self, int f) -{ - if (self->vtype != TYPE_FUNCTION) - return false; - self->constval.vint = f; - self->hasvalue = true; - return true; -} - -bool ir_value_set_vector(ir_value *self, vec3_t v) -{ - if (self->vtype != TYPE_VECTOR) - return false; - self->constval.vvec = v; - self->hasvalue = true; - return true; -} - -bool ir_value_set_field(ir_value *self, ir_value *fld) -{ - if (self->vtype != TYPE_FIELD) - return false; - self->constval.vpointer = fld; - self->hasvalue = true; - return true; -} - -bool ir_value_set_string(ir_value *self, const char *str) -{ - if (self->vtype != TYPE_STRING) - return false; - self->constval.vstring = util_strdupe(str); - self->hasvalue = true; - return true; -} - -#if 0 -bool ir_value_set_int(ir_value *self, int i) -{ - if (self->vtype != TYPE_INTEGER) - return false; - self->constval.vint = i; - self->hasvalue = true; - return true; -} -#endif - -bool ir_value_lives(ir_value *self, size_t at) -{ - size_t i; - for (i = 0; i < vec_size(self->life); ++i) - { - ir_life_entry_t *life = &self->life[i]; - if (life->start <= at && at <= life->end) - return true; - if (life->start > at) /* since it's ordered */ - return false; - } - return false; -} - -static bool ir_value_life_insert(ir_value *self, size_t idx, ir_life_entry_t e) -{ - size_t k; - vec_push(self->life, e); - for (k = vec_size(self->life)-1; k > idx; --k) - self->life[k] = self->life[k-1]; - self->life[idx] = e; - return true; -} - -static bool ir_value_life_merge(ir_value *self, size_t s) -{ - size_t i; - const size_t vs = vec_size(self->life); - ir_life_entry_t *life = NULL; - ir_life_entry_t *before = NULL; - ir_life_entry_t new_entry; - - /* Find the first range >= s */ - for (i = 0; i < vs; ++i) - { - before = life; - life = &self->life[i]; - if (life->start > s) - break; - } - /* nothing found? append */ - if (i == vs) { - ir_life_entry_t e; - if (life && life->end+1 == s) - { - /* previous life range can be merged in */ - life->end++; - return true; - } - if (life && life->end >= s) - return false; - e.start = e.end = s; - vec_push(self->life, e); - return true; - } - /* found */ - if (before) - { - if (before->end + 1 == s && - life->start - 1 == s) - { - /* merge */ - before->end = life->end; - vec_remove(self->life, i, 1); - return true; - } - if (before->end + 1 == s) - { - /* extend before */ - before->end++; - return true; - } - /* already contained */ - if (before->end >= s) - return false; - } - /* extend */ - if (life->start - 1 == s) - { - life->start--; - return true; - } - /* insert a new entry */ - new_entry.start = new_entry.end = s; - return ir_value_life_insert(self, i, new_entry); -} - -static bool ir_value_life_merge_into(ir_value *self, const ir_value *other) -{ - size_t i, myi; - - if (!vec_size(other->life)) - return true; - - if (!vec_size(self->life)) { - size_t count = vec_size(other->life); - ir_life_entry_t *life = vec_add(self->life, count); - memcpy(life, other->life, count * sizeof(*life)); - return true; - } - - myi = 0; - for (i = 0; i < vec_size(other->life); ++i) - { - const ir_life_entry_t *life = &other->life[i]; - while (true) - { - ir_life_entry_t *entry = &self->life[myi]; - - if (life->end+1 < entry->start) - { - /* adding an interval before entry */ - if (!ir_value_life_insert(self, myi, *life)) - return false; - ++myi; - break; - } - - if (life->start < entry->start && - life->end+1 >= entry->start) - { - /* starts earlier and overlaps */ - entry->start = life->start; - } - - if (life->end > entry->end && - life->start <= entry->end+1) - { - /* ends later and overlaps */ - entry->end = life->end; - } - - /* see if our change combines it with the next ranges */ - while (myi+1 < vec_size(self->life) && - entry->end+1 >= self->life[1+myi].start) - { - /* overlaps with (myi+1) */ - if (entry->end < self->life[1+myi].end) - entry->end = self->life[1+myi].end; - vec_remove(self->life, myi+1, 1); - entry = &self->life[myi]; - } - - /* see if we're after the entry */ - if (life->start > entry->end) - { - ++myi; - /* append if we're at the end */ - if (myi >= vec_size(self->life)) { - vec_push(self->life, *life); - break; - } - /* otherweise check the next range */ - continue; - } - break; - } - } - return true; -} - -static bool ir_values_overlap(const ir_value *a, const ir_value *b) -{ - /* For any life entry in A see if it overlaps with - * any life entry in B. - * Note that the life entries are orderes, so we can make a - * more efficient algorithm there than naively translating the - * statement above. - */ - - ir_life_entry_t *la, *lb, *enda, *endb; - - /* first of all, if either has no life range, they cannot clash */ - if (!vec_size(a->life) || !vec_size(b->life)) - return false; - - la = a->life; - lb = b->life; - enda = la + vec_size(a->life); - endb = lb + vec_size(b->life); - while (true) - { - /* check if the entries overlap, for that, - * both must start before the other one ends. - */ - if (la->start < lb->end && - lb->start < la->end) - { - return true; - } - - /* entries are ordered - * one entry is earlier than the other - * that earlier entry will be moved forward - */ - if (la->start < lb->start) - { - /* order: A B, move A forward - * check if we hit the end with A - */ - if (++la == enda) - break; - } - else /* if (lb->start < la->start) actually <= */ - { - /* order: B A, move B forward - * check if we hit the end with B - */ - if (++lb == endb) - break; - } - } - return false; -} - -/*********************************************************************** - *IR main operations - */ - -static bool ir_check_unreachable(ir_block *self) -{ - /* The IR should never have to deal with unreachable code */ - if (!self->final/* || OPTS_FLAG(ALLOW_UNREACHABLE_CODE)*/) - return true; - irerror(self->context, "unreachable statement (%s)", self->label); - return false; -} - -bool ir_block_create_store_op(ir_block *self, lex_ctx_t ctx, int op, ir_value *target, ir_value *what) -{ - ir_instr *in; - if (!ir_check_unreachable(self)) - return false; - - if (target->store == store_value && - (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC)) - { - irerror(self->context, "cannot store to an SSA value"); - irerror(self->context, "trying to store: %s <- %s", target->name, what->name); - irerror(self->context, "instruction: %s", util_instr_str[op]); - return false; - } - - in = ir_instr_new(ctx, self, op); - if (!in) - return false; - - if (!ir_instr_op(in, 0, target, (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC)) || - !ir_instr_op(in, 1, what, false)) - { - ir_instr_delete(in); - return false; - } - vec_push(self->instr, in); - 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; - int vtype; - if (target->vtype == TYPE_VARIANT) - vtype = what->vtype; - else - vtype = target->vtype; - -#if 0 - if (vtype == TYPE_FLOAT && what->vtype == TYPE_INTEGER) - op = INSTR_CONV_ITOF; - else if (vtype == TYPE_INTEGER && what->vtype == TYPE_FLOAT) - op = INSTR_CONV_FTOI; -#endif - op = type_store_instr[vtype]; - - if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) { - if (op == INSTR_STORE_FLD && what->fieldtype == TYPE_VECTOR) - op = INSTR_STORE_V; - } - - return ir_block_create_store_op(self, ctx, op, target, what); -} - -bool ir_block_create_storep(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what) -{ - int op = 0; - int vtype; - - if (target->vtype != TYPE_POINTER) - return false; - - /* storing using pointer - target is a pointer, type must be - * inferred from source - */ - vtype = what->vtype; - - op = type_storep_instr[vtype]; - if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) { - if (op == INSTR_STOREP_FLD && what->fieldtype == TYPE_VECTOR) - op = INSTR_STOREP_V; - } - - return ir_block_create_store_op(self, ctx, op, target, what); -} - -bool ir_block_create_return(ir_block *self, lex_ctx_t ctx, ir_value *v) -{ - ir_instr *in; - if (!ir_check_unreachable(self)) - return false; - - self->final = true; - - self->is_return = true; - in = ir_instr_new(ctx, self, INSTR_RETURN); - if (!in) - return false; - - if (v && !ir_instr_op(in, 0, v, false)) { - ir_instr_delete(in); - return false; - } - - vec_push(self->instr, in); - return true; -} - -bool ir_block_create_if(ir_block *self, lex_ctx_t ctx, ir_value *v, - ir_block *ontrue, ir_block *onfalse) -{ - ir_instr *in; - if (!ir_check_unreachable(self)) - return false; - self->final = true; - /*in = ir_instr_new(ctx, self, (v->vtype == TYPE_STRING ? INSTR_IF_S : INSTR_IF_F));*/ - in = ir_instr_new(ctx, self, VINSTR_COND); - if (!in) - return false; - - if (!ir_instr_op(in, 0, v, false)) { - ir_instr_delete(in); - return false; - } - - in->bops[0] = ontrue; - in->bops[1] = onfalse; - - vec_push(self->instr, in); - - vec_push(self->exits, ontrue); - vec_push(self->exits, onfalse); - vec_push(ontrue->entries, self); - vec_push(onfalse->entries, self); - return true; -} - -bool ir_block_create_jump(ir_block *self, lex_ctx_t ctx, ir_block *to) -{ - ir_instr *in; - if (!ir_check_unreachable(self)) - return false; - self->final = true; - in = ir_instr_new(ctx, self, VINSTR_JUMP); - if (!in) - return false; - - in->bops[0] = to; - vec_push(self->instr, in); - - vec_push(self->exits, to); - vec_push(to->entries, self); - return true; -} - -bool ir_block_create_goto(ir_block *self, lex_ctx_t ctx, ir_block *to) -{ - self->owner->flags |= IR_FLAG_HAS_GOTO; - return ir_block_create_jump(self, ctx, to); -} - -ir_instr* ir_block_create_phi(ir_block *self, lex_ctx_t ctx, const char *label, int ot) -{ - ir_value *out; - ir_instr *in; - if (!ir_check_unreachable(self)) - return NULL; - in = ir_instr_new(ctx, self, VINSTR_PHI); - if (!in) - return NULL; - out = ir_value_out(self->owner, label, store_value, ot); - if (!out) { - ir_instr_delete(in); - return NULL; - } - if (!ir_instr_op(in, 0, out, true)) { - ir_instr_delete(in); - ir_value_delete(out); - return NULL; - } - vec_push(self->instr, in); - return in; -} - -ir_value* ir_phi_value(ir_instr *self) -{ - return self->_ops[0]; -} - -void ir_phi_add(ir_instr* self, ir_block *b, ir_value *v) -{ - ir_phi_entry_t pe; - - if (!vec_ir_block_find(self->owner->entries, b, NULL)) { - /* Must not be possible to cause this, otherwise the AST - * is doing something wrong. - */ - irerror(self->context, "Invalid entry block for PHI"); - exit(EXIT_FAILURE); - } - - pe.value = v; - pe.from = b; - vec_push(v->reads, self); - vec_push(self->phi, pe); -} - -/* call related code */ -ir_instr* ir_block_create_call(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *func, bool noreturn) -{ - ir_value *out; - ir_instr *in; - if (!ir_check_unreachable(self)) - return NULL; - in = ir_instr_new(ctx, self, (noreturn ? VINSTR_NRCALL : INSTR_CALL0)); - if (!in) - return NULL; - if (noreturn) { - self->final = true; - self->is_return = true; - } - out = ir_value_out(self->owner, label, (func->outtype == TYPE_VOID) ? store_return : store_value, func->outtype); - if (!out) { - ir_instr_delete(in); - return NULL; - } - if (!ir_instr_op(in, 0, out, true) || - !ir_instr_op(in, 1, func, false)) - { - ir_instr_delete(in); - ir_value_delete(out); - return NULL; - } - vec_push(self->instr, in); - /* - if (noreturn) { - if (!ir_block_create_return(self, ctx, NULL)) { - compile_error(ctx, "internal error: failed to generate dummy-return instruction"); - ir_instr_delete(in); - return NULL; - } - } - */ - return in; -} - -ir_value* ir_call_value(ir_instr *self) -{ - return self->_ops[0]; -} - -void ir_call_param(ir_instr* self, ir_value *v) -{ - vec_push(self->params, v); - vec_push(v->reads, self); -} - -/* binary op related code */ - -ir_value* ir_block_create_binop(ir_block *self, lex_ctx_t ctx, - const char *label, int opcode, - ir_value *left, ir_value *right) -{ - int ot = TYPE_VOID; - switch (opcode) { - case INSTR_ADD_F: - case INSTR_SUB_F: - case INSTR_DIV_F: - case INSTR_MUL_F: - case INSTR_MUL_V: - case INSTR_AND: - case INSTR_OR: -#if 0 - case INSTR_AND_I: - case INSTR_AND_IF: - case INSTR_AND_FI: - case INSTR_OR_I: - case INSTR_OR_IF: - case INSTR_OR_FI: -#endif - case INSTR_BITAND: - case INSTR_BITOR: - case VINSTR_BITXOR: -#if 0 - case INSTR_SUB_S: /* -- offset of string as float */ - case INSTR_MUL_IF: - case INSTR_MUL_FI: - case INSTR_DIV_IF: - case INSTR_DIV_FI: - case INSTR_BITOR_IF: - case INSTR_BITOR_FI: - case INSTR_BITAND_FI: - case INSTR_BITAND_IF: - case INSTR_EQ_I: - case INSTR_NE_I: -#endif - ot = TYPE_FLOAT; - break; -#if 0 - case INSTR_ADD_I: - case INSTR_ADD_IF: - case INSTR_ADD_FI: - case INSTR_SUB_I: - case INSTR_SUB_FI: - case INSTR_SUB_IF: - case INSTR_MUL_I: - case INSTR_DIV_I: - case INSTR_BITAND_I: - case INSTR_BITOR_I: - case INSTR_XOR_I: - case INSTR_RSHIFT_I: - case INSTR_LSHIFT_I: - ot = TYPE_INTEGER; - break; -#endif - case INSTR_ADD_V: - case INSTR_SUB_V: - case INSTR_MUL_VF: - case INSTR_MUL_FV: - case VINSTR_BITAND_V: - case VINSTR_BITOR_V: - case VINSTR_BITXOR_V: - case VINSTR_BITAND_VF: - case VINSTR_BITOR_VF: - case VINSTR_BITXOR_VF: - case VINSTR_CROSS: -#if 0 - case INSTR_DIV_VF: - case INSTR_MUL_IV: - case INSTR_MUL_VI: -#endif - ot = TYPE_VECTOR; - break; -#if 0 - case INSTR_ADD_SF: - ot = TYPE_POINTER; - break; -#endif - /* - * after the following default case, the value of opcode can never - * be 1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65 - */ - default: - /* ranges: */ - /* boolean operations result in floats */ - - /* - * opcode >= 10 takes true branch opcode is at least 10 - * opcode <= 23 takes false branch opcode is at least 24 - */ - if (opcode >= INSTR_EQ_F && opcode <= INSTR_GT) - ot = TYPE_FLOAT; - - /* - * At condition "opcode <= 23", the value of "opcode" must be - * at least 24. - * At condition "opcode <= 23", the value of "opcode" cannot be - * equal to any of {1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65}. - * The condition "opcode <= 23" cannot be true. - * - * Thus ot=2 (TYPE_FLOAT) can never be true - */ -#if 0 - else if (opcode >= INSTR_LE && opcode <= INSTR_GT) - ot = TYPE_FLOAT; - else if (opcode >= INSTR_LE_I && opcode <= INSTR_EQ_FI) - ot = TYPE_FLOAT; -#endif - break; - }; - if (ot == TYPE_VOID) { - /* The AST or parser were supposed to check this! */ - return NULL; - } - - return ir_block_create_general_instr(self, ctx, label, opcode, left, right, ot); -} - -ir_value* ir_block_create_unary(ir_block *self, lex_ctx_t ctx, - const char *label, int opcode, - ir_value *operand) -{ - int ot = TYPE_FLOAT; - switch (opcode) { - case INSTR_NOT_F: - case INSTR_NOT_V: - case INSTR_NOT_S: - case INSTR_NOT_ENT: - case INSTR_NOT_FNC: /* - case INSTR_NOT_I: */ - ot = TYPE_FLOAT; - break; - - /* - * Negation for virtual instructions is emulated with 0-value. Thankfully - * the operand for 0 already exists so we just source it from here. - */ - case VINSTR_NEG_F: - return ir_block_create_general_instr(self, ctx, label, INSTR_SUB_F, NULL, operand, ot); - case VINSTR_NEG_V: - return ir_block_create_general_instr(self, ctx, label, INSTR_SUB_V, NULL, operand, TYPE_VECTOR); - - default: - ot = operand->vtype; - break; - }; - if (ot == TYPE_VOID) { - /* The AST or parser were supposed to check this! */ - return NULL; - } - - /* let's use the general instruction creator and pass NULL for OPB */ - return ir_block_create_general_instr(self, ctx, label, opcode, operand, NULL, ot); -} - -static ir_value* ir_block_create_general_instr(ir_block *self, lex_ctx_t ctx, const char *label, - int op, ir_value *a, ir_value *b, int outype) -{ - ir_instr *instr; - ir_value *out; - - out = ir_value_out(self->owner, label, store_value, outype); - if (!out) - return NULL; - - instr = ir_instr_new(ctx, self, op); - if (!instr) { - ir_value_delete(out); - return NULL; - } - - if (!ir_instr_op(instr, 0, out, true) || - !ir_instr_op(instr, 1, a, false) || - !ir_instr_op(instr, 2, b, false) ) - { - goto on_error; - } - - vec_push(self->instr, instr); - - return out; -on_error: - ir_instr_delete(instr); - ir_value_delete(out); - return NULL; -} - -ir_value* ir_block_create_fieldaddress(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field) -{ - ir_value *v; - - /* Support for various pointer types todo if so desired */ - if (ent->vtype != TYPE_ENTITY) - return NULL; - - if (field->vtype != TYPE_FIELD) - return NULL; - - v = ir_block_create_general_instr(self, ctx, label, INSTR_ADDRESS, ent, field, TYPE_POINTER); - v->fieldtype = field->fieldtype; - return v; -} - -ir_value* ir_block_create_load_from_ent(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field, int outype) -{ - int op; - if (ent->vtype != TYPE_ENTITY) - return NULL; - - /* at some point we could redirect for TYPE_POINTER... but that could lead to carelessness */ - if (field->vtype != TYPE_FIELD) - return NULL; - - switch (outype) - { - case TYPE_FLOAT: op = INSTR_LOAD_F; break; - case TYPE_VECTOR: op = INSTR_LOAD_V; break; - case TYPE_STRING: op = INSTR_LOAD_S; break; - case TYPE_FIELD: op = INSTR_LOAD_FLD; break; - case TYPE_ENTITY: op = INSTR_LOAD_ENT; break; - case TYPE_FUNCTION: op = INSTR_LOAD_FNC; break; -#if 0 - case TYPE_POINTER: op = INSTR_LOAD_I; break; - case TYPE_INTEGER: op = INSTR_LOAD_I; break; -#endif - default: - irerror(self->context, "invalid type for ir_block_create_load_from_ent: %s", type_name[outype]); - return NULL; - } - - return ir_block_create_general_instr(self, ctx, label, op, ent, field, outype); -} - -/* PHI resolving breaks the SSA, and must thus be the last - * step before life-range calculation. - */ - -static bool ir_block_naive_phi(ir_block *self); -bool ir_function_naive_phi(ir_function *self) -{ - size_t i; - - for (i = 0; i < vec_size(self->blocks); ++i) - { - if (!ir_block_naive_phi(self->blocks[i])) - return false; - } - return true; -} - -static bool ir_block_naive_phi(ir_block *self) -{ - size_t i, p; /*, w;*/ - /* FIXME: optionally, create_phi can add the phis - * to a list so we don't need to loop through blocks - * - anyway: "don't optimize YET" - */ - for (i = 0; i < vec_size(self->instr); ++i) - { - ir_instr *instr = self->instr[i]; - if (instr->opcode != VINSTR_PHI) - continue; - - vec_remove(self->instr, i, 1); - --i; /* NOTE: i+1 below */ - - for (p = 0; p < vec_size(instr->phi); ++p) - { - ir_value *v = instr->phi[p].value; - ir_block *b = instr->phi[p].from; - - if (v->store == store_value && - vec_size(v->reads) == 1 && - vec_size(v->writes) == 1) - { - /* replace the value */ - if (!ir_instr_op(v->writes[0], 0, instr->_ops[0], true)) - return false; - } - else - { - /* force a move instruction */ - ir_instr *prevjump = vec_last(b->instr); - vec_pop(b->instr); - b->final = false; - instr->_ops[0]->store = store_global; - if (!ir_block_create_store(b, instr->context, instr->_ops[0], v)) - return false; - instr->_ops[0]->store = store_value; - vec_push(b->instr, prevjump); - b->final = true; - } - } - ir_instr_delete(instr); - } - return true; -} - -/*********************************************************************** - *IR Temp allocation code - * Propagating value life ranges by walking through the function backwards - * until no more changes are made. - * In theory this should happen once more than once for every nested loop - * level. - * Though this implementation might run an additional time for if nests. - */ - -/* Enumerate instructions used by value's life-ranges - */ -static void ir_block_enumerate(ir_block *self, size_t *_eid) -{ - size_t i; - size_t eid = *_eid; - for (i = 0; i < vec_size(self->instr); ++i) - { - self->instr[i]->eid = eid++; - } - *_eid = eid; -} - -/* Enumerate blocks and instructions. - * The block-enumeration is unordered! - * We do not really use the block enumreation, however - * the instruction enumeration is important for life-ranges. - */ -void ir_function_enumerate(ir_function *self) -{ - size_t i; - size_t instruction_id = 0; - for (i = 0; i < vec_size(self->blocks); ++i) - { - /* each block now gets an additional "entry" instruction id - * we can use to avoid point-life issues - */ - self->blocks[i]->entry_id = instruction_id; - ++instruction_id; - - self->blocks[i]->eid = i; - ir_block_enumerate(self->blocks[i], &instruction_id); - } -} - -/* Local-value allocator - * After finishing creating the liferange of all values used in a function - * we can allocate their global-positions. - * This is the counterpart to register-allocation in register machines. - */ -typedef struct { - ir_value **locals; - size_t *sizes; - size_t *positions; - bool *unique; -} function_allocator; - -static bool function_allocator_alloc(function_allocator *alloc, ir_value *var) -{ - ir_value *slot; - size_t vsize = ir_value_sizeof(var); - - var->code.local = vec_size(alloc->locals); - - slot = ir_value_var("reg", store_global, var->vtype); - if (!slot) - return false; - - if (!ir_value_life_merge_into(slot, var)) - goto localerror; - - vec_push(alloc->locals, slot); - vec_push(alloc->sizes, vsize); - vec_push(alloc->unique, var->unique_life); - - return true; - -localerror: - ir_value_delete(slot); - return false; -} - -static bool ir_function_allocator_assign(ir_function *self, function_allocator *alloc, ir_value *v) -{ - size_t a; - ir_value *slot; - - if (v->unique_life) - return function_allocator_alloc(alloc, v); - - for (a = 0; a < vec_size(alloc->locals); ++a) - { - /* if it's reserved for a unique liferange: skip */ - if (alloc->unique[a]) - continue; - - slot = alloc->locals[a]; - - /* never resize parameters - * will be required later when overlapping temps + locals - */ - if (a < vec_size(self->params) && - alloc->sizes[a] < ir_value_sizeof(v)) - { - continue; - } - - if (ir_values_overlap(v, slot)) - continue; - - if (!ir_value_life_merge_into(slot, v)) - return false; - - /* adjust size for this slot */ - if (alloc->sizes[a] < ir_value_sizeof(v)) - alloc->sizes[a] = ir_value_sizeof(v); - - v->code.local = a; - return true; - } - if (a >= vec_size(alloc->locals)) { - if (!function_allocator_alloc(alloc, v)) - return false; - } - return true; -} - -bool ir_function_allocate_locals(ir_function *self) -{ - size_t i; - bool retval = true; - size_t pos; - bool opt_gt = OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS); - - ir_value *v; - - function_allocator lockalloc, globalloc; - - if (!vec_size(self->locals) && !vec_size(self->values)) - return true; - - globalloc.locals = NULL; - globalloc.sizes = NULL; - globalloc.positions = NULL; - globalloc.unique = NULL; - lockalloc.locals = NULL; - lockalloc.sizes = NULL; - lockalloc.positions = NULL; - lockalloc.unique = NULL; - - for (i = 0; i < vec_size(self->locals); ++i) - { - v = self->locals[i]; - if ((self->flags & IR_FLAG_MASK_NO_LOCAL_TEMPS) || !OPTS_OPTIMIZATION(OPTIM_LOCAL_TEMPS)) { - v->locked = true; - v->unique_life = true; - } - else if (i >= vec_size(self->params)) - break; - else - v->locked = true; /* lock parameters locals */ - if (!function_allocator_alloc((v->locked || !opt_gt ? &lockalloc : &globalloc), v)) - goto error; - } - for (; i < vec_size(self->locals); ++i) - { - v = self->locals[i]; - if (!vec_size(v->life)) - continue; - if (!ir_function_allocator_assign(self, (v->locked || !opt_gt ? &lockalloc : &globalloc), v)) - goto error; - } - - /* Allocate a slot for any value that still exists */ - for (i = 0; i < vec_size(self->values); ++i) - { - v = self->values[i]; - - if (!vec_size(v->life)) - continue; - - /* CALL optimization: - * If the value is a parameter-temp: 1 write, 1 read from a CALL - * and it's not "locked", write it to the OFS_PARM directly. - */ - if (OPTS_OPTIMIZATION(OPTIM_CALL_STORES) && !v->locked && !v->unique_life) { - if (vec_size(v->reads) == 1 && vec_size(v->writes) == 1 && - (v->reads[0]->opcode == VINSTR_NRCALL || - (v->reads[0]->opcode >= INSTR_CALL0 && v->reads[0]->opcode <= INSTR_CALL8) - ) - ) - { - size_t param; - ir_instr *call = v->reads[0]; - if (!vec_ir_value_find(call->params, v, ¶m)) { - irerror(call->context, "internal error: unlocked parameter %s not found", v->name); - goto error; - } - ++opts_optimizationcount[OPTIM_CALL_STORES]; - v->callparam = true; - if (param < 8) - ir_value_code_setaddr(v, OFS_PARM0 + 3*param); - else { - size_t nprotos = vec_size(self->owner->extparam_protos); - ir_value *ep; - param -= 8; - if (nprotos > param) - ep = self->owner->extparam_protos[param]; - else - { - ep = ir_gen_extparam_proto(self->owner); - while (++nprotos <= param) - ep = ir_gen_extparam_proto(self->owner); - } - ir_instr_op(v->writes[0], 0, ep, true); - call->params[param+8] = ep; - } - continue; - } - if (vec_size(v->writes) == 1 && v->writes[0]->opcode == INSTR_CALL0) - { - v->store = store_return; - if (v->members[0]) v->members[0]->store = store_return; - if (v->members[1]) v->members[1]->store = store_return; - if (v->members[2]) v->members[2]->store = store_return; - ++opts_optimizationcount[OPTIM_CALL_STORES]; - continue; - } - } - - if (!ir_function_allocator_assign(self, (v->locked || !opt_gt ? &lockalloc : &globalloc), v)) - goto error; - } - - if (!lockalloc.sizes && !globalloc.sizes) { - goto cleanup; - } - vec_push(lockalloc.positions, 0); - vec_push(globalloc.positions, 0); - - /* Adjust slot positions based on sizes */ - if (lockalloc.sizes) { - pos = (vec_size(lockalloc.sizes) ? lockalloc.positions[0] : 0); - for (i = 1; i < vec_size(lockalloc.sizes); ++i) - { - pos = lockalloc.positions[i-1] + lockalloc.sizes[i-1]; - vec_push(lockalloc.positions, pos); - } - self->allocated_locals = pos + vec_last(lockalloc.sizes); - } - if (globalloc.sizes) { - pos = (vec_size(globalloc.sizes) ? globalloc.positions[0] : 0); - for (i = 1; i < vec_size(globalloc.sizes); ++i) - { - pos = globalloc.positions[i-1] + globalloc.sizes[i-1]; - vec_push(globalloc.positions, pos); - } - self->globaltemps = pos + vec_last(globalloc.sizes); - } - - /* Locals need to know their new position */ - for (i = 0; i < vec_size(self->locals); ++i) { - v = self->locals[i]; - if (v->locked || !opt_gt) - v->code.local = lockalloc.positions[v->code.local]; - else - v->code.local = globalloc.positions[v->code.local]; - } - /* Take over the actual slot positions on values */ - for (i = 0; i < vec_size(self->values); ++i) { - v = self->values[i]; - if (v->locked || !opt_gt) - v->code.local = lockalloc.positions[v->code.local]; - else - v->code.local = globalloc.positions[v->code.local]; - } - - goto cleanup; - -error: - retval = false; -cleanup: - for (i = 0; i < vec_size(lockalloc.locals); ++i) - ir_value_delete(lockalloc.locals[i]); - for (i = 0; i < vec_size(globalloc.locals); ++i) - ir_value_delete(globalloc.locals[i]); - vec_free(globalloc.unique); - vec_free(globalloc.locals); - vec_free(globalloc.sizes); - vec_free(globalloc.positions); - vec_free(lockalloc.unique); - vec_free(lockalloc.locals); - vec_free(lockalloc.sizes); - vec_free(lockalloc.positions); - return retval; -} - -/* Get information about which operand - * is read from, or written to. - */ -static void ir_op_read_write(int op, size_t *read, size_t *write) -{ - switch (op) - { - case VINSTR_JUMP: - case INSTR_GOTO: - *write = 0; - *read = 0; - break; - case INSTR_IF: - case INSTR_IFNOT: -#if 0 - case INSTR_IF_S: - case INSTR_IFNOT_S: -#endif - case INSTR_RETURN: - case VINSTR_COND: - *write = 0; - *read = 1; - break; - case INSTR_STOREP_F: - case INSTR_STOREP_V: - case INSTR_STOREP_S: - case INSTR_STOREP_ENT: - case INSTR_STOREP_FLD: - case INSTR_STOREP_FNC: - *write = 0; - *read = 7; - break; - default: - *write = 1; - *read = 6; - break; - }; -} - -static bool ir_block_living_add_instr(ir_block *self, size_t eid) -{ - size_t i; - const size_t vs = vec_size(self->living); - bool changed = false; - for (i = 0; i != vs; ++i) - { - if (ir_value_life_merge(self->living[i], eid)) - changed = true; - } - return changed; -} - -static bool ir_block_living_lock(ir_block *self) -{ - size_t i; - bool changed = false; - for (i = 0; i != vec_size(self->living); ++i) - { - if (!self->living[i]->locked) { - self->living[i]->locked = true; - changed = true; - } - } - return changed; -} - -static bool ir_block_life_propagate(ir_block *self, bool *changed) -{ - ir_instr *instr; - ir_value *value; - size_t i, o, p, mem, cnt; - /* bitmasks which operands are read from or written to */ - size_t read, write; - char dbg_ind[16]; - dbg_ind[0] = '#'; - dbg_ind[1] = '0'; - (void)dbg_ind; - - vec_free(self->living); - - p = vec_size(self->exits); - for (i = 0; i < p; ++i) { - ir_block *prev = self->exits[i]; - cnt = vec_size(prev->living); - for (o = 0; o < cnt; ++o) { - if (!vec_ir_value_find(self->living, prev->living[o], NULL)) - vec_push(self->living, prev->living[o]); - } - } - - i = vec_size(self->instr); - while (i) - { --i; - instr = self->instr[i]; - - /* See which operands are read and write operands */ - ir_op_read_write(instr->opcode, &read, &write); - - /* Go through the 3 main operands - * writes first, then reads - */ - for (o = 0; o < 3; ++o) - { - if (!instr->_ops[o]) /* no such operand */ - continue; - - value = instr->_ops[o]; - - /* We only care about locals */ - /* we also calculate parameter liferanges so that locals - * can take up parameter slots */ - if (value->store != store_value && - value->store != store_local && - value->store != store_param) - continue; - - /* write operands */ - /* When we write to a local, we consider it "dead" for the - * remaining upper part of the function, since in SSA a value - * can only be written once (== created) - */ - if (write & (1<living, value, &idx); - if (!in_living) - { - /* If the value isn't alive it hasn't been read before... */ - /* TODO: See if the warning can be emitted during parsing or AST processing - * otherwise have warning printed here. - * IF printing a warning here: include filecontext_t, - * and make sure it's only printed once - * since this function is run multiple times. - */ - /* con_err( "Value only written %s\n", value->name); */ - if (ir_value_life_merge(value, instr->eid)) - *changed = true; - } else { - /* since 'living' won't contain it - * anymore, merge the value, since - * (A) doesn't. - */ - if (ir_value_life_merge(value, instr->eid)) - *changed = true; - /* Then remove */ - vec_remove(self->living, idx, 1); - } - /* Removing a vector removes all members */ - for (mem = 0; mem < 3; ++mem) { - if (value->members[mem] && vec_ir_value_find(self->living, value->members[mem], &idx)) { - if (ir_value_life_merge(value->members[mem], instr->eid)) - *changed = true; - vec_remove(self->living, idx, 1); - } - } - /* Removing the last member removes the vector */ - if (value->memberof) { - value = value->memberof; - for (mem = 0; mem < 3; ++mem) { - if (value->members[mem] && vec_ir_value_find(self->living, value->members[mem], NULL)) - break; - } - if (mem == 3 && vec_ir_value_find(self->living, value, &idx)) { - if (ir_value_life_merge(value, instr->eid)) - *changed = true; - vec_remove(self->living, idx, 1); - } - } - } - } - - /* These operations need a special case as they can break when using - * same source and destination operand otherwise, as the engine may - * read the source multiple times. */ - if (instr->opcode == INSTR_MUL_VF || - instr->opcode == VINSTR_BITAND_VF || - instr->opcode == VINSTR_BITOR_VF || - instr->opcode == VINSTR_BITXOR || - instr->opcode == VINSTR_BITXOR_VF || - instr->opcode == VINSTR_BITXOR_V || - instr->opcode == VINSTR_CROSS) - { - value = instr->_ops[2]; - /* the float source will get an additional lifetime */ - if (ir_value_life_merge(value, instr->eid+1)) - *changed = true; - if (value->memberof && ir_value_life_merge(value->memberof, instr->eid+1)) - *changed = true; - } - - if (instr->opcode == INSTR_MUL_FV || - instr->opcode == INSTR_LOAD_V || - instr->opcode == VINSTR_BITXOR || - instr->opcode == VINSTR_BITXOR_VF || - instr->opcode == VINSTR_BITXOR_V || - instr->opcode == VINSTR_CROSS) - { - value = instr->_ops[1]; - /* the float source will get an additional lifetime */ - if (ir_value_life_merge(value, instr->eid+1)) - *changed = true; - if (value->memberof && ir_value_life_merge(value->memberof, instr->eid+1)) - *changed = true; - } - - for (o = 0; o < 3; ++o) - { - if (!instr->_ops[o]) /* no such operand */ - continue; - - value = instr->_ops[o]; - - /* We only care about locals */ - /* we also calculate parameter liferanges so that locals - * can take up parameter slots */ - if (value->store != store_value && - value->store != store_local && - value->store != store_param) - continue; - - /* read operands */ - if (read & (1<living, value, NULL)) - vec_push(self->living, value); - /* reading adds the full vector */ - if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL)) - vec_push(self->living, value->memberof); - for (mem = 0; mem < 3; ++mem) { - if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL)) - vec_push(self->living, value->members[mem]); - } - } - } - /* PHI operands are always read operands */ - for (p = 0; p < vec_size(instr->phi); ++p) - { - value = instr->phi[p].value; - if (!vec_ir_value_find(self->living, value, NULL)) - vec_push(self->living, value); - /* reading adds the full vector */ - if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL)) - vec_push(self->living, value->memberof); - for (mem = 0; mem < 3; ++mem) { - if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL)) - vec_push(self->living, value->members[mem]); - } - } - - /* on a call, all these values must be "locked" */ - if (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8) { - if (ir_block_living_lock(self)) - *changed = true; - } - /* call params are read operands too */ - for (p = 0; p < vec_size(instr->params); ++p) - { - value = instr->params[p]; - if (!vec_ir_value_find(self->living, value, NULL)) - vec_push(self->living, value); - /* reading adds the full vector */ - if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL)) - vec_push(self->living, value->memberof); - for (mem = 0; mem < 3; ++mem) { - if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL)) - vec_push(self->living, value->members[mem]); - } - } - - /* (A) */ - if (ir_block_living_add_instr(self, instr->eid)) - *changed = true; - } - /* the "entry" instruction ID */ - if (ir_block_living_add_instr(self, self->entry_id)) - *changed = true; - - return true; -} - -bool ir_function_calculate_liferanges(ir_function *self) -{ - size_t i, s; - bool changed; - - /* parameters live at 0 */ - for (i = 0; i < vec_size(self->params); ++i) - if (!ir_value_life_merge(self->locals[i], 0)) - compile_error(self->context, "internal error: failed value-life merging"); - - do { - self->run_id++; - changed = false; - i = vec_size(self->blocks); - while (i--) { - ir_block_life_propagate(self->blocks[i], &changed); - } - } while (changed); - - if (vec_size(self->blocks)) { - ir_block *block = self->blocks[0]; - for (i = 0; i < vec_size(block->living); ++i) { - ir_value *v = block->living[i]; - if (v->store != store_local) - continue; - if (v->vtype == TYPE_VECTOR) - continue; - self->flags |= IR_FLAG_HAS_UNINITIALIZED; - /* find the instruction reading from it */ - for (s = 0; s < vec_size(v->reads); ++s) { - if (v->reads[s]->eid == v->life[0].end) - break; - } - if (s < vec_size(v->reads)) { - if (irwarning(v->context, WARN_USED_UNINITIALIZED, - "variable `%s` may be used uninitialized in this function\n" - " -> %s:%i", - v->name, - v->reads[s]->context.file, v->reads[s]->context.line) - ) - { - return false; - } - continue; - } - if (v->memberof) { - ir_value *vec = v->memberof; - for (s = 0; s < vec_size(vec->reads); ++s) { - if (vec->reads[s]->eid == v->life[0].end) - break; - } - if (s < vec_size(vec->reads)) { - if (irwarning(v->context, WARN_USED_UNINITIALIZED, - "variable `%s` may be used uninitialized in this function\n" - " -> %s:%i", - v->name, - vec->reads[s]->context.file, vec->reads[s]->context.line) - ) - { - return false; - } - continue; - } - } - if (irwarning(v->context, WARN_USED_UNINITIALIZED, - "variable `%s` may be used uninitialized in this function", v->name)) - { - return false; - } - } - } - return true; -} - -/*********************************************************************** - *IR Code-Generation - * - * Since the IR has the convention of putting 'write' operands - * at the beginning, we have to rotate the operands of instructions - * properly in order to generate valid QCVM code. - * - * Having destinations at a fixed position is more convenient. In QC - * this is *mostly* OPC, but FTE adds at least 2 instructions which - * read from from OPA, and store to OPB rather than OPC. Which is - * partially the reason why the implementation of these instructions - * in darkplaces has been delayed for so long. - * - * Breaking conventions is annoying... - */ -static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool islocal); - -static bool gen_global_field(code_t *code, ir_value *global) -{ - if (global->hasvalue) - { - ir_value *fld = global->constval.vpointer; - if (!fld) { - irerror(global->context, "Invalid field constant with no field: %s", global->name); - return false; - } - - /* copy the field's value */ - ir_value_code_setaddr(global, vec_size(code->globals)); - vec_push(code->globals, fld->code.fieldaddr); - if (global->fieldtype == TYPE_VECTOR) { - vec_push(code->globals, fld->code.fieldaddr+1); - vec_push(code->globals, fld->code.fieldaddr+2); - } - } - else - { - ir_value_code_setaddr(global, vec_size(code->globals)); - vec_push(code->globals, 0); - if (global->fieldtype == TYPE_VECTOR) { - vec_push(code->globals, 0); - vec_push(code->globals, 0); - } - } - if (global->code.globaladdr < 0) - return false; - return true; -} - -static bool gen_global_pointer(code_t *code, ir_value *global) -{ - if (global->hasvalue) - { - ir_value *target = global->constval.vpointer; - if (!target) { - irerror(global->context, "Invalid pointer constant: %s", global->name); - /* NULL pointers are pointing to the NULL constant, which also - * sits at address 0, but still has an ir_value for itself. - */ - return false; - } - - /* Here, relocations ARE possible - in fteqcc-enhanced-qc: - * void() foo; <- proto - * void() *fooptr = &foo; - * void() foo = { code } - */ - if (!target->code.globaladdr) { - /* FIXME: Check for the constant nullptr ir_value! - * because then code.globaladdr being 0 is valid. - */ - irerror(global->context, "FIXME: Relocation support"); - return false; - } - - ir_value_code_setaddr(global, vec_size(code->globals)); - vec_push(code->globals, target->code.globaladdr); - } - else - { - ir_value_code_setaddr(global, vec_size(code->globals)); - vec_push(code->globals, 0); - } - if (global->code.globaladdr < 0) - return false; - return true; -} - -static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *block) -{ - prog_section_statement_t stmt; - ir_instr *instr; - ir_block *target; - ir_block *ontrue; - ir_block *onfalse; - size_t stidx; - size_t i; - int j; - - block->generated = true; - block->code_start = vec_size(code->statements); - for (i = 0; i < vec_size(block->instr); ++i) - { - instr = block->instr[i]; - - if (instr->opcode == VINSTR_PHI) { - irerror(block->context, "cannot generate virtual instruction (phi)"); - return false; - } - - if (instr->opcode == VINSTR_JUMP) { - target = instr->bops[0]; - /* for uncoditional jumps, if the target hasn't been generated - * yet, we generate them right here. - */ - if (!target->generated) - return gen_blocks_recursive(code, func, target); - - /* otherwise we generate a jump instruction */ - stmt.opcode = INSTR_GOTO; - stmt.o1.s1 = (target->code_start) - vec_size(code->statements); - stmt.o2.s1 = 0; - stmt.o3.s1 = 0; - if (stmt.o1.s1 != 1) - code_push_statement(code, &stmt, instr->context); - - /* no further instructions can be in this block */ - return true; - } - - if (instr->opcode == VINSTR_BITXOR) { - stmt.opcode = INSTR_BITOR; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - stmt.opcode = INSTR_BITAND; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); - code_push_statement(code, &stmt, instr->context); - stmt.opcode = INSTR_SUB_F; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); - stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_BITAND_V) { - stmt.opcode = INSTR_BITAND; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o2.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o2.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_BITOR_V) { - stmt.opcode = INSTR_BITOR; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o2.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o2.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_BITXOR_V) { - for (j = 0; j < 3; ++j) { - stmt.opcode = INSTR_BITOR; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j; - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j; - code_push_statement(code, &stmt, instr->context); - stmt.opcode = INSTR_BITAND; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j; - stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j; - code_push_statement(code, &stmt, instr->context); - } - stmt.opcode = INSTR_SUB_V; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); - stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_BITAND_VF) { - stmt.opcode = INSTR_BITAND; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_BITOR_VF) { - stmt.opcode = INSTR_BITOR; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - ++stmt.o1.s1; - ++stmt.o3.s1; - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_BITXOR_VF) { - for (j = 0; j < 3; ++j) { - stmt.opcode = INSTR_BITOR; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j; - code_push_statement(code, &stmt, instr->context); - stmt.opcode = INSTR_BITAND; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); - stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j; - code_push_statement(code, &stmt, instr->context); - } - stmt.opcode = INSTR_SUB_V; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); - stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_CROSS) { - stmt.opcode = INSTR_MUL_F; - for (j = 0; j < 3; ++j) { - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + (j + 1) % 3; - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + (j + 2) % 3; - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j; - code_push_statement(code, &stmt, instr->context); - stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + (j + 2) % 3; - stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + (j + 1) % 3; - stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j; - code_push_statement(code, &stmt, instr->context); - } - stmt.opcode = INSTR_SUB_V; - stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); - stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); - stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); - code_push_statement(code, &stmt, instr->context); - - /* instruction generated */ - continue; - } - - if (instr->opcode == VINSTR_COND) { - ontrue = instr->bops[0]; - onfalse = instr->bops[1]; - /* TODO: have the AST signal which block should - * come first: eg. optimize IFs without ELSE... - */ - - stmt.o1.u1 = ir_value_code_addr(instr->_ops[0]); - stmt.o2.u1 = 0; - stmt.o3.s1 = 0; - - if (ontrue->generated) { - stmt.opcode = INSTR_IF; - stmt.o2.s1 = (ontrue->code_start) - vec_size(code->statements); - if (stmt.o2.s1 != 1) - code_push_statement(code, &stmt, instr->context); - } - if (onfalse->generated) { - stmt.opcode = INSTR_IFNOT; - stmt.o2.s1 = (onfalse->code_start) - vec_size(code->statements); - if (stmt.o2.s1 != 1) - code_push_statement(code, &stmt, instr->context); - } - if (!ontrue->generated) { - if (onfalse->generated) - return gen_blocks_recursive(code, func, ontrue); - } - if (!onfalse->generated) { - if (ontrue->generated) - return gen_blocks_recursive(code, func, onfalse); - } - /* neither ontrue nor onfalse exist */ - stmt.opcode = INSTR_IFNOT; - if (!instr->likely) { - /* Honor the likelyhood hint */ - ir_block *tmp = onfalse; - stmt.opcode = INSTR_IF; - onfalse = ontrue; - ontrue = tmp; - } - stidx = vec_size(code->statements); - code_push_statement(code, &stmt, instr->context); - /* on false we jump, so add ontrue-path */ - if (!gen_blocks_recursive(code, func, ontrue)) - return false; - /* fixup the jump address */ - code->statements[stidx].o2.s1 = vec_size(code->statements) - stidx; - /* generate onfalse path */ - if (onfalse->generated) { - /* fixup the jump address */ - code->statements[stidx].o2.s1 = (onfalse->code_start) - (stidx); - if (stidx+2 == vec_size(code->statements) && code->statements[stidx].o2.s1 == 1) { - code->statements[stidx] = code->statements[stidx+1]; - if (code->statements[stidx].o1.s1 < 0) - code->statements[stidx].o1.s1++; - code_pop_statement(code); - } - stmt.opcode = vec_last(code->statements).opcode; - if (stmt.opcode == INSTR_GOTO || - stmt.opcode == INSTR_IF || - stmt.opcode == INSTR_IFNOT || - stmt.opcode == INSTR_RETURN || - stmt.opcode == INSTR_DONE) - { - /* no use jumping from here */ - return true; - } - /* may have been generated in the previous recursive call */ - stmt.opcode = INSTR_GOTO; - stmt.o1.s1 = (onfalse->code_start) - vec_size(code->statements); - stmt.o2.s1 = 0; - stmt.o3.s1 = 0; - if (stmt.o1.s1 != 1) - code_push_statement(code, &stmt, instr->context); - return true; - } - else if (stidx+2 == vec_size(code->statements) && code->statements[stidx].o2.s1 == 1) { - code->statements[stidx] = code->statements[stidx+1]; - if (code->statements[stidx].o1.s1 < 0) - code->statements[stidx].o1.s1++; - code_pop_statement(code); - } - /* if not, generate now */ - return gen_blocks_recursive(code, func, onfalse); - } - - if ( (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8) - || instr->opcode == VINSTR_NRCALL) - { - size_t p, first; - ir_value *retvalue; - - first = vec_size(instr->params); - if (first > 8) - first = 8; - for (p = 0; p < first; ++p) - { - ir_value *param = instr->params[p]; - if (param->callparam) - continue; - - stmt.opcode = INSTR_STORE_F; - stmt.o3.u1 = 0; - - if (param->vtype == TYPE_FIELD) - stmt.opcode = field_store_instr[param->fieldtype]; - else if (param->vtype == TYPE_NIL) - stmt.opcode = INSTR_STORE_V; - else - stmt.opcode = type_store_instr[param->vtype]; - stmt.o1.u1 = ir_value_code_addr(param); - stmt.o2.u1 = OFS_PARM0 + 3 * p; - - 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); - for (; p < first; ++p) - { - ir_builder *ir = func->owner; - ir_value *param = instr->params[p]; - ir_value *targetparam; - - if (param->callparam) - continue; - - if (p-8 >= vec_size(ir->extparams)) - ir_gen_extparam(ir); - - targetparam = ir->extparams[p-8]; - - stmt.opcode = INSTR_STORE_F; - stmt.o3.u1 = 0; - - if (param->vtype == TYPE_FIELD) - stmt.opcode = field_store_instr[param->fieldtype]; - else if (param->vtype == TYPE_NIL) - stmt.opcode = INSTR_STORE_V; - else - stmt.opcode = type_store_instr[param->vtype]; - stmt.o1.u1 = ir_value_code_addr(param); - stmt.o2.u1 = ir_value_code_addr(targetparam); - 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); - if (stmt.opcode > INSTR_CALL8) - stmt.opcode = INSTR_CALL8; - stmt.o1.u1 = ir_value_code_addr(instr->_ops[1]); - stmt.o2.u1 = 0; - stmt.o3.u1 = 0; - code_push_statement(code, &stmt, instr->context); - - retvalue = instr->_ops[0]; - if (retvalue && retvalue->store != store_return && - (retvalue->store == store_global || vec_size(retvalue->life))) - { - /* not to be kept in OFS_RETURN */ - if (retvalue->vtype == TYPE_FIELD && OPTS_FLAG(ADJUST_VECTOR_FIELDS)) - stmt.opcode = field_store_instr[retvalue->fieldtype]; - else - stmt.opcode = type_store_instr[retvalue->vtype]; - stmt.o1.u1 = OFS_RETURN; - stmt.o2.u1 = ir_value_code_addr(retvalue); - stmt.o3.u1 = 0; - code_push_statement(code, &stmt, instr->context); - } - continue; - } - - if (instr->opcode == INSTR_STATE) { - 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; - stmt.o1.u1 = 0; - stmt.o2.u1 = 0; - stmt.o3.u1 = 0; - - /* This is the general order of operands */ - if (instr->_ops[0]) - stmt.o3.u1 = ir_value_code_addr(instr->_ops[0]); - - if (instr->_ops[1]) - stmt.o1.u1 = ir_value_code_addr(instr->_ops[1]); - - if (instr->_ops[2]) - stmt.o2.u1 = ir_value_code_addr(instr->_ops[2]); - - if (stmt.opcode == INSTR_RETURN || stmt.opcode == INSTR_DONE) - { - stmt.o1.u1 = stmt.o3.u1; - stmt.o3.u1 = 0; - } - else if ((stmt.opcode >= INSTR_STORE_F && - stmt.opcode <= INSTR_STORE_FNC) || - (stmt.opcode >= INSTR_STOREP_F && - stmt.opcode <= INSTR_STOREP_FNC)) - { - /* 2-operand instructions with A -> B */ - stmt.o2.u1 = stmt.o3.u1; - stmt.o3.u1 = 0; - - /* tiny optimization, don't output - * STORE a, a - */ - if (stmt.o2.u1 == stmt.o1.u1 && - OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) - { - ++opts_optimizationcount[OPTIM_PEEPHOLE]; - continue; - } - } - code_push_statement(code, &stmt, instr->context); - } - return true; -} - -static bool gen_function_code(code_t *code, ir_function *self) -{ - ir_block *block; - prog_section_statement_t stmt, *retst; - - /* Starting from entry point, we generate blocks "as they come" - * for now. Dead blocks will not be translated obviously. - */ - if (!vec_size(self->blocks)) { - irerror(self->context, "Function '%s' declared without body.", self->name); - return false; - } - - block = self->blocks[0]; - if (block->generated) - return true; - - if (!gen_blocks_recursive(code, self, block)) { - irerror(self->context, "failed to generate blocks for '%s'", self->name); - return false; - } - - /* code_write and qcvm -disasm need to know that the function ends here */ - retst = &vec_last(code->statements); - if (OPTS_OPTIMIZATION(OPTIM_VOID_RETURN) && - self->outtype == TYPE_VOID && - retst->opcode == INSTR_RETURN && - !retst->o1.u1 && !retst->o2.u1 && !retst->o3.u1) - { - retst->opcode = INSTR_DONE; - ++opts_optimizationcount[OPTIM_VOID_RETURN]; - } else { - lex_ctx_t last; - - stmt.opcode = INSTR_DONE; - stmt.o1.u1 = 0; - stmt.o2.u1 = 0; - stmt.o3.u1 = 0; - last.line = vec_last(code->linenums); - last.column = vec_last(code->columnnums); - - code_push_statement(code, &stmt, last); - } - return true; -} - -static qcint_t ir_builder_filestring(ir_builder *ir, const char *filename) -{ - /* NOTE: filename pointers are copied, we never strdup them, - * thus we can use pointer-comparison to find the string. - */ - size_t i; - qcint_t str; - - for (i = 0; i < vec_size(ir->filenames); ++i) { - if (ir->filenames[i] == filename) - return ir->filestrings[i]; - } - - str = code_genstring(ir->code, filename); - vec_push(ir->filenames, filename); - vec_push(ir->filestrings, str); - return str; -} - -static bool gen_global_function(ir_builder *ir, ir_value *global) -{ - prog_section_function_t fun; - ir_function *irfun; - - size_t i; - - if (!global->hasvalue || (!global->constval.vfunc)) - { - irerror(global->context, "Invalid state of function-global: not constant: %s", global->name); - return false; - } - - irfun = global->constval.vfunc; - - fun.name = global->code.name; - fun.file = ir_builder_filestring(ir, global->context.file); - fun.profile = 0; /* always 0 */ - fun.nargs = vec_size(irfun->params); - if (fun.nargs > 8) - fun.nargs = 8; - - for (i = 0;i < 8; ++i) { - if ((int32_t)i >= fun.nargs) - fun.argsize[i] = 0; - else - fun.argsize[i] = type_sizeof_[irfun->params[i]]; - } - - fun.firstlocal = 0; - fun.locals = irfun->allocated_locals; - - if (irfun->builtin) - fun.entry = irfun->builtin+1; - else { - irfun->code_function_def = vec_size(ir->code->functions); - fun.entry = vec_size(ir->code->statements); - } - - vec_push(ir->code->functions, fun); - return true; -} - -static ir_value* ir_gen_extparam_proto(ir_builder *ir) -{ - ir_value *global; - char name[128]; - - util_snprintf(name, sizeof(name), "EXTPARM#%i", (int)(vec_size(ir->extparam_protos))); - global = ir_value_var(name, store_global, TYPE_VECTOR); - - vec_push(ir->extparam_protos, global); - return global; -} - -static void ir_gen_extparam(ir_builder *ir) -{ - prog_section_def_t def; - ir_value *global; - - if (vec_size(ir->extparam_protos) < vec_size(ir->extparams)+1) - global = ir_gen_extparam_proto(ir); - else - global = ir->extparam_protos[vec_size(ir->extparams)]; - - def.name = code_genstring(ir->code, global->name); - def.type = TYPE_VECTOR; - def.offset = vec_size(ir->code->globals); - - vec_push(ir->code->defs, def); - - ir_value_code_setaddr(global, def.offset); - - vec_push(ir->code->globals, 0); - vec_push(ir->code->globals, 0); - vec_push(ir->code->globals, 0); - - vec_push(ir->extparams, global); -} - -static bool gen_function_extparam_copy(code_t *code, ir_function *self) -{ - size_t i, ext, numparams; - - ir_builder *ir = self->owner; - ir_value *ep; - prog_section_statement_t stmt; - - numparams = vec_size(self->params); - if (!numparams) - return true; - - stmt.opcode = INSTR_STORE_F; - stmt.o3.s1 = 0; - for (i = 8; i < numparams; ++i) { - ext = i - 8; - if (ext >= vec_size(ir->extparams)) - ir_gen_extparam(ir); - - ep = ir->extparams[ext]; - - stmt.opcode = type_store_instr[self->locals[i]->vtype]; - if (self->locals[i]->vtype == TYPE_FIELD && - self->locals[i]->fieldtype == TYPE_VECTOR) - { - stmt.opcode = INSTR_STORE_V; - } - stmt.o1.u1 = ir_value_code_addr(ep); - stmt.o2.u1 = ir_value_code_addr(self->locals[i]); - code_push_statement(code, &stmt, self->context); - } - - return true; -} - -static bool gen_function_varargs_copy(code_t *code, ir_function *self) -{ - size_t i, ext, numparams, maxparams; - - ir_builder *ir = self->owner; - ir_value *ep; - prog_section_statement_t stmt; - - numparams = vec_size(self->params); - if (!numparams) - return true; - - stmt.opcode = INSTR_STORE_V; - stmt.o3.s1 = 0; - maxparams = numparams + self->max_varargs; - for (i = numparams; i < maxparams; ++i) { - if (i < 8) { - stmt.o1.u1 = OFS_PARM0 + 3*i; - stmt.o2.u1 = ir_value_code_addr(self->locals[i]); - code_push_statement(code, &stmt, self->context); - continue; - } - ext = i - 8; - while (ext >= vec_size(ir->extparams)) - ir_gen_extparam(ir); - - ep = ir->extparams[ext]; - - stmt.o1.u1 = ir_value_code_addr(ep); - stmt.o2.u1 = ir_value_code_addr(self->locals[i]); - code_push_statement(code, &stmt, self->context); - } - - return true; -} - -static bool gen_function_locals(ir_builder *ir, ir_value *global) -{ - prog_section_function_t *def; - ir_function *irfun; - size_t i; - uint32_t firstlocal, firstglobal; - - irfun = global->constval.vfunc; - def = ir->code->functions + irfun->code_function_def; - - if (OPTS_OPTION_BOOL(OPTION_G) || - !OPTS_OPTIMIZATION(OPTIM_OVERLAP_LOCALS) || - (irfun->flags & IR_FLAG_MASK_NO_OVERLAP)) - { - firstlocal = def->firstlocal = vec_size(ir->code->globals); - } else { - firstlocal = def->firstlocal = ir->first_common_local; - ++opts_optimizationcount[OPTIM_OVERLAP_LOCALS]; - } - - firstglobal = (OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS) ? ir->first_common_globaltemp : firstlocal); - - for (i = vec_size(ir->code->globals); i < firstlocal + irfun->allocated_locals; ++i) - vec_push(ir->code->globals, 0); - for (i = 0; i < vec_size(irfun->locals); ++i) { - ir_value *v = irfun->locals[i]; - if (v->locked || !OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS)) { - ir_value_code_setaddr(v, firstlocal + v->code.local); - if (!ir_builder_gen_global(ir, irfun->locals[i], true)) { - irerror(irfun->locals[i]->context, "failed to generate local %s", irfun->locals[i]->name); - return false; - } - } - else - ir_value_code_setaddr(v, firstglobal + v->code.local); - } - for (i = 0; i < vec_size(irfun->values); ++i) - { - ir_value *v = irfun->values[i]; - if (v->callparam) - continue; - if (v->locked) - ir_value_code_setaddr(v, firstlocal + v->code.local); - else - ir_value_code_setaddr(v, firstglobal + v->code.local); - } - return true; -} - -static bool gen_global_function_code(ir_builder *ir, ir_value *global) -{ - prog_section_function_t *fundef; - ir_function *irfun; - - (void)ir; - - irfun = global->constval.vfunc; - if (!irfun) { - if (global->cvq == CV_NONE) { - if (irwarning(global->context, WARN_IMPLICIT_FUNCTION_POINTER, - "function `%s` has no body and in QC implicitly becomes a function-pointer", - global->name)) - { - /* Not bailing out just now. If this happens a lot you don't want to have - * to rerun gmqcc for each such function. - */ - - /* return false; */ - } - } - /* this was a function pointer, don't generate code for those */ - return true; - } - - if (irfun->builtin) - return true; - - /* - * If there is no definition and the thing is eraseable, we can ignore - * outputting the function to begin with. - */ - if (global->flags & IR_FLAG_ERASABLE && irfun->code_function_def < 0) { - return true; - } - - if (irfun->code_function_def < 0) { - irerror(irfun->context, "`%s`: IR global wasn't generated, failed to access function-def", irfun->name); - return false; - } - fundef = &ir->code->functions[irfun->code_function_def]; - - fundef->entry = vec_size(ir->code->statements); - if (!gen_function_locals(ir, global)) { - irerror(irfun->context, "Failed to generate locals for function %s", irfun->name); - return false; - } - if (!gen_function_extparam_copy(ir->code, irfun)) { - irerror(irfun->context, "Failed to generate extparam-copy code for function %s", irfun->name); - return false; - } - if (irfun->max_varargs && !gen_function_varargs_copy(ir->code, irfun)) { - irerror(irfun->context, "Failed to generate vararg-copy code for function %s", irfun->name); - return false; - } - if (!gen_function_code(ir->code, irfun)) { - irerror(irfun->context, "Failed to generate code for function %s", irfun->name); - return false; - } - return true; -} - -static void gen_vector_defs(code_t *code, prog_section_def_t def, const char *name) -{ - char *component; - size_t len, i; - - if (!name || name[0] == '#' || OPTS_FLAG(SINGLE_VECTOR_DEFS)) - return; - - def.type = TYPE_FLOAT; - - len = strlen(name); - - component = (char*)mem_a(len+3); - memcpy(component, name, len); - len += 2; - component[len-0] = 0; - component[len-2] = '_'; - - component[len-1] = 'x'; - - for (i = 0; i < 3; ++i) { - def.name = code_genstring(code, component); - vec_push(code->defs, def); - def.offset++; - component[len-1]++; - } - - mem_d(component); -} - -static void gen_vector_fields(code_t *code, prog_section_field_t fld, const char *name) -{ - char *component; - size_t len, i; - - if (!name || OPTS_FLAG(SINGLE_VECTOR_DEFS)) - return; - - fld.type = TYPE_FLOAT; - - len = strlen(name); - - component = (char*)mem_a(len+3); - memcpy(component, name, len); - len += 2; - component[len-0] = 0; - component[len-2] = '_'; - - component[len-1] = 'x'; - - for (i = 0; i < 3; ++i) { - fld.name = code_genstring(code, component); - vec_push(code->fields, fld); - fld.offset++; - component[len-1]++; - } - - mem_d(component); -} - -static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool islocal) -{ - size_t i; - int32_t *iptr; - 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; - if (OPTS_OPTION_BOOL(OPTION_G) || !islocal) - { - pushdef = true; - - /* - * if we're eraseable and the function isn't referenced ignore outputting - * the function. - */ - if (global->flags & IR_FLAG_ERASABLE && vec_size(global->reads) == 0) { - return true; - } - - if (OPTS_OPTIMIZATION(OPTIM_STRIP_CONSTANT_NAMES) && - !(global->flags & IR_FLAG_INCLUDE_DEF) && - (global->name[0] == '#' || global->cvq == CV_CONST)) - { - pushdef = false; - } - - if (pushdef) { - if (global->name[0] == '#') { - if (!self->str_immediate) - self->str_immediate = code_genstring(self->code, "IMMEDIATE"); - def.name = global->code.name = self->str_immediate; - } - else - def.name = global->code.name = code_genstring(self->code, global->name); - } - else - def.name = 0; - if (islocal) { - def.offset = ir_value_code_addr(global); - vec_push(self->code->defs, def); - if (global->vtype == TYPE_VECTOR) - gen_vector_defs(self->code, def, global->name); - else if (global->vtype == TYPE_FIELD && global->fieldtype == TYPE_VECTOR) - gen_vector_defs(self->code, def, global->name); - return true; - } - } - if (islocal) - return true; - - switch (global->vtype) - { - case TYPE_VOID: - if (!strcmp(global->name, "end_sys_globals")) { - /* TODO: remember this point... all the defs before this one - * should be checksummed and added to progdefs.h when we generate it. - */ - } - else if (!strcmp(global->name, "end_sys_fields")) { - /* TODO: same as above but for entity-fields rather than globsl - */ - } - else if(irwarning(global->context, WARN_VOID_VARIABLES, "unrecognized variable of type void `%s`", - global->name)) - { - /* Not bailing out */ - /* return false; */ - } - /* I'd argue setting it to 0 is sufficient, but maybe some depend on knowing how far - * the system fields actually go? Though the engine knows this anyway... - * Maybe this could be an -foption - * fteqcc creates data for end_sys_* - of size 1, so let's do the same - */ - ir_value_code_setaddr(global, vec_size(self->code->globals)); - vec_push(self->code->globals, 0); - /* Add the def */ - if (pushdef) vec_push(self->code->defs, def); - return true; - case TYPE_POINTER: - if (pushdef) vec_push(self->code->defs, def); - return gen_global_pointer(self->code, global); - case TYPE_FIELD: - if (pushdef) { - vec_push(self->code->defs, def); - if (global->fieldtype == TYPE_VECTOR) - gen_vector_defs(self->code, def, global->name); - } - return gen_global_field(self->code, global); - case TYPE_ENTITY: - /* fall through */ - case TYPE_FLOAT: - { - ir_value_code_setaddr(global, vec_size(self->code->globals)); - if (global->hasvalue) { - iptr = (int32_t*)&global->constval.ivec[0]; - vec_push(self->code->globals, *iptr); - } else { - vec_push(self->code->globals, 0); - } - if (!islocal && global->cvq != CV_CONST) - def.type |= DEF_SAVEGLOBAL; - if (pushdef) vec_push(self->code->defs, def); - - return global->code.globaladdr >= 0; - } - case TYPE_STRING: - { - ir_value_code_setaddr(global, vec_size(self->code->globals)); - if (global->hasvalue) { - uint32_t load = code_genstring(self->code, global->constval.vstring); - vec_push(self->code->globals, load); - } else { - vec_push(self->code->globals, 0); - } - if (!islocal && global->cvq != CV_CONST) - def.type |= DEF_SAVEGLOBAL; - if (pushdef) vec_push(self->code->defs, def); - return global->code.globaladdr >= 0; - } - case TYPE_VECTOR: - { - size_t d; - ir_value_code_setaddr(global, vec_size(self->code->globals)); - if (global->hasvalue) { - iptr = (int32_t*)&global->constval.ivec[0]; - vec_push(self->code->globals, iptr[0]); - if (global->code.globaladdr < 0) - return false; - for (d = 1; d < type_sizeof_[global->vtype]; ++d) { - vec_push(self->code->globals, iptr[d]); - } - } else { - vec_push(self->code->globals, 0); - if (global->code.globaladdr < 0) - return false; - for (d = 1; d < type_sizeof_[global->vtype]; ++d) { - vec_push(self->code->globals, 0); - } - } - if (!islocal && global->cvq != CV_CONST) - def.type |= DEF_SAVEGLOBAL; - - if (pushdef) { - vec_push(self->code->defs, def); - def.type &= ~DEF_SAVEGLOBAL; - gen_vector_defs(self->code, def, global->name); - } - return global->code.globaladdr >= 0; - } - case TYPE_FUNCTION: - ir_value_code_setaddr(global, vec_size(self->code->globals)); - if (!global->hasvalue) { - vec_push(self->code->globals, 0); - if (global->code.globaladdr < 0) - return false; - } else { - vec_push(self->code->globals, vec_size(self->code->functions)); - if (!gen_global_function(self, global)) - return false; - } - if (!islocal && global->cvq != CV_CONST) - def.type |= DEF_SAVEGLOBAL; - if (pushdef) vec_push(self->code->defs, def); - return true; - case TYPE_VARIANT: - /* assume biggest type */ - ir_value_code_setaddr(global, vec_size(self->code->globals)); - vec_push(self->code->globals, 0); - for (i = 1; i < type_sizeof_[TYPE_VARIANT]; ++i) - vec_push(self->code->globals, 0); - return true; - default: - /* refuse to create 'void' type or any other fancy business. */ - irerror(global->context, "Invalid type for global variable `%s`: %s", - global->name, type_name[global->vtype]); - return false; - } -} - -static GMQCC_INLINE void ir_builder_prepare_field(code_t *code, ir_value *field) -{ - field->code.fieldaddr = code_alloc_field(code, type_sizeof_[field->fieldtype]); -} - -static bool ir_builder_gen_field(ir_builder *self, ir_value *field) -{ - prog_section_def_t def; - prog_section_field_t fld; - - (void)self; - - def.type = (uint16_t)field->vtype; - def.offset = (uint16_t)vec_size(self->code->globals); - - /* create a global named the same as the field */ - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) { - /* in our standard, the global gets a dot prefix */ - size_t len = strlen(field->name); - char name[1024]; - - /* we really don't want to have to allocate this, and 1024 - * bytes is more than enough for a variable/field name - */ - if (len+2 >= sizeof(name)) { - irerror(field->context, "invalid field name size: %u", (unsigned int)len); - return false; - } - - name[0] = '.'; - memcpy(name+1, field->name, len); /* no strncpy - we used strlen above */ - name[len+1] = 0; - - def.name = code_genstring(self->code, name); - fld.name = def.name + 1; /* we reuse that string table entry */ - } else { - /* in plain QC, there cannot be a global with the same name, - * and so we also name the global the same. - * FIXME: fteqcc should create a global as well - * check if it actually uses the same name. Probably does - */ - def.name = code_genstring(self->code, field->name); - fld.name = def.name; - } - - field->code.name = def.name; - - vec_push(self->code->defs, def); - - fld.type = field->fieldtype; - - if (fld.type == TYPE_VOID) { - irerror(field->context, "field is missing a type: %s - don't know its size", field->name); - return false; - } - - fld.offset = field->code.fieldaddr; - - vec_push(self->code->fields, fld); - - ir_value_code_setaddr(field, vec_size(self->code->globals)); - vec_push(self->code->globals, fld.offset); - if (fld.type == TYPE_VECTOR) { - vec_push(self->code->globals, fld.offset+1); - vec_push(self->code->globals, fld.offset+2); - } - - if (field->fieldtype == TYPE_VECTOR) { - gen_vector_defs (self->code, def, field->name); - gen_vector_fields(self->code, fld, field->name); - } - - 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]); - } - - for (i = 0; i < vec_size(self->globals); ++i) - { - if (!ir_builder_gen_global(self, self->globals[i], false)) { - return false; - } - if (self->globals[i]->vtype == TYPE_FUNCTION) { - ir_function *func = self->globals[i]->constval.vfunc; - if (func && self->max_locals < func->allocated_locals && - !(func->flags & IR_FLAG_MASK_NO_OVERLAP)) - { - self->max_locals = func->allocated_locals; - } - if (func && self->max_globaltemps < func->globaltemps) - self->max_globaltemps = func->globaltemps; - } - } - - for (i = 0; i < vec_size(self->fields); ++i) - { - if (!ir_builder_gen_field(self, self->fields[i])) { - return false; - } - } - - /* generate nil */ - ir_value_code_setaddr(self->nil, vec_size(self->code->globals)); - vec_push(self->code->globals, 0); - vec_push(self->code->globals, 0); - vec_push(self->code->globals, 0); - - /* generate virtual-instruction temps */ - for (i = 0; i < IR_MAX_VINSTR_TEMPS; ++i) { - ir_value_code_setaddr(self->vinstr_temp[i], vec_size(self->code->globals)); - vec_push(self->code->globals, 0); - vec_push(self->code->globals, 0); - vec_push(self->code->globals, 0); - } - - /* generate global temps */ - self->first_common_globaltemp = vec_size(self->code->globals); - for (i = 0; i < self->max_globaltemps; ++i) { - vec_push(self->code->globals, 0); - } - /* generate common locals */ - self->first_common_local = vec_size(self->code->globals); - for (i = 0; i < self->max_locals; ++i) { - vec_push(self->code->globals, 0); - } - - /* generate function code */ - for (i = 0; i < vec_size(self->globals); ++i) - { - if (self->globals[i]->vtype == TYPE_FUNCTION) { - if (!gen_global_function_code(self, self->globals[i])) { - return false; - } - } - } - - 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 (%u). Bailing out.", (unsigned int)vec_size(self->code->globals)); - return false; - } - - /* DP errors if the last instruction is not an INSTR_DONE. */ - if (vec_last(self->code->statements).opcode != INSTR_DONE) - { - lex_ctx_t last; - - stmt.opcode = INSTR_DONE; - stmt.o1.u1 = 0; - stmt.o2.u1 = 0; - stmt.o3.u1 = 0; - last.line = vec_last(self->code->linenums); - last.column = vec_last(self->code->columnnums); - - code_push_statement(self->code, &stmt, last); - } - - if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) - return true; - - if (vec_size(self->code->statements) != vec_size(self->code->linenums)) { - con_err("Linecounter wrong: %lu != %lu\n", - (unsigned long)vec_size(self->code->statements), - (unsigned long)vec_size(self->code->linenums)); - } else if (OPTS_FLAG(LNO)) { - char *dot; - size_t filelen = strlen(filename); - - memcpy(vec_add(lnofile, filelen+1), filename, filelen+1); - dot = strrchr(lnofile, '.'); - if (!dot) { - vec_pop(lnofile); - } else { - vec_shrinkto(lnofile, dot - lnofile); - } - memcpy(vec_add(lnofile, 5), ".lno", 5); - } - - if (!code_write(self->code, filename, lnofile)) { - vec_free(lnofile); - return false; - } - - vec_free(lnofile); - return true; -} - -/*********************************************************************** - *IR DEBUG Dump functions... - */ - -#define IND_BUFSZ 1024 - -static const char *qc_opname(int op) -{ - if (op < 0) return ""; - if (op < VINSTR_END) - return util_instr_str[op]; - switch (op) { - case VINSTR_END: return "END"; - case VINSTR_PHI: return "PHI"; - case VINSTR_JUMP: return "JUMP"; - case VINSTR_COND: return "COND"; - case VINSTR_BITXOR: return "BITXOR"; - case VINSTR_BITAND_V: return "BITAND_V"; - case VINSTR_BITOR_V: return "BITOR_V"; - case VINSTR_BITXOR_V: return "BITXOR_V"; - case VINSTR_BITAND_VF: return "BITAND_VF"; - case VINSTR_BITOR_VF: return "BITOR_VF"; - case VINSTR_BITXOR_VF: return "BITXOR_VF"; - case VINSTR_CROSS: return "CROSS"; - case VINSTR_NEG_F: return "NEG_F"; - case VINSTR_NEG_V: return "NEG_V"; - default: return ""; - } -} - -void ir_builder_dump(ir_builder *b, int (*oprintf)(const char*, ...)) -{ - size_t i; - char indent[IND_BUFSZ]; - indent[0] = '\t'; - indent[1] = 0; - - oprintf("module %s\n", b->name); - for (i = 0; i < vec_size(b->globals); ++i) - { - oprintf("global "); - if (b->globals[i]->hasvalue) - oprintf("%s = ", b->globals[i]->name); - ir_value_dump(b->globals[i], oprintf); - oprintf("\n"); - } - for (i = 0; i < vec_size(b->functions); ++i) - ir_function_dump(b->functions[i], indent, oprintf); - oprintf("endmodule %s\n", b->name); -} - -static const char *storenames[] = { - "[global]", "[local]", "[param]", "[value]", "[return]" -}; - -void ir_function_dump(ir_function *f, char *ind, - int (*oprintf)(const char*, ...)) -{ - size_t i; - if (f->builtin != 0) { - oprintf("%sfunction %s = builtin %i\n", ind, f->name, -f->builtin); - return; - } - oprintf("%sfunction %s\n", ind, f->name); - util_strncat(ind, "\t", IND_BUFSZ-1); - if (vec_size(f->locals)) - { - oprintf("%s%i locals:\n", ind, (int)vec_size(f->locals)); - for (i = 0; i < vec_size(f->locals); ++i) { - oprintf("%s\t", ind); - ir_value_dump(f->locals[i], oprintf); - oprintf("\n"); - } - } - oprintf("%sliferanges:\n", ind); - for (i = 0; i < vec_size(f->locals); ++i) { - const char *attr = ""; - size_t l, m; - ir_value *v = f->locals[i]; - if (v->unique_life && v->locked) - attr = "unique,locked "; - else if (v->unique_life) - attr = "unique "; - else if (v->locked) - attr = "locked "; - oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->name, type_name[v->vtype], - storenames[v->store], - attr, (v->callparam ? "callparam " : ""), - (int)v->code.local); - if (!v->life) - oprintf("[null]"); - for (l = 0; l < vec_size(v->life); ++l) { - oprintf("[%i,%i] ", v->life[l].start, v->life[l].end); - } - oprintf("\n"); - for (m = 0; m < 3; ++m) { - ir_value *vm = v->members[m]; - if (!vm) - continue; - oprintf("%s\t%s: @%i ", ind, vm->name, (int)vm->code.local); - for (l = 0; l < vec_size(vm->life); ++l) { - oprintf("[%i,%i] ", vm->life[l].start, vm->life[l].end); - } - oprintf("\n"); - } - } - for (i = 0; i < vec_size(f->values); ++i) { - const char *attr = ""; - size_t l, m; - ir_value *v = f->values[i]; - if (v->unique_life && v->locked) - attr = "unique,locked "; - else if (v->unique_life) - attr = "unique "; - else if (v->locked) - attr = "locked "; - oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->name, type_name[v->vtype], - storenames[v->store], - attr, (v->callparam ? "callparam " : ""), - (int)v->code.local); - if (!v->life) - oprintf("[null]"); - for (l = 0; l < vec_size(v->life); ++l) { - oprintf("[%i,%i] ", v->life[l].start, v->life[l].end); - } - oprintf("\n"); - for (m = 0; m < 3; ++m) { - ir_value *vm = v->members[m]; - if (!vm) - continue; - if (vm->unique_life && vm->locked) - attr = "unique,locked "; - else if (vm->unique_life) - attr = "unique "; - else if (vm->locked) - attr = "locked "; - oprintf("%s\t%s: %s@%i ", ind, vm->name, attr, (int)vm->code.local); - for (l = 0; l < vec_size(vm->life); ++l) { - oprintf("[%i,%i] ", vm->life[l].start, vm->life[l].end); - } - oprintf("\n"); - } - } - if (vec_size(f->blocks)) - { - oprintf("%slife passes: %i\n", ind, (int)f->run_id); - for (i = 0; i < vec_size(f->blocks); ++i) { - ir_block_dump(f->blocks[i], ind, oprintf); - } - - } - ind[strlen(ind)-1] = 0; - oprintf("%sendfunction %s\n", ind, f->name); -} - -void ir_block_dump(ir_block* b, char *ind, - int (*oprintf)(const char*, ...)) -{ - size_t i; - oprintf("%s:%s\n", ind, b->label); - util_strncat(ind, "\t", IND_BUFSZ-1); - - if (b->instr && b->instr[0]) - oprintf("%s (%i) [entry]\n", ind, (int)(b->instr[0]->eid-1)); - for (i = 0; i < vec_size(b->instr); ++i) - ir_instr_dump(b->instr[i], ind, oprintf); - ind[strlen(ind)-1] = 0; -} - -static void dump_phi(ir_instr *in, int (*oprintf)(const char*, ...)) -{ - size_t i; - oprintf("%s <- phi ", in->_ops[0]->name); - for (i = 0; i < vec_size(in->phi); ++i) - { - oprintf("([%s] : %s) ", in->phi[i].from->label, - in->phi[i].value->name); - } - oprintf("\n"); -} - -void ir_instr_dump(ir_instr *in, char *ind, - int (*oprintf)(const char*, ...)) -{ - size_t i; - const char *comma = NULL; - - oprintf("%s (%i) ", ind, (int)in->eid); - - if (in->opcode == VINSTR_PHI) { - dump_phi(in, oprintf); - return; - } - - util_strncat(ind, "\t", IND_BUFSZ-1); - - if (in->_ops[0] && (in->_ops[1] || in->_ops[2])) { - ir_value_dump(in->_ops[0], oprintf); - if (in->_ops[1] || in->_ops[2]) - oprintf(" <- "); - } - if (in->opcode == INSTR_CALL0 || in->opcode == VINSTR_NRCALL) { - oprintf("CALL%i\t", vec_size(in->params)); - } else - oprintf("%s\t", qc_opname(in->opcode)); - - if (in->_ops[0] && !(in->_ops[1] || in->_ops[2])) { - ir_value_dump(in->_ops[0], oprintf); - comma = ",\t"; - } - else - { - for (i = 1; i != 3; ++i) { - if (in->_ops[i]) { - if (comma) - oprintf(comma); - ir_value_dump(in->_ops[i], oprintf); - comma = ",\t"; - } - } - } - if (in->bops[0]) { - if (comma) - oprintf(comma); - oprintf("[%s]", in->bops[0]->label); - comma = ",\t"; - } - if (in->bops[1]) - oprintf("%s[%s]", comma, in->bops[1]->label); - if (vec_size(in->params)) { - oprintf("\tparams: "); - for (i = 0; i != vec_size(in->params); ++i) { - oprintf("%s, ", in->params[i]->name); - } - } - oprintf("\n"); - ind[strlen(ind)-1] = 0; -} - -static void ir_value_dump_string(const char *str, int (*oprintf)(const char*, ...)) -{ - oprintf("\""); - for (; *str; ++str) { - switch (*str) { - case '\n': oprintf("\\n"); break; - case '\r': oprintf("\\r"); break; - case '\t': oprintf("\\t"); break; - case '\v': oprintf("\\v"); break; - case '\f': oprintf("\\f"); break; - case '\b': oprintf("\\b"); break; - case '\a': oprintf("\\a"); break; - case '\\': oprintf("\\\\"); break; - case '"': oprintf("\\\""); break; - default: oprintf("%c", *str); break; - } - } - oprintf("\""); -} - -void ir_value_dump(ir_value* v, int (*oprintf)(const char*, ...)) -{ - if (v->hasvalue) { - switch (v->vtype) { - default: - case TYPE_VOID: - oprintf("(void)"); - break; - case TYPE_FUNCTION: - oprintf("fn:%s", v->name); - break; - case TYPE_FLOAT: - oprintf("%g", v->constval.vfloat); - break; - case TYPE_VECTOR: - oprintf("'%g %g %g'", - v->constval.vvec.x, - v->constval.vvec.y, - v->constval.vvec.z); - break; - case TYPE_ENTITY: - oprintf("(entity)"); - break; - case TYPE_STRING: - ir_value_dump_string(v->constval.vstring, oprintf); - break; -#if 0 - case TYPE_INTEGER: - oprintf("%i", v->constval.vint); - break; -#endif - case TYPE_POINTER: - oprintf("&%s", - v->constval.vpointer->name); - break; - } - } else { - oprintf("%s", v->name); - } -} - -void ir_value_dump_life(const ir_value *self, int (*oprintf)(const char*,...)) -{ - size_t i; - oprintf("Life of %12s:", self->name); - for (i = 0; i < vec_size(self->life); ++i) - { - oprintf(" + [%i, %i]\n", self->life[i].start, self->life[i].end); - } -} diff --git a/ir.cpp b/ir.cpp new file mode 100644 index 0000000..8c3d234 --- /dev/null +++ b/ir.cpp @@ -0,0 +1,4441 @@ +#include +#include + +#include "gmqcc.h" +#include "ir.h" + +/*********************************************************************** + * Type sizes used at multiple points in the IR codegen + */ + +const char *type_name[TYPE_COUNT] = { + "void", + "string", + "float", + "vector", + "entity", + "field", + "function", + "pointer", + "integer", + "variant", + "struct", + "union", + "array", + + "nil", + "" +}; + +static size_t type_sizeof_[TYPE_COUNT] = { + 1, /* TYPE_VOID */ + 1, /* TYPE_STRING */ + 1, /* TYPE_FLOAT */ + 3, /* TYPE_VECTOR */ + 1, /* TYPE_ENTITY */ + 1, /* TYPE_FIELD */ + 1, /* TYPE_FUNCTION */ + 1, /* TYPE_POINTER */ + 1, /* TYPE_INTEGER */ + 3, /* TYPE_VARIANT */ + 0, /* TYPE_STRUCT */ + 0, /* TYPE_UNION */ + 0, /* TYPE_ARRAY */ + 0, /* TYPE_NIL */ + 0, /* TYPE_NOESPR */ +}; + +const uint16_t type_store_instr[TYPE_COUNT] = { + INSTR_STORE_F, /* should use I when having integer support */ + INSTR_STORE_S, + INSTR_STORE_F, + INSTR_STORE_V, + INSTR_STORE_ENT, + INSTR_STORE_FLD, + INSTR_STORE_FNC, + INSTR_STORE_ENT, /* should use I */ +#if 0 + INSTR_STORE_I, /* integer type */ +#else + INSTR_STORE_F, +#endif + + INSTR_STORE_V, /* variant, should never be accessed */ + + VINSTR_END, /* struct */ + VINSTR_END, /* union */ + VINSTR_END, /* array */ + VINSTR_END, /* nil */ + VINSTR_END, /* noexpr */ +}; + +const uint16_t field_store_instr[TYPE_COUNT] = { + INSTR_STORE_FLD, + INSTR_STORE_FLD, + INSTR_STORE_FLD, + INSTR_STORE_V, + INSTR_STORE_FLD, + INSTR_STORE_FLD, + INSTR_STORE_FLD, + INSTR_STORE_FLD, +#if 0 + INSTR_STORE_FLD, /* integer type */ +#else + INSTR_STORE_FLD, +#endif + + INSTR_STORE_V, /* variant, should never be accessed */ + + VINSTR_END, /* struct */ + VINSTR_END, /* union */ + VINSTR_END, /* array */ + VINSTR_END, /* nil */ + VINSTR_END, /* noexpr */ +}; + +const uint16_t type_storep_instr[TYPE_COUNT] = { + INSTR_STOREP_F, /* should use I when having integer support */ + INSTR_STOREP_S, + INSTR_STOREP_F, + INSTR_STOREP_V, + INSTR_STOREP_ENT, + INSTR_STOREP_FLD, + INSTR_STOREP_FNC, + INSTR_STOREP_ENT, /* should use I */ +#if 0 + INSTR_STOREP_ENT, /* integer type */ +#else + INSTR_STOREP_F, +#endif + + INSTR_STOREP_V, /* variant, should never be accessed */ + + VINSTR_END, /* struct */ + VINSTR_END, /* union */ + VINSTR_END, /* array */ + VINSTR_END, /* nil */ + VINSTR_END, /* noexpr */ +}; + +const uint16_t type_eq_instr[TYPE_COUNT] = { + INSTR_EQ_F, /* should use I when having integer support */ + INSTR_EQ_S, + INSTR_EQ_F, + INSTR_EQ_V, + INSTR_EQ_E, + INSTR_EQ_E, /* FLD has no comparison */ + INSTR_EQ_FNC, + INSTR_EQ_E, /* should use I */ +#if 0 + INSTR_EQ_I, +#else + INSTR_EQ_F, +#endif + + INSTR_EQ_V, /* variant, should never be accessed */ + + VINSTR_END, /* struct */ + VINSTR_END, /* union */ + VINSTR_END, /* array */ + VINSTR_END, /* nil */ + VINSTR_END, /* noexpr */ +}; + +const uint16_t type_ne_instr[TYPE_COUNT] = { + INSTR_NE_F, /* should use I when having integer support */ + INSTR_NE_S, + INSTR_NE_F, + INSTR_NE_V, + INSTR_NE_E, + INSTR_NE_E, /* FLD has no comparison */ + INSTR_NE_FNC, + INSTR_NE_E, /* should use I */ +#if 0 + INSTR_NE_I, +#else + INSTR_NE_F, +#endif + + INSTR_NE_V, /* variant, should never be accessed */ + + VINSTR_END, /* struct */ + VINSTR_END, /* union */ + VINSTR_END, /* array */ + VINSTR_END, /* nil */ + VINSTR_END, /* noexpr */ +}; + +const uint16_t type_not_instr[TYPE_COUNT] = { + INSTR_NOT_F, /* should use I when having integer support */ + VINSTR_END, /* not to be used, depends on string related -f flags */ + INSTR_NOT_F, + INSTR_NOT_V, + INSTR_NOT_ENT, + INSTR_NOT_ENT, + INSTR_NOT_FNC, + INSTR_NOT_ENT, /* should use I */ +#if 0 + INSTR_NOT_I, /* integer type */ +#else + INSTR_NOT_F, +#endif + + INSTR_NOT_V, /* variant, should never be accessed */ + + VINSTR_END, /* struct */ + VINSTR_END, /* union */ + VINSTR_END, /* array */ + VINSTR_END, /* nil */ + VINSTR_END, /* noexpr */ +}; + +/* protos */ +static ir_value* ir_value_var(const char *name, int st, int vtype); +static bool ir_value_set_name(ir_value*, const char *name); +static void ir_value_dump(ir_value*, int (*oprintf)(const char*,...)); + +static ir_value* ir_gen_extparam_proto(ir_builder *ir); +static void ir_gen_extparam (ir_builder *ir); + +static bool ir_builder_set_name(ir_builder *self, const char *name); + +static ir_function* ir_function_new(struct ir_builder_s *owner, int returntype); +static bool ir_function_set_name(ir_function*, const char *name); +static void ir_function_delete(ir_function*); +static void ir_function_dump(ir_function*, char *ind, int (*oprintf)(const char*,...)); + +static ir_value* ir_block_create_general_instr(ir_block *self, lex_ctx_t, const char *label, + int op, ir_value *a, ir_value *b, int outype); +static void ir_block_delete(ir_block*); +static ir_block* ir_block_new(struct ir_function_s *owner, const char *label); +static bool GMQCC_WARN ir_block_create_store(ir_block*, lex_ctx_t, ir_value *target, ir_value *what); +static bool ir_block_set_label(ir_block*, const char *label); +static void ir_block_dump(ir_block*, char *ind, int (*oprintf)(const char*,...)); + +static bool ir_instr_op(ir_instr*, int op, ir_value *value, bool writing); +static void ir_instr_delete(ir_instr*); +static void ir_instr_dump(ir_instr* in, char *ind, int (*oprintf)(const char*,...)); +/* error functions */ + +static void irerror(lex_ctx_t ctx, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + con_cvprintmsg(ctx, LVL_ERROR, "internal error", msg, ap); + va_end(ap); +} + +static bool GMQCC_WARN irwarning(lex_ctx_t ctx, int warntype, const char *fmt, ...) +{ + bool r; + va_list ap; + va_start(ap, fmt); + r = vcompile_warning(ctx, warntype, fmt, ap); + va_end(ap); + return r; +} + +/*********************************************************************** + * Vector utility functions + */ + +static bool GMQCC_WARN vec_ir_value_find(ir_value **vec, const ir_value *what, size_t *idx) +{ + size_t i; + size_t len = vec_size(vec); + for (i = 0; i < len; ++i) { + if (vec[i] == what) { + if (idx) *idx = i; + return true; + } + } + return false; +} + +static bool GMQCC_WARN vec_ir_block_find(ir_block **vec, ir_block *what, size_t *idx) +{ + size_t i; + size_t len = vec_size(vec); + for (i = 0; i < len; ++i) { + if (vec[i] == what) { + if (idx) *idx = i; + return true; + } + } + return false; +} + +static bool GMQCC_WARN vec_ir_instr_find(ir_instr **vec, ir_instr *what, size_t *idx) +{ + size_t i; + size_t len = vec_size(vec); + for (i = 0; i < len; ++i) { + if (vec[i] == what) { + if (idx) *idx = i; + return true; + } + } + return false; +} + +/*********************************************************************** + * IR Builder + */ + +static void ir_block_delete_quick(ir_block* self); +static void ir_instr_delete_quick(ir_instr *self); +static void ir_function_delete_quick(ir_function *self); + +ir_builder* ir_builder_new(const char *modulename) +{ + ir_builder* self; + size_t i; + + self = (ir_builder*)mem_a(sizeof(*self)); + if (!self) + return NULL; + + self->functions = NULL; + self->globals = NULL; + self->fields = NULL; + self->filenames = NULL; + self->filestrings = NULL; + self->htglobals = util_htnew(IR_HT_SIZE); + self->htfields = util_htnew(IR_HT_SIZE); + self->htfunctions = util_htnew(IR_HT_SIZE); + + self->extparams = NULL; + self->extparam_protos = NULL; + + self->first_common_globaltemp = 0; + self->max_globaltemps = 0; + self->first_common_local = 0; + self->max_locals = 0; + + self->str_immediate = 0; + self->name = NULL; + if (!ir_builder_set_name(self, modulename)) { + mem_d(self); + return NULL; + } + + self->nil = ir_value_var("nil", store_value, TYPE_NIL); + self->nil->cvq = CV_CONST; + + for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) { + /* we write to them, but they're not supposed to be used outside the IR, so + * let's not allow the generation of ir_instrs which use these. + * So it's a constant noexpr. + */ + self->vinstr_temp[i] = ir_value_var("vinstr_temp", store_value, TYPE_NOEXPR); + self->vinstr_temp[i]->cvq = CV_CONST; + } + + self->reserved_va_count = NULL; + self->coverage_func = NULL; + + self->code = code_init(); + + return self; +} + +void ir_builder_delete(ir_builder* self) +{ + size_t i; + util_htdel(self->htglobals); + util_htdel(self->htfields); + util_htdel(self->htfunctions); + mem_d((void*)self->name); + for (i = 0; i != vec_size(self->functions); ++i) { + ir_function_delete_quick(self->functions[i]); + } + vec_free(self->functions); + for (i = 0; i != vec_size(self->extparams); ++i) { + ir_value_delete(self->extparams[i]); + } + vec_free(self->extparams); + vec_free(self->extparam_protos); + for (i = 0; i != vec_size(self->globals); ++i) { + ir_value_delete(self->globals[i]); + } + vec_free(self->globals); + for (i = 0; i != vec_size(self->fields); ++i) { + ir_value_delete(self->fields[i]); + } + ir_value_delete(self->nil); + for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) { + ir_value_delete(self->vinstr_temp[i]); + } + vec_free(self->fields); + vec_free(self->filenames); + vec_free(self->filestrings); + + code_cleanup(self->code); + mem_d(self); +} + +bool ir_builder_set_name(ir_builder *self, const char *name) +{ + if (self->name) + mem_d((void*)self->name); + self->name = util_strdup(name); + return !!self->name; +} + +static ir_function* ir_builder_get_function(ir_builder *self, const char *name) +{ + return (ir_function*)util_htget(self->htfunctions, name); +} + +ir_function* ir_builder_create_function(ir_builder *self, const char *name, int outtype) +{ + ir_function *fn = ir_builder_get_function(self, name); + if (fn) { + return NULL; + } + + fn = ir_function_new(self, outtype); + if (!ir_function_set_name(fn, name)) + { + ir_function_delete(fn); + return NULL; + } + vec_push(self->functions, fn); + util_htset(self->htfunctions, name, fn); + + fn->value = ir_builder_create_global(self, fn->name, TYPE_FUNCTION); + if (!fn->value) { + ir_function_delete(fn); + return NULL; + } + + fn->value->hasvalue = true; + fn->value->outtype = outtype; + fn->value->constval.vfunc = fn; + fn->value->context = fn->context; + + return fn; +} + +static ir_value* ir_builder_get_global(ir_builder *self, const char *name) +{ + return (ir_value*)util_htget(self->htglobals, name); +} + +ir_value* ir_builder_create_global(ir_builder *self, const char *name, int vtype) +{ + ir_value *ve; + + if (name[0] != '#') + { + ve = ir_builder_get_global(self, name); + if (ve) { + return NULL; + } + } + + ve = ir_value_var(name, store_global, vtype); + vec_push(self->globals, ve); + util_htset(self->htglobals, name, ve); + return ve; +} + +ir_value* ir_builder_get_va_count(ir_builder *self) +{ + if (self->reserved_va_count) + return self->reserved_va_count; + return (self->reserved_va_count = ir_builder_create_global(self, "reserved:va_count", TYPE_FLOAT)); +} + +static ir_value* ir_builder_get_field(ir_builder *self, const char *name) +{ + return (ir_value*)util_htget(self->htfields, name); +} + + +ir_value* ir_builder_create_field(ir_builder *self, const char *name, int vtype) +{ + ir_value *ve = ir_builder_get_field(self, name); + if (ve) { + return NULL; + } + + ve = ir_value_var(name, store_global, TYPE_FIELD); + ve->fieldtype = vtype; + vec_push(self->fields, ve); + util_htset(self->htfields, name, ve); + return ve; +} + +/*********************************************************************** + *IR Function + */ + +static bool ir_function_naive_phi(ir_function*); +static void ir_function_enumerate(ir_function*); +static bool ir_function_calculate_liferanges(ir_function*); +static bool ir_function_allocate_locals(ir_function*); + +ir_function* ir_function_new(ir_builder* owner, int outtype) +{ + ir_function *self; + self = (ir_function*)mem_a(sizeof(*self)); + + if (!self) + return NULL; + + memset(self, 0, sizeof(*self)); + + self->name = NULL; + if (!ir_function_set_name(self, "<@unnamed>")) { + mem_d(self); + return NULL; + } + self->flags = 0; + + self->owner = owner; + self->context.file = "<@no context>"; + self->context.line = 0; + self->outtype = outtype; + self->value = NULL; + self->builtin = 0; + + self->params = NULL; + self->blocks = NULL; + self->values = NULL; + self->locals = NULL; + + self->max_varargs = 0; + + self->code_function_def = -1; + self->allocated_locals = 0; + self->globaltemps = 0; + + self->run_id = 0; + return self; +} + +bool ir_function_set_name(ir_function *self, const char *name) +{ + if (self->name) + mem_d((void*)self->name); + self->name = util_strdup(name); + return !!self->name; +} + +static void ir_function_delete_quick(ir_function *self) +{ + size_t i; + mem_d((void*)self->name); + + for (i = 0; i != vec_size(self->blocks); ++i) + ir_block_delete_quick(self->blocks[i]); + vec_free(self->blocks); + + vec_free(self->params); + + for (i = 0; i != vec_size(self->values); ++i) + ir_value_delete(self->values[i]); + vec_free(self->values); + + for (i = 0; i != vec_size(self->locals); ++i) + ir_value_delete(self->locals[i]); + vec_free(self->locals); + + /* self->value is deleted by the builder */ + + mem_d(self); +} + +void ir_function_delete(ir_function *self) +{ + size_t i; + mem_d((void*)self->name); + + for (i = 0; i != vec_size(self->blocks); ++i) + ir_block_delete(self->blocks[i]); + vec_free(self->blocks); + + vec_free(self->params); + + for (i = 0; i != vec_size(self->values); ++i) + ir_value_delete(self->values[i]); + vec_free(self->values); + + for (i = 0; i != vec_size(self->locals); ++i) + ir_value_delete(self->locals[i]); + vec_free(self->locals); + + /* self->value is deleted by the builder */ + + mem_d(self); +} + +static void ir_function_collect_value(ir_function *self, ir_value *v) +{ + vec_push(self->values, v); +} + +ir_block* ir_function_create_block(lex_ctx_t ctx, ir_function *self, const char *label) +{ + ir_block* bn = ir_block_new(self, label); + bn->context = ctx; + vec_push(self->blocks, bn); + + if ((self->flags & IR_FLAG_BLOCK_COVERAGE) && self->owner->coverage_func) + (void)ir_block_create_call(bn, ctx, NULL, self->owner->coverage_func, false); + + return bn; +} + +static bool instr_is_operation(uint16_t op) +{ + return ( (op >= INSTR_MUL_F && op <= INSTR_GT) || + (op >= INSTR_LOAD_F && op <= INSTR_LOAD_FNC) || + (op == INSTR_ADDRESS) || + (op >= INSTR_NOT_F && op <= INSTR_NOT_FNC) || + (op >= INSTR_AND && op <= INSTR_BITOR) || + (op >= INSTR_CALL0 && op <= INSTR_CALL8) || + (op >= VINSTR_BITAND_V && op <= VINSTR_NEG_V) ); +} + +static bool ir_function_pass_peephole(ir_function *self) +{ + size_t b; + + for (b = 0; b < vec_size(self->blocks); ++b) { + size_t i; + ir_block *block = self->blocks[b]; + + for (i = 0; i < vec_size(block->instr); ++i) { + ir_instr *inst; + inst = block->instr[i]; + + if (i >= 1 && + (inst->opcode >= INSTR_STORE_F && + inst->opcode <= INSTR_STORE_FNC)) + { + ir_instr *store; + ir_instr *oper; + ir_value *value; + + store = inst; + + oper = block->instr[i-1]; + if (!instr_is_operation(oper->opcode)) + continue; + + /* Don't change semantics of MUL_VF in engines where these may not alias. */ + if (OPTS_FLAG(LEGACY_VECTOR_MATHS)) { + if (oper->opcode == INSTR_MUL_VF && oper->_ops[2]->memberof == oper->_ops[1]) + continue; + if (oper->opcode == INSTR_MUL_FV && oper->_ops[1]->memberof == oper->_ops[2]) + continue; + } + + value = oper->_ops[0]; + + /* only do it for SSA values */ + if (value->store != store_value) + continue; + + /* don't optimize out the temp if it's used later again */ + if (vec_size(value->reads) != 1) + continue; + + /* The very next store must use this value */ + if (value->reads[0] != store) + continue; + + /* And of course the store must _read_ from it, so it's in + * OP 1 */ + if (store->_ops[1] != value) + continue; + + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + (void)!ir_instr_op(oper, 0, store->_ops[0], true); + + vec_remove(block->instr, i, 1); + ir_instr_delete(store); + } + else if (inst->opcode == VINSTR_COND) + { + /* COND on a value resulting from a NOT could + * remove the NOT and swap its operands + */ + while (true) { + ir_block *tmp; + size_t inotid; + ir_instr *inot; + ir_value *value; + value = inst->_ops[0]; + + if (value->store != store_value || + vec_size(value->reads) != 1 || + value->reads[0] != inst) + { + break; + } + + inot = value->writes[0]; + if (inot->_ops[0] != value || + inot->opcode < INSTR_NOT_F || + inot->opcode > INSTR_NOT_FNC || + inot->opcode == INSTR_NOT_V || /* can't do these */ + inot->opcode == INSTR_NOT_S) + { + break; + } + + /* count */ + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + /* change operand */ + (void)!ir_instr_op(inst, 0, inot->_ops[1], false); + /* remove NOT */ + tmp = inot->owner; + for (inotid = 0; inotid < vec_size(tmp->instr); ++inotid) { + if (tmp->instr[inotid] == inot) + break; + } + if (inotid >= vec_size(tmp->instr)) { + compile_error(inst->context, "sanity-check failed: failed to find instruction to optimize out"); + return false; + } + vec_remove(tmp->instr, inotid, 1); + ir_instr_delete(inot); + /* swap ontrue/onfalse */ + tmp = inst->bops[0]; + inst->bops[0] = inst->bops[1]; + inst->bops[1] = tmp; + } + continue; + } + } + } + + return true; +} + +static bool ir_function_pass_tailrecursion(ir_function *self) +{ + size_t b, p; + + for (b = 0; b < vec_size(self->blocks); ++b) { + ir_value *funcval; + ir_instr *ret, *call, *store = NULL; + ir_block *block = self->blocks[b]; + + if (!block->final || vec_size(block->instr) < 2) + continue; + + ret = block->instr[vec_size(block->instr)-1]; + if (ret->opcode != INSTR_DONE && ret->opcode != INSTR_RETURN) + continue; + + call = block->instr[vec_size(block->instr)-2]; + if (call->opcode >= INSTR_STORE_F && call->opcode <= INSTR_STORE_FNC) { + /* account for the unoptimized + * CALL + * STORE %return, %tmp + * RETURN %tmp + * version + */ + if (vec_size(block->instr) < 3) + continue; + + store = call; + call = block->instr[vec_size(block->instr)-3]; + } + + if (call->opcode < INSTR_CALL0 || call->opcode > INSTR_CALL8) + continue; + + if (store) { + /* optimize out the STORE */ + if (ret->_ops[0] && + ret->_ops[0] == store->_ops[0] && + store->_ops[1] == call->_ops[0]) + { + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + call->_ops[0] = store->_ops[0]; + vec_remove(block->instr, vec_size(block->instr) - 2, 1); + ir_instr_delete(store); + } + else + continue; + } + + if (!call->_ops[0]) + continue; + + funcval = call->_ops[1]; + if (!funcval) + continue; + if (funcval->vtype != TYPE_FUNCTION || funcval->constval.vfunc != self) + continue; + + /* now we have a CALL and a RET, check if it's a tailcall */ + if (ret->_ops[0] && call->_ops[0] != ret->_ops[0]) + continue; + + ++opts_optimizationcount[OPTIM_TAIL_RECURSION]; + vec_shrinkby(block->instr, 2); + + block->final = false; /* open it back up */ + + /* emite parameter-stores */ + for (p = 0; p < vec_size(call->params); ++p) { + /* assert(call->params_count <= self->locals_count); */ + if (!ir_block_create_store(block, call->context, self->locals[p], call->params[p])) { + irerror(call->context, "failed to create tailcall store instruction for parameter %i", (int)p); + return false; + } + } + if (!ir_block_create_jump(block, call->context, self->blocks[0])) { + irerror(call->context, "failed to create tailcall jump"); + return false; + } + + ir_instr_delete(call); + ir_instr_delete(ret); + } + + return true; +} + +bool ir_function_finalize(ir_function *self) +{ + size_t i; + + if (self->builtin) + return true; + + if (OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) { + if (!ir_function_pass_peephole(self)) { + irerror(self->context, "generic optimization pass broke something in `%s`", self->name); + return false; + } + } + + if (OPTS_OPTIMIZATION(OPTIM_TAIL_RECURSION)) { + if (!ir_function_pass_tailrecursion(self)) { + irerror(self->context, "tail-recursion optimization pass broke something in `%s`", self->name); + return false; + } + } + + if (!ir_function_naive_phi(self)) { + irerror(self->context, "internal error: ir_function_naive_phi failed"); + return false; + } + + for (i = 0; i < vec_size(self->locals); ++i) { + ir_value *v = self->locals[i]; + if (v->vtype == TYPE_VECTOR || + (v->vtype == TYPE_FIELD && v->outtype == TYPE_VECTOR)) + { + ir_value_vector_member(v, 0); + ir_value_vector_member(v, 1); + ir_value_vector_member(v, 2); + } + } + for (i = 0; i < vec_size(self->values); ++i) { + ir_value *v = self->values[i]; + if (v->vtype == TYPE_VECTOR || + (v->vtype == TYPE_FIELD && v->outtype == TYPE_VECTOR)) + { + ir_value_vector_member(v, 0); + ir_value_vector_member(v, 1); + ir_value_vector_member(v, 2); + } + } + + ir_function_enumerate(self); + + if (!ir_function_calculate_liferanges(self)) + return false; + if (!ir_function_allocate_locals(self)) + return false; + return true; +} + +ir_value* ir_function_create_local(ir_function *self, const char *name, int vtype, bool param) +{ + ir_value *ve; + + if (param && + vec_size(self->locals) && + self->locals[vec_size(self->locals)-1]->store != store_param) { + irerror(self->context, "cannot add parameters after adding locals"); + return NULL; + } + + ve = ir_value_var(name, (param ? store_param : store_local), vtype); + if (param) + ve->locked = true; + vec_push(self->locals, ve); + return ve; +} + +/*********************************************************************** + *IR Block + */ + +ir_block* ir_block_new(ir_function* owner, const char *name) +{ + ir_block *self; + self = (ir_block*)mem_a(sizeof(*self)); + if (!self) + return NULL; + + memset(self, 0, sizeof(*self)); + + self->label = NULL; + if (name && !ir_block_set_label(self, name)) { + mem_d(self); + return NULL; + } + self->owner = owner; + self->context.file = "<@no context>"; + self->context.line = 0; + self->final = false; + + self->instr = NULL; + self->entries = NULL; + self->exits = NULL; + + self->eid = 0; + self->is_return = false; + + self->living = NULL; + + self->generated = false; + + return self; +} + +static void ir_block_delete_quick(ir_block* self) +{ + size_t i; + if (self->label) mem_d(self->label); + for (i = 0; i != vec_size(self->instr); ++i) + ir_instr_delete_quick(self->instr[i]); + vec_free(self->instr); + vec_free(self->entries); + vec_free(self->exits); + vec_free(self->living); + mem_d(self); +} + +void ir_block_delete(ir_block* self) +{ + size_t i; + if (self->label) mem_d(self->label); + for (i = 0; i != vec_size(self->instr); ++i) + ir_instr_delete(self->instr[i]); + vec_free(self->instr); + vec_free(self->entries); + vec_free(self->exits); + vec_free(self->living); + mem_d(self); +} + +bool ir_block_set_label(ir_block *self, const char *name) +{ + if (self->label) + mem_d((void*)self->label); + self->label = util_strdup(name); + return !!self->label; +} + +/*********************************************************************** + *IR Instructions + */ + +static ir_instr* ir_instr_new(lex_ctx_t ctx, ir_block* owner, int op) +{ + ir_instr *self; + self = (ir_instr*)mem_a(sizeof(*self)); + if (!self) + return NULL; + + self->owner = owner; + self->context = ctx; + self->opcode = op; + self->_ops[0] = NULL; + self->_ops[1] = NULL; + self->_ops[2] = NULL; + self->bops[0] = NULL; + self->bops[1] = NULL; + + self->phi = NULL; + self->params = NULL; + + self->eid = 0; + + self->likely = true; + return self; +} + +static void ir_instr_delete_quick(ir_instr *self) +{ + vec_free(self->phi); + vec_free(self->params); + mem_d(self); +} + +static void ir_instr_delete(ir_instr *self) +{ + size_t i; + /* The following calls can only delete from + * vectors, we still want to delete this instruction + * so ignore the return value. Since with the warn_unused_result attribute + * gcc doesn't care about an explicit: (void)foo(); to ignore the result, + * I have to improvise here and use if(foo()); + */ + for (i = 0; i < vec_size(self->phi); ++i) { + size_t idx; + if (vec_ir_instr_find(self->phi[i].value->writes, self, &idx)) + vec_remove(self->phi[i].value->writes, idx, 1); + if (vec_ir_instr_find(self->phi[i].value->reads, self, &idx)) + vec_remove(self->phi[i].value->reads, idx, 1); + } + vec_free(self->phi); + for (i = 0; i < vec_size(self->params); ++i) { + size_t idx; + if (vec_ir_instr_find(self->params[i]->writes, self, &idx)) + vec_remove(self->params[i]->writes, idx, 1); + if (vec_ir_instr_find(self->params[i]->reads, self, &idx)) + vec_remove(self->params[i]->reads, idx, 1); + } + vec_free(self->params); + (void)!ir_instr_op(self, 0, NULL, false); + (void)!ir_instr_op(self, 1, NULL, false); + (void)!ir_instr_op(self, 2, NULL, false); + mem_d(self); +} + +static bool ir_instr_op(ir_instr *self, int op, ir_value *v, bool writing) +{ + if (v && v->vtype == TYPE_NOEXPR) { + irerror(self->context, "tried to use a NOEXPR value"); + return false; + } + + if (self->_ops[op]) { + size_t idx; + if (writing && vec_ir_instr_find(self->_ops[op]->writes, self, &idx)) + vec_remove(self->_ops[op]->writes, idx, 1); + else if (vec_ir_instr_find(self->_ops[op]->reads, self, &idx)) + vec_remove(self->_ops[op]->reads, idx, 1); + } + if (v) { + if (writing) + vec_push(v->writes, self); + else + vec_push(v->reads, self); + } + self->_ops[op] = v; + return true; +} + +/*********************************************************************** + *IR Value + */ + +static void ir_value_code_setaddr(ir_value *self, int32_t gaddr) +{ + self->code.globaladdr = gaddr; + if (self->members[0]) self->members[0]->code.globaladdr = gaddr; + if (self->members[1]) self->members[1]->code.globaladdr = gaddr; + if (self->members[2]) self->members[2]->code.globaladdr = gaddr; +} + +static int32_t ir_value_code_addr(const ir_value *self) +{ + if (self->store == store_return) + return OFS_RETURN + self->code.addroffset; + return self->code.globaladdr + self->code.addroffset; +} + +ir_value* ir_value_var(const char *name, int storetype, int vtype) +{ + ir_value *self; + self = (ir_value*)mem_a(sizeof(*self)); + self->vtype = vtype; + self->fieldtype = TYPE_VOID; + self->outtype = TYPE_VOID; + self->store = storetype; + self->flags = 0; + + self->reads = NULL; + self->writes = NULL; + + self->cvq = CV_NONE; + self->hasvalue = false; + self->context.file = "<@no context>"; + self->context.line = 0; + self->name = NULL; + if (name && !ir_value_set_name(self, name)) { + irerror(self->context, "out of memory"); + mem_d(self); + return NULL; + } + + memset(&self->constval, 0, sizeof(self->constval)); + memset(&self->code, 0, sizeof(self->code)); + + self->members[0] = NULL; + self->members[1] = NULL; + self->members[2] = NULL; + self->memberof = NULL; + + self->unique_life = false; + self->locked = false; + self->callparam = false; + + self->life = NULL; + 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; + size_t len; + ir_value *m; + if (member >= 3) + return NULL; + + if (self->members[member]) + return self->members[member]; + + if (self->name) { + len = strlen(self->name); + name = (char*)mem_a(len + 3); + memcpy(name, self->name, len); + name[len+0] = '_'; + name[len+1] = 'x' + member; + name[len+2] = '\0'; + } + else + name = NULL; + + if (self->vtype == TYPE_VECTOR) + { + m = ir_value_var(name, self->store, TYPE_FLOAT); + if (name) + mem_d(name); + if (!m) + return NULL; + m->context = self->context; + + self->members[member] = m; + m->code.addroffset = member; + } + else if (self->vtype == TYPE_FIELD) + { + if (self->fieldtype != TYPE_VECTOR) + return NULL; + m = ir_value_var(name, self->store, TYPE_FIELD); + if (name) + mem_d(name); + if (!m) + return NULL; + m->fieldtype = TYPE_FLOAT; + m->context = self->context; + + self->members[member] = m; + m->code.addroffset = member; + } + else + { + irerror(self->context, "invalid member access on %s", self->name); + return NULL; + } + + m->memberof = self; + return m; +} + +static GMQCC_INLINE size_t ir_value_sizeof(const ir_value *self) +{ + if (self->vtype == TYPE_FIELD && self->fieldtype == TYPE_VECTOR) + return type_sizeof_[TYPE_VECTOR]; + return type_sizeof_[self->vtype]; +} + +static ir_value* ir_value_out(ir_function *owner, const char *name, int storetype, int vtype) +{ + ir_value *v = ir_value_var(name, storetype, vtype); + if (!v) + return NULL; + ir_function_collect_value(owner, v); + return v; +} + +void ir_value_delete(ir_value* self) +{ + size_t i; + if (self->name) + mem_d((void*)self->name); + if (self->hasvalue) + { + if (self->vtype == TYPE_STRING) + mem_d((void*)self->constval.vstring); + } + 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); + vec_free(self->life); + mem_d(self); +} + +bool ir_value_set_name(ir_value *self, const char *name) +{ + if (self->name) + mem_d((void*)self->name); + self->name = util_strdup(name); + return !!self->name; +} + +bool ir_value_set_float(ir_value *self, float f) +{ + if (self->vtype != TYPE_FLOAT) + return false; + self->constval.vfloat = f; + self->hasvalue = true; + return true; +} + +bool ir_value_set_func(ir_value *self, int f) +{ + if (self->vtype != TYPE_FUNCTION) + return false; + self->constval.vint = f; + self->hasvalue = true; + return true; +} + +bool ir_value_set_vector(ir_value *self, vec3_t v) +{ + if (self->vtype != TYPE_VECTOR) + return false; + self->constval.vvec = v; + self->hasvalue = true; + return true; +} + +bool ir_value_set_field(ir_value *self, ir_value *fld) +{ + if (self->vtype != TYPE_FIELD) + return false; + self->constval.vpointer = fld; + self->hasvalue = true; + return true; +} + +bool ir_value_set_string(ir_value *self, const char *str) +{ + if (self->vtype != TYPE_STRING) + return false; + self->constval.vstring = util_strdupe(str); + self->hasvalue = true; + return true; +} + +#if 0 +bool ir_value_set_int(ir_value *self, int i) +{ + if (self->vtype != TYPE_INTEGER) + return false; + self->constval.vint = i; + self->hasvalue = true; + return true; +} +#endif + +bool ir_value_lives(ir_value *self, size_t at) +{ + size_t i; + for (i = 0; i < vec_size(self->life); ++i) + { + ir_life_entry_t *life = &self->life[i]; + if (life->start <= at && at <= life->end) + return true; + if (life->start > at) /* since it's ordered */ + return false; + } + return false; +} + +static bool ir_value_life_insert(ir_value *self, size_t idx, ir_life_entry_t e) +{ + size_t k; + vec_push(self->life, e); + for (k = vec_size(self->life)-1; k > idx; --k) + self->life[k] = self->life[k-1]; + self->life[idx] = e; + return true; +} + +static bool ir_value_life_merge(ir_value *self, size_t s) +{ + size_t i; + const size_t vs = vec_size(self->life); + ir_life_entry_t *life = NULL; + ir_life_entry_t *before = NULL; + ir_life_entry_t new_entry; + + /* Find the first range >= s */ + for (i = 0; i < vs; ++i) + { + before = life; + life = &self->life[i]; + if (life->start > s) + break; + } + /* nothing found? append */ + if (i == vs) { + ir_life_entry_t e; + if (life && life->end+1 == s) + { + /* previous life range can be merged in */ + life->end++; + return true; + } + if (life && life->end >= s) + return false; + e.start = e.end = s; + vec_push(self->life, e); + return true; + } + /* found */ + if (before) + { + if (before->end + 1 == s && + life->start - 1 == s) + { + /* merge */ + before->end = life->end; + vec_remove(self->life, i, 1); + return true; + } + if (before->end + 1 == s) + { + /* extend before */ + before->end++; + return true; + } + /* already contained */ + if (before->end >= s) + return false; + } + /* extend */ + if (life->start - 1 == s) + { + life->start--; + return true; + } + /* insert a new entry */ + new_entry.start = new_entry.end = s; + return ir_value_life_insert(self, i, new_entry); +} + +static bool ir_value_life_merge_into(ir_value *self, const ir_value *other) +{ + size_t i, myi; + + if (!vec_size(other->life)) + return true; + + if (!vec_size(self->life)) { + size_t count = vec_size(other->life); + ir_life_entry_t *life = vec_add(self->life, count); + memcpy(life, other->life, count * sizeof(*life)); + return true; + } + + myi = 0; + for (i = 0; i < vec_size(other->life); ++i) + { + const ir_life_entry_t *life = &other->life[i]; + while (true) + { + ir_life_entry_t *entry = &self->life[myi]; + + if (life->end+1 < entry->start) + { + /* adding an interval before entry */ + if (!ir_value_life_insert(self, myi, *life)) + return false; + ++myi; + break; + } + + if (life->start < entry->start && + life->end+1 >= entry->start) + { + /* starts earlier and overlaps */ + entry->start = life->start; + } + + if (life->end > entry->end && + life->start <= entry->end+1) + { + /* ends later and overlaps */ + entry->end = life->end; + } + + /* see if our change combines it with the next ranges */ + while (myi+1 < vec_size(self->life) && + entry->end+1 >= self->life[1+myi].start) + { + /* overlaps with (myi+1) */ + if (entry->end < self->life[1+myi].end) + entry->end = self->life[1+myi].end; + vec_remove(self->life, myi+1, 1); + entry = &self->life[myi]; + } + + /* see if we're after the entry */ + if (life->start > entry->end) + { + ++myi; + /* append if we're at the end */ + if (myi >= vec_size(self->life)) { + vec_push(self->life, *life); + break; + } + /* otherweise check the next range */ + continue; + } + break; + } + } + return true; +} + +static bool ir_values_overlap(const ir_value *a, const ir_value *b) +{ + /* For any life entry in A see if it overlaps with + * any life entry in B. + * Note that the life entries are orderes, so we can make a + * more efficient algorithm there than naively translating the + * statement above. + */ + + ir_life_entry_t *la, *lb, *enda, *endb; + + /* first of all, if either has no life range, they cannot clash */ + if (!vec_size(a->life) || !vec_size(b->life)) + return false; + + la = a->life; + lb = b->life; + enda = la + vec_size(a->life); + endb = lb + vec_size(b->life); + while (true) + { + /* check if the entries overlap, for that, + * both must start before the other one ends. + */ + if (la->start < lb->end && + lb->start < la->end) + { + return true; + } + + /* entries are ordered + * one entry is earlier than the other + * that earlier entry will be moved forward + */ + if (la->start < lb->start) + { + /* order: A B, move A forward + * check if we hit the end with A + */ + if (++la == enda) + break; + } + else /* if (lb->start < la->start) actually <= */ + { + /* order: B A, move B forward + * check if we hit the end with B + */ + if (++lb == endb) + break; + } + } + return false; +} + +/*********************************************************************** + *IR main operations + */ + +static bool ir_check_unreachable(ir_block *self) +{ + /* The IR should never have to deal with unreachable code */ + if (!self->final/* || OPTS_FLAG(ALLOW_UNREACHABLE_CODE)*/) + return true; + irerror(self->context, "unreachable statement (%s)", self->label); + return false; +} + +bool ir_block_create_store_op(ir_block *self, lex_ctx_t ctx, int op, ir_value *target, ir_value *what) +{ + ir_instr *in; + if (!ir_check_unreachable(self)) + return false; + + if (target->store == store_value && + (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC)) + { + irerror(self->context, "cannot store to an SSA value"); + irerror(self->context, "trying to store: %s <- %s", target->name, what->name); + irerror(self->context, "instruction: %s", util_instr_str[op]); + return false; + } + + in = ir_instr_new(ctx, self, op); + if (!in) + return false; + + if (!ir_instr_op(in, 0, target, (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC)) || + !ir_instr_op(in, 1, what, false)) + { + ir_instr_delete(in); + return false; + } + vec_push(self->instr, in); + 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; + int vtype; + if (target->vtype == TYPE_VARIANT) + vtype = what->vtype; + else + vtype = target->vtype; + +#if 0 + if (vtype == TYPE_FLOAT && what->vtype == TYPE_INTEGER) + op = INSTR_CONV_ITOF; + else if (vtype == TYPE_INTEGER && what->vtype == TYPE_FLOAT) + op = INSTR_CONV_FTOI; +#endif + op = type_store_instr[vtype]; + + if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) { + if (op == INSTR_STORE_FLD && what->fieldtype == TYPE_VECTOR) + op = INSTR_STORE_V; + } + + return ir_block_create_store_op(self, ctx, op, target, what); +} + +bool ir_block_create_storep(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what) +{ + int op = 0; + int vtype; + + if (target->vtype != TYPE_POINTER) + return false; + + /* storing using pointer - target is a pointer, type must be + * inferred from source + */ + vtype = what->vtype; + + op = type_storep_instr[vtype]; + if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) { + if (op == INSTR_STOREP_FLD && what->fieldtype == TYPE_VECTOR) + op = INSTR_STOREP_V; + } + + return ir_block_create_store_op(self, ctx, op, target, what); +} + +bool ir_block_create_return(ir_block *self, lex_ctx_t ctx, ir_value *v) +{ + ir_instr *in; + if (!ir_check_unreachable(self)) + return false; + + self->final = true; + + self->is_return = true; + in = ir_instr_new(ctx, self, INSTR_RETURN); + if (!in) + return false; + + if (v && !ir_instr_op(in, 0, v, false)) { + ir_instr_delete(in); + return false; + } + + vec_push(self->instr, in); + return true; +} + +bool ir_block_create_if(ir_block *self, lex_ctx_t ctx, ir_value *v, + ir_block *ontrue, ir_block *onfalse) +{ + ir_instr *in; + if (!ir_check_unreachable(self)) + return false; + self->final = true; + /*in = ir_instr_new(ctx, self, (v->vtype == TYPE_STRING ? INSTR_IF_S : INSTR_IF_F));*/ + in = ir_instr_new(ctx, self, VINSTR_COND); + if (!in) + return false; + + if (!ir_instr_op(in, 0, v, false)) { + ir_instr_delete(in); + return false; + } + + in->bops[0] = ontrue; + in->bops[1] = onfalse; + + vec_push(self->instr, in); + + vec_push(self->exits, ontrue); + vec_push(self->exits, onfalse); + vec_push(ontrue->entries, self); + vec_push(onfalse->entries, self); + return true; +} + +bool ir_block_create_jump(ir_block *self, lex_ctx_t ctx, ir_block *to) +{ + ir_instr *in; + if (!ir_check_unreachable(self)) + return false; + self->final = true; + in = ir_instr_new(ctx, self, VINSTR_JUMP); + if (!in) + return false; + + in->bops[0] = to; + vec_push(self->instr, in); + + vec_push(self->exits, to); + vec_push(to->entries, self); + return true; +} + +bool ir_block_create_goto(ir_block *self, lex_ctx_t ctx, ir_block *to) +{ + self->owner->flags |= IR_FLAG_HAS_GOTO; + return ir_block_create_jump(self, ctx, to); +} + +ir_instr* ir_block_create_phi(ir_block *self, lex_ctx_t ctx, const char *label, int ot) +{ + ir_value *out; + ir_instr *in; + if (!ir_check_unreachable(self)) + return NULL; + in = ir_instr_new(ctx, self, VINSTR_PHI); + if (!in) + return NULL; + out = ir_value_out(self->owner, label, store_value, ot); + if (!out) { + ir_instr_delete(in); + return NULL; + } + if (!ir_instr_op(in, 0, out, true)) { + ir_instr_delete(in); + ir_value_delete(out); + return NULL; + } + vec_push(self->instr, in); + return in; +} + +ir_value* ir_phi_value(ir_instr *self) +{ + return self->_ops[0]; +} + +void ir_phi_add(ir_instr* self, ir_block *b, ir_value *v) +{ + ir_phi_entry_t pe; + + if (!vec_ir_block_find(self->owner->entries, b, NULL)) { + /* Must not be possible to cause this, otherwise the AST + * is doing something wrong. + */ + irerror(self->context, "Invalid entry block for PHI"); + exit(EXIT_FAILURE); + } + + pe.value = v; + pe.from = b; + vec_push(v->reads, self); + vec_push(self->phi, pe); +} + +/* call related code */ +ir_instr* ir_block_create_call(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *func, bool noreturn) +{ + ir_value *out; + ir_instr *in; + if (!ir_check_unreachable(self)) + return NULL; + in = ir_instr_new(ctx, self, (noreturn ? VINSTR_NRCALL : INSTR_CALL0)); + if (!in) + return NULL; + if (noreturn) { + self->final = true; + self->is_return = true; + } + out = ir_value_out(self->owner, label, (func->outtype == TYPE_VOID) ? store_return : store_value, func->outtype); + if (!out) { + ir_instr_delete(in); + return NULL; + } + if (!ir_instr_op(in, 0, out, true) || + !ir_instr_op(in, 1, func, false)) + { + ir_instr_delete(in); + ir_value_delete(out); + return NULL; + } + vec_push(self->instr, in); + /* + if (noreturn) { + if (!ir_block_create_return(self, ctx, NULL)) { + compile_error(ctx, "internal error: failed to generate dummy-return instruction"); + ir_instr_delete(in); + return NULL; + } + } + */ + return in; +} + +ir_value* ir_call_value(ir_instr *self) +{ + return self->_ops[0]; +} + +void ir_call_param(ir_instr* self, ir_value *v) +{ + vec_push(self->params, v); + vec_push(v->reads, self); +} + +/* binary op related code */ + +ir_value* ir_block_create_binop(ir_block *self, lex_ctx_t ctx, + const char *label, int opcode, + ir_value *left, ir_value *right) +{ + int ot = TYPE_VOID; + switch (opcode) { + case INSTR_ADD_F: + case INSTR_SUB_F: + case INSTR_DIV_F: + case INSTR_MUL_F: + case INSTR_MUL_V: + case INSTR_AND: + case INSTR_OR: +#if 0 + case INSTR_AND_I: + case INSTR_AND_IF: + case INSTR_AND_FI: + case INSTR_OR_I: + case INSTR_OR_IF: + case INSTR_OR_FI: +#endif + case INSTR_BITAND: + case INSTR_BITOR: + case VINSTR_BITXOR: +#if 0 + case INSTR_SUB_S: /* -- offset of string as float */ + case INSTR_MUL_IF: + case INSTR_MUL_FI: + case INSTR_DIV_IF: + case INSTR_DIV_FI: + case INSTR_BITOR_IF: + case INSTR_BITOR_FI: + case INSTR_BITAND_FI: + case INSTR_BITAND_IF: + case INSTR_EQ_I: + case INSTR_NE_I: +#endif + ot = TYPE_FLOAT; + break; +#if 0 + case INSTR_ADD_I: + case INSTR_ADD_IF: + case INSTR_ADD_FI: + case INSTR_SUB_I: + case INSTR_SUB_FI: + case INSTR_SUB_IF: + case INSTR_MUL_I: + case INSTR_DIV_I: + case INSTR_BITAND_I: + case INSTR_BITOR_I: + case INSTR_XOR_I: + case INSTR_RSHIFT_I: + case INSTR_LSHIFT_I: + ot = TYPE_INTEGER; + break; +#endif + case INSTR_ADD_V: + case INSTR_SUB_V: + case INSTR_MUL_VF: + case INSTR_MUL_FV: + case VINSTR_BITAND_V: + case VINSTR_BITOR_V: + case VINSTR_BITXOR_V: + case VINSTR_BITAND_VF: + case VINSTR_BITOR_VF: + case VINSTR_BITXOR_VF: + case VINSTR_CROSS: +#if 0 + case INSTR_DIV_VF: + case INSTR_MUL_IV: + case INSTR_MUL_VI: +#endif + ot = TYPE_VECTOR; + break; +#if 0 + case INSTR_ADD_SF: + ot = TYPE_POINTER; + break; +#endif + /* + * after the following default case, the value of opcode can never + * be 1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65 + */ + default: + /* ranges: */ + /* boolean operations result in floats */ + + /* + * opcode >= 10 takes true branch opcode is at least 10 + * opcode <= 23 takes false branch opcode is at least 24 + */ + if (opcode >= INSTR_EQ_F && opcode <= INSTR_GT) + ot = TYPE_FLOAT; + + /* + * At condition "opcode <= 23", the value of "opcode" must be + * at least 24. + * At condition "opcode <= 23", the value of "opcode" cannot be + * equal to any of {1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65}. + * The condition "opcode <= 23" cannot be true. + * + * Thus ot=2 (TYPE_FLOAT) can never be true + */ +#if 0 + else if (opcode >= INSTR_LE && opcode <= INSTR_GT) + ot = TYPE_FLOAT; + else if (opcode >= INSTR_LE_I && opcode <= INSTR_EQ_FI) + ot = TYPE_FLOAT; +#endif + break; + }; + if (ot == TYPE_VOID) { + /* The AST or parser were supposed to check this! */ + return NULL; + } + + return ir_block_create_general_instr(self, ctx, label, opcode, left, right, ot); +} + +ir_value* ir_block_create_unary(ir_block *self, lex_ctx_t ctx, + const char *label, int opcode, + ir_value *operand) +{ + int ot = TYPE_FLOAT; + switch (opcode) { + case INSTR_NOT_F: + case INSTR_NOT_V: + case INSTR_NOT_S: + case INSTR_NOT_ENT: + case INSTR_NOT_FNC: /* + case INSTR_NOT_I: */ + ot = TYPE_FLOAT; + break; + + /* + * Negation for virtual instructions is emulated with 0-value. Thankfully + * the operand for 0 already exists so we just source it from here. + */ + case VINSTR_NEG_F: + return ir_block_create_general_instr(self, ctx, label, INSTR_SUB_F, NULL, operand, ot); + case VINSTR_NEG_V: + return ir_block_create_general_instr(self, ctx, label, INSTR_SUB_V, NULL, operand, TYPE_VECTOR); + + default: + ot = operand->vtype; + break; + }; + if (ot == TYPE_VOID) { + /* The AST or parser were supposed to check this! */ + return NULL; + } + + /* let's use the general instruction creator and pass NULL for OPB */ + return ir_block_create_general_instr(self, ctx, label, opcode, operand, NULL, ot); +} + +static ir_value* ir_block_create_general_instr(ir_block *self, lex_ctx_t ctx, const char *label, + int op, ir_value *a, ir_value *b, int outype) +{ + ir_instr *instr; + ir_value *out; + + out = ir_value_out(self->owner, label, store_value, outype); + if (!out) + return NULL; + + instr = ir_instr_new(ctx, self, op); + if (!instr) { + ir_value_delete(out); + return NULL; + } + + if (!ir_instr_op(instr, 0, out, true) || + !ir_instr_op(instr, 1, a, false) || + !ir_instr_op(instr, 2, b, false) ) + { + goto on_error; + } + + vec_push(self->instr, instr); + + return out; +on_error: + ir_instr_delete(instr); + ir_value_delete(out); + return NULL; +} + +ir_value* ir_block_create_fieldaddress(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field) +{ + ir_value *v; + + /* Support for various pointer types todo if so desired */ + if (ent->vtype != TYPE_ENTITY) + return NULL; + + if (field->vtype != TYPE_FIELD) + return NULL; + + v = ir_block_create_general_instr(self, ctx, label, INSTR_ADDRESS, ent, field, TYPE_POINTER); + v->fieldtype = field->fieldtype; + return v; +} + +ir_value* ir_block_create_load_from_ent(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field, int outype) +{ + int op; + if (ent->vtype != TYPE_ENTITY) + return NULL; + + /* at some point we could redirect for TYPE_POINTER... but that could lead to carelessness */ + if (field->vtype != TYPE_FIELD) + return NULL; + + switch (outype) + { + case TYPE_FLOAT: op = INSTR_LOAD_F; break; + case TYPE_VECTOR: op = INSTR_LOAD_V; break; + case TYPE_STRING: op = INSTR_LOAD_S; break; + case TYPE_FIELD: op = INSTR_LOAD_FLD; break; + case TYPE_ENTITY: op = INSTR_LOAD_ENT; break; + case TYPE_FUNCTION: op = INSTR_LOAD_FNC; break; +#if 0 + case TYPE_POINTER: op = INSTR_LOAD_I; break; + case TYPE_INTEGER: op = INSTR_LOAD_I; break; +#endif + default: + irerror(self->context, "invalid type for ir_block_create_load_from_ent: %s", type_name[outype]); + return NULL; + } + + return ir_block_create_general_instr(self, ctx, label, op, ent, field, outype); +} + +/* PHI resolving breaks the SSA, and must thus be the last + * step before life-range calculation. + */ + +static bool ir_block_naive_phi(ir_block *self); +bool ir_function_naive_phi(ir_function *self) +{ + size_t i; + + for (i = 0; i < vec_size(self->blocks); ++i) + { + if (!ir_block_naive_phi(self->blocks[i])) + return false; + } + return true; +} + +static bool ir_block_naive_phi(ir_block *self) +{ + size_t i, p; /*, w;*/ + /* FIXME: optionally, create_phi can add the phis + * to a list so we don't need to loop through blocks + * - anyway: "don't optimize YET" + */ + for (i = 0; i < vec_size(self->instr); ++i) + { + ir_instr *instr = self->instr[i]; + if (instr->opcode != VINSTR_PHI) + continue; + + vec_remove(self->instr, i, 1); + --i; /* NOTE: i+1 below */ + + for (p = 0; p < vec_size(instr->phi); ++p) + { + ir_value *v = instr->phi[p].value; + ir_block *b = instr->phi[p].from; + + if (v->store == store_value && + vec_size(v->reads) == 1 && + vec_size(v->writes) == 1) + { + /* replace the value */ + if (!ir_instr_op(v->writes[0], 0, instr->_ops[0], true)) + return false; + } + else + { + /* force a move instruction */ + ir_instr *prevjump = vec_last(b->instr); + vec_pop(b->instr); + b->final = false; + instr->_ops[0]->store = store_global; + if (!ir_block_create_store(b, instr->context, instr->_ops[0], v)) + return false; + instr->_ops[0]->store = store_value; + vec_push(b->instr, prevjump); + b->final = true; + } + } + ir_instr_delete(instr); + } + return true; +} + +/*********************************************************************** + *IR Temp allocation code + * Propagating value life ranges by walking through the function backwards + * until no more changes are made. + * In theory this should happen once more than once for every nested loop + * level. + * Though this implementation might run an additional time for if nests. + */ + +/* Enumerate instructions used by value's life-ranges + */ +static void ir_block_enumerate(ir_block *self, size_t *_eid) +{ + size_t i; + size_t eid = *_eid; + for (i = 0; i < vec_size(self->instr); ++i) + { + self->instr[i]->eid = eid++; + } + *_eid = eid; +} + +/* Enumerate blocks and instructions. + * The block-enumeration is unordered! + * We do not really use the block enumreation, however + * the instruction enumeration is important for life-ranges. + */ +void ir_function_enumerate(ir_function *self) +{ + size_t i; + size_t instruction_id = 0; + for (i = 0; i < vec_size(self->blocks); ++i) + { + /* each block now gets an additional "entry" instruction id + * we can use to avoid point-life issues + */ + self->blocks[i]->entry_id = instruction_id; + ++instruction_id; + + self->blocks[i]->eid = i; + ir_block_enumerate(self->blocks[i], &instruction_id); + } +} + +/* Local-value allocator + * After finishing creating the liferange of all values used in a function + * we can allocate their global-positions. + * This is the counterpart to register-allocation in register machines. + */ +typedef struct { + ir_value **locals; + size_t *sizes; + size_t *positions; + bool *unique; +} function_allocator; + +static bool function_allocator_alloc(function_allocator *alloc, ir_value *var) +{ + ir_value *slot; + size_t vsize = ir_value_sizeof(var); + + var->code.local = vec_size(alloc->locals); + + slot = ir_value_var("reg", store_global, var->vtype); + if (!slot) + return false; + + if (!ir_value_life_merge_into(slot, var)) + goto localerror; + + vec_push(alloc->locals, slot); + vec_push(alloc->sizes, vsize); + vec_push(alloc->unique, var->unique_life); + + return true; + +localerror: + ir_value_delete(slot); + return false; +} + +static bool ir_function_allocator_assign(ir_function *self, function_allocator *alloc, ir_value *v) +{ + size_t a; + ir_value *slot; + + if (v->unique_life) + return function_allocator_alloc(alloc, v); + + for (a = 0; a < vec_size(alloc->locals); ++a) + { + /* if it's reserved for a unique liferange: skip */ + if (alloc->unique[a]) + continue; + + slot = alloc->locals[a]; + + /* never resize parameters + * will be required later when overlapping temps + locals + */ + if (a < vec_size(self->params) && + alloc->sizes[a] < ir_value_sizeof(v)) + { + continue; + } + + if (ir_values_overlap(v, slot)) + continue; + + if (!ir_value_life_merge_into(slot, v)) + return false; + + /* adjust size for this slot */ + if (alloc->sizes[a] < ir_value_sizeof(v)) + alloc->sizes[a] = ir_value_sizeof(v); + + v->code.local = a; + return true; + } + if (a >= vec_size(alloc->locals)) { + if (!function_allocator_alloc(alloc, v)) + return false; + } + return true; +} + +bool ir_function_allocate_locals(ir_function *self) +{ + size_t i; + bool retval = true; + size_t pos; + bool opt_gt = OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS); + + ir_value *v; + + function_allocator lockalloc, globalloc; + + if (!vec_size(self->locals) && !vec_size(self->values)) + return true; + + globalloc.locals = NULL; + globalloc.sizes = NULL; + globalloc.positions = NULL; + globalloc.unique = NULL; + lockalloc.locals = NULL; + lockalloc.sizes = NULL; + lockalloc.positions = NULL; + lockalloc.unique = NULL; + + for (i = 0; i < vec_size(self->locals); ++i) + { + v = self->locals[i]; + if ((self->flags & IR_FLAG_MASK_NO_LOCAL_TEMPS) || !OPTS_OPTIMIZATION(OPTIM_LOCAL_TEMPS)) { + v->locked = true; + v->unique_life = true; + } + else if (i >= vec_size(self->params)) + break; + else + v->locked = true; /* lock parameters locals */ + if (!function_allocator_alloc((v->locked || !opt_gt ? &lockalloc : &globalloc), v)) + goto error; + } + for (; i < vec_size(self->locals); ++i) + { + v = self->locals[i]; + if (!vec_size(v->life)) + continue; + if (!ir_function_allocator_assign(self, (v->locked || !opt_gt ? &lockalloc : &globalloc), v)) + goto error; + } + + /* Allocate a slot for any value that still exists */ + for (i = 0; i < vec_size(self->values); ++i) + { + v = self->values[i]; + + if (!vec_size(v->life)) + continue; + + /* CALL optimization: + * If the value is a parameter-temp: 1 write, 1 read from a CALL + * and it's not "locked", write it to the OFS_PARM directly. + */ + if (OPTS_OPTIMIZATION(OPTIM_CALL_STORES) && !v->locked && !v->unique_life) { + if (vec_size(v->reads) == 1 && vec_size(v->writes) == 1 && + (v->reads[0]->opcode == VINSTR_NRCALL || + (v->reads[0]->opcode >= INSTR_CALL0 && v->reads[0]->opcode <= INSTR_CALL8) + ) + ) + { + size_t param; + ir_instr *call = v->reads[0]; + if (!vec_ir_value_find(call->params, v, ¶m)) { + irerror(call->context, "internal error: unlocked parameter %s not found", v->name); + goto error; + } + ++opts_optimizationcount[OPTIM_CALL_STORES]; + v->callparam = true; + if (param < 8) + ir_value_code_setaddr(v, OFS_PARM0 + 3*param); + else { + size_t nprotos = vec_size(self->owner->extparam_protos); + ir_value *ep; + param -= 8; + if (nprotos > param) + ep = self->owner->extparam_protos[param]; + else + { + ep = ir_gen_extparam_proto(self->owner); + while (++nprotos <= param) + ep = ir_gen_extparam_proto(self->owner); + } + ir_instr_op(v->writes[0], 0, ep, true); + call->params[param+8] = ep; + } + continue; + } + if (vec_size(v->writes) == 1 && v->writes[0]->opcode == INSTR_CALL0) + { + v->store = store_return; + if (v->members[0]) v->members[0]->store = store_return; + if (v->members[1]) v->members[1]->store = store_return; + if (v->members[2]) v->members[2]->store = store_return; + ++opts_optimizationcount[OPTIM_CALL_STORES]; + continue; + } + } + + if (!ir_function_allocator_assign(self, (v->locked || !opt_gt ? &lockalloc : &globalloc), v)) + goto error; + } + + if (!lockalloc.sizes && !globalloc.sizes) { + goto cleanup; + } + vec_push(lockalloc.positions, 0); + vec_push(globalloc.positions, 0); + + /* Adjust slot positions based on sizes */ + if (lockalloc.sizes) { + pos = (vec_size(lockalloc.sizes) ? lockalloc.positions[0] : 0); + for (i = 1; i < vec_size(lockalloc.sizes); ++i) + { + pos = lockalloc.positions[i-1] + lockalloc.sizes[i-1]; + vec_push(lockalloc.positions, pos); + } + self->allocated_locals = pos + vec_last(lockalloc.sizes); + } + if (globalloc.sizes) { + pos = (vec_size(globalloc.sizes) ? globalloc.positions[0] : 0); + for (i = 1; i < vec_size(globalloc.sizes); ++i) + { + pos = globalloc.positions[i-1] + globalloc.sizes[i-1]; + vec_push(globalloc.positions, pos); + } + self->globaltemps = pos + vec_last(globalloc.sizes); + } + + /* Locals need to know their new position */ + for (i = 0; i < vec_size(self->locals); ++i) { + v = self->locals[i]; + if (v->locked || !opt_gt) + v->code.local = lockalloc.positions[v->code.local]; + else + v->code.local = globalloc.positions[v->code.local]; + } + /* Take over the actual slot positions on values */ + for (i = 0; i < vec_size(self->values); ++i) { + v = self->values[i]; + if (v->locked || !opt_gt) + v->code.local = lockalloc.positions[v->code.local]; + else + v->code.local = globalloc.positions[v->code.local]; + } + + goto cleanup; + +error: + retval = false; +cleanup: + for (i = 0; i < vec_size(lockalloc.locals); ++i) + ir_value_delete(lockalloc.locals[i]); + for (i = 0; i < vec_size(globalloc.locals); ++i) + ir_value_delete(globalloc.locals[i]); + vec_free(globalloc.unique); + vec_free(globalloc.locals); + vec_free(globalloc.sizes); + vec_free(globalloc.positions); + vec_free(lockalloc.unique); + vec_free(lockalloc.locals); + vec_free(lockalloc.sizes); + vec_free(lockalloc.positions); + return retval; +} + +/* Get information about which operand + * is read from, or written to. + */ +static void ir_op_read_write(int op, size_t *read, size_t *write) +{ + switch (op) + { + case VINSTR_JUMP: + case INSTR_GOTO: + *write = 0; + *read = 0; + break; + case INSTR_IF: + case INSTR_IFNOT: +#if 0 + case INSTR_IF_S: + case INSTR_IFNOT_S: +#endif + case INSTR_RETURN: + case VINSTR_COND: + *write = 0; + *read = 1; + break; + case INSTR_STOREP_F: + case INSTR_STOREP_V: + case INSTR_STOREP_S: + case INSTR_STOREP_ENT: + case INSTR_STOREP_FLD: + case INSTR_STOREP_FNC: + *write = 0; + *read = 7; + break; + default: + *write = 1; + *read = 6; + break; + }; +} + +static bool ir_block_living_add_instr(ir_block *self, size_t eid) +{ + size_t i; + const size_t vs = vec_size(self->living); + bool changed = false; + for (i = 0; i != vs; ++i) + { + if (ir_value_life_merge(self->living[i], eid)) + changed = true; + } + return changed; +} + +static bool ir_block_living_lock(ir_block *self) +{ + size_t i; + bool changed = false; + for (i = 0; i != vec_size(self->living); ++i) + { + if (!self->living[i]->locked) { + self->living[i]->locked = true; + changed = true; + } + } + return changed; +} + +static bool ir_block_life_propagate(ir_block *self, bool *changed) +{ + ir_instr *instr; + ir_value *value; + size_t i, o, p, mem, cnt; + /* bitmasks which operands are read from or written to */ + size_t read, write; + char dbg_ind[16]; + dbg_ind[0] = '#'; + dbg_ind[1] = '0'; + (void)dbg_ind; + + vec_free(self->living); + + p = vec_size(self->exits); + for (i = 0; i < p; ++i) { + ir_block *prev = self->exits[i]; + cnt = vec_size(prev->living); + for (o = 0; o < cnt; ++o) { + if (!vec_ir_value_find(self->living, prev->living[o], NULL)) + vec_push(self->living, prev->living[o]); + } + } + + i = vec_size(self->instr); + while (i) + { --i; + instr = self->instr[i]; + + /* See which operands are read and write operands */ + ir_op_read_write(instr->opcode, &read, &write); + + /* Go through the 3 main operands + * writes first, then reads + */ + for (o = 0; o < 3; ++o) + { + if (!instr->_ops[o]) /* no such operand */ + continue; + + value = instr->_ops[o]; + + /* We only care about locals */ + /* we also calculate parameter liferanges so that locals + * can take up parameter slots */ + if (value->store != store_value && + value->store != store_local && + value->store != store_param) + continue; + + /* write operands */ + /* When we write to a local, we consider it "dead" for the + * remaining upper part of the function, since in SSA a value + * can only be written once (== created) + */ + if (write & (1<living, value, &idx); + if (!in_living) + { + /* If the value isn't alive it hasn't been read before... */ + /* TODO: See if the warning can be emitted during parsing or AST processing + * otherwise have warning printed here. + * IF printing a warning here: include filecontext_t, + * and make sure it's only printed once + * since this function is run multiple times. + */ + /* con_err( "Value only written %s\n", value->name); */ + if (ir_value_life_merge(value, instr->eid)) + *changed = true; + } else { + /* since 'living' won't contain it + * anymore, merge the value, since + * (A) doesn't. + */ + if (ir_value_life_merge(value, instr->eid)) + *changed = true; + /* Then remove */ + vec_remove(self->living, idx, 1); + } + /* Removing a vector removes all members */ + for (mem = 0; mem < 3; ++mem) { + if (value->members[mem] && vec_ir_value_find(self->living, value->members[mem], &idx)) { + if (ir_value_life_merge(value->members[mem], instr->eid)) + *changed = true; + vec_remove(self->living, idx, 1); + } + } + /* Removing the last member removes the vector */ + if (value->memberof) { + value = value->memberof; + for (mem = 0; mem < 3; ++mem) { + if (value->members[mem] && vec_ir_value_find(self->living, value->members[mem], NULL)) + break; + } + if (mem == 3 && vec_ir_value_find(self->living, value, &idx)) { + if (ir_value_life_merge(value, instr->eid)) + *changed = true; + vec_remove(self->living, idx, 1); + } + } + } + } + + /* These operations need a special case as they can break when using + * same source and destination operand otherwise, as the engine may + * read the source multiple times. */ + if (instr->opcode == INSTR_MUL_VF || + instr->opcode == VINSTR_BITAND_VF || + instr->opcode == VINSTR_BITOR_VF || + instr->opcode == VINSTR_BITXOR || + instr->opcode == VINSTR_BITXOR_VF || + instr->opcode == VINSTR_BITXOR_V || + instr->opcode == VINSTR_CROSS) + { + value = instr->_ops[2]; + /* the float source will get an additional lifetime */ + if (ir_value_life_merge(value, instr->eid+1)) + *changed = true; + if (value->memberof && ir_value_life_merge(value->memberof, instr->eid+1)) + *changed = true; + } + + if (instr->opcode == INSTR_MUL_FV || + instr->opcode == INSTR_LOAD_V || + instr->opcode == VINSTR_BITXOR || + instr->opcode == VINSTR_BITXOR_VF || + instr->opcode == VINSTR_BITXOR_V || + instr->opcode == VINSTR_CROSS) + { + value = instr->_ops[1]; + /* the float source will get an additional lifetime */ + if (ir_value_life_merge(value, instr->eid+1)) + *changed = true; + if (value->memberof && ir_value_life_merge(value->memberof, instr->eid+1)) + *changed = true; + } + + for (o = 0; o < 3; ++o) + { + if (!instr->_ops[o]) /* no such operand */ + continue; + + value = instr->_ops[o]; + + /* We only care about locals */ + /* we also calculate parameter liferanges so that locals + * can take up parameter slots */ + if (value->store != store_value && + value->store != store_local && + value->store != store_param) + continue; + + /* read operands */ + if (read & (1<living, value, NULL)) + vec_push(self->living, value); + /* reading adds the full vector */ + if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL)) + vec_push(self->living, value->memberof); + for (mem = 0; mem < 3; ++mem) { + if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL)) + vec_push(self->living, value->members[mem]); + } + } + } + /* PHI operands are always read operands */ + for (p = 0; p < vec_size(instr->phi); ++p) + { + value = instr->phi[p].value; + if (!vec_ir_value_find(self->living, value, NULL)) + vec_push(self->living, value); + /* reading adds the full vector */ + if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL)) + vec_push(self->living, value->memberof); + for (mem = 0; mem < 3; ++mem) { + if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL)) + vec_push(self->living, value->members[mem]); + } + } + + /* on a call, all these values must be "locked" */ + if (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8) { + if (ir_block_living_lock(self)) + *changed = true; + } + /* call params are read operands too */ + for (p = 0; p < vec_size(instr->params); ++p) + { + value = instr->params[p]; + if (!vec_ir_value_find(self->living, value, NULL)) + vec_push(self->living, value); + /* reading adds the full vector */ + if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL)) + vec_push(self->living, value->memberof); + for (mem = 0; mem < 3; ++mem) { + if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL)) + vec_push(self->living, value->members[mem]); + } + } + + /* (A) */ + if (ir_block_living_add_instr(self, instr->eid)) + *changed = true; + } + /* the "entry" instruction ID */ + if (ir_block_living_add_instr(self, self->entry_id)) + *changed = true; + + return true; +} + +bool ir_function_calculate_liferanges(ir_function *self) +{ + size_t i, s; + bool changed; + + /* parameters live at 0 */ + for (i = 0; i < vec_size(self->params); ++i) + if (!ir_value_life_merge(self->locals[i], 0)) + compile_error(self->context, "internal error: failed value-life merging"); + + do { + self->run_id++; + changed = false; + i = vec_size(self->blocks); + while (i--) { + ir_block_life_propagate(self->blocks[i], &changed); + } + } while (changed); + + if (vec_size(self->blocks)) { + ir_block *block = self->blocks[0]; + for (i = 0; i < vec_size(block->living); ++i) { + ir_value *v = block->living[i]; + if (v->store != store_local) + continue; + if (v->vtype == TYPE_VECTOR) + continue; + self->flags |= IR_FLAG_HAS_UNINITIALIZED; + /* find the instruction reading from it */ + for (s = 0; s < vec_size(v->reads); ++s) { + if (v->reads[s]->eid == v->life[0].end) + break; + } + if (s < vec_size(v->reads)) { + if (irwarning(v->context, WARN_USED_UNINITIALIZED, + "variable `%s` may be used uninitialized in this function\n" + " -> %s:%i", + v->name, + v->reads[s]->context.file, v->reads[s]->context.line) + ) + { + return false; + } + continue; + } + if (v->memberof) { + ir_value *vec = v->memberof; + for (s = 0; s < vec_size(vec->reads); ++s) { + if (vec->reads[s]->eid == v->life[0].end) + break; + } + if (s < vec_size(vec->reads)) { + if (irwarning(v->context, WARN_USED_UNINITIALIZED, + "variable `%s` may be used uninitialized in this function\n" + " -> %s:%i", + v->name, + vec->reads[s]->context.file, vec->reads[s]->context.line) + ) + { + return false; + } + continue; + } + } + if (irwarning(v->context, WARN_USED_UNINITIALIZED, + "variable `%s` may be used uninitialized in this function", v->name)) + { + return false; + } + } + } + return true; +} + +/*********************************************************************** + *IR Code-Generation + * + * Since the IR has the convention of putting 'write' operands + * at the beginning, we have to rotate the operands of instructions + * properly in order to generate valid QCVM code. + * + * Having destinations at a fixed position is more convenient. In QC + * this is *mostly* OPC, but FTE adds at least 2 instructions which + * read from from OPA, and store to OPB rather than OPC. Which is + * partially the reason why the implementation of these instructions + * in darkplaces has been delayed for so long. + * + * Breaking conventions is annoying... + */ +static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool islocal); + +static bool gen_global_field(code_t *code, ir_value *global) +{ + if (global->hasvalue) + { + ir_value *fld = global->constval.vpointer; + if (!fld) { + irerror(global->context, "Invalid field constant with no field: %s", global->name); + return false; + } + + /* copy the field's value */ + ir_value_code_setaddr(global, vec_size(code->globals)); + vec_push(code->globals, fld->code.fieldaddr); + if (global->fieldtype == TYPE_VECTOR) { + vec_push(code->globals, fld->code.fieldaddr+1); + vec_push(code->globals, fld->code.fieldaddr+2); + } + } + else + { + ir_value_code_setaddr(global, vec_size(code->globals)); + vec_push(code->globals, 0); + if (global->fieldtype == TYPE_VECTOR) { + vec_push(code->globals, 0); + vec_push(code->globals, 0); + } + } + if (global->code.globaladdr < 0) + return false; + return true; +} + +static bool gen_global_pointer(code_t *code, ir_value *global) +{ + if (global->hasvalue) + { + ir_value *target = global->constval.vpointer; + if (!target) { + irerror(global->context, "Invalid pointer constant: %s", global->name); + /* NULL pointers are pointing to the NULL constant, which also + * sits at address 0, but still has an ir_value for itself. + */ + return false; + } + + /* Here, relocations ARE possible - in fteqcc-enhanced-qc: + * void() foo; <- proto + * void() *fooptr = &foo; + * void() foo = { code } + */ + if (!target->code.globaladdr) { + /* FIXME: Check for the constant nullptr ir_value! + * because then code.globaladdr being 0 is valid. + */ + irerror(global->context, "FIXME: Relocation support"); + return false; + } + + ir_value_code_setaddr(global, vec_size(code->globals)); + vec_push(code->globals, target->code.globaladdr); + } + else + { + ir_value_code_setaddr(global, vec_size(code->globals)); + vec_push(code->globals, 0); + } + if (global->code.globaladdr < 0) + return false; + return true; +} + +static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *block) +{ + prog_section_statement_t stmt; + ir_instr *instr; + ir_block *target; + ir_block *ontrue; + ir_block *onfalse; + size_t stidx; + size_t i; + int j; + + block->generated = true; + block->code_start = vec_size(code->statements); + for (i = 0; i < vec_size(block->instr); ++i) + { + instr = block->instr[i]; + + if (instr->opcode == VINSTR_PHI) { + irerror(block->context, "cannot generate virtual instruction (phi)"); + return false; + } + + if (instr->opcode == VINSTR_JUMP) { + target = instr->bops[0]; + /* for uncoditional jumps, if the target hasn't been generated + * yet, we generate them right here. + */ + if (!target->generated) + return gen_blocks_recursive(code, func, target); + + /* otherwise we generate a jump instruction */ + stmt.opcode = INSTR_GOTO; + stmt.o1.s1 = (target->code_start) - vec_size(code->statements); + stmt.o2.s1 = 0; + stmt.o3.s1 = 0; + if (stmt.o1.s1 != 1) + code_push_statement(code, &stmt, instr->context); + + /* no further instructions can be in this block */ + return true; + } + + if (instr->opcode == VINSTR_BITXOR) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context); + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); + code_push_statement(code, &stmt, instr->context); + stmt.opcode = INSTR_SUB_F; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); + stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_BITAND_V) { + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context); + ++stmt.o1.s1; + ++stmt.o2.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context); + ++stmt.o1.s1; + ++stmt.o2.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_BITOR_V) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context); + ++stmt.o1.s1; + ++stmt.o2.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context); + ++stmt.o1.s1; + ++stmt.o2.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_BITXOR_V) { + for (j = 0; j < 3; ++j) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j; + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j; + code_push_statement(code, &stmt, instr->context); + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j; + stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j; + code_push_statement(code, &stmt, instr->context); + } + stmt.opcode = INSTR_SUB_V; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); + stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_BITAND_VF) { + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context); + ++stmt.o1.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context); + ++stmt.o1.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_BITOR_VF) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context); + ++stmt.o1.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context); + ++stmt.o1.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_BITXOR_VF) { + for (j = 0; j < 3; ++j) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j; + code_push_statement(code, &stmt, instr->context); + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j; + code_push_statement(code, &stmt, instr->context); + } + stmt.opcode = INSTR_SUB_V; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); + stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_CROSS) { + stmt.opcode = INSTR_MUL_F; + for (j = 0; j < 3; ++j) { + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + (j + 1) % 3; + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + (j + 2) % 3; + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j; + code_push_statement(code, &stmt, instr->context); + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + (j + 2) % 3; + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + (j + 1) % 3; + stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j; + code_push_statement(code, &stmt, instr->context); + } + stmt.opcode = INSTR_SUB_V; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); + stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_COND) { + ontrue = instr->bops[0]; + onfalse = instr->bops[1]; + /* TODO: have the AST signal which block should + * come first: eg. optimize IFs without ELSE... + */ + + stmt.o1.u1 = ir_value_code_addr(instr->_ops[0]); + stmt.o2.u1 = 0; + stmt.o3.s1 = 0; + + if (ontrue->generated) { + stmt.opcode = INSTR_IF; + stmt.o2.s1 = (ontrue->code_start) - vec_size(code->statements); + if (stmt.o2.s1 != 1) + code_push_statement(code, &stmt, instr->context); + } + if (onfalse->generated) { + stmt.opcode = INSTR_IFNOT; + stmt.o2.s1 = (onfalse->code_start) - vec_size(code->statements); + if (stmt.o2.s1 != 1) + code_push_statement(code, &stmt, instr->context); + } + if (!ontrue->generated) { + if (onfalse->generated) + return gen_blocks_recursive(code, func, ontrue); + } + if (!onfalse->generated) { + if (ontrue->generated) + return gen_blocks_recursive(code, func, onfalse); + } + /* neither ontrue nor onfalse exist */ + stmt.opcode = INSTR_IFNOT; + if (!instr->likely) { + /* Honor the likelyhood hint */ + ir_block *tmp = onfalse; + stmt.opcode = INSTR_IF; + onfalse = ontrue; + ontrue = tmp; + } + stidx = vec_size(code->statements); + code_push_statement(code, &stmt, instr->context); + /* on false we jump, so add ontrue-path */ + if (!gen_blocks_recursive(code, func, ontrue)) + return false; + /* fixup the jump address */ + code->statements[stidx].o2.s1 = vec_size(code->statements) - stidx; + /* generate onfalse path */ + if (onfalse->generated) { + /* fixup the jump address */ + code->statements[stidx].o2.s1 = (onfalse->code_start) - (stidx); + if (stidx+2 == vec_size(code->statements) && code->statements[stidx].o2.s1 == 1) { + code->statements[stidx] = code->statements[stidx+1]; + if (code->statements[stidx].o1.s1 < 0) + code->statements[stidx].o1.s1++; + code_pop_statement(code); + } + stmt.opcode = vec_last(code->statements).opcode; + if (stmt.opcode == INSTR_GOTO || + stmt.opcode == INSTR_IF || + stmt.opcode == INSTR_IFNOT || + stmt.opcode == INSTR_RETURN || + stmt.opcode == INSTR_DONE) + { + /* no use jumping from here */ + return true; + } + /* may have been generated in the previous recursive call */ + stmt.opcode = INSTR_GOTO; + stmt.o1.s1 = (onfalse->code_start) - vec_size(code->statements); + stmt.o2.s1 = 0; + stmt.o3.s1 = 0; + if (stmt.o1.s1 != 1) + code_push_statement(code, &stmt, instr->context); + return true; + } + else if (stidx+2 == vec_size(code->statements) && code->statements[stidx].o2.s1 == 1) { + code->statements[stidx] = code->statements[stidx+1]; + if (code->statements[stidx].o1.s1 < 0) + code->statements[stidx].o1.s1++; + code_pop_statement(code); + } + /* if not, generate now */ + return gen_blocks_recursive(code, func, onfalse); + } + + if ( (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8) + || instr->opcode == VINSTR_NRCALL) + { + size_t p, first; + ir_value *retvalue; + + first = vec_size(instr->params); + if (first > 8) + first = 8; + for (p = 0; p < first; ++p) + { + ir_value *param = instr->params[p]; + if (param->callparam) + continue; + + stmt.opcode = INSTR_STORE_F; + stmt.o3.u1 = 0; + + if (param->vtype == TYPE_FIELD) + stmt.opcode = field_store_instr[param->fieldtype]; + else if (param->vtype == TYPE_NIL) + stmt.opcode = INSTR_STORE_V; + else + stmt.opcode = type_store_instr[param->vtype]; + stmt.o1.u1 = ir_value_code_addr(param); + stmt.o2.u1 = OFS_PARM0 + 3 * p; + + 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); + for (; p < first; ++p) + { + ir_builder *ir = func->owner; + ir_value *param = instr->params[p]; + ir_value *targetparam; + + if (param->callparam) + continue; + + if (p-8 >= vec_size(ir->extparams)) + ir_gen_extparam(ir); + + targetparam = ir->extparams[p-8]; + + stmt.opcode = INSTR_STORE_F; + stmt.o3.u1 = 0; + + if (param->vtype == TYPE_FIELD) + stmt.opcode = field_store_instr[param->fieldtype]; + else if (param->vtype == TYPE_NIL) + stmt.opcode = INSTR_STORE_V; + else + stmt.opcode = type_store_instr[param->vtype]; + stmt.o1.u1 = ir_value_code_addr(param); + stmt.o2.u1 = ir_value_code_addr(targetparam); + 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); + if (stmt.opcode > INSTR_CALL8) + stmt.opcode = INSTR_CALL8; + stmt.o1.u1 = ir_value_code_addr(instr->_ops[1]); + stmt.o2.u1 = 0; + stmt.o3.u1 = 0; + code_push_statement(code, &stmt, instr->context); + + retvalue = instr->_ops[0]; + if (retvalue && retvalue->store != store_return && + (retvalue->store == store_global || vec_size(retvalue->life))) + { + /* not to be kept in OFS_RETURN */ + if (retvalue->vtype == TYPE_FIELD && OPTS_FLAG(ADJUST_VECTOR_FIELDS)) + stmt.opcode = field_store_instr[retvalue->fieldtype]; + else + stmt.opcode = type_store_instr[retvalue->vtype]; + stmt.o1.u1 = OFS_RETURN; + stmt.o2.u1 = ir_value_code_addr(retvalue); + stmt.o3.u1 = 0; + code_push_statement(code, &stmt, instr->context); + } + continue; + } + + if (instr->opcode == INSTR_STATE) { + 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; + stmt.o1.u1 = 0; + stmt.o2.u1 = 0; + stmt.o3.u1 = 0; + + /* This is the general order of operands */ + if (instr->_ops[0]) + stmt.o3.u1 = ir_value_code_addr(instr->_ops[0]); + + if (instr->_ops[1]) + stmt.o1.u1 = ir_value_code_addr(instr->_ops[1]); + + if (instr->_ops[2]) + stmt.o2.u1 = ir_value_code_addr(instr->_ops[2]); + + if (stmt.opcode == INSTR_RETURN || stmt.opcode == INSTR_DONE) + { + stmt.o1.u1 = stmt.o3.u1; + stmt.o3.u1 = 0; + } + else if ((stmt.opcode >= INSTR_STORE_F && + stmt.opcode <= INSTR_STORE_FNC) || + (stmt.opcode >= INSTR_STOREP_F && + stmt.opcode <= INSTR_STOREP_FNC)) + { + /* 2-operand instructions with A -> B */ + stmt.o2.u1 = stmt.o3.u1; + stmt.o3.u1 = 0; + + /* tiny optimization, don't output + * STORE a, a + */ + if (stmt.o2.u1 == stmt.o1.u1 && + OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) + { + ++opts_optimizationcount[OPTIM_PEEPHOLE]; + continue; + } + } + code_push_statement(code, &stmt, instr->context); + } + return true; +} + +static bool gen_function_code(code_t *code, ir_function *self) +{ + ir_block *block; + prog_section_statement_t stmt, *retst; + + /* Starting from entry point, we generate blocks "as they come" + * for now. Dead blocks will not be translated obviously. + */ + if (!vec_size(self->blocks)) { + irerror(self->context, "Function '%s' declared without body.", self->name); + return false; + } + + block = self->blocks[0]; + if (block->generated) + return true; + + if (!gen_blocks_recursive(code, self, block)) { + irerror(self->context, "failed to generate blocks for '%s'", self->name); + return false; + } + + /* code_write and qcvm -disasm need to know that the function ends here */ + retst = &vec_last(code->statements); + if (OPTS_OPTIMIZATION(OPTIM_VOID_RETURN) && + self->outtype == TYPE_VOID && + retst->opcode == INSTR_RETURN && + !retst->o1.u1 && !retst->o2.u1 && !retst->o3.u1) + { + retst->opcode = INSTR_DONE; + ++opts_optimizationcount[OPTIM_VOID_RETURN]; + } else { + lex_ctx_t last; + + stmt.opcode = INSTR_DONE; + stmt.o1.u1 = 0; + stmt.o2.u1 = 0; + stmt.o3.u1 = 0; + last.line = vec_last(code->linenums); + last.column = vec_last(code->columnnums); + + code_push_statement(code, &stmt, last); + } + return true; +} + +static qcint_t ir_builder_filestring(ir_builder *ir, const char *filename) +{ + /* NOTE: filename pointers are copied, we never strdup them, + * thus we can use pointer-comparison to find the string. + */ + size_t i; + qcint_t str; + + for (i = 0; i < vec_size(ir->filenames); ++i) { + if (ir->filenames[i] == filename) + return ir->filestrings[i]; + } + + str = code_genstring(ir->code, filename); + vec_push(ir->filenames, filename); + vec_push(ir->filestrings, str); + return str; +} + +static bool gen_global_function(ir_builder *ir, ir_value *global) +{ + prog_section_function_t fun; + ir_function *irfun; + + size_t i; + + if (!global->hasvalue || (!global->constval.vfunc)) + { + irerror(global->context, "Invalid state of function-global: not constant: %s", global->name); + return false; + } + + irfun = global->constval.vfunc; + + fun.name = global->code.name; + fun.file = ir_builder_filestring(ir, global->context.file); + fun.profile = 0; /* always 0 */ + fun.nargs = vec_size(irfun->params); + if (fun.nargs > 8) + fun.nargs = 8; + + for (i = 0;i < 8; ++i) { + if ((int32_t)i >= fun.nargs) + fun.argsize[i] = 0; + else + fun.argsize[i] = type_sizeof_[irfun->params[i]]; + } + + fun.firstlocal = 0; + fun.locals = irfun->allocated_locals; + + if (irfun->builtin) + fun.entry = irfun->builtin+1; + else { + irfun->code_function_def = vec_size(ir->code->functions); + fun.entry = vec_size(ir->code->statements); + } + + vec_push(ir->code->functions, fun); + return true; +} + +static ir_value* ir_gen_extparam_proto(ir_builder *ir) +{ + ir_value *global; + char name[128]; + + util_snprintf(name, sizeof(name), "EXTPARM#%i", (int)(vec_size(ir->extparam_protos))); + global = ir_value_var(name, store_global, TYPE_VECTOR); + + vec_push(ir->extparam_protos, global); + return global; +} + +static void ir_gen_extparam(ir_builder *ir) +{ + prog_section_def_t def; + ir_value *global; + + if (vec_size(ir->extparam_protos) < vec_size(ir->extparams)+1) + global = ir_gen_extparam_proto(ir); + else + global = ir->extparam_protos[vec_size(ir->extparams)]; + + def.name = code_genstring(ir->code, global->name); + def.type = TYPE_VECTOR; + def.offset = vec_size(ir->code->globals); + + vec_push(ir->code->defs, def); + + ir_value_code_setaddr(global, def.offset); + + vec_push(ir->code->globals, 0); + vec_push(ir->code->globals, 0); + vec_push(ir->code->globals, 0); + + vec_push(ir->extparams, global); +} + +static bool gen_function_extparam_copy(code_t *code, ir_function *self) +{ + size_t i, ext, numparams; + + ir_builder *ir = self->owner; + ir_value *ep; + prog_section_statement_t stmt; + + numparams = vec_size(self->params); + if (!numparams) + return true; + + stmt.opcode = INSTR_STORE_F; + stmt.o3.s1 = 0; + for (i = 8; i < numparams; ++i) { + ext = i - 8; + if (ext >= vec_size(ir->extparams)) + ir_gen_extparam(ir); + + ep = ir->extparams[ext]; + + stmt.opcode = type_store_instr[self->locals[i]->vtype]; + if (self->locals[i]->vtype == TYPE_FIELD && + self->locals[i]->fieldtype == TYPE_VECTOR) + { + stmt.opcode = INSTR_STORE_V; + } + stmt.o1.u1 = ir_value_code_addr(ep); + stmt.o2.u1 = ir_value_code_addr(self->locals[i]); + code_push_statement(code, &stmt, self->context); + } + + return true; +} + +static bool gen_function_varargs_copy(code_t *code, ir_function *self) +{ + size_t i, ext, numparams, maxparams; + + ir_builder *ir = self->owner; + ir_value *ep; + prog_section_statement_t stmt; + + numparams = vec_size(self->params); + if (!numparams) + return true; + + stmt.opcode = INSTR_STORE_V; + stmt.o3.s1 = 0; + maxparams = numparams + self->max_varargs; + for (i = numparams; i < maxparams; ++i) { + if (i < 8) { + stmt.o1.u1 = OFS_PARM0 + 3*i; + stmt.o2.u1 = ir_value_code_addr(self->locals[i]); + code_push_statement(code, &stmt, self->context); + continue; + } + ext = i - 8; + while (ext >= vec_size(ir->extparams)) + ir_gen_extparam(ir); + + ep = ir->extparams[ext]; + + stmt.o1.u1 = ir_value_code_addr(ep); + stmt.o2.u1 = ir_value_code_addr(self->locals[i]); + code_push_statement(code, &stmt, self->context); + } + + return true; +} + +static bool gen_function_locals(ir_builder *ir, ir_value *global) +{ + prog_section_function_t *def; + ir_function *irfun; + size_t i; + uint32_t firstlocal, firstglobal; + + irfun = global->constval.vfunc; + def = ir->code->functions + irfun->code_function_def; + + if (OPTS_OPTION_BOOL(OPTION_G) || + !OPTS_OPTIMIZATION(OPTIM_OVERLAP_LOCALS) || + (irfun->flags & IR_FLAG_MASK_NO_OVERLAP)) + { + firstlocal = def->firstlocal = vec_size(ir->code->globals); + } else { + firstlocal = def->firstlocal = ir->first_common_local; + ++opts_optimizationcount[OPTIM_OVERLAP_LOCALS]; + } + + firstglobal = (OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS) ? ir->first_common_globaltemp : firstlocal); + + for (i = vec_size(ir->code->globals); i < firstlocal + irfun->allocated_locals; ++i) + vec_push(ir->code->globals, 0); + for (i = 0; i < vec_size(irfun->locals); ++i) { + ir_value *v = irfun->locals[i]; + if (v->locked || !OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS)) { + ir_value_code_setaddr(v, firstlocal + v->code.local); + if (!ir_builder_gen_global(ir, irfun->locals[i], true)) { + irerror(irfun->locals[i]->context, "failed to generate local %s", irfun->locals[i]->name); + return false; + } + } + else + ir_value_code_setaddr(v, firstglobal + v->code.local); + } + for (i = 0; i < vec_size(irfun->values); ++i) + { + ir_value *v = irfun->values[i]; + if (v->callparam) + continue; + if (v->locked) + ir_value_code_setaddr(v, firstlocal + v->code.local); + else + ir_value_code_setaddr(v, firstglobal + v->code.local); + } + return true; +} + +static bool gen_global_function_code(ir_builder *ir, ir_value *global) +{ + prog_section_function_t *fundef; + ir_function *irfun; + + (void)ir; + + irfun = global->constval.vfunc; + if (!irfun) { + if (global->cvq == CV_NONE) { + if (irwarning(global->context, WARN_IMPLICIT_FUNCTION_POINTER, + "function `%s` has no body and in QC implicitly becomes a function-pointer", + global->name)) + { + /* Not bailing out just now. If this happens a lot you don't want to have + * to rerun gmqcc for each such function. + */ + + /* return false; */ + } + } + /* this was a function pointer, don't generate code for those */ + return true; + } + + if (irfun->builtin) + return true; + + /* + * If there is no definition and the thing is eraseable, we can ignore + * outputting the function to begin with. + */ + if (global->flags & IR_FLAG_ERASABLE && irfun->code_function_def < 0) { + return true; + } + + if (irfun->code_function_def < 0) { + irerror(irfun->context, "`%s`: IR global wasn't generated, failed to access function-def", irfun->name); + return false; + } + fundef = &ir->code->functions[irfun->code_function_def]; + + fundef->entry = vec_size(ir->code->statements); + if (!gen_function_locals(ir, global)) { + irerror(irfun->context, "Failed to generate locals for function %s", irfun->name); + return false; + } + if (!gen_function_extparam_copy(ir->code, irfun)) { + irerror(irfun->context, "Failed to generate extparam-copy code for function %s", irfun->name); + return false; + } + if (irfun->max_varargs && !gen_function_varargs_copy(ir->code, irfun)) { + irerror(irfun->context, "Failed to generate vararg-copy code for function %s", irfun->name); + return false; + } + if (!gen_function_code(ir->code, irfun)) { + irerror(irfun->context, "Failed to generate code for function %s", irfun->name); + return false; + } + return true; +} + +static void gen_vector_defs(code_t *code, prog_section_def_t def, const char *name) +{ + char *component; + size_t len, i; + + if (!name || name[0] == '#' || OPTS_FLAG(SINGLE_VECTOR_DEFS)) + return; + + def.type = TYPE_FLOAT; + + len = strlen(name); + + component = (char*)mem_a(len+3); + memcpy(component, name, len); + len += 2; + component[len-0] = 0; + component[len-2] = '_'; + + component[len-1] = 'x'; + + for (i = 0; i < 3; ++i) { + def.name = code_genstring(code, component); + vec_push(code->defs, def); + def.offset++; + component[len-1]++; + } + + mem_d(component); +} + +static void gen_vector_fields(code_t *code, prog_section_field_t fld, const char *name) +{ + char *component; + size_t len, i; + + if (!name || OPTS_FLAG(SINGLE_VECTOR_DEFS)) + return; + + fld.type = TYPE_FLOAT; + + len = strlen(name); + + component = (char*)mem_a(len+3); + memcpy(component, name, len); + len += 2; + component[len-0] = 0; + component[len-2] = '_'; + + component[len-1] = 'x'; + + for (i = 0; i < 3; ++i) { + fld.name = code_genstring(code, component); + vec_push(code->fields, fld); + fld.offset++; + component[len-1]++; + } + + mem_d(component); +} + +static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool islocal) +{ + size_t i; + int32_t *iptr; + 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; + if (OPTS_OPTION_BOOL(OPTION_G) || !islocal) + { + pushdef = true; + + /* + * if we're eraseable and the function isn't referenced ignore outputting + * the function. + */ + if (global->flags & IR_FLAG_ERASABLE && vec_size(global->reads) == 0) { + return true; + } + + if (OPTS_OPTIMIZATION(OPTIM_STRIP_CONSTANT_NAMES) && + !(global->flags & IR_FLAG_INCLUDE_DEF) && + (global->name[0] == '#' || global->cvq == CV_CONST)) + { + pushdef = false; + } + + if (pushdef) { + if (global->name[0] == '#') { + if (!self->str_immediate) + self->str_immediate = code_genstring(self->code, "IMMEDIATE"); + def.name = global->code.name = self->str_immediate; + } + else + def.name = global->code.name = code_genstring(self->code, global->name); + } + else + def.name = 0; + if (islocal) { + def.offset = ir_value_code_addr(global); + vec_push(self->code->defs, def); + if (global->vtype == TYPE_VECTOR) + gen_vector_defs(self->code, def, global->name); + else if (global->vtype == TYPE_FIELD && global->fieldtype == TYPE_VECTOR) + gen_vector_defs(self->code, def, global->name); + return true; + } + } + if (islocal) + return true; + + switch (global->vtype) + { + case TYPE_VOID: + if (!strcmp(global->name, "end_sys_globals")) { + /* TODO: remember this point... all the defs before this one + * should be checksummed and added to progdefs.h when we generate it. + */ + } + else if (!strcmp(global->name, "end_sys_fields")) { + /* TODO: same as above but for entity-fields rather than globsl + */ + } + else if(irwarning(global->context, WARN_VOID_VARIABLES, "unrecognized variable of type void `%s`", + global->name)) + { + /* Not bailing out */ + /* return false; */ + } + /* I'd argue setting it to 0 is sufficient, but maybe some depend on knowing how far + * the system fields actually go? Though the engine knows this anyway... + * Maybe this could be an -foption + * fteqcc creates data for end_sys_* - of size 1, so let's do the same + */ + ir_value_code_setaddr(global, vec_size(self->code->globals)); + vec_push(self->code->globals, 0); + /* Add the def */ + if (pushdef) vec_push(self->code->defs, def); + return true; + case TYPE_POINTER: + if (pushdef) vec_push(self->code->defs, def); + return gen_global_pointer(self->code, global); + case TYPE_FIELD: + if (pushdef) { + vec_push(self->code->defs, def); + if (global->fieldtype == TYPE_VECTOR) + gen_vector_defs(self->code, def, global->name); + } + return gen_global_field(self->code, global); + case TYPE_ENTITY: + /* fall through */ + case TYPE_FLOAT: + { + ir_value_code_setaddr(global, vec_size(self->code->globals)); + if (global->hasvalue) { + iptr = (int32_t*)&global->constval.ivec[0]; + vec_push(self->code->globals, *iptr); + } else { + vec_push(self->code->globals, 0); + } + if (!islocal && global->cvq != CV_CONST) + def.type |= DEF_SAVEGLOBAL; + if (pushdef) vec_push(self->code->defs, def); + + return global->code.globaladdr >= 0; + } + case TYPE_STRING: + { + ir_value_code_setaddr(global, vec_size(self->code->globals)); + if (global->hasvalue) { + uint32_t load = code_genstring(self->code, global->constval.vstring); + vec_push(self->code->globals, load); + } else { + vec_push(self->code->globals, 0); + } + if (!islocal && global->cvq != CV_CONST) + def.type |= DEF_SAVEGLOBAL; + if (pushdef) vec_push(self->code->defs, def); + return global->code.globaladdr >= 0; + } + case TYPE_VECTOR: + { + size_t d; + ir_value_code_setaddr(global, vec_size(self->code->globals)); + if (global->hasvalue) { + iptr = (int32_t*)&global->constval.ivec[0]; + vec_push(self->code->globals, iptr[0]); + if (global->code.globaladdr < 0) + return false; + for (d = 1; d < type_sizeof_[global->vtype]; ++d) { + vec_push(self->code->globals, iptr[d]); + } + } else { + vec_push(self->code->globals, 0); + if (global->code.globaladdr < 0) + return false; + for (d = 1; d < type_sizeof_[global->vtype]; ++d) { + vec_push(self->code->globals, 0); + } + } + if (!islocal && global->cvq != CV_CONST) + def.type |= DEF_SAVEGLOBAL; + + if (pushdef) { + vec_push(self->code->defs, def); + def.type &= ~DEF_SAVEGLOBAL; + gen_vector_defs(self->code, def, global->name); + } + return global->code.globaladdr >= 0; + } + case TYPE_FUNCTION: + ir_value_code_setaddr(global, vec_size(self->code->globals)); + if (!global->hasvalue) { + vec_push(self->code->globals, 0); + if (global->code.globaladdr < 0) + return false; + } else { + vec_push(self->code->globals, vec_size(self->code->functions)); + if (!gen_global_function(self, global)) + return false; + } + if (!islocal && global->cvq != CV_CONST) + def.type |= DEF_SAVEGLOBAL; + if (pushdef) vec_push(self->code->defs, def); + return true; + case TYPE_VARIANT: + /* assume biggest type */ + ir_value_code_setaddr(global, vec_size(self->code->globals)); + vec_push(self->code->globals, 0); + for (i = 1; i < type_sizeof_[TYPE_VARIANT]; ++i) + vec_push(self->code->globals, 0); + return true; + default: + /* refuse to create 'void' type or any other fancy business. */ + irerror(global->context, "Invalid type for global variable `%s`: %s", + global->name, type_name[global->vtype]); + return false; + } +} + +static GMQCC_INLINE void ir_builder_prepare_field(code_t *code, ir_value *field) +{ + field->code.fieldaddr = code_alloc_field(code, type_sizeof_[field->fieldtype]); +} + +static bool ir_builder_gen_field(ir_builder *self, ir_value *field) +{ + prog_section_def_t def; + prog_section_field_t fld; + + (void)self; + + def.type = (uint16_t)field->vtype; + def.offset = (uint16_t)vec_size(self->code->globals); + + /* create a global named the same as the field */ + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) { + /* in our standard, the global gets a dot prefix */ + size_t len = strlen(field->name); + char name[1024]; + + /* we really don't want to have to allocate this, and 1024 + * bytes is more than enough for a variable/field name + */ + if (len+2 >= sizeof(name)) { + irerror(field->context, "invalid field name size: %u", (unsigned int)len); + return false; + } + + name[0] = '.'; + memcpy(name+1, field->name, len); /* no strncpy - we used strlen above */ + name[len+1] = 0; + + def.name = code_genstring(self->code, name); + fld.name = def.name + 1; /* we reuse that string table entry */ + } else { + /* in plain QC, there cannot be a global with the same name, + * and so we also name the global the same. + * FIXME: fteqcc should create a global as well + * check if it actually uses the same name. Probably does + */ + def.name = code_genstring(self->code, field->name); + fld.name = def.name; + } + + field->code.name = def.name; + + vec_push(self->code->defs, def); + + fld.type = field->fieldtype; + + if (fld.type == TYPE_VOID) { + irerror(field->context, "field is missing a type: %s - don't know its size", field->name); + return false; + } + + fld.offset = field->code.fieldaddr; + + vec_push(self->code->fields, fld); + + ir_value_code_setaddr(field, vec_size(self->code->globals)); + vec_push(self->code->globals, fld.offset); + if (fld.type == TYPE_VECTOR) { + vec_push(self->code->globals, fld.offset+1); + vec_push(self->code->globals, fld.offset+2); + } + + if (field->fieldtype == TYPE_VECTOR) { + gen_vector_defs (self->code, def, field->name); + gen_vector_fields(self->code, fld, field->name); + } + + 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]); + } + + for (i = 0; i < vec_size(self->globals); ++i) + { + if (!ir_builder_gen_global(self, self->globals[i], false)) { + return false; + } + if (self->globals[i]->vtype == TYPE_FUNCTION) { + ir_function *func = self->globals[i]->constval.vfunc; + if (func && self->max_locals < func->allocated_locals && + !(func->flags & IR_FLAG_MASK_NO_OVERLAP)) + { + self->max_locals = func->allocated_locals; + } + if (func && self->max_globaltemps < func->globaltemps) + self->max_globaltemps = func->globaltemps; + } + } + + for (i = 0; i < vec_size(self->fields); ++i) + { + if (!ir_builder_gen_field(self, self->fields[i])) { + return false; + } + } + + /* generate nil */ + ir_value_code_setaddr(self->nil, vec_size(self->code->globals)); + vec_push(self->code->globals, 0); + vec_push(self->code->globals, 0); + vec_push(self->code->globals, 0); + + /* generate virtual-instruction temps */ + for (i = 0; i < IR_MAX_VINSTR_TEMPS; ++i) { + ir_value_code_setaddr(self->vinstr_temp[i], vec_size(self->code->globals)); + vec_push(self->code->globals, 0); + vec_push(self->code->globals, 0); + vec_push(self->code->globals, 0); + } + + /* generate global temps */ + self->first_common_globaltemp = vec_size(self->code->globals); + for (i = 0; i < self->max_globaltemps; ++i) { + vec_push(self->code->globals, 0); + } + /* generate common locals */ + self->first_common_local = vec_size(self->code->globals); + for (i = 0; i < self->max_locals; ++i) { + vec_push(self->code->globals, 0); + } + + /* generate function code */ + for (i = 0; i < vec_size(self->globals); ++i) + { + if (self->globals[i]->vtype == TYPE_FUNCTION) { + if (!gen_global_function_code(self, self->globals[i])) { + return false; + } + } + } + + 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 (%u). Bailing out.", (unsigned int)vec_size(self->code->globals)); + return false; + } + + /* DP errors if the last instruction is not an INSTR_DONE. */ + if (vec_last(self->code->statements).opcode != INSTR_DONE) + { + lex_ctx_t last; + + stmt.opcode = INSTR_DONE; + stmt.o1.u1 = 0; + stmt.o2.u1 = 0; + stmt.o3.u1 = 0; + last.line = vec_last(self->code->linenums); + last.column = vec_last(self->code->columnnums); + + code_push_statement(self->code, &stmt, last); + } + + if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) + return true; + + if (vec_size(self->code->statements) != vec_size(self->code->linenums)) { + con_err("Linecounter wrong: %lu != %lu\n", + (unsigned long)vec_size(self->code->statements), + (unsigned long)vec_size(self->code->linenums)); + } else if (OPTS_FLAG(LNO)) { + char *dot; + size_t filelen = strlen(filename); + + memcpy(vec_add(lnofile, filelen+1), filename, filelen+1); + dot = strrchr(lnofile, '.'); + if (!dot) { + vec_pop(lnofile); + } else { + vec_shrinkto(lnofile, dot - lnofile); + } + memcpy(vec_add(lnofile, 5), ".lno", 5); + } + + if (!code_write(self->code, filename, lnofile)) { + vec_free(lnofile); + return false; + } + + vec_free(lnofile); + return true; +} + +/*********************************************************************** + *IR DEBUG Dump functions... + */ + +#define IND_BUFSZ 1024 + +static const char *qc_opname(int op) +{ + if (op < 0) return ""; + if (op < VINSTR_END) + return util_instr_str[op]; + switch (op) { + case VINSTR_END: return "END"; + case VINSTR_PHI: return "PHI"; + case VINSTR_JUMP: return "JUMP"; + case VINSTR_COND: return "COND"; + case VINSTR_BITXOR: return "BITXOR"; + case VINSTR_BITAND_V: return "BITAND_V"; + case VINSTR_BITOR_V: return "BITOR_V"; + case VINSTR_BITXOR_V: return "BITXOR_V"; + case VINSTR_BITAND_VF: return "BITAND_VF"; + case VINSTR_BITOR_VF: return "BITOR_VF"; + case VINSTR_BITXOR_VF: return "BITXOR_VF"; + case VINSTR_CROSS: return "CROSS"; + case VINSTR_NEG_F: return "NEG_F"; + case VINSTR_NEG_V: return "NEG_V"; + default: return ""; + } +} + +void ir_builder_dump(ir_builder *b, int (*oprintf)(const char*, ...)) +{ + size_t i; + char indent[IND_BUFSZ]; + indent[0] = '\t'; + indent[1] = 0; + + oprintf("module %s\n", b->name); + for (i = 0; i < vec_size(b->globals); ++i) + { + oprintf("global "); + if (b->globals[i]->hasvalue) + oprintf("%s = ", b->globals[i]->name); + ir_value_dump(b->globals[i], oprintf); + oprintf("\n"); + } + for (i = 0; i < vec_size(b->functions); ++i) + ir_function_dump(b->functions[i], indent, oprintf); + oprintf("endmodule %s\n", b->name); +} + +static const char *storenames[] = { + "[global]", "[local]", "[param]", "[value]", "[return]" +}; + +void ir_function_dump(ir_function *f, char *ind, + int (*oprintf)(const char*, ...)) +{ + size_t i; + if (f->builtin != 0) { + oprintf("%sfunction %s = builtin %i\n", ind, f->name, -f->builtin); + return; + } + oprintf("%sfunction %s\n", ind, f->name); + util_strncat(ind, "\t", IND_BUFSZ-1); + if (vec_size(f->locals)) + { + oprintf("%s%i locals:\n", ind, (int)vec_size(f->locals)); + for (i = 0; i < vec_size(f->locals); ++i) { + oprintf("%s\t", ind); + ir_value_dump(f->locals[i], oprintf); + oprintf("\n"); + } + } + oprintf("%sliferanges:\n", ind); + for (i = 0; i < vec_size(f->locals); ++i) { + const char *attr = ""; + size_t l, m; + ir_value *v = f->locals[i]; + if (v->unique_life && v->locked) + attr = "unique,locked "; + else if (v->unique_life) + attr = "unique "; + else if (v->locked) + attr = "locked "; + oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->name, type_name[v->vtype], + storenames[v->store], + attr, (v->callparam ? "callparam " : ""), + (int)v->code.local); + if (!v->life) + oprintf("[null]"); + for (l = 0; l < vec_size(v->life); ++l) { + oprintf("[%i,%i] ", v->life[l].start, v->life[l].end); + } + oprintf("\n"); + for (m = 0; m < 3; ++m) { + ir_value *vm = v->members[m]; + if (!vm) + continue; + oprintf("%s\t%s: @%i ", ind, vm->name, (int)vm->code.local); + for (l = 0; l < vec_size(vm->life); ++l) { + oprintf("[%i,%i] ", vm->life[l].start, vm->life[l].end); + } + oprintf("\n"); + } + } + for (i = 0; i < vec_size(f->values); ++i) { + const char *attr = ""; + size_t l, m; + ir_value *v = f->values[i]; + if (v->unique_life && v->locked) + attr = "unique,locked "; + else if (v->unique_life) + attr = "unique "; + else if (v->locked) + attr = "locked "; + oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->name, type_name[v->vtype], + storenames[v->store], + attr, (v->callparam ? "callparam " : ""), + (int)v->code.local); + if (!v->life) + oprintf("[null]"); + for (l = 0; l < vec_size(v->life); ++l) { + oprintf("[%i,%i] ", v->life[l].start, v->life[l].end); + } + oprintf("\n"); + for (m = 0; m < 3; ++m) { + ir_value *vm = v->members[m]; + if (!vm) + continue; + if (vm->unique_life && vm->locked) + attr = "unique,locked "; + else if (vm->unique_life) + attr = "unique "; + else if (vm->locked) + attr = "locked "; + oprintf("%s\t%s: %s@%i ", ind, vm->name, attr, (int)vm->code.local); + for (l = 0; l < vec_size(vm->life); ++l) { + oprintf("[%i,%i] ", vm->life[l].start, vm->life[l].end); + } + oprintf("\n"); + } + } + if (vec_size(f->blocks)) + { + oprintf("%slife passes: %i\n", ind, (int)f->run_id); + for (i = 0; i < vec_size(f->blocks); ++i) { + ir_block_dump(f->blocks[i], ind, oprintf); + } + + } + ind[strlen(ind)-1] = 0; + oprintf("%sendfunction %s\n", ind, f->name); +} + +void ir_block_dump(ir_block* b, char *ind, + int (*oprintf)(const char*, ...)) +{ + size_t i; + oprintf("%s:%s\n", ind, b->label); + util_strncat(ind, "\t", IND_BUFSZ-1); + + if (b->instr && b->instr[0]) + oprintf("%s (%i) [entry]\n", ind, (int)(b->instr[0]->eid-1)); + for (i = 0; i < vec_size(b->instr); ++i) + ir_instr_dump(b->instr[i], ind, oprintf); + ind[strlen(ind)-1] = 0; +} + +static void dump_phi(ir_instr *in, int (*oprintf)(const char*, ...)) +{ + size_t i; + oprintf("%s <- phi ", in->_ops[0]->name); + for (i = 0; i < vec_size(in->phi); ++i) + { + oprintf("([%s] : %s) ", in->phi[i].from->label, + in->phi[i].value->name); + } + oprintf("\n"); +} + +void ir_instr_dump(ir_instr *in, char *ind, + int (*oprintf)(const char*, ...)) +{ + size_t i; + const char *comma = NULL; + + oprintf("%s (%i) ", ind, (int)in->eid); + + if (in->opcode == VINSTR_PHI) { + dump_phi(in, oprintf); + return; + } + + util_strncat(ind, "\t", IND_BUFSZ-1); + + if (in->_ops[0] && (in->_ops[1] || in->_ops[2])) { + ir_value_dump(in->_ops[0], oprintf); + if (in->_ops[1] || in->_ops[2]) + oprintf(" <- "); + } + if (in->opcode == INSTR_CALL0 || in->opcode == VINSTR_NRCALL) { + oprintf("CALL%i\t", vec_size(in->params)); + } else + oprintf("%s\t", qc_opname(in->opcode)); + + if (in->_ops[0] && !(in->_ops[1] || in->_ops[2])) { + ir_value_dump(in->_ops[0], oprintf); + comma = ",\t"; + } + else + { + for (i = 1; i != 3; ++i) { + if (in->_ops[i]) { + if (comma) + oprintf(comma); + ir_value_dump(in->_ops[i], oprintf); + comma = ",\t"; + } + } + } + if (in->bops[0]) { + if (comma) + oprintf(comma); + oprintf("[%s]", in->bops[0]->label); + comma = ",\t"; + } + if (in->bops[1]) + oprintf("%s[%s]", comma, in->bops[1]->label); + if (vec_size(in->params)) { + oprintf("\tparams: "); + for (i = 0; i != vec_size(in->params); ++i) { + oprintf("%s, ", in->params[i]->name); + } + } + oprintf("\n"); + ind[strlen(ind)-1] = 0; +} + +static void ir_value_dump_string(const char *str, int (*oprintf)(const char*, ...)) +{ + oprintf("\""); + for (; *str; ++str) { + switch (*str) { + case '\n': oprintf("\\n"); break; + case '\r': oprintf("\\r"); break; + case '\t': oprintf("\\t"); break; + case '\v': oprintf("\\v"); break; + case '\f': oprintf("\\f"); break; + case '\b': oprintf("\\b"); break; + case '\a': oprintf("\\a"); break; + case '\\': oprintf("\\\\"); break; + case '"': oprintf("\\\""); break; + default: oprintf("%c", *str); break; + } + } + oprintf("\""); +} + +void ir_value_dump(ir_value* v, int (*oprintf)(const char*, ...)) +{ + if (v->hasvalue) { + switch (v->vtype) { + default: + case TYPE_VOID: + oprintf("(void)"); + break; + case TYPE_FUNCTION: + oprintf("fn:%s", v->name); + break; + case TYPE_FLOAT: + oprintf("%g", v->constval.vfloat); + break; + case TYPE_VECTOR: + oprintf("'%g %g %g'", + v->constval.vvec.x, + v->constval.vvec.y, + v->constval.vvec.z); + break; + case TYPE_ENTITY: + oprintf("(entity)"); + break; + case TYPE_STRING: + ir_value_dump_string(v->constval.vstring, oprintf); + break; +#if 0 + case TYPE_INTEGER: + oprintf("%i", v->constval.vint); + break; +#endif + case TYPE_POINTER: + oprintf("&%s", + v->constval.vpointer->name); + break; + } + } else { + oprintf("%s", v->name); + } +} + +void ir_value_dump_life(const ir_value *self, int (*oprintf)(const char*,...)) +{ + size_t i; + oprintf("Life of %12s:", self->name); + for (i = 0; i < vec_size(self->life); ++i) + { + oprintf(" + [%i, %i]\n", self->life[i].start, self->life[i].end); + } +} diff --git a/lexer.c b/lexer.c deleted file mode 100644 index 3dc6981..0000000 --- a/lexer.c +++ /dev/null @@ -1,1423 +0,0 @@ -#include -#include - -#include "gmqcc.h" -#include "lexer.h" - -/* - * List of Keywords - */ - -/* original */ -static const char *keywords_qc[] = { - "for", "do", "while", - "if", "else", - "local", - "return", - "const" -}; -/* For fte/gmgqcc */ -static const char *keywords_fg[] = { - "switch", "case", "default", - "struct", "union", - "break", "continue", - "typedef", - "goto", - - "__builtin_debug_printtype" -}; - -/* - * Lexer code - */ -static char* *lex_filenames; - -static void lexerror(lex_file *lex, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - if (lex) - con_vprintmsg(LVL_ERROR, lex->name, lex->sline, lex->column, "parse error", fmt, ap); - else - con_vprintmsg(LVL_ERROR, "", 0, 0, "parse error", fmt, ap); - va_end(ap); -} - -static bool lexwarn(lex_file *lex, int warntype, const char *fmt, ...) -{ - bool r; - lex_ctx_t ctx; - va_list ap; - - ctx.file = lex->name; - ctx.line = lex->sline; - ctx.column = lex->column; - - va_start(ap, fmt); - r = vcompile_warning(ctx, warntype, fmt, ap); - va_end(ap); - return r; -} - -static void lex_token_new(lex_file *lex) -{ - if (lex->tok.value) - vec_shrinkto(lex->tok.value, 0); - - lex->tok.constval.t = 0; - lex->tok.ctx.line = lex->sline; - lex->tok.ctx.file = lex->name; - lex->tok.ctx.column = lex->column; -} - -static void lex_ungetch(lex_file *lex, int ch); -static int lex_getch(lex_file *lex); - -lex_file* lex_open(const char *file) -{ - lex_file *lex; - FILE *in = fopen(file, "rb"); - uint32_t read; - - if (!in) { - lexerror(NULL, "open failed: '%s'\n", file); - return NULL; - } - - lex = (lex_file*)mem_a(sizeof(*lex)); - if (!lex) { - fclose(in); - lexerror(NULL, "out of memory\n"); - return NULL; - } - - memset(lex, 0, sizeof(*lex)); - - lex->file = in; - lex->name = util_strdup(file); - lex->line = 1; /* we start counting at 1 */ - lex->column = 0; - lex->peekpos = 0; - lex->eof = false; - - /* handle BOM */ - if ((read = (lex_getch(lex) << 16) | (lex_getch(lex) << 8) | lex_getch(lex)) != 0xEFBBBF) { - lex_ungetch(lex, (read & 0x0000FF)); - lex_ungetch(lex, (read & 0x00FF00) >> 8); - lex_ungetch(lex, (read & 0xFF0000) >> 16); - } else { - /* - * otherwise the lexer has advanced 3 bytes for the BOM, we need - * to set the column back to 0 - */ - lex->column = 0; - } - - vec_push(lex_filenames, lex->name); - return lex; -} - -lex_file* lex_open_string(const char *str, size_t len, const char *name) -{ - lex_file *lex; - - lex = (lex_file*)mem_a(sizeof(*lex)); - if (!lex) { - lexerror(NULL, "out of memory\n"); - return NULL; - } - - memset(lex, 0, sizeof(*lex)); - - lex->file = NULL; - lex->open_string = str; - lex->open_string_length = len; - lex->open_string_pos = 0; - - lex->name = util_strdup(name ? name : ""); - lex->line = 1; /* we start counting at 1 */ - lex->peekpos = 0; - lex->eof = false; - lex->column = 0; - - vec_push(lex_filenames, lex->name); - - return lex; -} - -void lex_cleanup(void) -{ - size_t i; - for (i = 0; i < vec_size(lex_filenames); ++i) - mem_d(lex_filenames[i]); - vec_free(lex_filenames); -} - -void lex_close(lex_file *lex) -{ - size_t i; - for (i = 0; i < vec_size(lex->frames); ++i) - mem_d(lex->frames[i].name); - vec_free(lex->frames); - - if (lex->modelname) - vec_free(lex->modelname); - - if (lex->file) - fclose(lex->file); - - vec_free(lex->tok.value); - - /* mem_d(lex->name); collected in lex_filenames */ - mem_d(lex); -} - - - -static int lex_fgetc(lex_file *lex) -{ - if (lex->file) { - lex->column++; - return fgetc(lex->file); - } - if (lex->open_string) { - if (lex->open_string_pos >= lex->open_string_length) - return EOF; - lex->column++; - return lex->open_string[lex->open_string_pos++]; - } - return EOF; -} - -/* Get or put-back data - * The following to functions do NOT understand what kind of data they - * are working on. - * The are merely wrapping get/put in order to count line numbers. - */ -static int lex_try_trigraph(lex_file *lex, int old) -{ - int c2, c3; - c2 = lex_fgetc(lex); - if (!lex->push_line && c2 == '\n') { - lex->line++; - lex->column = 0; - } - - if (c2 != '?') { - lex_ungetch(lex, c2); - return old; - } - - c3 = lex_fgetc(lex); - if (!lex->push_line && c3 == '\n') { - lex->line++; - lex->column = 0; - } - - switch (c3) { - case '=': return '#'; - case '/': return '\\'; - case '\'': return '^'; - case '(': return '['; - case ')': return ']'; - case '!': return '|'; - case '<': return '{'; - case '>': return '}'; - case '-': return '~'; - default: - lex_ungetch(lex, c3); - lex_ungetch(lex, c2); - return old; - } -} - -static int lex_try_digraph(lex_file *lex, int ch) -{ - int c2; - c2 = lex_fgetc(lex); - /* we just used fgetc() so count lines - * need to offset a \n the ungetch would recognize - */ - if (!lex->push_line && c2 == '\n') - lex->line++; - if (ch == '<' && c2 == ':') - return '['; - else if (ch == ':' && c2 == '>') - return ']'; - else if (ch == '<' && c2 == '%') - return '{'; - else if (ch == '%' && c2 == '>') - return '}'; - else if (ch == '%' && c2 == ':') - return '#'; - lex_ungetch(lex, c2); - return ch; -} - -static int lex_getch(lex_file *lex) -{ - int ch; - - if (lex->peekpos) { - lex->peekpos--; - if (!lex->push_line && lex->peek[lex->peekpos] == '\n') { - lex->line++; - lex->column = 0; - } - return lex->peek[lex->peekpos]; - } - - ch = lex_fgetc(lex); - if (!lex->push_line && ch == '\n') { - lex->line++; - lex->column = 0; - } - else if (ch == '?') - return lex_try_trigraph(lex, ch); - else if (!lex->flags.nodigraphs && (ch == '<' || ch == ':' || ch == '%')) - return lex_try_digraph(lex, ch); - return ch; -} - -static void lex_ungetch(lex_file *lex, int ch) -{ - lex->peek[lex->peekpos++] = ch; - lex->column--; - if (!lex->push_line && ch == '\n') { - lex->line--; - lex->column = 0; - } -} - -/* classify characters - * some additions to the is*() functions of ctype.h - */ - -/* Idents are alphanumberic, but they start with alpha or _ */ -static bool isident_start(int ch) -{ - return util_isalpha(ch) || ch == '_'; -} - -static bool isident(int ch) -{ - return isident_start(ch) || util_isdigit(ch); -} - -/* isxdigit_only is used when we already know it's not a digit - * and want to see if it's a hex digit anyway. - */ -static bool isxdigit_only(int ch) -{ - return (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); -} - -/* Append a character to the token buffer */ -static void lex_tokench(lex_file *lex, int ch) -{ - vec_push(lex->tok.value, ch); -} - -/* Append a trailing null-byte */ -static void lex_endtoken(lex_file *lex) -{ - vec_push(lex->tok.value, 0); - vec_shrinkby(lex->tok.value, 1); -} - -static bool lex_try_pragma(lex_file *lex) -{ - int ch; - char *pragma = NULL; - char *command = NULL; - char *param = NULL; - size_t line; - - if (lex->flags.preprocessing) - return false; - - line = lex->line; - - ch = lex_getch(lex); - if (ch != '#') { - lex_ungetch(lex, ch); - return false; - } - - for (ch = lex_getch(lex); vec_size(pragma) < 8 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex)) - vec_push(pragma, ch); - vec_push(pragma, 0); - - if (ch != ' ' || strcmp(pragma, "pragma")) { - lex_ungetch(lex, ch); - goto unroll; - } - - for (ch = lex_getch(lex); vec_size(command) < 32 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex)) - vec_push(command, ch); - vec_push(command, 0); - - if (ch != '(') { - lex_ungetch(lex, ch); - goto unroll; - } - - for (ch = lex_getch(lex); vec_size(param) < 1024 && ch != ')' && ch != '\n'; ch = lex_getch(lex)) - vec_push(param, ch); - vec_push(param, 0); - - if (ch != ')') { - lex_ungetch(lex, ch); - goto unroll; - } - - if (!strcmp(command, "push")) { - if (!strcmp(param, "line")) { - lex->push_line++; - if (lex->push_line == 1) - --line; - } - else - goto unroll; - } - else if (!strcmp(command, "pop")) { - if (!strcmp(param, "line")) { - if (lex->push_line) - lex->push_line--; - if (lex->push_line == 0) - --line; - } - else - goto unroll; - } - else if (!strcmp(command, "file")) { - lex->name = util_strdup(param); - vec_push(lex_filenames, lex->name); - } - else if (!strcmp(command, "line")) { - line = strtol(param, NULL, 0)-1; - } - else - goto unroll; - - lex->line = line; - while (ch != '\n' && ch != EOF) - ch = lex_getch(lex); - vec_free(command); - vec_free(param); - vec_free(pragma); - return true; - -unroll: - if (command) { - vec_pop(command); - while (vec_size(command)) { - lex_ungetch(lex, (unsigned char)vec_last(command)); - vec_pop(command); - } - vec_free(command); - lex_ungetch(lex, ' '); - } - if (param) { - vec_pop(param); - while (vec_size(param)) { - lex_ungetch(lex, (unsigned char)vec_last(param)); - vec_pop(param); - } - vec_free(param); - lex_ungetch(lex, ' '); - } - if (pragma) { - vec_pop(pragma); - while (vec_size(pragma)) { - lex_ungetch(lex, (unsigned char)vec_last(pragma)); - vec_pop(pragma); - } - vec_free(pragma); - } - lex_ungetch(lex, '#'); - - lex->line = line; - return false; -} - -/* Skip whitespace and comments and return the first - * non-white character. - * As this makes use of the above getch() ungetch() functions, - * we don't need to care at all about line numbering anymore. - * - * In theory, this function should only be used at the beginning - * of lexing, or when we *know* the next character is part of the token. - * Otherwise, if the parser throws an error, the linenumber may not be - * the line of the error, but the line of the next token AFTER the error. - * - * This is currently only problematic when using c-like string-continuation, - * since comments and whitespaces are allowed between 2 such strings. - * Example: -printf( "line one\n" -// A comment - "A continuation of the previous string" -// This line is skipped - , foo); - - * In this case, if the parse decides it didn't actually want a string, - * and uses lex->line to print an error, it will show the ', foo);' line's - * linenumber. - * - * On the other hand, the parser is supposed to remember the line of the next - * token's beginning. In this case we would want skipwhite() to be called - * AFTER reading a token, so that the parser, before reading the NEXT token, - * doesn't store teh *comment's* linenumber, but the actual token's linenumber. - * - * THIS SOLUTION - * here is to store the line of the first character after skipping - * the initial whitespace in lex->sline, this happens in lex_do. - */ -static int lex_skipwhite(lex_file *lex, bool hadwhite) -{ - int ch = 0; - bool haswhite = hadwhite; - - do - { - ch = lex_getch(lex); - while (ch != EOF && util_isspace(ch)) { - if (ch == '\n') { - if (lex_try_pragma(lex)) - continue; - } - if (lex->flags.preprocessing) { - if (ch == '\n') { - /* end-of-line */ - /* see if there was whitespace first */ - if (haswhite) { /* (vec_size(lex->tok.value)) { */ - lex_ungetch(lex, ch); - lex_endtoken(lex); - return TOKEN_WHITE; - } - /* otherwise return EOL */ - return TOKEN_EOL; - } - haswhite = true; - lex_tokench(lex, ch); - } - ch = lex_getch(lex); - } - - if (ch == '/') { - ch = lex_getch(lex); - if (ch == '/') - { - /* one line comment */ - ch = lex_getch(lex); - - if (lex->flags.preprocessing) { - haswhite = true; - lex_tokench(lex, ' '); - lex_tokench(lex, ' '); - } - - while (ch != EOF && ch != '\n') { - if (lex->flags.preprocessing) - lex_tokench(lex, ' '); /* ch); */ - ch = lex_getch(lex); - } - if (lex->flags.preprocessing) { - lex_ungetch(lex, '\n'); - lex_endtoken(lex); - return TOKEN_WHITE; - } - continue; - } - if (ch == '*') - { - /* multiline comment */ - if (lex->flags.preprocessing) { - haswhite = true; - lex_tokench(lex, ' '); - lex_tokench(lex, ' '); - } - - while (ch != EOF) - { - ch = lex_getch(lex); - if (ch == '*') { - ch = lex_getch(lex); - if (ch == '/') { - if (lex->flags.preprocessing) { - lex_tokench(lex, ' '); - lex_tokench(lex, ' '); - } - break; - } - lex_ungetch(lex, ch); - } - if (lex->flags.preprocessing) { - if (ch == '\n') - lex_tokench(lex, '\n'); - else - lex_tokench(lex, ' '); - } - } - ch = ' '; /* cause TRUE in the isspace check */ - continue; - } - /* Otherwise roll back to the slash and break out of the loop */ - lex_ungetch(lex, ch); - ch = '/'; - break; - } - } while (ch != EOF && util_isspace(ch)); - - if (haswhite) { - lex_endtoken(lex); - lex_ungetch(lex, ch); - return TOKEN_WHITE; - } - return ch; -} - -/* Get a token */ -static bool GMQCC_WARN lex_finish_ident(lex_file *lex) -{ - int ch; - - ch = lex_getch(lex); - while (ch != EOF && isident(ch)) - { - lex_tokench(lex, ch); - ch = lex_getch(lex); - } - - /* last ch was not an ident ch: */ - lex_ungetch(lex, ch); - - return true; -} - -/* read one ident for the frame list */ -static int lex_parse_frame(lex_file *lex) -{ - int ch; - - lex_token_new(lex); - - ch = lex_getch(lex); - while (ch != EOF && ch != '\n' && util_isspace(ch)) - ch = lex_getch(lex); - - if (ch == '\n') - return 1; - - if (!isident_start(ch)) { - lexerror(lex, "invalid framename, must start with one of a-z or _, got %c", ch); - return -1; - } - - lex_tokench(lex, ch); - if (!lex_finish_ident(lex)) - return -1; - lex_endtoken(lex); - return 0; -} - -/* read a list of $frames */ -static bool lex_finish_frames(lex_file *lex) -{ - do { - size_t i; - int rc; - frame_macro m; - - rc = lex_parse_frame(lex); - if (rc > 0) /* end of line */ - return true; - if (rc < 0) /* error */ - return false; - - for (i = 0; i < vec_size(lex->frames); ++i) { - if (!strcmp(lex->tok.value, lex->frames[i].name)) { - lex->frames[i].value = lex->framevalue++; - if (lexwarn(lex, WARN_FRAME_MACROS, "duplicate frame macro defined: `%s`", lex->tok.value)) - return false; - break; - } - } - if (i < vec_size(lex->frames)) - continue; - - m.value = lex->framevalue++; - m.name = util_strdup(lex->tok.value); - vec_shrinkto(lex->tok.value, 0); - vec_push(lex->frames, m); - } while (true); - - return false; -} - -static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote) -{ - utf8ch_t chr = 0; - int ch = 0, texttype = 0; - int nextch; - bool hex; - bool oct; - char u8buf[8]; /* way more than enough */ - int u8len, uc; - - while (ch != EOF) - { - ch = lex_getch(lex); - if (ch == quote) - return TOKEN_STRINGCONST; - - if (lex->flags.preprocessing && ch == '\\') { - lex_tokench(lex, ch); - ch = lex_getch(lex); - if (ch == EOF) { - lexerror(lex, "unexpected end of file"); - lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */ - return (lex->tok.ttype = TOKEN_ERROR); - } - lex_tokench(lex, ch); - } - else if (ch == '\\') { - ch = lex_getch(lex); - if (ch == EOF) { - lexerror(lex, "unexpected end of file"); - lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */ - return (lex->tok.ttype = TOKEN_ERROR); - } - - switch (ch) { - case '\\': break; - case '\'': break; - case '"': 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 */ - ch = 0; - nextch = lex_getch(lex); - if (nextch >= '0' && nextch <= '9') - ch += nextch - '0'; - else if (nextch >= 'a' && nextch <= 'f') - ch += nextch - 'a' + 10; - else if (nextch >= 'A' && nextch <= 'F') - ch += nextch - 'A' + 10; - else { - lexerror(lex, "bad character code"); - lex_ungetch(lex, nextch); - return (lex->tok.ttype = TOKEN_ERROR); - } - - ch *= 0x10; - nextch = lex_getch(lex); - if (nextch >= '0' && nextch <= '9') - ch += nextch - '0'; - else if (nextch >= 'a' && nextch <= 'f') - ch += nextch - 'a' + 10; - else if (nextch >= 'A' && nextch <= 'F') - ch += nextch - 'A' + 10; - else { - lexerror(lex, "bad character code"); - lex_ungetch(lex, nextch); - return (lex->tok.ttype = TOKEN_ERROR); - } - break; - - /* fteqcc support */ - case '0': case '1': case '2': case '3': - case '4': case '5': case '6': case '7': - case '8': case '9': - ch = 18 + ch - '0'; - break; - case '<': ch = 29; break; - case '-': ch = 30; break; - case '>': ch = 31; break; - case '[': ch = 16; break; - case ']': ch = 17; break; - case '{': - chr = 0; - nextch = lex_getch(lex); - hex = (nextch == 'x'); - oct = (nextch == '0'); - if (!hex && !oct) - lex_ungetch(lex, nextch); - for (nextch = lex_getch(lex); nextch != '}'; nextch = lex_getch(lex)) { - if (!hex && !oct) { - if (nextch >= '0' && nextch <= '9') - chr = chr * 10 + nextch - '0'; - else { - lexerror(lex, "bad character code"); - return (lex->tok.ttype = TOKEN_ERROR); - } - } else if (!oct) { - if (nextch >= '0' && nextch <= '9') - chr = chr * 0x10 + nextch - '0'; - else if (nextch >= 'a' && nextch <= 'f') - chr = chr * 0x10 + nextch - 'a' + 10; - else if (nextch >= 'A' && nextch <= 'F') - chr = chr * 0x10 + nextch - 'A' + 10; - else { - lexerror(lex, "bad character code"); - return (lex->tok.ttype = TOKEN_ERROR); - } - } else { - if (nextch >= '0' && nextch <= '9') - chr = chr * 8 + chr - '0'; - else { - lexerror(lex, "bad character code"); - return (lex->tok.ttype = TOKEN_ERROR); - } - } - if (chr > 0x10FFFF || (!OPTS_FLAG(UTF8) && chr > 255)) - { - lexerror(lex, "character code out of range"); - return (lex->tok.ttype = TOKEN_ERROR); - } - } - if (OPTS_FLAG(UTF8) && chr >= 128) { - u8len = utf8_from(u8buf, chr); - if (!u8len) - ch = 0; - else { - --u8len; - lex->column += u8len; - for (uc = 0; uc < u8len; ++uc) - lex_tokench(lex, u8buf[uc]); - /* - * the last character will be inserted with the tokench() call - * below the switch - */ - ch = u8buf[uc]; - } - } - else - ch = chr; - 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); - /* so we just add the character plus backslash no matter what it actually is */ - lex_tokench(lex, '\\'); - } - /* add the character finally */ - lex_tokench(lex, ch | texttype); - } - else - lex_tokench(lex, ch); - } - lexerror(lex, "unexpected end of file within string constant"); - lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */ - return (lex->tok.ttype = TOKEN_ERROR); -} - -static int GMQCC_WARN lex_finish_digit(lex_file *lex, int lastch) -{ - bool ishex = false; - - int ch = lastch; - - /* parse a number... */ - if (ch == '.') - lex->tok.ttype = TOKEN_FLOATCONST; - else - lex->tok.ttype = TOKEN_INTCONST; - - lex_tokench(lex, ch); - - ch = lex_getch(lex); - if (ch != '.' && !util_isdigit(ch)) - { - if (lastch != '0' || ch != 'x') - { - /* end of the number or EOF */ - lex_ungetch(lex, ch); - lex_endtoken(lex); - - lex->tok.constval.i = lastch - '0'; - return lex->tok.ttype; - } - - ishex = true; - } - - /* EOF would have been caught above */ - - if (ch != '.') - { - lex_tokench(lex, ch); - ch = lex_getch(lex); - while (util_isdigit(ch) || (ishex && isxdigit_only(ch))) - { - lex_tokench(lex, ch); - ch = lex_getch(lex); - } - } - /* NOT else, '.' can come from above as well */ - if (lex->tok.ttype != TOKEN_FLOATCONST && ch == '.' && !ishex) - { - /* Allow floating comma in non-hex mode */ - lex->tok.ttype = TOKEN_FLOATCONST; - lex_tokench(lex, ch); - - /* continue digits-only */ - ch = lex_getch(lex); - while (util_isdigit(ch)) - { - lex_tokench(lex, ch); - ch = lex_getch(lex); - } - } - /* put back the last character */ - /* but do not put back the trailing 'f' or a float */ - if (lex->tok.ttype == TOKEN_FLOATCONST && ch == 'f') - ch = lex_getch(lex); - - /* generally we don't want words to follow numbers: */ - if (isident(ch)) { - lexerror(lex, "unexpected trailing characters after number"); - return (lex->tok.ttype = TOKEN_ERROR); - } - lex_ungetch(lex, ch); - - lex_endtoken(lex); - if (lex->tok.ttype == TOKEN_FLOATCONST) - lex->tok.constval.f = strtod(lex->tok.value, NULL); - else - lex->tok.constval.i = strtol(lex->tok.value, NULL, 0); - return lex->tok.ttype; -} - -int lex_do(lex_file *lex) -{ - int ch, nextch, thirdch; - bool hadwhite = false; - - lex_token_new(lex); - - while (true) { - ch = lex_skipwhite(lex, hadwhite); - hadwhite = true; - if (!lex->flags.mergelines || ch != '\\') - break; - ch = lex_getch(lex); - if (ch == '\r') - ch = lex_getch(lex); - if (ch != '\n') { - lex_ungetch(lex, ch); - ch = '\\'; - break; - } - /* we reached a linemerge */ - lex_tokench(lex, '\n'); - continue; - } - - if (lex->flags.preprocessing && (ch == TOKEN_WHITE || ch == TOKEN_EOL || ch == TOKEN_FATAL)) { - return (lex->tok.ttype = ch); - } - - lex->sline = lex->line; - lex->tok.ctx.line = lex->sline; - lex->tok.ctx.file = lex->name; - - if (lex->eof) - return (lex->tok.ttype = TOKEN_FATAL); - - if (ch == EOF) { - lex->eof = true; - return (lex->tok.ttype = TOKEN_EOF); - } - - /* modelgen / spiritgen commands */ - if (ch == '$' && !lex->flags.preprocessing) { - const char *v; - size_t frame; - - ch = lex_getch(lex); - if (!isident_start(ch)) { - lexerror(lex, "hanging '$' modelgen/spritegen command line"); - return lex_do(lex); - } - lex_tokench(lex, ch); - if (!lex_finish_ident(lex)) - return (lex->tok.ttype = TOKEN_ERROR); - lex_endtoken(lex); - /* skip the known commands */ - v = lex->tok.value; - - if (!strcmp(v, "frame") || !strcmp(v, "framesave")) - { - /* frame/framesave command works like an enum - * similar to fteqcc we handle this in the lexer. - * The reason for this is that it is sensitive to newlines, - * which the parser is unaware of - */ - if (!lex_finish_frames(lex)) - return (lex->tok.ttype = TOKEN_ERROR); - return lex_do(lex); - } - - if (!strcmp(v, "framevalue")) - { - ch = lex_getch(lex); - while (ch != EOF && util_isspace(ch) && ch != '\n') - ch = lex_getch(lex); - - if (!util_isdigit(ch)) { - lexerror(lex, "$framevalue requires an integer parameter"); - return lex_do(lex); - } - - lex_token_new(lex); - lex->tok.ttype = lex_finish_digit(lex, ch); - lex_endtoken(lex); - if (lex->tok.ttype != TOKEN_INTCONST) { - lexerror(lex, "$framevalue requires an integer parameter"); - return lex_do(lex); - } - lex->framevalue = lex->tok.constval.i; - return lex_do(lex); - } - - if (!strcmp(v, "framerestore")) - { - int rc; - - lex_token_new(lex); - - rc = lex_parse_frame(lex); - - if (rc > 0) { - lexerror(lex, "$framerestore requires a framename parameter"); - return lex_do(lex); - } - if (rc < 0) - return (lex->tok.ttype = TOKEN_FATAL); - - v = lex->tok.value; - for (frame = 0; frame < vec_size(lex->frames); ++frame) { - if (!strcmp(v, lex->frames[frame].name)) { - lex->framevalue = lex->frames[frame].value; - return lex_do(lex); - } - } - lexerror(lex, "unknown framename `%s`", v); - return lex_do(lex); - } - - if (!strcmp(v, "modelname")) - { - int rc; - - lex_token_new(lex); - - rc = lex_parse_frame(lex); - - if (rc > 0) { - lexerror(lex, "$modelname requires a parameter"); - return lex_do(lex); - } - if (rc < 0) - return (lex->tok.ttype = TOKEN_FATAL); - - if (lex->modelname) { - frame_macro m; - m.value = lex->framevalue; - m.name = lex->modelname; - lex->modelname = NULL; - vec_push(lex->frames, m); - } - lex->modelname = lex->tok.value; - lex->tok.value = NULL; - return lex_do(lex); - } - - if (!strcmp(v, "flush")) - { - size_t fi; - for (fi = 0; fi < vec_size(lex->frames); ++fi) - mem_d(lex->frames[fi].name); - vec_free(lex->frames); - /* skip line (fteqcc does it too) */ - ch = lex_getch(lex); - while (ch != EOF && ch != '\n') - ch = lex_getch(lex); - return lex_do(lex); - } - - if (!strcmp(v, "cd") || - !strcmp(v, "origin") || - !strcmp(v, "base") || - !strcmp(v, "flags") || - !strcmp(v, "scale") || - !strcmp(v, "skin")) - { - /* skip line */ - ch = lex_getch(lex); - while (ch != EOF && ch != '\n') - ch = lex_getch(lex); - return lex_do(lex); - } - - for (frame = 0; frame < vec_size(lex->frames); ++frame) { - if (!strcmp(v, lex->frames[frame].name)) { - lex->tok.constval.i = lex->frames[frame].value; - return (lex->tok.ttype = TOKEN_INTCONST); - } - } - - lexerror(lex, "invalid frame macro"); - return lex_do(lex); - } - - /* single-character tokens */ - switch (ch) - { - case '[': - nextch = lex_getch(lex); - if (nextch == '[') { - lex_tokench(lex, ch); - lex_tokench(lex, nextch); - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_ATTRIBUTE_OPEN); - } - lex_ungetch(lex, nextch); - /* FALL THROUGH */ - case '(': - case ':': - case '?': - lex_tokench(lex, ch); - lex_endtoken(lex); - if (lex->flags.noops) - return (lex->tok.ttype = ch); - else - return (lex->tok.ttype = TOKEN_OPERATOR); - - case ']': - if (lex->flags.noops) { - nextch = lex_getch(lex); - if (nextch == ']') { - lex_tokench(lex, ch); - lex_tokench(lex, nextch); - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_ATTRIBUTE_CLOSE); - } - lex_ungetch(lex, nextch); - } - /* FALL THROUGH */ - case ')': - case ';': - case '{': - case '}': - - case '#': - lex_tokench(lex, ch); - lex_endtoken(lex); - return (lex->tok.ttype = ch); - default: - break; - } - - if (ch == '.') { - nextch = lex_getch(lex); - /* digits starting with a dot */ - if (util_isdigit(nextch)) { - lex_ungetch(lex, nextch); - lex->tok.ttype = lex_finish_digit(lex, ch); - lex_endtoken(lex); - return lex->tok.ttype; - } - lex_ungetch(lex, nextch); - } - - if (lex->flags.noops) - { - /* Detect characters early which are normally - * operators OR PART of an operator. - */ - switch (ch) - { - case '*': - case '/': - case '<': - case '>': - case '=': - case '&': - case '|': - case '^': - case '~': - case ',': - case '!': - lex_tokench(lex, ch); - lex_endtoken(lex); - return (lex->tok.ttype = ch); - default: - break; - } - } - - if (ch == '.') - { - lex_tokench(lex, ch); - /* peak ahead once */ - nextch = lex_getch(lex); - if (nextch != '.') { - lex_ungetch(lex, nextch); - lex_endtoken(lex); - if (lex->flags.noops) - return (lex->tok.ttype = ch); - else - return (lex->tok.ttype = TOKEN_OPERATOR); - } - /* peak ahead again */ - nextch = lex_getch(lex); - if (nextch != '.') { - lex_ungetch(lex, nextch); - lex_ungetch(lex, '.'); - lex_endtoken(lex); - if (lex->flags.noops) - return (lex->tok.ttype = ch); - else - return (lex->tok.ttype = TOKEN_OPERATOR); - } - /* fill the token to be "..." */ - lex_tokench(lex, ch); - lex_tokench(lex, ch); - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_DOTS); - } - - if (ch == ',' || ch == '.') { - lex_tokench(lex, ch); - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_OPERATOR); - } - - if (ch == '+' || ch == '-' || /* ++, --, +=, -= and -> as well! */ - ch == '>' || ch == '<' || /* <<, >>, <=, >= and >< as well! */ - ch == '=' || ch == '!' || /* <=>, ==, != */ - ch == '&' || ch == '|' || /* &&, ||, &=, |= */ - ch == '~' || ch == '^' /* ~=, ~, ^ */ - ) { - lex_tokench(lex, ch); - nextch = lex_getch(lex); - - if ((nextch == '=' && ch != '<') || (nextch == '<' && ch == '>')) - lex_tokench(lex, nextch); - else if (nextch == ch && ch != '!') { - lex_tokench(lex, nextch); - if ((thirdch = lex_getch(lex)) == '=') - lex_tokench(lex, thirdch); - else - lex_ungetch(lex, thirdch); - } else if (ch == '<' && nextch == '=') { - lex_tokench(lex, nextch); - if ((thirdch = lex_getch(lex)) == '>') - lex_tokench(lex, thirdch); - else - lex_ungetch(lex, thirdch); - - } else if (ch == '-' && nextch == '>') { - lex_tokench(lex, nextch); - } else if (ch == '&' && nextch == '~') { - thirdch = lex_getch(lex); - if (thirdch != '=') { - lex_ungetch(lex, thirdch); - lex_ungetch(lex, nextch); - } - else { - lex_tokench(lex, nextch); - lex_tokench(lex, thirdch); - } - } - else if (lex->flags.preprocessing && - ch == '-' && util_isdigit(nextch)) - { - lex->tok.ttype = lex_finish_digit(lex, nextch); - if (lex->tok.ttype == TOKEN_INTCONST) - lex->tok.constval.i = -lex->tok.constval.i; - else - lex->tok.constval.f = -lex->tok.constval.f; - lex_endtoken(lex); - return lex->tok.ttype; - } else { - lex_ungetch(lex, nextch); - } - - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_OPERATOR); - } - - if (ch == '*' || ch == '/') /* *=, /= */ - { - lex_tokench(lex, ch); - - nextch = lex_getch(lex); - if (nextch == '=' || nextch == '*') { - lex_tokench(lex, nextch); - } else - lex_ungetch(lex, nextch); - - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_OPERATOR); - } - - if (ch == '%') { - lex_tokench(lex, ch); - lex_endtoken(lex); - return (lex->tok.ttype = TOKEN_OPERATOR); - } - - if (isident_start(ch)) - { - const char *v; - - lex_tokench(lex, ch); - if (!lex_finish_ident(lex)) { - /* error? */ - return (lex->tok.ttype = TOKEN_ERROR); - } - lex_endtoken(lex); - lex->tok.ttype = TOKEN_IDENT; - - v = lex->tok.value; - if (!strcmp(v, "void")) { - lex->tok.ttype = TOKEN_TYPENAME; - lex->tok.constval.t = TYPE_VOID; - } else if (!strcmp(v, "int")) { - lex->tok.ttype = TOKEN_TYPENAME; - lex->tok.constval.t = TYPE_INTEGER; - } else if (!strcmp(v, "float")) { - lex->tok.ttype = TOKEN_TYPENAME; - lex->tok.constval.t = TYPE_FLOAT; - } else if (!strcmp(v, "string")) { - lex->tok.ttype = TOKEN_TYPENAME; - lex->tok.constval.t = TYPE_STRING; - } else if (!strcmp(v, "entity")) { - lex->tok.ttype = TOKEN_TYPENAME; - lex->tok.constval.t = TYPE_ENTITY; - } 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) { - if (!strcmp(v, keywords_qc[kw])) - return (lex->tok.ttype = TOKEN_KEYWORD); - } - if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) { - for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_fg); ++kw) { - if (!strcmp(v, keywords_fg[kw])) - return (lex->tok.ttype = TOKEN_KEYWORD); - } - } - } - - return lex->tok.ttype; - } - - if (ch == '"') - { - lex->flags.nodigraphs = true; - if (lex->flags.preprocessing) - lex_tokench(lex, ch); - lex->tok.ttype = lex_finish_string(lex, '"'); - if (lex->flags.preprocessing) - lex_tokench(lex, ch); - while (!lex->flags.preprocessing && lex->tok.ttype == TOKEN_STRINGCONST) - { - /* Allow c style "string" "continuation" */ - ch = lex_skipwhite(lex, false); - if (ch != '"') { - lex_ungetch(lex, ch); - break; - } - - lex->tok.ttype = lex_finish_string(lex, '"'); - } - lex->flags.nodigraphs = false; - lex_endtoken(lex); - return lex->tok.ttype; - } - - if (ch == '\'') - { - /* we parse character constants like string, - * but return TOKEN_CHARCONST, or a vector type if it fits... - * Likewise actual unescaping has to be done by the parser. - * The difference is we don't allow 'char' 'continuation'. - */ - if (lex->flags.preprocessing) - lex_tokench(lex, ch); - lex->tok.ttype = lex_finish_string(lex, '\''); - if (lex->flags.preprocessing) - lex_tokench(lex, ch); - lex_endtoken(lex); - - lex->tok.ttype = TOKEN_CHARCONST; - - /* It's a vector if we can successfully scan 3 floats */ - if (util_sscanf(lex->tok.value, " %f %f %f ", - &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3) - - { - lex->tok.ttype = TOKEN_VECTORCONST; - } - else - { - if (!lex->flags.preprocessing && strlen(lex->tok.value) > 1) { - utf8ch_t u8char; - /* check for a valid utf8 character */ - if (!OPTS_FLAG(UTF8) || !utf8_to(&u8char, (const unsigned char *)lex->tok.value, 8)) { - if (lexwarn(lex, WARN_MULTIBYTE_CHARACTER, - ( OPTS_FLAG(UTF8) ? "invalid multibyte character sequence `%s`" - : "multibyte character: `%s`" ), - lex->tok.value)) - return (lex->tok.ttype = TOKEN_ERROR); - } - else - lex->tok.constval.i = u8char; - } - else - lex->tok.constval.i = lex->tok.value[0]; - } - - return lex->tok.ttype; - } - - if (util_isdigit(ch)) - { - lex->tok.ttype = lex_finish_digit(lex, ch); - lex_endtoken(lex); - return lex->tok.ttype; - } - - if (lex->flags.preprocessing) { - lex_tokench(lex, ch); - lex_endtoken(lex); - return (lex->tok.ttype = ch); - } - - lexerror(lex, "unknown token: `%c`", ch); - return (lex->tok.ttype = TOKEN_ERROR); -} diff --git a/lexer.cpp b/lexer.cpp new file mode 100644 index 0000000..3dc6981 --- /dev/null +++ b/lexer.cpp @@ -0,0 +1,1423 @@ +#include +#include + +#include "gmqcc.h" +#include "lexer.h" + +/* + * List of Keywords + */ + +/* original */ +static const char *keywords_qc[] = { + "for", "do", "while", + "if", "else", + "local", + "return", + "const" +}; +/* For fte/gmgqcc */ +static const char *keywords_fg[] = { + "switch", "case", "default", + "struct", "union", + "break", "continue", + "typedef", + "goto", + + "__builtin_debug_printtype" +}; + +/* + * Lexer code + */ +static char* *lex_filenames; + +static void lexerror(lex_file *lex, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (lex) + con_vprintmsg(LVL_ERROR, lex->name, lex->sline, lex->column, "parse error", fmt, ap); + else + con_vprintmsg(LVL_ERROR, "", 0, 0, "parse error", fmt, ap); + va_end(ap); +} + +static bool lexwarn(lex_file *lex, int warntype, const char *fmt, ...) +{ + bool r; + lex_ctx_t ctx; + va_list ap; + + ctx.file = lex->name; + ctx.line = lex->sline; + ctx.column = lex->column; + + va_start(ap, fmt); + r = vcompile_warning(ctx, warntype, fmt, ap); + va_end(ap); + return r; +} + +static void lex_token_new(lex_file *lex) +{ + if (lex->tok.value) + vec_shrinkto(lex->tok.value, 0); + + lex->tok.constval.t = 0; + lex->tok.ctx.line = lex->sline; + lex->tok.ctx.file = lex->name; + lex->tok.ctx.column = lex->column; +} + +static void lex_ungetch(lex_file *lex, int ch); +static int lex_getch(lex_file *lex); + +lex_file* lex_open(const char *file) +{ + lex_file *lex; + FILE *in = fopen(file, "rb"); + uint32_t read; + + if (!in) { + lexerror(NULL, "open failed: '%s'\n", file); + return NULL; + } + + lex = (lex_file*)mem_a(sizeof(*lex)); + if (!lex) { + fclose(in); + lexerror(NULL, "out of memory\n"); + return NULL; + } + + memset(lex, 0, sizeof(*lex)); + + lex->file = in; + lex->name = util_strdup(file); + lex->line = 1; /* we start counting at 1 */ + lex->column = 0; + lex->peekpos = 0; + lex->eof = false; + + /* handle BOM */ + if ((read = (lex_getch(lex) << 16) | (lex_getch(lex) << 8) | lex_getch(lex)) != 0xEFBBBF) { + lex_ungetch(lex, (read & 0x0000FF)); + lex_ungetch(lex, (read & 0x00FF00) >> 8); + lex_ungetch(lex, (read & 0xFF0000) >> 16); + } else { + /* + * otherwise the lexer has advanced 3 bytes for the BOM, we need + * to set the column back to 0 + */ + lex->column = 0; + } + + vec_push(lex_filenames, lex->name); + return lex; +} + +lex_file* lex_open_string(const char *str, size_t len, const char *name) +{ + lex_file *lex; + + lex = (lex_file*)mem_a(sizeof(*lex)); + if (!lex) { + lexerror(NULL, "out of memory\n"); + return NULL; + } + + memset(lex, 0, sizeof(*lex)); + + lex->file = NULL; + lex->open_string = str; + lex->open_string_length = len; + lex->open_string_pos = 0; + + lex->name = util_strdup(name ? name : ""); + lex->line = 1; /* we start counting at 1 */ + lex->peekpos = 0; + lex->eof = false; + lex->column = 0; + + vec_push(lex_filenames, lex->name); + + return lex; +} + +void lex_cleanup(void) +{ + size_t i; + for (i = 0; i < vec_size(lex_filenames); ++i) + mem_d(lex_filenames[i]); + vec_free(lex_filenames); +} + +void lex_close(lex_file *lex) +{ + size_t i; + for (i = 0; i < vec_size(lex->frames); ++i) + mem_d(lex->frames[i].name); + vec_free(lex->frames); + + if (lex->modelname) + vec_free(lex->modelname); + + if (lex->file) + fclose(lex->file); + + vec_free(lex->tok.value); + + /* mem_d(lex->name); collected in lex_filenames */ + mem_d(lex); +} + + + +static int lex_fgetc(lex_file *lex) +{ + if (lex->file) { + lex->column++; + return fgetc(lex->file); + } + if (lex->open_string) { + if (lex->open_string_pos >= lex->open_string_length) + return EOF; + lex->column++; + return lex->open_string[lex->open_string_pos++]; + } + return EOF; +} + +/* Get or put-back data + * The following to functions do NOT understand what kind of data they + * are working on. + * The are merely wrapping get/put in order to count line numbers. + */ +static int lex_try_trigraph(lex_file *lex, int old) +{ + int c2, c3; + c2 = lex_fgetc(lex); + if (!lex->push_line && c2 == '\n') { + lex->line++; + lex->column = 0; + } + + if (c2 != '?') { + lex_ungetch(lex, c2); + return old; + } + + c3 = lex_fgetc(lex); + if (!lex->push_line && c3 == '\n') { + lex->line++; + lex->column = 0; + } + + switch (c3) { + case '=': return '#'; + case '/': return '\\'; + case '\'': return '^'; + case '(': return '['; + case ')': return ']'; + case '!': return '|'; + case '<': return '{'; + case '>': return '}'; + case '-': return '~'; + default: + lex_ungetch(lex, c3); + lex_ungetch(lex, c2); + return old; + } +} + +static int lex_try_digraph(lex_file *lex, int ch) +{ + int c2; + c2 = lex_fgetc(lex); + /* we just used fgetc() so count lines + * need to offset a \n the ungetch would recognize + */ + if (!lex->push_line && c2 == '\n') + lex->line++; + if (ch == '<' && c2 == ':') + return '['; + else if (ch == ':' && c2 == '>') + return ']'; + else if (ch == '<' && c2 == '%') + return '{'; + else if (ch == '%' && c2 == '>') + return '}'; + else if (ch == '%' && c2 == ':') + return '#'; + lex_ungetch(lex, c2); + return ch; +} + +static int lex_getch(lex_file *lex) +{ + int ch; + + if (lex->peekpos) { + lex->peekpos--; + if (!lex->push_line && lex->peek[lex->peekpos] == '\n') { + lex->line++; + lex->column = 0; + } + return lex->peek[lex->peekpos]; + } + + ch = lex_fgetc(lex); + if (!lex->push_line && ch == '\n') { + lex->line++; + lex->column = 0; + } + else if (ch == '?') + return lex_try_trigraph(lex, ch); + else if (!lex->flags.nodigraphs && (ch == '<' || ch == ':' || ch == '%')) + return lex_try_digraph(lex, ch); + return ch; +} + +static void lex_ungetch(lex_file *lex, int ch) +{ + lex->peek[lex->peekpos++] = ch; + lex->column--; + if (!lex->push_line && ch == '\n') { + lex->line--; + lex->column = 0; + } +} + +/* classify characters + * some additions to the is*() functions of ctype.h + */ + +/* Idents are alphanumberic, but they start with alpha or _ */ +static bool isident_start(int ch) +{ + return util_isalpha(ch) || ch == '_'; +} + +static bool isident(int ch) +{ + return isident_start(ch) || util_isdigit(ch); +} + +/* isxdigit_only is used when we already know it's not a digit + * and want to see if it's a hex digit anyway. + */ +static bool isxdigit_only(int ch) +{ + return (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); +} + +/* Append a character to the token buffer */ +static void lex_tokench(lex_file *lex, int ch) +{ + vec_push(lex->tok.value, ch); +} + +/* Append a trailing null-byte */ +static void lex_endtoken(lex_file *lex) +{ + vec_push(lex->tok.value, 0); + vec_shrinkby(lex->tok.value, 1); +} + +static bool lex_try_pragma(lex_file *lex) +{ + int ch; + char *pragma = NULL; + char *command = NULL; + char *param = NULL; + size_t line; + + if (lex->flags.preprocessing) + return false; + + line = lex->line; + + ch = lex_getch(lex); + if (ch != '#') { + lex_ungetch(lex, ch); + return false; + } + + for (ch = lex_getch(lex); vec_size(pragma) < 8 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex)) + vec_push(pragma, ch); + vec_push(pragma, 0); + + if (ch != ' ' || strcmp(pragma, "pragma")) { + lex_ungetch(lex, ch); + goto unroll; + } + + for (ch = lex_getch(lex); vec_size(command) < 32 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex)) + vec_push(command, ch); + vec_push(command, 0); + + if (ch != '(') { + lex_ungetch(lex, ch); + goto unroll; + } + + for (ch = lex_getch(lex); vec_size(param) < 1024 && ch != ')' && ch != '\n'; ch = lex_getch(lex)) + vec_push(param, ch); + vec_push(param, 0); + + if (ch != ')') { + lex_ungetch(lex, ch); + goto unroll; + } + + if (!strcmp(command, "push")) { + if (!strcmp(param, "line")) { + lex->push_line++; + if (lex->push_line == 1) + --line; + } + else + goto unroll; + } + else if (!strcmp(command, "pop")) { + if (!strcmp(param, "line")) { + if (lex->push_line) + lex->push_line--; + if (lex->push_line == 0) + --line; + } + else + goto unroll; + } + else if (!strcmp(command, "file")) { + lex->name = util_strdup(param); + vec_push(lex_filenames, lex->name); + } + else if (!strcmp(command, "line")) { + line = strtol(param, NULL, 0)-1; + } + else + goto unroll; + + lex->line = line; + while (ch != '\n' && ch != EOF) + ch = lex_getch(lex); + vec_free(command); + vec_free(param); + vec_free(pragma); + return true; + +unroll: + if (command) { + vec_pop(command); + while (vec_size(command)) { + lex_ungetch(lex, (unsigned char)vec_last(command)); + vec_pop(command); + } + vec_free(command); + lex_ungetch(lex, ' '); + } + if (param) { + vec_pop(param); + while (vec_size(param)) { + lex_ungetch(lex, (unsigned char)vec_last(param)); + vec_pop(param); + } + vec_free(param); + lex_ungetch(lex, ' '); + } + if (pragma) { + vec_pop(pragma); + while (vec_size(pragma)) { + lex_ungetch(lex, (unsigned char)vec_last(pragma)); + vec_pop(pragma); + } + vec_free(pragma); + } + lex_ungetch(lex, '#'); + + lex->line = line; + return false; +} + +/* Skip whitespace and comments and return the first + * non-white character. + * As this makes use of the above getch() ungetch() functions, + * we don't need to care at all about line numbering anymore. + * + * In theory, this function should only be used at the beginning + * of lexing, or when we *know* the next character is part of the token. + * Otherwise, if the parser throws an error, the linenumber may not be + * the line of the error, but the line of the next token AFTER the error. + * + * This is currently only problematic when using c-like string-continuation, + * since comments and whitespaces are allowed between 2 such strings. + * Example: +printf( "line one\n" +// A comment + "A continuation of the previous string" +// This line is skipped + , foo); + + * In this case, if the parse decides it didn't actually want a string, + * and uses lex->line to print an error, it will show the ', foo);' line's + * linenumber. + * + * On the other hand, the parser is supposed to remember the line of the next + * token's beginning. In this case we would want skipwhite() to be called + * AFTER reading a token, so that the parser, before reading the NEXT token, + * doesn't store teh *comment's* linenumber, but the actual token's linenumber. + * + * THIS SOLUTION + * here is to store the line of the first character after skipping + * the initial whitespace in lex->sline, this happens in lex_do. + */ +static int lex_skipwhite(lex_file *lex, bool hadwhite) +{ + int ch = 0; + bool haswhite = hadwhite; + + do + { + ch = lex_getch(lex); + while (ch != EOF && util_isspace(ch)) { + if (ch == '\n') { + if (lex_try_pragma(lex)) + continue; + } + if (lex->flags.preprocessing) { + if (ch == '\n') { + /* end-of-line */ + /* see if there was whitespace first */ + if (haswhite) { /* (vec_size(lex->tok.value)) { */ + lex_ungetch(lex, ch); + lex_endtoken(lex); + return TOKEN_WHITE; + } + /* otherwise return EOL */ + return TOKEN_EOL; + } + haswhite = true; + lex_tokench(lex, ch); + } + ch = lex_getch(lex); + } + + if (ch == '/') { + ch = lex_getch(lex); + if (ch == '/') + { + /* one line comment */ + ch = lex_getch(lex); + + if (lex->flags.preprocessing) { + haswhite = true; + lex_tokench(lex, ' '); + lex_tokench(lex, ' '); + } + + while (ch != EOF && ch != '\n') { + if (lex->flags.preprocessing) + lex_tokench(lex, ' '); /* ch); */ + ch = lex_getch(lex); + } + if (lex->flags.preprocessing) { + lex_ungetch(lex, '\n'); + lex_endtoken(lex); + return TOKEN_WHITE; + } + continue; + } + if (ch == '*') + { + /* multiline comment */ + if (lex->flags.preprocessing) { + haswhite = true; + lex_tokench(lex, ' '); + lex_tokench(lex, ' '); + } + + while (ch != EOF) + { + ch = lex_getch(lex); + if (ch == '*') { + ch = lex_getch(lex); + if (ch == '/') { + if (lex->flags.preprocessing) { + lex_tokench(lex, ' '); + lex_tokench(lex, ' '); + } + break; + } + lex_ungetch(lex, ch); + } + if (lex->flags.preprocessing) { + if (ch == '\n') + lex_tokench(lex, '\n'); + else + lex_tokench(lex, ' '); + } + } + ch = ' '; /* cause TRUE in the isspace check */ + continue; + } + /* Otherwise roll back to the slash and break out of the loop */ + lex_ungetch(lex, ch); + ch = '/'; + break; + } + } while (ch != EOF && util_isspace(ch)); + + if (haswhite) { + lex_endtoken(lex); + lex_ungetch(lex, ch); + return TOKEN_WHITE; + } + return ch; +} + +/* Get a token */ +static bool GMQCC_WARN lex_finish_ident(lex_file *lex) +{ + int ch; + + ch = lex_getch(lex); + while (ch != EOF && isident(ch)) + { + lex_tokench(lex, ch); + ch = lex_getch(lex); + } + + /* last ch was not an ident ch: */ + lex_ungetch(lex, ch); + + return true; +} + +/* read one ident for the frame list */ +static int lex_parse_frame(lex_file *lex) +{ + int ch; + + lex_token_new(lex); + + ch = lex_getch(lex); + while (ch != EOF && ch != '\n' && util_isspace(ch)) + ch = lex_getch(lex); + + if (ch == '\n') + return 1; + + if (!isident_start(ch)) { + lexerror(lex, "invalid framename, must start with one of a-z or _, got %c", ch); + return -1; + } + + lex_tokench(lex, ch); + if (!lex_finish_ident(lex)) + return -1; + lex_endtoken(lex); + return 0; +} + +/* read a list of $frames */ +static bool lex_finish_frames(lex_file *lex) +{ + do { + size_t i; + int rc; + frame_macro m; + + rc = lex_parse_frame(lex); + if (rc > 0) /* end of line */ + return true; + if (rc < 0) /* error */ + return false; + + for (i = 0; i < vec_size(lex->frames); ++i) { + if (!strcmp(lex->tok.value, lex->frames[i].name)) { + lex->frames[i].value = lex->framevalue++; + if (lexwarn(lex, WARN_FRAME_MACROS, "duplicate frame macro defined: `%s`", lex->tok.value)) + return false; + break; + } + } + if (i < vec_size(lex->frames)) + continue; + + m.value = lex->framevalue++; + m.name = util_strdup(lex->tok.value); + vec_shrinkto(lex->tok.value, 0); + vec_push(lex->frames, m); + } while (true); + + return false; +} + +static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote) +{ + utf8ch_t chr = 0; + int ch = 0, texttype = 0; + int nextch; + bool hex; + bool oct; + char u8buf[8]; /* way more than enough */ + int u8len, uc; + + while (ch != EOF) + { + ch = lex_getch(lex); + if (ch == quote) + return TOKEN_STRINGCONST; + + if (lex->flags.preprocessing && ch == '\\') { + lex_tokench(lex, ch); + ch = lex_getch(lex); + if (ch == EOF) { + lexerror(lex, "unexpected end of file"); + lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */ + return (lex->tok.ttype = TOKEN_ERROR); + } + lex_tokench(lex, ch); + } + else if (ch == '\\') { + ch = lex_getch(lex); + if (ch == EOF) { + lexerror(lex, "unexpected end of file"); + lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */ + return (lex->tok.ttype = TOKEN_ERROR); + } + + switch (ch) { + case '\\': break; + case '\'': break; + case '"': 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 */ + ch = 0; + nextch = lex_getch(lex); + if (nextch >= '0' && nextch <= '9') + ch += nextch - '0'; + else if (nextch >= 'a' && nextch <= 'f') + ch += nextch - 'a' + 10; + else if (nextch >= 'A' && nextch <= 'F') + ch += nextch - 'A' + 10; + else { + lexerror(lex, "bad character code"); + lex_ungetch(lex, nextch); + return (lex->tok.ttype = TOKEN_ERROR); + } + + ch *= 0x10; + nextch = lex_getch(lex); + if (nextch >= '0' && nextch <= '9') + ch += nextch - '0'; + else if (nextch >= 'a' && nextch <= 'f') + ch += nextch - 'a' + 10; + else if (nextch >= 'A' && nextch <= 'F') + ch += nextch - 'A' + 10; + else { + lexerror(lex, "bad character code"); + lex_ungetch(lex, nextch); + return (lex->tok.ttype = TOKEN_ERROR); + } + break; + + /* fteqcc support */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': + ch = 18 + ch - '0'; + break; + case '<': ch = 29; break; + case '-': ch = 30; break; + case '>': ch = 31; break; + case '[': ch = 16; break; + case ']': ch = 17; break; + case '{': + chr = 0; + nextch = lex_getch(lex); + hex = (nextch == 'x'); + oct = (nextch == '0'); + if (!hex && !oct) + lex_ungetch(lex, nextch); + for (nextch = lex_getch(lex); nextch != '}'; nextch = lex_getch(lex)) { + if (!hex && !oct) { + if (nextch >= '0' && nextch <= '9') + chr = chr * 10 + nextch - '0'; + else { + lexerror(lex, "bad character code"); + return (lex->tok.ttype = TOKEN_ERROR); + } + } else if (!oct) { + if (nextch >= '0' && nextch <= '9') + chr = chr * 0x10 + nextch - '0'; + else if (nextch >= 'a' && nextch <= 'f') + chr = chr * 0x10 + nextch - 'a' + 10; + else if (nextch >= 'A' && nextch <= 'F') + chr = chr * 0x10 + nextch - 'A' + 10; + else { + lexerror(lex, "bad character code"); + return (lex->tok.ttype = TOKEN_ERROR); + } + } else { + if (nextch >= '0' && nextch <= '9') + chr = chr * 8 + chr - '0'; + else { + lexerror(lex, "bad character code"); + return (lex->tok.ttype = TOKEN_ERROR); + } + } + if (chr > 0x10FFFF || (!OPTS_FLAG(UTF8) && chr > 255)) + { + lexerror(lex, "character code out of range"); + return (lex->tok.ttype = TOKEN_ERROR); + } + } + if (OPTS_FLAG(UTF8) && chr >= 128) { + u8len = utf8_from(u8buf, chr); + if (!u8len) + ch = 0; + else { + --u8len; + lex->column += u8len; + for (uc = 0; uc < u8len; ++uc) + lex_tokench(lex, u8buf[uc]); + /* + * the last character will be inserted with the tokench() call + * below the switch + */ + ch = u8buf[uc]; + } + } + else + ch = chr; + 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); + /* so we just add the character plus backslash no matter what it actually is */ + lex_tokench(lex, '\\'); + } + /* add the character finally */ + lex_tokench(lex, ch | texttype); + } + else + lex_tokench(lex, ch); + } + lexerror(lex, "unexpected end of file within string constant"); + lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */ + return (lex->tok.ttype = TOKEN_ERROR); +} + +static int GMQCC_WARN lex_finish_digit(lex_file *lex, int lastch) +{ + bool ishex = false; + + int ch = lastch; + + /* parse a number... */ + if (ch == '.') + lex->tok.ttype = TOKEN_FLOATCONST; + else + lex->tok.ttype = TOKEN_INTCONST; + + lex_tokench(lex, ch); + + ch = lex_getch(lex); + if (ch != '.' && !util_isdigit(ch)) + { + if (lastch != '0' || ch != 'x') + { + /* end of the number or EOF */ + lex_ungetch(lex, ch); + lex_endtoken(lex); + + lex->tok.constval.i = lastch - '0'; + return lex->tok.ttype; + } + + ishex = true; + } + + /* EOF would have been caught above */ + + if (ch != '.') + { + lex_tokench(lex, ch); + ch = lex_getch(lex); + while (util_isdigit(ch) || (ishex && isxdigit_only(ch))) + { + lex_tokench(lex, ch); + ch = lex_getch(lex); + } + } + /* NOT else, '.' can come from above as well */ + if (lex->tok.ttype != TOKEN_FLOATCONST && ch == '.' && !ishex) + { + /* Allow floating comma in non-hex mode */ + lex->tok.ttype = TOKEN_FLOATCONST; + lex_tokench(lex, ch); + + /* continue digits-only */ + ch = lex_getch(lex); + while (util_isdigit(ch)) + { + lex_tokench(lex, ch); + ch = lex_getch(lex); + } + } + /* put back the last character */ + /* but do not put back the trailing 'f' or a float */ + if (lex->tok.ttype == TOKEN_FLOATCONST && ch == 'f') + ch = lex_getch(lex); + + /* generally we don't want words to follow numbers: */ + if (isident(ch)) { + lexerror(lex, "unexpected trailing characters after number"); + return (lex->tok.ttype = TOKEN_ERROR); + } + lex_ungetch(lex, ch); + + lex_endtoken(lex); + if (lex->tok.ttype == TOKEN_FLOATCONST) + lex->tok.constval.f = strtod(lex->tok.value, NULL); + else + lex->tok.constval.i = strtol(lex->tok.value, NULL, 0); + return lex->tok.ttype; +} + +int lex_do(lex_file *lex) +{ + int ch, nextch, thirdch; + bool hadwhite = false; + + lex_token_new(lex); + + while (true) { + ch = lex_skipwhite(lex, hadwhite); + hadwhite = true; + if (!lex->flags.mergelines || ch != '\\') + break; + ch = lex_getch(lex); + if (ch == '\r') + ch = lex_getch(lex); + if (ch != '\n') { + lex_ungetch(lex, ch); + ch = '\\'; + break; + } + /* we reached a linemerge */ + lex_tokench(lex, '\n'); + continue; + } + + if (lex->flags.preprocessing && (ch == TOKEN_WHITE || ch == TOKEN_EOL || ch == TOKEN_FATAL)) { + return (lex->tok.ttype = ch); + } + + lex->sline = lex->line; + lex->tok.ctx.line = lex->sline; + lex->tok.ctx.file = lex->name; + + if (lex->eof) + return (lex->tok.ttype = TOKEN_FATAL); + + if (ch == EOF) { + lex->eof = true; + return (lex->tok.ttype = TOKEN_EOF); + } + + /* modelgen / spiritgen commands */ + if (ch == '$' && !lex->flags.preprocessing) { + const char *v; + size_t frame; + + ch = lex_getch(lex); + if (!isident_start(ch)) { + lexerror(lex, "hanging '$' modelgen/spritegen command line"); + return lex_do(lex); + } + lex_tokench(lex, ch); + if (!lex_finish_ident(lex)) + return (lex->tok.ttype = TOKEN_ERROR); + lex_endtoken(lex); + /* skip the known commands */ + v = lex->tok.value; + + if (!strcmp(v, "frame") || !strcmp(v, "framesave")) + { + /* frame/framesave command works like an enum + * similar to fteqcc we handle this in the lexer. + * The reason for this is that it is sensitive to newlines, + * which the parser is unaware of + */ + if (!lex_finish_frames(lex)) + return (lex->tok.ttype = TOKEN_ERROR); + return lex_do(lex); + } + + if (!strcmp(v, "framevalue")) + { + ch = lex_getch(lex); + while (ch != EOF && util_isspace(ch) && ch != '\n') + ch = lex_getch(lex); + + if (!util_isdigit(ch)) { + lexerror(lex, "$framevalue requires an integer parameter"); + return lex_do(lex); + } + + lex_token_new(lex); + lex->tok.ttype = lex_finish_digit(lex, ch); + lex_endtoken(lex); + if (lex->tok.ttype != TOKEN_INTCONST) { + lexerror(lex, "$framevalue requires an integer parameter"); + return lex_do(lex); + } + lex->framevalue = lex->tok.constval.i; + return lex_do(lex); + } + + if (!strcmp(v, "framerestore")) + { + int rc; + + lex_token_new(lex); + + rc = lex_parse_frame(lex); + + if (rc > 0) { + lexerror(lex, "$framerestore requires a framename parameter"); + return lex_do(lex); + } + if (rc < 0) + return (lex->tok.ttype = TOKEN_FATAL); + + v = lex->tok.value; + for (frame = 0; frame < vec_size(lex->frames); ++frame) { + if (!strcmp(v, lex->frames[frame].name)) { + lex->framevalue = lex->frames[frame].value; + return lex_do(lex); + } + } + lexerror(lex, "unknown framename `%s`", v); + return lex_do(lex); + } + + if (!strcmp(v, "modelname")) + { + int rc; + + lex_token_new(lex); + + rc = lex_parse_frame(lex); + + if (rc > 0) { + lexerror(lex, "$modelname requires a parameter"); + return lex_do(lex); + } + if (rc < 0) + return (lex->tok.ttype = TOKEN_FATAL); + + if (lex->modelname) { + frame_macro m; + m.value = lex->framevalue; + m.name = lex->modelname; + lex->modelname = NULL; + vec_push(lex->frames, m); + } + lex->modelname = lex->tok.value; + lex->tok.value = NULL; + return lex_do(lex); + } + + if (!strcmp(v, "flush")) + { + size_t fi; + for (fi = 0; fi < vec_size(lex->frames); ++fi) + mem_d(lex->frames[fi].name); + vec_free(lex->frames); + /* skip line (fteqcc does it too) */ + ch = lex_getch(lex); + while (ch != EOF && ch != '\n') + ch = lex_getch(lex); + return lex_do(lex); + } + + if (!strcmp(v, "cd") || + !strcmp(v, "origin") || + !strcmp(v, "base") || + !strcmp(v, "flags") || + !strcmp(v, "scale") || + !strcmp(v, "skin")) + { + /* skip line */ + ch = lex_getch(lex); + while (ch != EOF && ch != '\n') + ch = lex_getch(lex); + return lex_do(lex); + } + + for (frame = 0; frame < vec_size(lex->frames); ++frame) { + if (!strcmp(v, lex->frames[frame].name)) { + lex->tok.constval.i = lex->frames[frame].value; + return (lex->tok.ttype = TOKEN_INTCONST); + } + } + + lexerror(lex, "invalid frame macro"); + return lex_do(lex); + } + + /* single-character tokens */ + switch (ch) + { + case '[': + nextch = lex_getch(lex); + if (nextch == '[') { + lex_tokench(lex, ch); + lex_tokench(lex, nextch); + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_ATTRIBUTE_OPEN); + } + lex_ungetch(lex, nextch); + /* FALL THROUGH */ + case '(': + case ':': + case '?': + lex_tokench(lex, ch); + lex_endtoken(lex); + if (lex->flags.noops) + return (lex->tok.ttype = ch); + else + return (lex->tok.ttype = TOKEN_OPERATOR); + + case ']': + if (lex->flags.noops) { + nextch = lex_getch(lex); + if (nextch == ']') { + lex_tokench(lex, ch); + lex_tokench(lex, nextch); + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_ATTRIBUTE_CLOSE); + } + lex_ungetch(lex, nextch); + } + /* FALL THROUGH */ + case ')': + case ';': + case '{': + case '}': + + case '#': + lex_tokench(lex, ch); + lex_endtoken(lex); + return (lex->tok.ttype = ch); + default: + break; + } + + if (ch == '.') { + nextch = lex_getch(lex); + /* digits starting with a dot */ + if (util_isdigit(nextch)) { + lex_ungetch(lex, nextch); + lex->tok.ttype = lex_finish_digit(lex, ch); + lex_endtoken(lex); + return lex->tok.ttype; + } + lex_ungetch(lex, nextch); + } + + if (lex->flags.noops) + { + /* Detect characters early which are normally + * operators OR PART of an operator. + */ + switch (ch) + { + case '*': + case '/': + case '<': + case '>': + case '=': + case '&': + case '|': + case '^': + case '~': + case ',': + case '!': + lex_tokench(lex, ch); + lex_endtoken(lex); + return (lex->tok.ttype = ch); + default: + break; + } + } + + if (ch == '.') + { + lex_tokench(lex, ch); + /* peak ahead once */ + nextch = lex_getch(lex); + if (nextch != '.') { + lex_ungetch(lex, nextch); + lex_endtoken(lex); + if (lex->flags.noops) + return (lex->tok.ttype = ch); + else + return (lex->tok.ttype = TOKEN_OPERATOR); + } + /* peak ahead again */ + nextch = lex_getch(lex); + if (nextch != '.') { + lex_ungetch(lex, nextch); + lex_ungetch(lex, '.'); + lex_endtoken(lex); + if (lex->flags.noops) + return (lex->tok.ttype = ch); + else + return (lex->tok.ttype = TOKEN_OPERATOR); + } + /* fill the token to be "..." */ + lex_tokench(lex, ch); + lex_tokench(lex, ch); + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_DOTS); + } + + if (ch == ',' || ch == '.') { + lex_tokench(lex, ch); + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_OPERATOR); + } + + if (ch == '+' || ch == '-' || /* ++, --, +=, -= and -> as well! */ + ch == '>' || ch == '<' || /* <<, >>, <=, >= and >< as well! */ + ch == '=' || ch == '!' || /* <=>, ==, != */ + ch == '&' || ch == '|' || /* &&, ||, &=, |= */ + ch == '~' || ch == '^' /* ~=, ~, ^ */ + ) { + lex_tokench(lex, ch); + nextch = lex_getch(lex); + + if ((nextch == '=' && ch != '<') || (nextch == '<' && ch == '>')) + lex_tokench(lex, nextch); + else if (nextch == ch && ch != '!') { + lex_tokench(lex, nextch); + if ((thirdch = lex_getch(lex)) == '=') + lex_tokench(lex, thirdch); + else + lex_ungetch(lex, thirdch); + } else if (ch == '<' && nextch == '=') { + lex_tokench(lex, nextch); + if ((thirdch = lex_getch(lex)) == '>') + lex_tokench(lex, thirdch); + else + lex_ungetch(lex, thirdch); + + } else if (ch == '-' && nextch == '>') { + lex_tokench(lex, nextch); + } else if (ch == '&' && nextch == '~') { + thirdch = lex_getch(lex); + if (thirdch != '=') { + lex_ungetch(lex, thirdch); + lex_ungetch(lex, nextch); + } + else { + lex_tokench(lex, nextch); + lex_tokench(lex, thirdch); + } + } + else if (lex->flags.preprocessing && + ch == '-' && util_isdigit(nextch)) + { + lex->tok.ttype = lex_finish_digit(lex, nextch); + if (lex->tok.ttype == TOKEN_INTCONST) + lex->tok.constval.i = -lex->tok.constval.i; + else + lex->tok.constval.f = -lex->tok.constval.f; + lex_endtoken(lex); + return lex->tok.ttype; + } else { + lex_ungetch(lex, nextch); + } + + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_OPERATOR); + } + + if (ch == '*' || ch == '/') /* *=, /= */ + { + lex_tokench(lex, ch); + + nextch = lex_getch(lex); + if (nextch == '=' || nextch == '*') { + lex_tokench(lex, nextch); + } else + lex_ungetch(lex, nextch); + + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_OPERATOR); + } + + if (ch == '%') { + lex_tokench(lex, ch); + lex_endtoken(lex); + return (lex->tok.ttype = TOKEN_OPERATOR); + } + + if (isident_start(ch)) + { + const char *v; + + lex_tokench(lex, ch); + if (!lex_finish_ident(lex)) { + /* error? */ + return (lex->tok.ttype = TOKEN_ERROR); + } + lex_endtoken(lex); + lex->tok.ttype = TOKEN_IDENT; + + v = lex->tok.value; + if (!strcmp(v, "void")) { + lex->tok.ttype = TOKEN_TYPENAME; + lex->tok.constval.t = TYPE_VOID; + } else if (!strcmp(v, "int")) { + lex->tok.ttype = TOKEN_TYPENAME; + lex->tok.constval.t = TYPE_INTEGER; + } else if (!strcmp(v, "float")) { + lex->tok.ttype = TOKEN_TYPENAME; + lex->tok.constval.t = TYPE_FLOAT; + } else if (!strcmp(v, "string")) { + lex->tok.ttype = TOKEN_TYPENAME; + lex->tok.constval.t = TYPE_STRING; + } else if (!strcmp(v, "entity")) { + lex->tok.ttype = TOKEN_TYPENAME; + lex->tok.constval.t = TYPE_ENTITY; + } 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) { + if (!strcmp(v, keywords_qc[kw])) + return (lex->tok.ttype = TOKEN_KEYWORD); + } + if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) { + for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_fg); ++kw) { + if (!strcmp(v, keywords_fg[kw])) + return (lex->tok.ttype = TOKEN_KEYWORD); + } + } + } + + return lex->tok.ttype; + } + + if (ch == '"') + { + lex->flags.nodigraphs = true; + if (lex->flags.preprocessing) + lex_tokench(lex, ch); + lex->tok.ttype = lex_finish_string(lex, '"'); + if (lex->flags.preprocessing) + lex_tokench(lex, ch); + while (!lex->flags.preprocessing && lex->tok.ttype == TOKEN_STRINGCONST) + { + /* Allow c style "string" "continuation" */ + ch = lex_skipwhite(lex, false); + if (ch != '"') { + lex_ungetch(lex, ch); + break; + } + + lex->tok.ttype = lex_finish_string(lex, '"'); + } + lex->flags.nodigraphs = false; + lex_endtoken(lex); + return lex->tok.ttype; + } + + if (ch == '\'') + { + /* we parse character constants like string, + * but return TOKEN_CHARCONST, or a vector type if it fits... + * Likewise actual unescaping has to be done by the parser. + * The difference is we don't allow 'char' 'continuation'. + */ + if (lex->flags.preprocessing) + lex_tokench(lex, ch); + lex->tok.ttype = lex_finish_string(lex, '\''); + if (lex->flags.preprocessing) + lex_tokench(lex, ch); + lex_endtoken(lex); + + lex->tok.ttype = TOKEN_CHARCONST; + + /* It's a vector if we can successfully scan 3 floats */ + if (util_sscanf(lex->tok.value, " %f %f %f ", + &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3) + + { + lex->tok.ttype = TOKEN_VECTORCONST; + } + else + { + if (!lex->flags.preprocessing && strlen(lex->tok.value) > 1) { + utf8ch_t u8char; + /* check for a valid utf8 character */ + if (!OPTS_FLAG(UTF8) || !utf8_to(&u8char, (const unsigned char *)lex->tok.value, 8)) { + if (lexwarn(lex, WARN_MULTIBYTE_CHARACTER, + ( OPTS_FLAG(UTF8) ? "invalid multibyte character sequence `%s`" + : "multibyte character: `%s`" ), + lex->tok.value)) + return (lex->tok.ttype = TOKEN_ERROR); + } + else + lex->tok.constval.i = u8char; + } + else + lex->tok.constval.i = lex->tok.value[0]; + } + + return lex->tok.ttype; + } + + if (util_isdigit(ch)) + { + lex->tok.ttype = lex_finish_digit(lex, ch); + lex_endtoken(lex); + return lex->tok.ttype; + } + + if (lex->flags.preprocessing) { + lex_tokench(lex, ch); + lex_endtoken(lex); + return (lex->tok.ttype = ch); + } + + lexerror(lex, "unknown token: `%c`", ch); + return (lex->tok.ttype = TOKEN_ERROR); +} diff --git a/main.c b/main.c deleted file mode 100644 index ac715d5..0000000 --- a/main.c +++ /dev/null @@ -1,755 +0,0 @@ -#include -#include - -#include "gmqcc.h" -#include "lexer.h" - -/* TODO: cleanup this whole file .. it's a fuckign mess */ - -/* set by the standard */ -const oper_info *operators = NULL; -size_t operator_count = 0; -static bool opts_output_wasset = false; - -typedef struct { char *filename; int type; } argitem; -typedef struct { char *name; char *value; } ppitem; -static argitem *items = NULL; -static ppitem *ppems = NULL; - -#define TYPE_QC 0 -#define TYPE_ASM 1 -#define TYPE_SRC 2 - -static const char *app_name; - -static void version(void) { - con_out("GMQCC %d.%d.%d Built %s %s\n" GMQCC_DEV_VERSION_STRING, - GMQCC_VERSION_MAJOR, - GMQCC_VERSION_MINOR, - GMQCC_VERSION_PATCH, - __DATE__, - __TIME__ - ); -} - -static int usage(void) { - con_out("usage: %s [options] [files...]", app_name); - con_out("options:\n" - " -h, --help show this help message\n" - " -debug turns on compiler debug messages\n"); - con_out(" -o, --output=file output file, defaults to progs.dat\n" - " -s filename add a progs.src file to be used\n"); - con_out(" -E stop after preprocessing\n"); - con_out(" -q, --quiet be less verbose\n"); - con_out(" -config file use the specified ini file\n"); - con_out(" -std=standard select one of the following standards\n" - " -std=qcc original QuakeC\n" - " -std=fteqcc fteqcc QuakeC\n" - " -std=gmqcc this compiler (default)\n"); - con_out(" -f enable a flag\n" - " -fno- disable a flag\n" - " -fhelp list possible flags\n"); - con_out(" -W enable a warning\n" - " -Wno- disable a warning\n" - " -Wall enable all warnings\n"); - con_out(" -Werror treat warnings as errors\n" - " -Werror- treat a warning as error\n" - " -Wno-error- opposite of the above\n"); - con_out(" -Whelp list possible warnings\n"); - con_out(" -O optimization level\n" - " -O enable specific optimization\n" - " -Ono- 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; -} - -/* command line parsing */ -static bool options_witharg(int *argc_, char ***argv_, char **out) { - int argc = *argc_; - char **argv = *argv_; - - if (argv[0][2]) { - *out = argv[0]+2; - return true; - } - /* eat up the next */ - if (argc < 2) /* no parameter was provided */ - return false; - - *out = argv[1]; - --*argc_; - ++*argv_; - return true; -} - -static bool options_long_witharg_all(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) { - int argc = *argc_; - char **argv = *argv_; - - size_t len = strlen(optname); - - if (strncmp(argv[0]+ds, optname, len)) - return false; - - /* it's --optname, check how the parameter is supplied */ - if (argv[0][ds+len] == '=') { - /* using --opt=param */ - *out = argv[0]+ds+len+1; - return true; - } - - if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */ - return false; - - /* using --opt param */ - *out = argv[1]; - --*argc_; - ++*argv_; - return true; -} -static bool options_long_witharg(const char *optname, int *argc_, char ***argv_, char **out) { - return options_long_witharg_all(optname, argc_, argv_, out, 2, true); -} -static bool options_long_gcc(const char *optname, int *argc_, char ***argv_, char **out) { - return options_long_witharg_all(optname, argc_, argv_, out, 1, false); -} - -static bool options_parse(int argc, char **argv) { - bool argend = false; - size_t itr; - char buffer[1024]; - char *config = NULL; - - while (!argend && argc > 1) { - char *argarg; - argitem item; - ppitem macro; - - ++argv; - --argc; - - if (argv[0][0] == '-') { - /* All gcc-type long options */ - if (options_long_gcc("std", &argc, &argv, &argarg)) { - if (!strcmp(argarg, "gmqcc") || !strcmp(argarg, "default")) { - - opts_set(opts.flags, ADJUST_VECTOR_FIELDS, true); - opts_set(opts.flags, CORRECT_LOGIC, true); - opts_set(opts.flags, SHORT_LOGIC, true); - opts_set(opts.flags, UNTYPED_NIL, true); - opts_set(opts.flags, VARIADIC_ARGS, true); - opts_set(opts.flags, FALSE_EMPTY_STRINGS, false); - opts_set(opts.flags, TRUE_EMPTY_STRINGS, true); - opts_set(opts.flags, LOOP_LABELS, true); - opts_set(opts.flags, TRANSLATABLE_STRINGS, true); - opts_set(opts.flags, INITIALIZED_NONCONSTANTS, true); - opts_set(opts.werror, WARN_INVALID_PARAMETER_COUNT, true); - opts_set(opts.werror, WARN_MISSING_RETURN_VALUES, true); - opts_set(opts.flags, EXPRESSIONS_FOR_BUILTINS, true); - opts_set(opts.warn, WARN_BREAKDEF, true); - - - - OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_GMQCC; - - } else if (!strcmp(argarg, "qcc")) { - - opts_set(opts.flags, ADJUST_VECTOR_FIELDS, false); - opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true); - - OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCC; - - } else if (!strcmp(argarg, "fte") || !strcmp(argarg, "fteqcc")) { - - opts_set(opts.flags, FTEPP, true); - opts_set(opts.flags, TRANSLATABLE_STRINGS, true); - opts_set(opts.flags, ADJUST_VECTOR_FIELDS, false); - opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true); - opts_set(opts.flags, CORRECT_TERNARY, false); - opts_set(opts.warn, WARN_TERNARY_PRECEDENCE, true); - opts_set(opts.warn, WARN_BREAKDEF, true); - - OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_FTEQCC; - - } else if (!strcmp(argarg, "qccx")) { - - opts_set(opts.flags, ADJUST_VECTOR_FIELDS, false); - OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCCX; - - } else { - con_out("Unknown standard: %s\n", argarg); - return false; - } - continue; - } - if (options_long_gcc("force-crc", &argc, &argv, &argarg)) { - - OPTS_OPTION_BOOL(OPTION_FORCECRC) = true; - 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("config", &argc, &argv, &argarg)) { - config = argarg; - continue; - } - if (options_long_gcc("progsrc", &argc, &argv, &argarg)) { - OPTS_OPTION_STR(OPTION_PROGSRC) = argarg; - continue; - } - - /* show defaults (like pathscale) */ - if (!strcmp(argv[0]+1, "show-defaults")) { - for (itr = 0; itr < COUNT_FLAGS; ++itr) { - if (!OPTS_FLAG(itr)) - continue; - - memset(buffer, 0, sizeof(buffer)); - util_strtononcmd(opts_flag_list[itr].name, buffer, strlen(opts_flag_list[itr].name) + 1); - - con_out("-f%s ", buffer); - } - for (itr = 0; itr < COUNT_WARNINGS; ++itr) { - if (!OPTS_WARN(itr)) - continue; - - memset(buffer, 0, sizeof(buffer)); - util_strtononcmd(opts_warn_list[itr].name, buffer, strlen(opts_warn_list[itr].name) + 1); - con_out("-W%s ", buffer); - } - con_out("\n"); - exit(0); - } - - if (!strcmp(argv[0]+1, "debug")) { - OPTS_OPTION_BOOL(OPTION_DEBUG) = true; - continue; - } - if (!strcmp(argv[0]+1, "dump")) { - OPTS_OPTION_BOOL(OPTION_DUMP) = true; - continue; - } - if (!strcmp(argv[0]+1, "dumpfin")) { - OPTS_OPTION_BOOL(OPTION_DUMPFIN) = true; - continue; - } - if (!strcmp(argv[0]+1, "nocolor")) { - con_color(0); - continue; - } - if (!strcmp(argv[0]+1, "coverage")) { - OPTS_OPTION_BOOL(OPTION_COVERAGE) = true; - continue; - } - - switch (argv[0][1]) { - /* -h, show usage but exit with 0 */ - case 'h': - usage(); - exit(0); - /* break; never reached because of exit(0) */ - - case 'v': - version(); - exit(0); - - case 'E': - OPTS_OPTION_BOOL(OPTION_PP_ONLY) = true; - opts_set(opts.flags, FTEPP_PREDEFS, true); /* predefs on for -E */ - break; - - /* debug turns on -flno */ - case 'g': - opts_setflag("LNO", true); - OPTS_OPTION_BOOL(OPTION_G) = true; - break; - - case 'q': - OPTS_OPTION_BOOL(OPTION_QUIET) = true; - break; - - case 'D': - if (!strlen(argv[0]+2)) { - con_err("expected name after -D\n"); - exit(0); - } - - if (!(argarg = strchr(argv[0] + 2, '='))) { - macro.name = util_strdup(argv[0]+2); - macro.value = NULL; - } else { - *argarg='\0'; /* terminate for name */ - macro.name = util_strdup(argv[0]+2); - macro.value = util_strdup(argarg+1); - } - vec_push(ppems, macro); - break; - - /* handle all -fflags */ - case 'f': - util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1); - if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') { - con_out("Possible flags:\n\n"); - for (itr = 0; itr < COUNT_FLAGS; ++itr) { - util_strtononcmd(opts_flag_list[itr].name, buffer, sizeof(buffer)); - con_out(" -f%s\n", buffer); - } - exit(0); - } - else if (!strncmp(argv[0]+2, "NO_", 3)) { - if (!opts_setflag(argv[0]+5, false)) { - con_out("unknown flag: %s\n", argv[0]+2); - return false; - } - } - else if (!opts_setflag(argv[0]+2, true)) { - con_out("unknown flag: %s\n", argv[0]+2); - return false; - } - break; - case 'W': - util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1); - if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') { - con_out("Possible warnings:\n"); - for (itr = 0; itr < COUNT_WARNINGS; ++itr) { - util_strtononcmd(opts_warn_list[itr].name, buffer, sizeof(buffer)); - con_out(" -W%s\n", buffer); - if (itr == WARN_DEBUG) - con_out(" Warnings included by -Wall:\n"); - } - exit(0); - } - else if (!strcmp(argv[0]+2, "NO_ERROR") || - !strcmp(argv[0]+2, "NO_ERROR_ALL")) - { - for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr) - opts.werror[itr] = 0; - break; - } - else if (!strcmp(argv[0]+2, "ERROR") || - !strcmp(argv[0]+2, "ERROR_ALL")) - { - opts_backup_non_Werror_all(); - for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr) - opts.werror[itr] = 0xFFFFFFFFL; - opts_restore_non_Werror_all(); - break; - } - else if (!strcmp(argv[0]+2, "NONE")) { - for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr) - opts.warn[itr] = 0; - break; - } - else if (!strcmp(argv[0]+2, "ALL")) { - opts_backup_non_Wall(); - for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr) - opts.warn[itr] = 0xFFFFFFFFL; - opts_restore_non_Wall(); - break; - } - else if (!strncmp(argv[0]+2, "ERROR_", 6)) { - if (!opts_setwerror(argv[0]+8, true)) { - con_out("unknown warning: %s\n", argv[0]+2); - return false; - } - } - else if (!strncmp(argv[0]+2, "NO_ERROR_", 9)) { - if (!opts_setwerror(argv[0]+11, false)) { - con_out("unknown warning: %s\n", argv[0]+2); - return false; - } - } - else if (!strncmp(argv[0]+2, "NO_", 3)) { - if (!opts_setwarn(argv[0]+5, false)) { - con_out("unknown warning: %s\n", argv[0]+2); - return false; - } - } - else if (!opts_setwarn(argv[0]+2, true)) { - con_out("unknown warning: %s\n", argv[0]+2); - return false; - } - break; - - case 'O': - if (!options_witharg(&argc, &argv, &argarg)) { - con_out("option -O requires a numerical argument, or optimization name with an optional 'no-' prefix\n"); - return false; - } - if (util_isdigit(argarg[0])) { - uint32_t val = (uint32_t)strtol(argarg, NULL, 10); - OPTS_OPTION_U32(OPTION_O) = val; - opts_setoptimlevel(val); - } else { - util_strtocmd(argarg, argarg, strlen(argarg)+1); - if (!strcmp(argarg, "HELP")) { - con_out("Possible optimizations:\n"); - for (itr = 0; itr < COUNT_OPTIMIZATIONS; ++itr) { - util_strtononcmd(opts_opt_list[itr].name, buffer, sizeof(buffer)); - con_out(" -O%-20s (-O%u)\n", buffer, opts_opt_oflag[itr]); - } - exit(0); - } - else if (!strcmp(argarg, "ALL")) - opts_setoptimlevel(OPTS_OPTION_U32(OPTION_O) = 9999); - else if (!strncmp(argarg, "NO_", 3)) { - /* constant folding cannot be turned off for obvious reasons */ - if (!strcmp(argarg, "NO_CONST_FOLD") || !opts_setoptim(argarg+3, false)) { - con_out("unknown optimization: %s\n", argarg+3); - return false; - } - } - else { - if (!opts_setoptim(argarg, true)) { - con_out("unknown optimization: %s\n", argarg); - return false; - } - } - } - break; - - case 'o': - if (!options_witharg(&argc, &argv, &argarg)) { - con_out("option -o requires an argument: the output file name\n"); - return false; - } - OPTS_OPTION_STR(OPTION_OUTPUT) = argarg; - opts_output_wasset = true; - break; - - case 'a': - case 's': - item.type = argv[0][1] == 'a' ? TYPE_ASM : TYPE_SRC; - if (!options_witharg(&argc, &argv, &argarg)) { - con_out("option -a requires a filename %s\n", - (argv[0][1] == 'a' ? "containing QC-asm" : "containing a progs.src formatted list")); - return false; - } - item.filename = argarg; - vec_push(items, item); - break; - - case '-': - if (!argv[0][2]) { - /* anything following -- is considered a non-option argument */ - argend = true; - break; - } - /* All long options without arguments */ - else if (!strcmp(argv[0]+2, "help")) { - usage(); - exit(0); - } - else if (!strcmp(argv[0]+2, "version")) { - version(); - exit(0); - } - else if (!strcmp(argv[0]+2, "quiet")) { - OPTS_OPTION_BOOL(OPTION_QUIET) = true; - break; - } - else if (!strcmp(argv[0]+2, "add-info")) { - OPTS_OPTION_BOOL(OPTION_ADD_INFO) = true; - break; - } - else { - /* All long options with arguments */ - if (options_long_witharg("output", &argc, &argv, &argarg)) { - OPTS_OPTION_STR(OPTION_OUTPUT) = argarg; - opts_output_wasset = true; - } else { - con_out("Unknown parameter: %s\n", argv[0]); - return false; - } - } - break; - - default: - con_out("Unknown parameter: %s\n", argv[0]); - return false; - } - } - else - { - /* it's a QC filename */ - item.filename = argv[0]; - item.type = TYPE_QC; - vec_push(items, item); - } - } - opts_ini_init(config); - return true; -} - -/* returns the line number, or -1 on error */ -static bool progs_nextline(char **out, size_t *alen, FILE *src) { - int len; - char *line; - char *start; - char *end; - - line = *out; - len = util_getline(&line, alen, src); - if (len == -1) - return false; - - /* start at first non-blank */ - for (start = line; util_isspace(*start); ++start) {} - /* end at the first non-blank */ - for (end = start; *end && !util_isspace(*end); ++end) {} - - *out = line; - /* move the actual filename to the beginning */ - while (start != end) { - *line++ = *start++; - } - *line = 0; - return true; -} - -int main(int argc, char **argv) { - size_t itr; - int retval = 0; - bool operators_free = false; - bool progs_src = false; - FILE *outfile = NULL; - struct parser_s *parser = NULL; - struct ftepp_s *ftepp = NULL; - - app_name = argv[0]; - con_init (); - opts_init("progs.dat", COMPILER_QCC, (1024 << 3)); - - util_seed(time(0)); - - if (!options_parse(argc, argv)) { - return usage(); - } - - if (OPTS_FLAG(TRUE_EMPTY_STRINGS) && OPTS_FLAG(FALSE_EMPTY_STRINGS)) { - con_err("-ftrue-empty-strings and -ffalse-empty-strings are mutually exclusive"); - exit(EXIT_FAILURE); - } - - /* the standard decides which set of operators to use */ - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) { - operators = c_operators; - operator_count = GMQCC_ARRAY_COUNT(c_operators); - } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) { - operators = fte_operators; - operator_count = GMQCC_ARRAY_COUNT(fte_operators); - } else { - operators = qcc_operators; - operator_count = GMQCC_ARRAY_COUNT(qcc_operators); - } - - if (operators == fte_operators) { - /* fix ternary? */ - if (OPTS_FLAG(CORRECT_TERNARY)) { - oper_info *newops; - if (operators[operator_count-2].id != opid1(',') || - operators[operator_count-1].id != opid2(':','?')) - { - con_err("internal error: operator precedence table wasn't updated correctly!\n"); - exit(EXIT_FAILURE); - } - operators_free = true; - newops = (oper_info*)mem_a(sizeof(operators[0]) * operator_count); - memcpy(newops, operators, sizeof(operators[0]) * operator_count); - memcpy(&newops[operator_count-2], &operators[operator_count-1], sizeof(newops[0])); - memcpy(&newops[operator_count-1], &operators[operator_count-2], sizeof(newops[0])); - newops[operator_count-2].prec = newops[operator_count-1].prec+1; - operators = newops; - } - } - - if (OPTS_OPTION_BOOL(OPTION_DUMP)) { - for (itr = 0; itr < COUNT_FLAGS; ++itr) - con_out("Flag %s = %i\n", opts_flag_list[itr].name, OPTS_FLAG(itr)); - for (itr = 0; itr < COUNT_WARNINGS; ++itr) - con_out("Warning %s = %i\n", opts_warn_list[itr].name, OPTS_WARN(itr)); - - con_out("output = %s\n", OPTS_OPTION_STR(OPTION_OUTPUT)); - con_out("optimization level = %u\n", OPTS_OPTION_U32(OPTION_O)); - con_out("standard = %u\n", OPTS_OPTION_U32(OPTION_STANDARD)); - } - - if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { - if (opts_output_wasset) { - outfile = fopen(OPTS_OPTION_STR(OPTION_OUTPUT), "wb"); - if (!outfile) { - con_err("failed to open `%s` for writing\n", OPTS_OPTION_STR(OPTION_OUTPUT)); - retval = 1; - goto cleanup; - } - } - else { - outfile = con_default_out(); - } - } - - if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { - if (!(parser = parser_create())) { - con_err("failed to initialize parser\n"); - retval = 1; - goto cleanup; - } - } - - if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) { - if (!(ftepp = ftepp_create())) { - con_err("failed to initialize parser\n"); - retval = 1; - goto cleanup; - } - } - - /* add macros */ - if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) { - for (itr = 0; itr < vec_size(ppems); itr++) { - ftepp_add_macro(ftepp, ppems[itr].name, ppems[itr].value); - mem_d(ppems[itr].name); - - /* can be null */ - if (ppems[itr].value) - mem_d(ppems[itr].value); - } - } - - if (!vec_size(items)) { - FILE *src; - char *line = NULL; - size_t linelen = 0; - bool hasline = false; - - progs_src = true; - - src = fopen(OPTS_OPTION_STR(OPTION_PROGSRC), "rb"); - if (!src) { - con_err("failed to open `%s` for reading\n", OPTS_OPTION_STR(OPTION_PROGSRC)); - retval = 1; - goto cleanup; - } - - while (progs_nextline(&line, &linelen, src)) { - argitem item; - - if (!line[0] || (line[0] == '/' && line[1] == '/')) - continue; - - if (hasline) { - item.filename = util_strdup(line); - item.type = TYPE_QC; - vec_push(items, item); - } else if (!opts_output_wasset) { - OPTS_OPTION_DUP(OPTION_OUTPUT) = util_strdup(line); - hasline = true; - } - } - - fclose(src); - mem_d(line); - } - - if (vec_size(items)) { - if (!OPTS_OPTION_BOOL(OPTION_QUIET) && - !OPTS_OPTION_BOOL(OPTION_PP_ONLY)) - { - con_out("Mode: %s\n", (progs_src ? "progs.src" : "manual")); - con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items)); - } - - for (itr = 0; itr < vec_size(items); ++itr) { - if (!OPTS_OPTION_BOOL(OPTION_QUIET) && - !OPTS_OPTION_BOOL(OPTION_PP_ONLY)) - { - con_out(" item: %s (%s)\n", - items[itr].filename, - ( (items[itr].type == TYPE_QC ? "qc" : - (items[itr].type == TYPE_ASM ? "asm" : - (items[itr].type == TYPE_SRC ? "progs.src" : - ("unknown")))))); - } - - if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { - const char *out; - if (!ftepp_preprocess_file(ftepp, items[itr].filename)) { - retval = 1; - goto cleanup; - } - out = ftepp_get(ftepp); - if (out) - fprintf(outfile, "%s", out); - ftepp_flush(ftepp); - } - else { - if (OPTS_FLAG(FTEPP)) { - const char *data; - if (!ftepp_preprocess_file(ftepp, items[itr].filename)) { - retval = 1; - goto cleanup; - } - data = ftepp_get(ftepp); - if (vec_size(data)) { - if (!parser_compile_string(parser, items[itr].filename, data, vec_size(data))) { - retval = 1; - goto cleanup; - } - } - ftepp_flush(ftepp); - } - else { - if (!parser_compile_file(parser, items[itr].filename)) { - retval = 1; - goto cleanup; - } - } - } - - if (progs_src) { - mem_d(items[itr].filename); - items[itr].filename = NULL; - } - } - - ftepp_finish(ftepp); - ftepp = NULL; - if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { - if (!parser_finish(parser, OPTS_OPTION_STR(OPTION_OUTPUT))) { - retval = 1; - goto cleanup; - } - } - } - -cleanup: - if (ftepp) - ftepp_finish(ftepp); - con_close(); - vec_free(items); - vec_free(ppems); - - if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) - if(parser) parser_cleanup(parser); - - /* free allocated option strings */ - for (itr = 0; itr < OPTION_COUNT; itr++) - if (OPTS_OPTION_DUPED(itr)) - mem_d(OPTS_OPTION_STR(itr)); - - if (operators_free) - mem_d((void*)operators); - - lex_cleanup(); - - if (!retval && compile_errors) - retval = 1; - return retval; -} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..ac715d5 --- /dev/null +++ b/main.cpp @@ -0,0 +1,755 @@ +#include +#include + +#include "gmqcc.h" +#include "lexer.h" + +/* TODO: cleanup this whole file .. it's a fuckign mess */ + +/* set by the standard */ +const oper_info *operators = NULL; +size_t operator_count = 0; +static bool opts_output_wasset = false; + +typedef struct { char *filename; int type; } argitem; +typedef struct { char *name; char *value; } ppitem; +static argitem *items = NULL; +static ppitem *ppems = NULL; + +#define TYPE_QC 0 +#define TYPE_ASM 1 +#define TYPE_SRC 2 + +static const char *app_name; + +static void version(void) { + con_out("GMQCC %d.%d.%d Built %s %s\n" GMQCC_DEV_VERSION_STRING, + GMQCC_VERSION_MAJOR, + GMQCC_VERSION_MINOR, + GMQCC_VERSION_PATCH, + __DATE__, + __TIME__ + ); +} + +static int usage(void) { + con_out("usage: %s [options] [files...]", app_name); + con_out("options:\n" + " -h, --help show this help message\n" + " -debug turns on compiler debug messages\n"); + con_out(" -o, --output=file output file, defaults to progs.dat\n" + " -s filename add a progs.src file to be used\n"); + con_out(" -E stop after preprocessing\n"); + con_out(" -q, --quiet be less verbose\n"); + con_out(" -config file use the specified ini file\n"); + con_out(" -std=standard select one of the following standards\n" + " -std=qcc original QuakeC\n" + " -std=fteqcc fteqcc QuakeC\n" + " -std=gmqcc this compiler (default)\n"); + con_out(" -f enable a flag\n" + " -fno- disable a flag\n" + " -fhelp list possible flags\n"); + con_out(" -W enable a warning\n" + " -Wno- disable a warning\n" + " -Wall enable all warnings\n"); + con_out(" -Werror treat warnings as errors\n" + " -Werror- treat a warning as error\n" + " -Wno-error- opposite of the above\n"); + con_out(" -Whelp list possible warnings\n"); + con_out(" -O optimization level\n" + " -O enable specific optimization\n" + " -Ono- 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; +} + +/* command line parsing */ +static bool options_witharg(int *argc_, char ***argv_, char **out) { + int argc = *argc_; + char **argv = *argv_; + + if (argv[0][2]) { + *out = argv[0]+2; + return true; + } + /* eat up the next */ + if (argc < 2) /* no parameter was provided */ + return false; + + *out = argv[1]; + --*argc_; + ++*argv_; + return true; +} + +static bool options_long_witharg_all(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) { + int argc = *argc_; + char **argv = *argv_; + + size_t len = strlen(optname); + + if (strncmp(argv[0]+ds, optname, len)) + return false; + + /* it's --optname, check how the parameter is supplied */ + if (argv[0][ds+len] == '=') { + /* using --opt=param */ + *out = argv[0]+ds+len+1; + return true; + } + + if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */ + return false; + + /* using --opt param */ + *out = argv[1]; + --*argc_; + ++*argv_; + return true; +} +static bool options_long_witharg(const char *optname, int *argc_, char ***argv_, char **out) { + return options_long_witharg_all(optname, argc_, argv_, out, 2, true); +} +static bool options_long_gcc(const char *optname, int *argc_, char ***argv_, char **out) { + return options_long_witharg_all(optname, argc_, argv_, out, 1, false); +} + +static bool options_parse(int argc, char **argv) { + bool argend = false; + size_t itr; + char buffer[1024]; + char *config = NULL; + + while (!argend && argc > 1) { + char *argarg; + argitem item; + ppitem macro; + + ++argv; + --argc; + + if (argv[0][0] == '-') { + /* All gcc-type long options */ + if (options_long_gcc("std", &argc, &argv, &argarg)) { + if (!strcmp(argarg, "gmqcc") || !strcmp(argarg, "default")) { + + opts_set(opts.flags, ADJUST_VECTOR_FIELDS, true); + opts_set(opts.flags, CORRECT_LOGIC, true); + opts_set(opts.flags, SHORT_LOGIC, true); + opts_set(opts.flags, UNTYPED_NIL, true); + opts_set(opts.flags, VARIADIC_ARGS, true); + opts_set(opts.flags, FALSE_EMPTY_STRINGS, false); + opts_set(opts.flags, TRUE_EMPTY_STRINGS, true); + opts_set(opts.flags, LOOP_LABELS, true); + opts_set(opts.flags, TRANSLATABLE_STRINGS, true); + opts_set(opts.flags, INITIALIZED_NONCONSTANTS, true); + opts_set(opts.werror, WARN_INVALID_PARAMETER_COUNT, true); + opts_set(opts.werror, WARN_MISSING_RETURN_VALUES, true); + opts_set(opts.flags, EXPRESSIONS_FOR_BUILTINS, true); + opts_set(opts.warn, WARN_BREAKDEF, true); + + + + OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_GMQCC; + + } else if (!strcmp(argarg, "qcc")) { + + opts_set(opts.flags, ADJUST_VECTOR_FIELDS, false); + opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true); + + OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCC; + + } else if (!strcmp(argarg, "fte") || !strcmp(argarg, "fteqcc")) { + + opts_set(opts.flags, FTEPP, true); + opts_set(opts.flags, TRANSLATABLE_STRINGS, true); + opts_set(opts.flags, ADJUST_VECTOR_FIELDS, false); + opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true); + opts_set(opts.flags, CORRECT_TERNARY, false); + opts_set(opts.warn, WARN_TERNARY_PRECEDENCE, true); + opts_set(opts.warn, WARN_BREAKDEF, true); + + OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_FTEQCC; + + } else if (!strcmp(argarg, "qccx")) { + + opts_set(opts.flags, ADJUST_VECTOR_FIELDS, false); + OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCCX; + + } else { + con_out("Unknown standard: %s\n", argarg); + return false; + } + continue; + } + if (options_long_gcc("force-crc", &argc, &argv, &argarg)) { + + OPTS_OPTION_BOOL(OPTION_FORCECRC) = true; + 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("config", &argc, &argv, &argarg)) { + config = argarg; + continue; + } + if (options_long_gcc("progsrc", &argc, &argv, &argarg)) { + OPTS_OPTION_STR(OPTION_PROGSRC) = argarg; + continue; + } + + /* show defaults (like pathscale) */ + if (!strcmp(argv[0]+1, "show-defaults")) { + for (itr = 0; itr < COUNT_FLAGS; ++itr) { + if (!OPTS_FLAG(itr)) + continue; + + memset(buffer, 0, sizeof(buffer)); + util_strtononcmd(opts_flag_list[itr].name, buffer, strlen(opts_flag_list[itr].name) + 1); + + con_out("-f%s ", buffer); + } + for (itr = 0; itr < COUNT_WARNINGS; ++itr) { + if (!OPTS_WARN(itr)) + continue; + + memset(buffer, 0, sizeof(buffer)); + util_strtononcmd(opts_warn_list[itr].name, buffer, strlen(opts_warn_list[itr].name) + 1); + con_out("-W%s ", buffer); + } + con_out("\n"); + exit(0); + } + + if (!strcmp(argv[0]+1, "debug")) { + OPTS_OPTION_BOOL(OPTION_DEBUG) = true; + continue; + } + if (!strcmp(argv[0]+1, "dump")) { + OPTS_OPTION_BOOL(OPTION_DUMP) = true; + continue; + } + if (!strcmp(argv[0]+1, "dumpfin")) { + OPTS_OPTION_BOOL(OPTION_DUMPFIN) = true; + continue; + } + if (!strcmp(argv[0]+1, "nocolor")) { + con_color(0); + continue; + } + if (!strcmp(argv[0]+1, "coverage")) { + OPTS_OPTION_BOOL(OPTION_COVERAGE) = true; + continue; + } + + switch (argv[0][1]) { + /* -h, show usage but exit with 0 */ + case 'h': + usage(); + exit(0); + /* break; never reached because of exit(0) */ + + case 'v': + version(); + exit(0); + + case 'E': + OPTS_OPTION_BOOL(OPTION_PP_ONLY) = true; + opts_set(opts.flags, FTEPP_PREDEFS, true); /* predefs on for -E */ + break; + + /* debug turns on -flno */ + case 'g': + opts_setflag("LNO", true); + OPTS_OPTION_BOOL(OPTION_G) = true; + break; + + case 'q': + OPTS_OPTION_BOOL(OPTION_QUIET) = true; + break; + + case 'D': + if (!strlen(argv[0]+2)) { + con_err("expected name after -D\n"); + exit(0); + } + + if (!(argarg = strchr(argv[0] + 2, '='))) { + macro.name = util_strdup(argv[0]+2); + macro.value = NULL; + } else { + *argarg='\0'; /* terminate for name */ + macro.name = util_strdup(argv[0]+2); + macro.value = util_strdup(argarg+1); + } + vec_push(ppems, macro); + break; + + /* handle all -fflags */ + case 'f': + util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1); + if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') { + con_out("Possible flags:\n\n"); + for (itr = 0; itr < COUNT_FLAGS; ++itr) { + util_strtononcmd(opts_flag_list[itr].name, buffer, sizeof(buffer)); + con_out(" -f%s\n", buffer); + } + exit(0); + } + else if (!strncmp(argv[0]+2, "NO_", 3)) { + if (!opts_setflag(argv[0]+5, false)) { + con_out("unknown flag: %s\n", argv[0]+2); + return false; + } + } + else if (!opts_setflag(argv[0]+2, true)) { + con_out("unknown flag: %s\n", argv[0]+2); + return false; + } + break; + case 'W': + util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1); + if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') { + con_out("Possible warnings:\n"); + for (itr = 0; itr < COUNT_WARNINGS; ++itr) { + util_strtononcmd(opts_warn_list[itr].name, buffer, sizeof(buffer)); + con_out(" -W%s\n", buffer); + if (itr == WARN_DEBUG) + con_out(" Warnings included by -Wall:\n"); + } + exit(0); + } + else if (!strcmp(argv[0]+2, "NO_ERROR") || + !strcmp(argv[0]+2, "NO_ERROR_ALL")) + { + for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr) + opts.werror[itr] = 0; + break; + } + else if (!strcmp(argv[0]+2, "ERROR") || + !strcmp(argv[0]+2, "ERROR_ALL")) + { + opts_backup_non_Werror_all(); + for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr) + opts.werror[itr] = 0xFFFFFFFFL; + opts_restore_non_Werror_all(); + break; + } + else if (!strcmp(argv[0]+2, "NONE")) { + for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr) + opts.warn[itr] = 0; + break; + } + else if (!strcmp(argv[0]+2, "ALL")) { + opts_backup_non_Wall(); + for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr) + opts.warn[itr] = 0xFFFFFFFFL; + opts_restore_non_Wall(); + break; + } + else if (!strncmp(argv[0]+2, "ERROR_", 6)) { + if (!opts_setwerror(argv[0]+8, true)) { + con_out("unknown warning: %s\n", argv[0]+2); + return false; + } + } + else if (!strncmp(argv[0]+2, "NO_ERROR_", 9)) { + if (!opts_setwerror(argv[0]+11, false)) { + con_out("unknown warning: %s\n", argv[0]+2); + return false; + } + } + else if (!strncmp(argv[0]+2, "NO_", 3)) { + if (!opts_setwarn(argv[0]+5, false)) { + con_out("unknown warning: %s\n", argv[0]+2); + return false; + } + } + else if (!opts_setwarn(argv[0]+2, true)) { + con_out("unknown warning: %s\n", argv[0]+2); + return false; + } + break; + + case 'O': + if (!options_witharg(&argc, &argv, &argarg)) { + con_out("option -O requires a numerical argument, or optimization name with an optional 'no-' prefix\n"); + return false; + } + if (util_isdigit(argarg[0])) { + uint32_t val = (uint32_t)strtol(argarg, NULL, 10); + OPTS_OPTION_U32(OPTION_O) = val; + opts_setoptimlevel(val); + } else { + util_strtocmd(argarg, argarg, strlen(argarg)+1); + if (!strcmp(argarg, "HELP")) { + con_out("Possible optimizations:\n"); + for (itr = 0; itr < COUNT_OPTIMIZATIONS; ++itr) { + util_strtononcmd(opts_opt_list[itr].name, buffer, sizeof(buffer)); + con_out(" -O%-20s (-O%u)\n", buffer, opts_opt_oflag[itr]); + } + exit(0); + } + else if (!strcmp(argarg, "ALL")) + opts_setoptimlevel(OPTS_OPTION_U32(OPTION_O) = 9999); + else if (!strncmp(argarg, "NO_", 3)) { + /* constant folding cannot be turned off for obvious reasons */ + if (!strcmp(argarg, "NO_CONST_FOLD") || !opts_setoptim(argarg+3, false)) { + con_out("unknown optimization: %s\n", argarg+3); + return false; + } + } + else { + if (!opts_setoptim(argarg, true)) { + con_out("unknown optimization: %s\n", argarg); + return false; + } + } + } + break; + + case 'o': + if (!options_witharg(&argc, &argv, &argarg)) { + con_out("option -o requires an argument: the output file name\n"); + return false; + } + OPTS_OPTION_STR(OPTION_OUTPUT) = argarg; + opts_output_wasset = true; + break; + + case 'a': + case 's': + item.type = argv[0][1] == 'a' ? TYPE_ASM : TYPE_SRC; + if (!options_witharg(&argc, &argv, &argarg)) { + con_out("option -a requires a filename %s\n", + (argv[0][1] == 'a' ? "containing QC-asm" : "containing a progs.src formatted list")); + return false; + } + item.filename = argarg; + vec_push(items, item); + break; + + case '-': + if (!argv[0][2]) { + /* anything following -- is considered a non-option argument */ + argend = true; + break; + } + /* All long options without arguments */ + else if (!strcmp(argv[0]+2, "help")) { + usage(); + exit(0); + } + else if (!strcmp(argv[0]+2, "version")) { + version(); + exit(0); + } + else if (!strcmp(argv[0]+2, "quiet")) { + OPTS_OPTION_BOOL(OPTION_QUIET) = true; + break; + } + else if (!strcmp(argv[0]+2, "add-info")) { + OPTS_OPTION_BOOL(OPTION_ADD_INFO) = true; + break; + } + else { + /* All long options with arguments */ + if (options_long_witharg("output", &argc, &argv, &argarg)) { + OPTS_OPTION_STR(OPTION_OUTPUT) = argarg; + opts_output_wasset = true; + } else { + con_out("Unknown parameter: %s\n", argv[0]); + return false; + } + } + break; + + default: + con_out("Unknown parameter: %s\n", argv[0]); + return false; + } + } + else + { + /* it's a QC filename */ + item.filename = argv[0]; + item.type = TYPE_QC; + vec_push(items, item); + } + } + opts_ini_init(config); + return true; +} + +/* returns the line number, or -1 on error */ +static bool progs_nextline(char **out, size_t *alen, FILE *src) { + int len; + char *line; + char *start; + char *end; + + line = *out; + len = util_getline(&line, alen, src); + if (len == -1) + return false; + + /* start at first non-blank */ + for (start = line; util_isspace(*start); ++start) {} + /* end at the first non-blank */ + for (end = start; *end && !util_isspace(*end); ++end) {} + + *out = line; + /* move the actual filename to the beginning */ + while (start != end) { + *line++ = *start++; + } + *line = 0; + return true; +} + +int main(int argc, char **argv) { + size_t itr; + int retval = 0; + bool operators_free = false; + bool progs_src = false; + FILE *outfile = NULL; + struct parser_s *parser = NULL; + struct ftepp_s *ftepp = NULL; + + app_name = argv[0]; + con_init (); + opts_init("progs.dat", COMPILER_QCC, (1024 << 3)); + + util_seed(time(0)); + + if (!options_parse(argc, argv)) { + return usage(); + } + + if (OPTS_FLAG(TRUE_EMPTY_STRINGS) && OPTS_FLAG(FALSE_EMPTY_STRINGS)) { + con_err("-ftrue-empty-strings and -ffalse-empty-strings are mutually exclusive"); + exit(EXIT_FAILURE); + } + + /* the standard decides which set of operators to use */ + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) { + operators = c_operators; + operator_count = GMQCC_ARRAY_COUNT(c_operators); + } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) { + operators = fte_operators; + operator_count = GMQCC_ARRAY_COUNT(fte_operators); + } else { + operators = qcc_operators; + operator_count = GMQCC_ARRAY_COUNT(qcc_operators); + } + + if (operators == fte_operators) { + /* fix ternary? */ + if (OPTS_FLAG(CORRECT_TERNARY)) { + oper_info *newops; + if (operators[operator_count-2].id != opid1(',') || + operators[operator_count-1].id != opid2(':','?')) + { + con_err("internal error: operator precedence table wasn't updated correctly!\n"); + exit(EXIT_FAILURE); + } + operators_free = true; + newops = (oper_info*)mem_a(sizeof(operators[0]) * operator_count); + memcpy(newops, operators, sizeof(operators[0]) * operator_count); + memcpy(&newops[operator_count-2], &operators[operator_count-1], sizeof(newops[0])); + memcpy(&newops[operator_count-1], &operators[operator_count-2], sizeof(newops[0])); + newops[operator_count-2].prec = newops[operator_count-1].prec+1; + operators = newops; + } + } + + if (OPTS_OPTION_BOOL(OPTION_DUMP)) { + for (itr = 0; itr < COUNT_FLAGS; ++itr) + con_out("Flag %s = %i\n", opts_flag_list[itr].name, OPTS_FLAG(itr)); + for (itr = 0; itr < COUNT_WARNINGS; ++itr) + con_out("Warning %s = %i\n", opts_warn_list[itr].name, OPTS_WARN(itr)); + + con_out("output = %s\n", OPTS_OPTION_STR(OPTION_OUTPUT)); + con_out("optimization level = %u\n", OPTS_OPTION_U32(OPTION_O)); + con_out("standard = %u\n", OPTS_OPTION_U32(OPTION_STANDARD)); + } + + if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { + if (opts_output_wasset) { + outfile = fopen(OPTS_OPTION_STR(OPTION_OUTPUT), "wb"); + if (!outfile) { + con_err("failed to open `%s` for writing\n", OPTS_OPTION_STR(OPTION_OUTPUT)); + retval = 1; + goto cleanup; + } + } + else { + outfile = con_default_out(); + } + } + + if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { + if (!(parser = parser_create())) { + con_err("failed to initialize parser\n"); + retval = 1; + goto cleanup; + } + } + + if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) { + if (!(ftepp = ftepp_create())) { + con_err("failed to initialize parser\n"); + retval = 1; + goto cleanup; + } + } + + /* add macros */ + if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) { + for (itr = 0; itr < vec_size(ppems); itr++) { + ftepp_add_macro(ftepp, ppems[itr].name, ppems[itr].value); + mem_d(ppems[itr].name); + + /* can be null */ + if (ppems[itr].value) + mem_d(ppems[itr].value); + } + } + + if (!vec_size(items)) { + FILE *src; + char *line = NULL; + size_t linelen = 0; + bool hasline = false; + + progs_src = true; + + src = fopen(OPTS_OPTION_STR(OPTION_PROGSRC), "rb"); + if (!src) { + con_err("failed to open `%s` for reading\n", OPTS_OPTION_STR(OPTION_PROGSRC)); + retval = 1; + goto cleanup; + } + + while (progs_nextline(&line, &linelen, src)) { + argitem item; + + if (!line[0] || (line[0] == '/' && line[1] == '/')) + continue; + + if (hasline) { + item.filename = util_strdup(line); + item.type = TYPE_QC; + vec_push(items, item); + } else if (!opts_output_wasset) { + OPTS_OPTION_DUP(OPTION_OUTPUT) = util_strdup(line); + hasline = true; + } + } + + fclose(src); + mem_d(line); + } + + if (vec_size(items)) { + if (!OPTS_OPTION_BOOL(OPTION_QUIET) && + !OPTS_OPTION_BOOL(OPTION_PP_ONLY)) + { + con_out("Mode: %s\n", (progs_src ? "progs.src" : "manual")); + con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items)); + } + + for (itr = 0; itr < vec_size(items); ++itr) { + if (!OPTS_OPTION_BOOL(OPTION_QUIET) && + !OPTS_OPTION_BOOL(OPTION_PP_ONLY)) + { + con_out(" item: %s (%s)\n", + items[itr].filename, + ( (items[itr].type == TYPE_QC ? "qc" : + (items[itr].type == TYPE_ASM ? "asm" : + (items[itr].type == TYPE_SRC ? "progs.src" : + ("unknown")))))); + } + + if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { + const char *out; + if (!ftepp_preprocess_file(ftepp, items[itr].filename)) { + retval = 1; + goto cleanup; + } + out = ftepp_get(ftepp); + if (out) + fprintf(outfile, "%s", out); + ftepp_flush(ftepp); + } + else { + if (OPTS_FLAG(FTEPP)) { + const char *data; + if (!ftepp_preprocess_file(ftepp, items[itr].filename)) { + retval = 1; + goto cleanup; + } + data = ftepp_get(ftepp); + if (vec_size(data)) { + if (!parser_compile_string(parser, items[itr].filename, data, vec_size(data))) { + retval = 1; + goto cleanup; + } + } + ftepp_flush(ftepp); + } + else { + if (!parser_compile_file(parser, items[itr].filename)) { + retval = 1; + goto cleanup; + } + } + } + + if (progs_src) { + mem_d(items[itr].filename); + items[itr].filename = NULL; + } + } + + ftepp_finish(ftepp); + ftepp = NULL; + if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) { + if (!parser_finish(parser, OPTS_OPTION_STR(OPTION_OUTPUT))) { + retval = 1; + goto cleanup; + } + } + } + +cleanup: + if (ftepp) + ftepp_finish(ftepp); + con_close(); + vec_free(items); + vec_free(ppems); + + if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) + if(parser) parser_cleanup(parser); + + /* free allocated option strings */ + for (itr = 0; itr < OPTION_COUNT; itr++) + if (OPTS_OPTION_DUPED(itr)) + mem_d(OPTS_OPTION_STR(itr)); + + if (operators_free) + mem_d((void*)operators); + + lex_cleanup(); + + if (!retval && compile_errors) + retval = 1; + return retval; +} diff --git a/opts.c b/opts.c deleted file mode 100644 index bc72824..0000000 --- a/opts.c +++ /dev/null @@ -1,424 +0,0 @@ -#include -#include - -#include "gmqcc.h" - -const unsigned int opts_opt_oflag[COUNT_OPTIMIZATIONS+1] = { -# define GMQCC_TYPE_OPTIMIZATIONS -# define GMQCC_DEFINE_FLAG(NAME, MIN_O) MIN_O, -# include "opts.def" - 0 -}; - -const opts_flag_def_t opts_opt_list[COUNT_OPTIMIZATIONS+1] = { -# define GMQCC_TYPE_OPTIMIZATIONS -# define GMQCC_DEFINE_FLAG(NAME, MIN_O) { #NAME, LONGBIT(OPTIM_##NAME) }, -# include "opts.def" - { NULL, LONGBIT(0) } -}; - -const opts_flag_def_t opts_warn_list[COUNT_WARNINGS+1] = { -# define GMQCC_TYPE_WARNS -# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(WARN_##X) }, -# include "opts.def" - { NULL, LONGBIT(0) } -}; - -const opts_flag_def_t opts_flag_list[COUNT_FLAGS+1] = { -# define GMQCC_TYPE_FLAGS -# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(X) }, -# include "opts.def" - { NULL, LONGBIT(0) } -}; - -unsigned int opts_optimizationcount[COUNT_OPTIMIZATIONS]; -opts_cmd_t opts; /* command line options */ - -static void opts_setdefault(void) { - memset(&opts, 0, sizeof(opts_cmd_t)); - OPTS_OPTION_STR(OPTION_PROGSRC) = "progs.src"; - - /* warnings */ - opts_set(opts.warn, WARN_UNUSED_VARIABLE, true); - opts_set(opts.warn, WARN_USED_UNINITIALIZED, true); - opts_set(opts.warn, WARN_UNKNOWN_CONTROL_SEQUENCE, true); - opts_set(opts.warn, WARN_EXTENSIONS, true); - opts_set(opts.warn, WARN_FIELD_REDECLARED, true); - opts_set(opts.warn, WARN_MISSING_RETURN_VALUES, true); - opts_set(opts.warn, WARN_INVALID_PARAMETER_COUNT, true); - opts_set(opts.warn, WARN_LOCAL_CONSTANTS, true); - opts_set(opts.warn, WARN_VOID_VARIABLES, true); - opts_set(opts.warn, WARN_IMPLICIT_FUNCTION_POINTER, true); - opts_set(opts.warn, WARN_VARIADIC_FUNCTION, true); - opts_set(opts.warn, WARN_FRAME_MACROS, true); - opts_set(opts.warn, WARN_EFFECTLESS_STATEMENT, true); - opts_set(opts.warn, WARN_END_SYS_FIELDS, true); - opts_set(opts.warn, WARN_ASSIGN_FUNCTION_TYPES, true); - opts_set(opts.warn, WARN_CPP, true); - opts_set(opts.warn, WARN_MULTIFILE_IF, true); - opts_set(opts.warn, WARN_DOUBLE_DECLARATION, true); - opts_set(opts.warn, WARN_CONST_VAR, true); - opts_set(opts.warn, WARN_MULTIBYTE_CHARACTER, true); - opts_set(opts.warn, WARN_UNKNOWN_PRAGMAS, true); - opts_set(opts.warn, WARN_UNREACHABLE_CODE, true); - opts_set(opts.warn, WARN_UNKNOWN_ATTRIBUTE, true); - opts_set(opts.warn, WARN_RESERVED_NAMES, true); - opts_set(opts.warn, WARN_UNINITIALIZED_CONSTANT, true); - opts_set(opts.warn, WARN_DEPRECATED, true); - opts_set(opts.warn, WARN_PARENTHESIS, true); - 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); - opts_set(opts.flags, CORRECT_TERNARY, true); - opts_set(opts.flags, BAIL_ON_WERROR, true); - 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() { - size_t i; - for (i = 0; i <= WARN_DEBUG; ++i) - opts_set(opts.warn_backup, i, OPTS_WARN(i)); -} - -void opts_restore_non_Wall() { - size_t i; - for (i = 0; i <= WARN_DEBUG; ++i) - opts_set(opts.warn, i, OPTS_GENERIC(opts.warn_backup, i)); -} - -void opts_backup_non_Werror_all() { - size_t i; - for (i = 0; i <= WARN_DEBUG; ++i) - opts_set(opts.werror_backup, i, OPTS_WERROR(i)); -} - -void opts_restore_non_Werror_all() { - size_t i; - for (i = 0; i <= WARN_DEBUG; ++i) - opts_set(opts.werror, i, OPTS_GENERIC(opts.werror_backup, i)); -} - -void opts_init(const char *output, int standard, size_t arraysize) { - opts_setdefault(); - - OPTS_OPTION_STR(OPTION_OUTPUT) = output; - OPTS_OPTION_U32(OPTION_STANDARD) = standard; - OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE) = arraysize; -} - -static bool opts_setflag_all(const char *name, bool on, uint32_t *flags, const opts_flag_def_t *list, size_t listsize) { - size_t i; - - for (i = 0; i < listsize; ++i) { - if (!strcmp(name, list[i].name)) { - longbit lb = list[i].bit; - - if (on) - flags[lb.idx] |= (1<<(lb.bit)); - else - flags[lb.idx] &= ~(1<<(lb.bit)); - - return true; - } - } - return false; -} -bool opts_setflag (const char *name, bool on) { - return opts_setflag_all(name, on, opts.flags, opts_flag_list, COUNT_FLAGS); -} -bool opts_setwarn (const char *name, bool on) { - return opts_setflag_all(name, on, opts.warn, opts_warn_list, COUNT_WARNINGS); -} -bool opts_setwerror(const char *name, bool on) { - return opts_setflag_all(name, on, opts.werror, opts_warn_list, COUNT_WARNINGS); -} -bool opts_setoptim (const char *name, bool on) { - return opts_setflag_all(name, on, opts.optimization, opts_opt_list, COUNT_OPTIMIZATIONS); -} - -void opts_set(uint32_t *flags, size_t idx, bool on) { - longbit lb; - LONGBIT_SET(lb, idx); - - if (on) - flags[lb.idx] |= (1<<(lb.bit)); - else - flags[lb.idx] &= ~(1<<(lb.bit)); -} - -void opts_setoptimlevel(unsigned int level) { - size_t i; - for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) - opts_set(opts.optimization, i, level >= opts_opt_oflag[i]); - - if (!level) - opts.optimizeoff = true; -} - -/* - * Standard configuration parser and subsystem. Yes, optionally you may - * create ini files or cfg (the driver accepts both) for a project opposed - * to supplying just a progs.src (since you also may need to supply command - * line arguments or set the options of the compiler) [which cannot be done - * from a progs.src. - */ -static char *opts_ini_rstrip(char *s) { - char *p = s + strlen(s) - 1; - while (p > s && util_isspace(*p)) - *p = '\0', p--; - return s; -} - -static char *opts_ini_lskip(const char *s) { - while (*s && util_isspace(*s)) - s++; - return (char*)s; -} - -static char *opts_ini_next(const char *s, char c) { - bool last = false; - while (*s && *s != c && !(last && *s == ';')) - last = !!util_isspace(*s), s++; - - return (char*)s; -} - -static size_t opts_ini_parse ( - FILE *filehandle, - char *(*loadhandle)(const char *, const char *, const char *, char **), - char **errorhandle, - char **parse_file -) { - size_t linesize; - size_t lineno = 1; - size_t error = 0; - char *line = NULL; - char section_data[2048] = ""; - char oldname_data[2048] = ""; - - /* parsing and reading variables */ - char *parse_beg; - char *parse_end; - char *read_name; - char *read_value; - - while (util_getline(&line, &linesize, filehandle) != EOF) { - parse_beg = line; - - /* handle BOM */ - if (lineno == 1 && ( - (unsigned char)parse_beg[0] == 0xEF && - (unsigned char)parse_beg[1] == 0xBB && - (unsigned char)parse_beg[2] == 0xBF - ) - ) { - parse_beg ++; /* 0xEF */ - parse_beg ++; /* 0xBB */ - parse_beg ++; /* 0xBF */ - } - - if (*(parse_beg = opts_ini_lskip(opts_ini_rstrip(parse_beg))) == ';' || *parse_beg == '#') { - /* ignore '#' is a perl extension */ - } else if (*parse_beg == '[') { - /* section found */ - if (*(parse_end = opts_ini_next(parse_beg + 1, ']')) == ']') { - * parse_end = '\0'; /* terminate bro */ - util_strncpy(section_data, parse_beg + 1, sizeof(section_data)); - section_data[sizeof(section_data) - 1] = '\0'; - *oldname_data = '\0'; - } else if (!error) { - /* otherwise set error to the current line number */ - error = lineno; - } - } 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, ':'); - - if (*parse_end == '=' || *parse_end == ':') { - *parse_end = '\0'; /* terminate bro */ - read_name = opts_ini_rstrip(parse_beg); - read_value = opts_ini_lskip(parse_end + 1); - if (*(parse_end = opts_ini_next(read_value, '\0')) == ';') - * parse_end = '\0'; - opts_ini_rstrip(read_value); - - /* valid name value pair, lets call down to handler */ - 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, 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; - } - } - lineno++; - } - mem_d(line); - return error; - -} - -/* - * returns true/false for a char that contains ("true" or "false" or numeric 0/1) - */ -static bool opts_ini_bool(const char *value) { - if (!strcmp(value, "true")) return true; - if (!strcmp(value, "false")) return false; - return !!strtol(value, NULL, 10); -} - -static char *opts_ini_load(const char *section, const char *name, const char *value, char **parse_file) { - char *error = NULL; - bool found = false; - - /* - * undef all of these because they may still be defined like in my - * case they where. - */ - #undef GMQCC_TYPE_FLAGS - #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"; - FILE *file = fopen(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); - fclose(file); - } - } - - /* flags */ - #define GMQCC_TYPE_FLAGS - #define GMQCC_DEFINE_FLAG(X) \ - if (!strcmp(section, "flags") && !strcmp(name, #X)) { \ - opts_set(opts.flags, X, opts_ini_bool(value)); \ - found = true; \ - } - #include "opts.def" - - /* warnings */ - #define GMQCC_TYPE_WARNS - #define GMQCC_DEFINE_FLAG(X) \ - if (!strcmp(section, "warnings") && !strcmp(name, #X)) { \ - opts_set(opts.warn, WARN_##X, opts_ini_bool(value)); \ - found = true; \ - } - #include "opts.def" - - /* Werror-individuals */ - #define GMQCC_TYPE_WARNS - #define GMQCC_DEFINE_FLAG(X) \ - if (!strcmp(section, "errors") && !strcmp(name, #X)) { \ - opts_set(opts.werror, WARN_##X, opts_ini_bool(value)); \ - found = true; \ - } - #include "opts.def" - - /* optimizations */ - #define GMQCC_TYPE_OPTIMIZATIONS - #define GMQCC_DEFINE_FLAG(X,Y) \ - if (!strcmp(section, "optimizations") && !strcmp(name, #X)) { \ - opts_set(opts.optimization, OPTIM_##X, opts_ini_bool(value)); \ - found = true; \ - } - #include "opts.def" - - /* nothing was found ever! */ - if (!found) { - if (strcmp(section, "includes") && - strcmp(section, "flags") && - strcmp(section, "warnings") && - strcmp(section, "optimizations")) - { - static const char *invalid_section = "invalid_section `"; - vec_append(error, strlen(invalid_section), invalid_section); - vec_append(error, strlen(section), section); - 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_append(error, strlen(in_section), in_section); - vec_append(error, strlen(section), section); - 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; -} - -/* - * Actual loading subsystem, this finds the ini or cfg file, and properly - * loads it and executes it to set compiler options. - */ -void opts_ini_init(const char *file) { - /* - * Possible matches are: - * gmqcc.ini - * gmqcc.cfg - */ - char *error = NULL; - char *parse_file = NULL; - size_t line; - FILE *ini; - - if (!file) { - /* try ini */ - if (!(ini = fopen((file = "gmqcc.ini"), "r"))) - /* try cfg */ - if (!(ini = fopen((file = "gmqcc.cfg"), "r"))) - return; - } else if (!(ini = fopen(file, "r"))) - return; - - con_out("found ini file `%s`\n", file); - - 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, parse_file, line, 0 /*TODO: column for ini error*/, "error", error); - vec_free(error); - } - mem_d(parse_file); - - fclose(ini); -} diff --git a/opts.cpp b/opts.cpp new file mode 100644 index 0000000..bc72824 --- /dev/null +++ b/opts.cpp @@ -0,0 +1,424 @@ +#include +#include + +#include "gmqcc.h" + +const unsigned int opts_opt_oflag[COUNT_OPTIMIZATIONS+1] = { +# define GMQCC_TYPE_OPTIMIZATIONS +# define GMQCC_DEFINE_FLAG(NAME, MIN_O) MIN_O, +# include "opts.def" + 0 +}; + +const opts_flag_def_t opts_opt_list[COUNT_OPTIMIZATIONS+1] = { +# define GMQCC_TYPE_OPTIMIZATIONS +# define GMQCC_DEFINE_FLAG(NAME, MIN_O) { #NAME, LONGBIT(OPTIM_##NAME) }, +# include "opts.def" + { NULL, LONGBIT(0) } +}; + +const opts_flag_def_t opts_warn_list[COUNT_WARNINGS+1] = { +# define GMQCC_TYPE_WARNS +# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(WARN_##X) }, +# include "opts.def" + { NULL, LONGBIT(0) } +}; + +const opts_flag_def_t opts_flag_list[COUNT_FLAGS+1] = { +# define GMQCC_TYPE_FLAGS +# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(X) }, +# include "opts.def" + { NULL, LONGBIT(0) } +}; + +unsigned int opts_optimizationcount[COUNT_OPTIMIZATIONS]; +opts_cmd_t opts; /* command line options */ + +static void opts_setdefault(void) { + memset(&opts, 0, sizeof(opts_cmd_t)); + OPTS_OPTION_STR(OPTION_PROGSRC) = "progs.src"; + + /* warnings */ + opts_set(opts.warn, WARN_UNUSED_VARIABLE, true); + opts_set(opts.warn, WARN_USED_UNINITIALIZED, true); + opts_set(opts.warn, WARN_UNKNOWN_CONTROL_SEQUENCE, true); + opts_set(opts.warn, WARN_EXTENSIONS, true); + opts_set(opts.warn, WARN_FIELD_REDECLARED, true); + opts_set(opts.warn, WARN_MISSING_RETURN_VALUES, true); + opts_set(opts.warn, WARN_INVALID_PARAMETER_COUNT, true); + opts_set(opts.warn, WARN_LOCAL_CONSTANTS, true); + opts_set(opts.warn, WARN_VOID_VARIABLES, true); + opts_set(opts.warn, WARN_IMPLICIT_FUNCTION_POINTER, true); + opts_set(opts.warn, WARN_VARIADIC_FUNCTION, true); + opts_set(opts.warn, WARN_FRAME_MACROS, true); + opts_set(opts.warn, WARN_EFFECTLESS_STATEMENT, true); + opts_set(opts.warn, WARN_END_SYS_FIELDS, true); + opts_set(opts.warn, WARN_ASSIGN_FUNCTION_TYPES, true); + opts_set(opts.warn, WARN_CPP, true); + opts_set(opts.warn, WARN_MULTIFILE_IF, true); + opts_set(opts.warn, WARN_DOUBLE_DECLARATION, true); + opts_set(opts.warn, WARN_CONST_VAR, true); + opts_set(opts.warn, WARN_MULTIBYTE_CHARACTER, true); + opts_set(opts.warn, WARN_UNKNOWN_PRAGMAS, true); + opts_set(opts.warn, WARN_UNREACHABLE_CODE, true); + opts_set(opts.warn, WARN_UNKNOWN_ATTRIBUTE, true); + opts_set(opts.warn, WARN_RESERVED_NAMES, true); + opts_set(opts.warn, WARN_UNINITIALIZED_CONSTANT, true); + opts_set(opts.warn, WARN_DEPRECATED, true); + opts_set(opts.warn, WARN_PARENTHESIS, true); + 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); + opts_set(opts.flags, CORRECT_TERNARY, true); + opts_set(opts.flags, BAIL_ON_WERROR, true); + 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() { + size_t i; + for (i = 0; i <= WARN_DEBUG; ++i) + opts_set(opts.warn_backup, i, OPTS_WARN(i)); +} + +void opts_restore_non_Wall() { + size_t i; + for (i = 0; i <= WARN_DEBUG; ++i) + opts_set(opts.warn, i, OPTS_GENERIC(opts.warn_backup, i)); +} + +void opts_backup_non_Werror_all() { + size_t i; + for (i = 0; i <= WARN_DEBUG; ++i) + opts_set(opts.werror_backup, i, OPTS_WERROR(i)); +} + +void opts_restore_non_Werror_all() { + size_t i; + for (i = 0; i <= WARN_DEBUG; ++i) + opts_set(opts.werror, i, OPTS_GENERIC(opts.werror_backup, i)); +} + +void opts_init(const char *output, int standard, size_t arraysize) { + opts_setdefault(); + + OPTS_OPTION_STR(OPTION_OUTPUT) = output; + OPTS_OPTION_U32(OPTION_STANDARD) = standard; + OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE) = arraysize; +} + +static bool opts_setflag_all(const char *name, bool on, uint32_t *flags, const opts_flag_def_t *list, size_t listsize) { + size_t i; + + for (i = 0; i < listsize; ++i) { + if (!strcmp(name, list[i].name)) { + longbit lb = list[i].bit; + + if (on) + flags[lb.idx] |= (1<<(lb.bit)); + else + flags[lb.idx] &= ~(1<<(lb.bit)); + + return true; + } + } + return false; +} +bool opts_setflag (const char *name, bool on) { + return opts_setflag_all(name, on, opts.flags, opts_flag_list, COUNT_FLAGS); +} +bool opts_setwarn (const char *name, bool on) { + return opts_setflag_all(name, on, opts.warn, opts_warn_list, COUNT_WARNINGS); +} +bool opts_setwerror(const char *name, bool on) { + return opts_setflag_all(name, on, opts.werror, opts_warn_list, COUNT_WARNINGS); +} +bool opts_setoptim (const char *name, bool on) { + return opts_setflag_all(name, on, opts.optimization, opts_opt_list, COUNT_OPTIMIZATIONS); +} + +void opts_set(uint32_t *flags, size_t idx, bool on) { + longbit lb; + LONGBIT_SET(lb, idx); + + if (on) + flags[lb.idx] |= (1<<(lb.bit)); + else + flags[lb.idx] &= ~(1<<(lb.bit)); +} + +void opts_setoptimlevel(unsigned int level) { + size_t i; + for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) + opts_set(opts.optimization, i, level >= opts_opt_oflag[i]); + + if (!level) + opts.optimizeoff = true; +} + +/* + * Standard configuration parser and subsystem. Yes, optionally you may + * create ini files or cfg (the driver accepts both) for a project opposed + * to supplying just a progs.src (since you also may need to supply command + * line arguments or set the options of the compiler) [which cannot be done + * from a progs.src. + */ +static char *opts_ini_rstrip(char *s) { + char *p = s + strlen(s) - 1; + while (p > s && util_isspace(*p)) + *p = '\0', p--; + return s; +} + +static char *opts_ini_lskip(const char *s) { + while (*s && util_isspace(*s)) + s++; + return (char*)s; +} + +static char *opts_ini_next(const char *s, char c) { + bool last = false; + while (*s && *s != c && !(last && *s == ';')) + last = !!util_isspace(*s), s++; + + return (char*)s; +} + +static size_t opts_ini_parse ( + FILE *filehandle, + char *(*loadhandle)(const char *, const char *, const char *, char **), + char **errorhandle, + char **parse_file +) { + size_t linesize; + size_t lineno = 1; + size_t error = 0; + char *line = NULL; + char section_data[2048] = ""; + char oldname_data[2048] = ""; + + /* parsing and reading variables */ + char *parse_beg; + char *parse_end; + char *read_name; + char *read_value; + + while (util_getline(&line, &linesize, filehandle) != EOF) { + parse_beg = line; + + /* handle BOM */ + if (lineno == 1 && ( + (unsigned char)parse_beg[0] == 0xEF && + (unsigned char)parse_beg[1] == 0xBB && + (unsigned char)parse_beg[2] == 0xBF + ) + ) { + parse_beg ++; /* 0xEF */ + parse_beg ++; /* 0xBB */ + parse_beg ++; /* 0xBF */ + } + + if (*(parse_beg = opts_ini_lskip(opts_ini_rstrip(parse_beg))) == ';' || *parse_beg == '#') { + /* ignore '#' is a perl extension */ + } else if (*parse_beg == '[') { + /* section found */ + if (*(parse_end = opts_ini_next(parse_beg + 1, ']')) == ']') { + * parse_end = '\0'; /* terminate bro */ + util_strncpy(section_data, parse_beg + 1, sizeof(section_data)); + section_data[sizeof(section_data) - 1] = '\0'; + *oldname_data = '\0'; + } else if (!error) { + /* otherwise set error to the current line number */ + error = lineno; + } + } 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, ':'); + + if (*parse_end == '=' || *parse_end == ':') { + *parse_end = '\0'; /* terminate bro */ + read_name = opts_ini_rstrip(parse_beg); + read_value = opts_ini_lskip(parse_end + 1); + if (*(parse_end = opts_ini_next(read_value, '\0')) == ';') + * parse_end = '\0'; + opts_ini_rstrip(read_value); + + /* valid name value pair, lets call down to handler */ + 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, 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; + } + } + lineno++; + } + mem_d(line); + return error; + +} + +/* + * returns true/false for a char that contains ("true" or "false" or numeric 0/1) + */ +static bool opts_ini_bool(const char *value) { + if (!strcmp(value, "true")) return true; + if (!strcmp(value, "false")) return false; + return !!strtol(value, NULL, 10); +} + +static char *opts_ini_load(const char *section, const char *name, const char *value, char **parse_file) { + char *error = NULL; + bool found = false; + + /* + * undef all of these because they may still be defined like in my + * case they where. + */ + #undef GMQCC_TYPE_FLAGS + #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"; + FILE *file = fopen(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); + fclose(file); + } + } + + /* flags */ + #define GMQCC_TYPE_FLAGS + #define GMQCC_DEFINE_FLAG(X) \ + if (!strcmp(section, "flags") && !strcmp(name, #X)) { \ + opts_set(opts.flags, X, opts_ini_bool(value)); \ + found = true; \ + } + #include "opts.def" + + /* warnings */ + #define GMQCC_TYPE_WARNS + #define GMQCC_DEFINE_FLAG(X) \ + if (!strcmp(section, "warnings") && !strcmp(name, #X)) { \ + opts_set(opts.warn, WARN_##X, opts_ini_bool(value)); \ + found = true; \ + } + #include "opts.def" + + /* Werror-individuals */ + #define GMQCC_TYPE_WARNS + #define GMQCC_DEFINE_FLAG(X) \ + if (!strcmp(section, "errors") && !strcmp(name, #X)) { \ + opts_set(opts.werror, WARN_##X, opts_ini_bool(value)); \ + found = true; \ + } + #include "opts.def" + + /* optimizations */ + #define GMQCC_TYPE_OPTIMIZATIONS + #define GMQCC_DEFINE_FLAG(X,Y) \ + if (!strcmp(section, "optimizations") && !strcmp(name, #X)) { \ + opts_set(opts.optimization, OPTIM_##X, opts_ini_bool(value)); \ + found = true; \ + } + #include "opts.def" + + /* nothing was found ever! */ + if (!found) { + if (strcmp(section, "includes") && + strcmp(section, "flags") && + strcmp(section, "warnings") && + strcmp(section, "optimizations")) + { + static const char *invalid_section = "invalid_section `"; + vec_append(error, strlen(invalid_section), invalid_section); + vec_append(error, strlen(section), section); + 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_append(error, strlen(in_section), in_section); + vec_append(error, strlen(section), section); + 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; +} + +/* + * Actual loading subsystem, this finds the ini or cfg file, and properly + * loads it and executes it to set compiler options. + */ +void opts_ini_init(const char *file) { + /* + * Possible matches are: + * gmqcc.ini + * gmqcc.cfg + */ + char *error = NULL; + char *parse_file = NULL; + size_t line; + FILE *ini; + + if (!file) { + /* try ini */ + if (!(ini = fopen((file = "gmqcc.ini"), "r"))) + /* try cfg */ + if (!(ini = fopen((file = "gmqcc.cfg"), "r"))) + return; + } else if (!(ini = fopen(file, "r"))) + return; + + con_out("found ini file `%s`\n", file); + + 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, parse_file, line, 0 /*TODO: column for ini error*/, "error", error); + vec_free(error); + } + mem_d(parse_file); + + fclose(ini); +} diff --git a/parser.c b/parser.c deleted file mode 100644 index 0370146..0000000 --- a/parser.c +++ /dev/null @@ -1,6380 +0,0 @@ -#include -#include - -#include "parser.h" - -#define PARSER_HT_LOCALS 2 -#define PARSER_HT_SIZE 512 -#define TYPEDEF_HT_SIZE 512 - -static void parser_enterblock(parser_t *parser); -static bool parser_leaveblock(parser_t *parser); -static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e); -static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e); -static bool parse_typedef(parser_t *parser); -static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring); -static ast_block* parse_block(parser_t *parser); -static bool parse_block_into(parser_t *parser, ast_block *block); -static bool parse_statement_or_block(parser_t *parser, ast_expression **out); -static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases); -static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels); -static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels); -static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname); -static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname); -static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg); - -static void parseerror(parser_t *parser, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - vcompile_error(parser->lex->tok.ctx, fmt, ap); - va_end(ap); -} - -/* returns true if it counts as an error */ -static bool GMQCC_WARN parsewarning(parser_t *parser, int warntype, const char *fmt, ...) -{ - bool r; - va_list ap; - va_start(ap, fmt); - r = vcompile_warning(parser->lex->tok.ctx, warntype, fmt, ap); - va_end(ap); - return r; -} - -/********************************************************************** - * parsing - */ - -static bool parser_next(parser_t *parser) -{ - /* lex_do kills the previous token */ - parser->tok = lex_do(parser->lex); - if (parser->tok == TOKEN_EOF) - return true; - if (parser->tok >= TOKEN_ERROR) { - parseerror(parser, "lex error"); - return false; - } - return true; -} - -#define parser_tokval(p) ((p)->lex->tok.value) -#define parser_token(p) (&((p)->lex->tok)) - -char *parser_strdup(const char *str) -{ - if (str && !*str) { - /* actually dup empty strings */ - char *out = (char*)mem_a(1); - *out = 0; - return out; - } - return util_strdup(str); -} - -static ast_expression* parser_find_field(parser_t *parser, const char *name) -{ - return ( ast_expression*)util_htget(parser->htfields, name); -} - -static ast_expression* parser_find_label(parser_t *parser, const char *name) -{ - size_t i; - for(i = 0; i < vec_size(parser->labels); i++) - if (!strcmp(parser->labels[i]->name, name)) - return (ast_expression*)parser->labels[i]; - return NULL; -} - -ast_expression* parser_find_global(parser_t *parser, const char *name) -{ - ast_expression *var = (ast_expression*)util_htget(parser->aliases, parser_tokval(parser)); - if (var) - return var; - return (ast_expression*)util_htget(parser->htglobals, name); -} - -static ast_expression* parser_find_param(parser_t *parser, const char *name) -{ - size_t i; - ast_value *fun; - if (!parser->function) - return NULL; - fun = parser->function->vtype; - for (i = 0; i < vec_size(fun->expression.params); ++i) { - if (!strcmp(fun->expression.params[i]->name, name)) - return (ast_expression*)(fun->expression.params[i]); - } - return NULL; -} - -static ast_expression* parser_find_local(parser_t *parser, const char *name, size_t upto, bool *isparam) -{ - size_t i, hash; - ast_expression *e; - - hash = util_hthash(parser->htglobals, name); - - *isparam = false; - for (i = vec_size(parser->variables); i > upto;) { - --i; - if ( (e = (ast_expression*)util_htgeth(parser->variables[i], name, hash)) ) - return e; - } - *isparam = true; - return parser_find_param(parser, name); -} - -static ast_expression* parser_find_var(parser_t *parser, const char *name) -{ - bool dummy; - ast_expression *v; - v = parser_find_local(parser, name, 0, &dummy); - if (!v) v = parser_find_global(parser, name); - return v; -} - -static ast_value* parser_find_typedef(parser_t *parser, const char *name, size_t upto) -{ - size_t i, hash; - ast_value *e; - hash = util_hthash(parser->typedefs[0], name); - - for (i = vec_size(parser->typedefs); i > upto;) { - --i; - if ( (e = (ast_value*)util_htgeth(parser->typedefs[i], name, hash)) ) - return e; - } - return NULL; -} - -typedef struct -{ - size_t etype; /* 0 = expression, others are operators */ - bool isparen; - size_t off; - ast_expression *out; - ast_block *block; /* for commas and function calls */ - lex_ctx_t ctx; -} sy_elem; - -enum { - PAREN_EXPR, - PAREN_FUNC, - PAREN_INDEX, - PAREN_TERNARY1, - PAREN_TERNARY2 -}; -typedef struct -{ - sy_elem *out; - sy_elem *ops; - size_t *argc; - unsigned int *paren; -} shunt; - -static sy_elem syexp(lex_ctx_t ctx, ast_expression *v) { - sy_elem e; - e.etype = 0; - e.off = 0; - e.out = v; - e.block = NULL; - e.ctx = ctx; - e.isparen = false; - return e; -} - -static sy_elem syblock(lex_ctx_t ctx, ast_block *v) { - sy_elem e; - e.etype = 0; - e.off = 0; - e.out = (ast_expression*)v; - e.block = v; - e.ctx = ctx; - e.isparen = false; - return e; -} - -static sy_elem syop(lex_ctx_t ctx, const oper_info *op) { - sy_elem e; - e.etype = 1 + (op - operators); - e.off = 0; - e.out = NULL; - e.block = NULL; - e.ctx = ctx; - e.isparen = false; - return e; -} - -static sy_elem syparen(lex_ctx_t ctx, size_t off) { - sy_elem e; - e.etype = 0; - e.off = off; - e.out = NULL; - e.block = NULL; - e.ctx = ctx; - e.isparen = true; - return e; -} - -/* With regular precedence rules, ent.foo[n] is the same as (ent.foo)[n], - * so we need to rotate it to become ent.(foo[n]). - */ -static bool rotate_entfield_array_index_nodes(ast_expression **out) -{ - ast_array_index *index, *oldindex; - ast_entfield *entfield; - - ast_value *field; - ast_expression *sub; - ast_expression *entity; - - lex_ctx_t ctx = ast_ctx(*out); - - if (!ast_istype(*out, ast_array_index)) - return false; - index = (ast_array_index*)*out; - - if (!ast_istype(index->array, ast_entfield)) - return false; - entfield = (ast_entfield*)index->array; - - if (!ast_istype(entfield->field, ast_value)) - return false; - field = (ast_value*)entfield->field; - - sub = index->index; - entity = entfield->entity; - - oldindex = index; - - index = ast_array_index_new(ctx, (ast_expression*)field, sub); - entfield = ast_entfield_new(ctx, entity, (ast_expression*)index); - *out = (ast_expression*)entfield; - - oldindex->array = NULL; - oldindex->index = NULL; - ast_delete(oldindex); - - return true; -} - -static bool check_write_to(lex_ctx_t ctx, ast_expression *expr) -{ - if (ast_istype(expr, ast_value)) { - ast_value *val = (ast_value*)expr; - if (val->cvq == CV_CONST) { - if (val->name[0] == '#') { - compile_error(ctx, "invalid assignment to a literal constant"); - return false; - } - /* - * To work around quakeworld we must elide the error and make it - * a warning instead. - */ - if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) - compile_error(ctx, "assignment to constant `%s`", val->name); - else - (void)!compile_warning(ctx, WARN_CONST_OVERWRITE, "assignment to constant `%s`", val->name); - return false; - } - } - return true; -} - -static bool parser_sy_apply_operator(parser_t *parser, shunt *sy) -{ - const oper_info *op; - lex_ctx_t ctx; - ast_expression *out = NULL; - ast_expression *exprs[3]; - ast_block *blocks[3]; - ast_binstore *asbinstore; - size_t i, assignop, addop, subop; - qcint_t generated_op = 0; - - char ty1[1024]; - char ty2[1024]; - - if (!vec_size(sy->ops)) { - parseerror(parser, "internal error: missing operator"); - return false; - } - - if (vec_last(sy->ops).isparen) { - parseerror(parser, "unmatched parenthesis"); - return false; - } - - op = &operators[vec_last(sy->ops).etype - 1]; - ctx = vec_last(sy->ops).ctx; - - if (vec_size(sy->out) < op->operands) { - if (op->flags & OP_PREFIX) - compile_error(ctx, "expected expression after unary operator `%s`", op->op, (int)op->id); - else /* this should have errored previously already */ - compile_error(ctx, "expected expression after operator `%s`", op->op, (int)op->id); - return false; - } - - vec_shrinkby(sy->ops, 1); - - /* op(:?) has no input and no output */ - if (!op->operands) - return true; - - vec_shrinkby(sy->out, op->operands); - for (i = 0; i < op->operands; ++i) { - exprs[i] = sy->out[vec_size(sy->out)+i].out; - blocks[i] = sy->out[vec_size(sy->out)+i].block; - - if (exprs[i]->vtype == TYPE_NOEXPR && - !(i != 0 && op->id == opid2('?',':')) && - !(i == 1 && op->id == opid1('.'))) - { - if (ast_istype(exprs[i], ast_label)) - compile_error(ast_ctx(exprs[i]), "expected expression, got an unknown identifier"); - else - compile_error(ast_ctx(exprs[i]), "not an expression"); - (void)!compile_warning(ast_ctx(exprs[i]), WARN_DEBUG, "expression %u\n", (unsigned int)i); - } - } - - if (blocks[0] && !vec_size(blocks[0]->exprs) && op->id != opid1(',')) { - compile_error(ctx, "internal error: operator cannot be applied on empty blocks"); - return false; - } - -#define NotSameType(T) \ - (exprs[0]->vtype != exprs[1]->vtype || \ - exprs[0]->vtype != T) - - switch (op->id) - { - default: - compile_error(ctx, "internal error: unhandled operator: %s (%i)", op->op, (int)op->id); - return false; - - case opid1('.'): - if (exprs[0]->vtype == TYPE_VECTOR && - exprs[1]->vtype == TYPE_NOEXPR) - { - if (exprs[1] == (ast_expression*)parser->const_vec[0]) - out = (ast_expression*)ast_member_new(ctx, exprs[0], 0, NULL); - else if (exprs[1] == (ast_expression*)parser->const_vec[1]) - out = (ast_expression*)ast_member_new(ctx, exprs[0], 1, NULL); - else if (exprs[1] == (ast_expression*)parser->const_vec[2]) - out = (ast_expression*)ast_member_new(ctx, exprs[0], 2, NULL); - else { - compile_error(ctx, "access to invalid vector component"); - return false; - } - } - else if (exprs[0]->vtype == TYPE_ENTITY) { - if (exprs[1]->vtype != TYPE_FIELD) { - compile_error(ast_ctx(exprs[1]), "type error: right hand of member-operand should be an entity-field"); - return false; - } - out = (ast_expression*)ast_entfield_new(ctx, exprs[0], exprs[1]); - } - else if (exprs[0]->vtype == TYPE_VECTOR) { - compile_error(ast_ctx(exprs[1]), "vectors cannot be accessed this way"); - return false; - } - else { - compile_error(ast_ctx(exprs[1]), "type error: member-of operator on something that is not an entity or vector"); - return false; - } - break; - - case opid1('['): - if (exprs[0]->vtype != TYPE_ARRAY && - !(exprs[0]->vtype == TYPE_FIELD && - exprs[0]->next->vtype == TYPE_ARRAY)) - { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - compile_error(ast_ctx(exprs[0]), "cannot index value of type %s", ty1); - return false; - } - if (exprs[1]->vtype != TYPE_FLOAT) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - compile_error(ast_ctx(exprs[1]), "index must be of type float, not %s", ty1); - return false; - } - out = (ast_expression*)ast_array_index_new(ctx, exprs[0], exprs[1]); - rotate_entfield_array_index_nodes(&out); - break; - - case opid1(','): - if (vec_size(sy->paren) && vec_last(sy->paren) == PAREN_FUNC) { - vec_push(sy->out, syexp(ctx, exprs[0])); - vec_push(sy->out, syexp(ctx, exprs[1])); - vec_last(sy->argc)++; - return true; - } - if (blocks[0]) { - if (!ast_block_add_expr(blocks[0], exprs[1])) - return false; - } else { - blocks[0] = ast_block_new(ctx); - if (!ast_block_add_expr(blocks[0], exprs[0]) || - !ast_block_add_expr(blocks[0], exprs[1])) - { - return false; - } - } - ast_block_set_type(blocks[0], exprs[1]); - - vec_push(sy->out, syblock(ctx, blocks[0])); - return true; - - case opid2('+','P'): - out = exprs[0]; - break; - case opid2('-','P'): - if ((out = fold_op(parser->fold, op, exprs))) - break; - - if (exprs[0]->vtype != TYPE_FLOAT && - exprs[0]->vtype != TYPE_VECTOR) { - compile_error(ctx, "invalid types used in unary expression: cannot negate type %s", - type_name[exprs[0]->vtype]); - return false; - } - if (exprs[0]->vtype == TYPE_FLOAT) - out = (ast_expression*)ast_unary_new(ctx, VINSTR_NEG_F, exprs[0]); - else - out = (ast_expression*)ast_unary_new(ctx, VINSTR_NEG_V, exprs[0]); - break; - - case opid2('!','P'): - if (!(out = fold_op(parser->fold, op, exprs))) { - switch (exprs[0]->vtype) { - case TYPE_FLOAT: - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, exprs[0]); - break; - case TYPE_VECTOR: - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_V, exprs[0]); - break; - case TYPE_STRING: - if (OPTS_FLAG(TRUE_EMPTY_STRINGS)) - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, exprs[0]); - else - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_S, exprs[0]); - break; - /* we don't constant-fold NOT for these types */ - case TYPE_ENTITY: - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_ENT, exprs[0]); - break; - case TYPE_FUNCTION: - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_FNC, exprs[0]); - break; - default: - compile_error(ctx, "invalid types used in expression: cannot logically negate type %s", - type_name[exprs[0]->vtype]); - return false; - } - } - break; - - case opid1('+'): - if (exprs[0]->vtype != exprs[1]->vtype || - (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT) ) - { - compile_error(ctx, "invalid types used in expression: cannot add type %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) { - switch (exprs[0]->vtype) { - case TYPE_FLOAT: - out = fold_binary(ctx, INSTR_ADD_F, exprs[0], exprs[1]); - break; - case TYPE_VECTOR: - out = fold_binary(ctx, INSTR_ADD_V, exprs[0], exprs[1]); - break; - default: - compile_error(ctx, "invalid types used in expression: cannot add type %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - } - break; - case opid1('-'): - if (exprs[0]->vtype != exprs[1]->vtype || - (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT)) - { - compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s", - type_name[exprs[1]->vtype], - type_name[exprs[0]->vtype]); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) { - switch (exprs[0]->vtype) { - case TYPE_FLOAT: - out = fold_binary(ctx, INSTR_SUB_F, exprs[0], exprs[1]); - break; - case TYPE_VECTOR: - out = fold_binary(ctx, INSTR_SUB_V, exprs[0], exprs[1]); - break; - default: - compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s", - type_name[exprs[1]->vtype], - type_name[exprs[0]->vtype]); - return false; - } - } - break; - case opid1('*'): - if (exprs[0]->vtype != exprs[1]->vtype && - !(exprs[0]->vtype == TYPE_VECTOR && - exprs[1]->vtype == TYPE_FLOAT) && - !(exprs[1]->vtype == TYPE_VECTOR && - exprs[0]->vtype == TYPE_FLOAT) - ) - { - compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s", - type_name[exprs[1]->vtype], - type_name[exprs[0]->vtype]); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) { - switch (exprs[0]->vtype) { - case TYPE_FLOAT: - if (exprs[1]->vtype == TYPE_VECTOR) - out = fold_binary(ctx, INSTR_MUL_FV, exprs[0], exprs[1]); - else - out = fold_binary(ctx, INSTR_MUL_F, exprs[0], exprs[1]); - break; - case TYPE_VECTOR: - if (exprs[1]->vtype == TYPE_FLOAT) - out = fold_binary(ctx, INSTR_MUL_VF, exprs[0], exprs[1]); - else - out = fold_binary(ctx, INSTR_MUL_V, exprs[0], exprs[1]); - break; - default: - compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s", - type_name[exprs[1]->vtype], - type_name[exprs[0]->vtype]); - return false; - } - } - break; - - case opid1('/'): - if (exprs[1]->vtype != TYPE_FLOAT) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) { - if (exprs[0]->vtype == TYPE_FLOAT) - out = fold_binary(ctx, INSTR_DIV_F, exprs[0], exprs[1]); - else { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2); - return false; - } - } - break; - - case opid1('%'): - if (NotSameType(TYPE_FLOAT)) { - compile_error(ctx, "invalid types used in expression: cannot perform modulo operation between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } else if (!(out = fold_op(parser->fold, op, exprs))) { - /* generate a call to __builtin_mod */ - ast_expression *mod = intrin_func(parser->intrin, "mod"); - ast_call *call = NULL; - if (!mod) return false; /* can return null for missing floor */ - - call = ast_call_new(parser_ctx(parser), mod); - vec_push(call->params, exprs[0]); - vec_push(call->params, exprs[1]); - - out = (ast_expression*)call; - } - break; - - case opid2('%','='): - compile_error(ctx, "%= is unimplemented"); - return false; - - case opid1('|'): - case opid1('&'): - case opid1('^'): - if ( !(exprs[0]->vtype == TYPE_FLOAT && exprs[1]->vtype == TYPE_FLOAT) && - !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_FLOAT) && - !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_VECTOR)) - { - compile_error(ctx, "invalid types used in expression: cannot perform bit operations between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - - if (!(out = fold_op(parser->fold, op, exprs))) { - /* - * IF the first expression is float, the following will be too - * since scalar ^ vector is not allowed. - */ - if (exprs[0]->vtype == TYPE_FLOAT) { - out = fold_binary(ctx, - (op->id == opid1('^') ? VINSTR_BITXOR : op->id == opid1('|') ? INSTR_BITOR : INSTR_BITAND), - exprs[0], exprs[1]); - } else { - /* - * The first is a vector: vector is allowed to bitop with vector and - * with scalar, branch here for the second operand. - */ - if (exprs[1]->vtype == TYPE_VECTOR) { - /* - * Bitop all the values of the vector components against the - * vectors components in question. - */ - out = fold_binary(ctx, - (op->id == opid1('^') ? VINSTR_BITXOR_V : op->id == opid1('|') ? VINSTR_BITOR_V : VINSTR_BITAND_V), - exprs[0], exprs[1]); - } else { - out = fold_binary(ctx, - (op->id == opid1('^') ? VINSTR_BITXOR_VF : op->id == opid1('|') ? VINSTR_BITOR_VF : VINSTR_BITAND_VF), - exprs[0], exprs[1]); - } - } - } - break; - - case opid2('<','<'): - case opid2('>','>'): - if (NotSameType(TYPE_FLOAT)) { - compile_error(ctx, "invalid types used in expression: cannot perform shift between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - - if (!(out = fold_op(parser->fold, op, exprs))) { - ast_expression *shift = intrin_func(parser->intrin, (op->id == opid2('<','<')) ? "__builtin_lshift" : "__builtin_rshift"); - ast_call *call = ast_call_new(parser_ctx(parser), shift); - vec_push(call->params, exprs[0]); - vec_push(call->params, exprs[1]); - out = (ast_expression*)call; - } - break; - - case opid3('<','<','='): - case opid3('>','>','='): - if (NotSameType(TYPE_FLOAT)) { - compile_error(ctx, "invalid types used in expression: cannot perform shift operation between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - - if(!(out = fold_op(parser->fold, op, exprs))) { - ast_expression *shift = intrin_func(parser->intrin, (op->id == opid3('<','<','=')) ? "__builtin_lshift" : "__builtin_rshift"); - ast_call *call = ast_call_new(parser_ctx(parser), shift); - vec_push(call->params, exprs[0]); - vec_push(call->params, exprs[1]); - out = (ast_expression*)ast_store_new( - parser_ctx(parser), - INSTR_STORE_F, - exprs[0], - (ast_expression*)call - ); - } - - break; - - case opid2('|','|'): - generated_op += 1; /* INSTR_OR */ - case opid2('&','&'): - generated_op += INSTR_AND; - if (!(out = fold_op(parser->fold, op, exprs))) { - if (OPTS_FLAG(PERL_LOGIC) && !ast_compare_type(exprs[0], exprs[1])) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types for logical operation with -fperl-logic: %s and %s", ty1, ty2); - return false; - } - for (i = 0; i < 2; ++i) { - if (OPTS_FLAG(CORRECT_LOGIC) && exprs[i]->vtype == TYPE_VECTOR) { - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_V, exprs[i]); - if (!out) break; - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, out); - if (!out) break; - exprs[i] = out; out = NULL; - if (OPTS_FLAG(PERL_LOGIC)) { - /* here we want to keep the right expressions' type */ - break; - } - } - else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && exprs[i]->vtype == TYPE_STRING) { - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_S, exprs[i]); - if (!out) break; - out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, out); - if (!out) break; - exprs[i] = out; out = NULL; - if (OPTS_FLAG(PERL_LOGIC)) { - /* here we want to keep the right expressions' type */ - break; - } - } - } - out = fold_binary(ctx, generated_op, exprs[0], exprs[1]); - } - break; - - case opid2('?',':'): - if (vec_last(sy->paren) != PAREN_TERNARY2) { - compile_error(ctx, "mismatched parenthesis/ternary"); - return false; - } - vec_pop(sy->paren); - if (!ast_compare_type(exprs[1], exprs[2])) { - ast_type_to_string(exprs[1], ty1, sizeof(ty1)); - ast_type_to_string(exprs[2], ty2, sizeof(ty2)); - compile_error(ctx, "operands of ternary expression must have the same type, got %s and %s", ty1, ty2); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) - out = (ast_expression*)ast_ternary_new(ctx, exprs[0], exprs[1], exprs[2]); - break; - - case opid2('*', '*'): - if (NotSameType(TYPE_FLOAT)) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in exponentiation: %s and %s", - ty1, ty2); - return false; - } - - if (!(out = fold_op(parser->fold, op, exprs))) { - ast_call *gencall = ast_call_new(parser_ctx(parser), intrin_func(parser->intrin, "pow")); - vec_push(gencall->params, exprs[0]); - vec_push(gencall->params, exprs[1]); - out = (ast_expression*)gencall; - } - break; - - case opid2('>', '<'): - if (NotSameType(TYPE_VECTOR)) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in cross product: %s and %s", - ty1, ty2); - return false; - } - - if (!(out = fold_op(parser->fold, op, exprs))) { - out = fold_binary( - parser_ctx(parser), - VINSTR_CROSS, - exprs[0], - exprs[1] - ); - } - - break; - - case opid3('<','=','>'): /* -1, 0, or 1 */ - if (NotSameType(TYPE_FLOAT)) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in comparision: %s and %s", - ty1, ty2); - - return false; - } - - if (!(out = fold_op(parser->fold, op, exprs))) { - /* This whole block is NOT fold_binary safe */ - ast_binary *eq = ast_binary_new(ctx, INSTR_EQ_F, exprs[0], exprs[1]); - - eq->refs = AST_REF_NONE; - - /* if (lt) { */ - out = (ast_expression*)ast_ternary_new(ctx, - (ast_expression*)ast_binary_new(ctx, INSTR_LT, exprs[0], exprs[1]), - /* out = -1 */ - (ast_expression*)parser->fold->imm_float[2], - /* } else { */ - /* if (eq) { */ - (ast_expression*)ast_ternary_new(ctx, (ast_expression*)eq, - /* out = 0 */ - (ast_expression*)parser->fold->imm_float[0], - /* } else { */ - /* out = 1 */ - (ast_expression*)parser->fold->imm_float[1] - /* } */ - ) - /* } */ - ); - - } - break; - - case opid1('>'): - generated_op += 1; /* INSTR_GT */ - case opid1('<'): - generated_op += 1; /* INSTR_LT */ - case opid2('>', '='): - generated_op += 1; /* INSTR_GE */ - case opid2('<', '='): - generated_op += INSTR_LE; - if (NotSameType(TYPE_FLOAT)) { - compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) - out = fold_binary(ctx, generated_op, exprs[0], exprs[1]); - break; - case opid2('!', '='): - if (exprs[0]->vtype != exprs[1]->vtype) { - compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) - out = fold_binary(ctx, type_ne_instr[exprs[0]->vtype], exprs[0], exprs[1]); - break; - case opid2('=', '='): - if (exprs[0]->vtype != exprs[1]->vtype) { - compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) - out = fold_binary(ctx, type_eq_instr[exprs[0]->vtype], exprs[0], exprs[1]); - break; - - case opid1('='): - if (ast_istype(exprs[0], ast_entfield)) { - ast_expression *field = ((ast_entfield*)exprs[0])->field; - if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) && - exprs[0]->vtype == TYPE_FIELD && - exprs[0]->next->vtype == TYPE_VECTOR) - { - assignop = type_storep_instr[TYPE_VECTOR]; - } - else - assignop = type_storep_instr[exprs[0]->vtype]; - if (assignop == VINSTR_END || !ast_compare_type(field->next, exprs[1])) - { - ast_type_to_string(field->next, ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) && - field->next->vtype == TYPE_FUNCTION && - exprs[1]->vtype == TYPE_FUNCTION) - { - (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES, - "invalid types in assignment: cannot assign %s to %s", ty2, ty1); - } - else - compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1); - } - } - else - { - if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) && - exprs[0]->vtype == TYPE_FIELD && - exprs[0]->next->vtype == TYPE_VECTOR) - { - assignop = type_store_instr[TYPE_VECTOR]; - } - else { - assignop = type_store_instr[exprs[0]->vtype]; - } - - if (assignop == VINSTR_END) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1); - } - else if (!ast_compare_type(exprs[0], exprs[1])) - { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) && - exprs[0]->vtype == TYPE_FUNCTION && - exprs[1]->vtype == TYPE_FUNCTION) - { - (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES, - "invalid types in assignment: cannot assign %s to %s", ty2, ty1); - } - else - compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1); - } - } - (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'): - case opid3('-','-','P'): - /* prefix ++ */ - if (exprs[0]->vtype != TYPE_FLOAT) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - compile_error(ast_ctx(exprs[0]), "invalid type for prefix increment: %s", ty1); - return false; - } - if (op->id == opid3('+','+','P')) - addop = INSTR_ADD_F; - else - addop = INSTR_SUB_F; - (void)check_write_to(ast_ctx(exprs[0]), exprs[0]); - if (ast_istype(exprs[0], ast_entfield)) { - out = (ast_expression*)ast_binstore_new(ctx, INSTR_STOREP_F, addop, - exprs[0], - (ast_expression*)parser->fold->imm_float[1]); - } else { - out = (ast_expression*)ast_binstore_new(ctx, INSTR_STORE_F, addop, - exprs[0], - (ast_expression*)parser->fold->imm_float[1]); - } - break; - case opid3('S','+','+'): - case opid3('S','-','-'): - /* prefix ++ */ - if (exprs[0]->vtype != TYPE_FLOAT) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - compile_error(ast_ctx(exprs[0]), "invalid type for suffix increment: %s", ty1); - return false; - } - if (op->id == opid3('S','+','+')) { - addop = INSTR_ADD_F; - subop = INSTR_SUB_F; - } else { - addop = INSTR_SUB_F; - subop = INSTR_ADD_F; - } - (void)check_write_to(ast_ctx(exprs[0]), exprs[0]); - if (ast_istype(exprs[0], ast_entfield)) { - out = (ast_expression*)ast_binstore_new(ctx, INSTR_STOREP_F, addop, - exprs[0], - (ast_expression*)parser->fold->imm_float[1]); - } else { - out = (ast_expression*)ast_binstore_new(ctx, INSTR_STORE_F, addop, - exprs[0], - (ast_expression*)parser->fold->imm_float[1]); - } - if (!out) - return false; - out = fold_binary(ctx, subop, - out, - (ast_expression*)parser->fold->imm_float[1]); - - break; - case opid2('+','='): - case opid2('-','='): - if (exprs[0]->vtype != exprs[1]->vtype || - (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT) ) - { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s", - ty1, ty2); - return false; - } - (void)check_write_to(ctx, exprs[0]); - if (ast_istype(exprs[0], ast_entfield)) - assignop = type_storep_instr[exprs[0]->vtype]; - else - assignop = type_store_instr[exprs[0]->vtype]; - switch (exprs[0]->vtype) { - case TYPE_FLOAT: - out = (ast_expression*)ast_binstore_new(ctx, assignop, - (op->id == opid2('+','=') ? INSTR_ADD_F : INSTR_SUB_F), - exprs[0], exprs[1]); - break; - case TYPE_VECTOR: - out = (ast_expression*)ast_binstore_new(ctx, assignop, - (op->id == opid2('+','=') ? INSTR_ADD_V : INSTR_SUB_V), - exprs[0], exprs[1]); - break; - default: - compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - }; - break; - case opid2('*','='): - case opid2('/','='): - if (exprs[1]->vtype != TYPE_FLOAT || - !(exprs[0]->vtype == TYPE_FLOAT || - exprs[0]->vtype == TYPE_VECTOR)) - { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in expression: %s and %s", - ty1, ty2); - return false; - } - (void)check_write_to(ctx, exprs[0]); - if (ast_istype(exprs[0], ast_entfield)) - assignop = type_storep_instr[exprs[0]->vtype]; - else - assignop = type_store_instr[exprs[0]->vtype]; - switch (exprs[0]->vtype) { - case TYPE_FLOAT: - out = (ast_expression*)ast_binstore_new(ctx, assignop, - (op->id == opid2('*','=') ? INSTR_MUL_F : INSTR_DIV_F), - exprs[0], exprs[1]); - break; - case TYPE_VECTOR: - if (op->id == opid2('*','=')) { - out = (ast_expression*)ast_binstore_new(ctx, assignop, INSTR_MUL_VF, - exprs[0], exprs[1]); - } else { - out = fold_binary(ctx, INSTR_DIV_F, - (ast_expression*)parser->fold->imm_float[1], - exprs[1]); - if (!out) { - compile_error(ctx, "internal error: failed to generate division"); - return false; - } - out = (ast_expression*)ast_binstore_new(ctx, assignop, INSTR_MUL_VF, - exprs[0], out); - } - break; - default: - compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - }; - break; - case opid2('&','='): - case opid2('|','='): - case opid2('^','='): - if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in expression: %s and %s", - ty1, ty2); - return false; - } - (void)check_write_to(ctx, exprs[0]); - if (ast_istype(exprs[0], ast_entfield)) - assignop = type_storep_instr[exprs[0]->vtype]; - else - assignop = type_store_instr[exprs[0]->vtype]; - if (exprs[0]->vtype == TYPE_FLOAT) - out = (ast_expression*)ast_binstore_new(ctx, assignop, - (op->id == opid2('^','=') ? VINSTR_BITXOR : op->id == opid2('&','=') ? INSTR_BITAND : INSTR_BITOR), - exprs[0], exprs[1]); - else - out = (ast_expression*)ast_binstore_new(ctx, assignop, - (op->id == opid2('^','=') ? VINSTR_BITXOR_V : op->id == opid2('&','=') ? VINSTR_BITAND_V : VINSTR_BITOR_V), - exprs[0], exprs[1]); - break; - case opid3('&','~','='): - /* This is like: a &= ~(b); - * But QC has no bitwise-not, so we implement it as - * a -= a & (b); - */ - if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) { - ast_type_to_string(exprs[0], ty1, sizeof(ty1)); - ast_type_to_string(exprs[1], ty2, sizeof(ty2)); - compile_error(ctx, "invalid types used in expression: %s and %s", - ty1, ty2); - return false; - } - if (ast_istype(exprs[0], ast_entfield)) - assignop = type_storep_instr[exprs[0]->vtype]; - else - assignop = type_store_instr[exprs[0]->vtype]; - if (exprs[0]->vtype == TYPE_FLOAT) - out = fold_binary(ctx, INSTR_BITAND, exprs[0], exprs[1]); - else - out = fold_binary(ctx, VINSTR_BITAND_V, exprs[0], exprs[1]); - if (!out) - return false; - (void)check_write_to(ctx, exprs[0]); - if (exprs[0]->vtype == TYPE_FLOAT) - asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_F, exprs[0], out); - else - asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_V, exprs[0], out); - asbinstore->keep_dest = true; - 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)); - compile_error(ast_ctx(exprs[0]), "invalid type for bit not: %s", ty1); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) { - if (exprs[0]->vtype == TYPE_FLOAT) { - out = fold_binary(ctx, INSTR_SUB_F, (ast_expression*)parser->fold->imm_float[2], exprs[0]); - } else { - out = fold_binary(ctx, INSTR_SUB_V, (ast_expression*)parser->fold->imm_vector[1], exprs[0]); - } - } - break; - } -#undef NotSameType - if (!out) { - compile_error(ctx, "failed to apply operator %s", op->op); - return false; - } - - vec_push(sy->out, syexp(ctx, out)); - return true; -} - -static bool parser_close_call(parser_t *parser, shunt *sy) -{ - /* was a function call */ - ast_expression *fun; - ast_value *funval = NULL; - ast_call *call; - - size_t fid; - size_t paramcount, i; - bool fold = true; - - fid = vec_last(sy->ops).off; - vec_shrinkby(sy->ops, 1); - - /* out[fid] is the function - * everything above is parameters... - */ - if (!vec_size(sy->argc)) { - parseerror(parser, "internal error: no argument counter available"); - return false; - } - - paramcount = vec_last(sy->argc); - vec_pop(sy->argc); - - if (vec_size(sy->out) < fid) { - parseerror(parser, "internal error: broken function call%lu < %lu+%lu\n", - (unsigned long)vec_size(sy->out), - (unsigned long)fid, - (unsigned long)paramcount); - return false; - } - - /* - * TODO handle this at the intrinsic level with an ast_intrinsic - * node and codegen. - */ - if ((fun = sy->out[fid].out) == intrin_debug_typestring(parser->intrin)) { - char ty[1024]; - if (fid+2 != vec_size(sy->out) || - vec_last(sy->out).block) - { - parseerror(parser, "intrinsic __builtin_debug_typestring requires exactly 1 parameter"); - return false; - } - ast_type_to_string(vec_last(sy->out).out, ty, sizeof(ty)); - ast_unref(vec_last(sy->out).out); - sy->out[fid] = syexp(ast_ctx(vec_last(sy->out).out), - (ast_expression*)fold_constgen_string(parser->fold, ty, false)); - vec_shrinkby(sy->out, 1); - return true; - } - - /* - * Now we need to determine if the function that is being called is - * an intrinsic so we can evaluate if the arguments to it are constant - * and than fruitfully fold them. - */ -#define fold_can_1(X) \ - (ast_istype(((ast_expression*)(X)), ast_value) && (X)->hasvalue && ((X)->cvq == CV_CONST) && \ - ((ast_expression*)(X))->vtype != TYPE_FUNCTION) - - if (fid + 1 < vec_size(sy->out)) - ++paramcount; - - for (i = 0; i < paramcount; ++i) { - if (!fold_can_1((ast_value*)sy->out[fid + 1 + i].out)) { - fold = false; - break; - } - } - - /* - * All is well which ends well, if we make it into here we can ignore the - * intrinsic call and just evaluate it i.e constant fold it. - */ - if (fold && ast_istype(fun, ast_value) && ((ast_value*)fun)->intrinsic) { - ast_expression **exprs = NULL; - ast_expression *foldval = NULL; - - for (i = 0; i < paramcount; i++) - vec_push(exprs, sy->out[fid+1 + i].out); - - if (!(foldval = intrin_fold(parser->intrin, (ast_value*)fun, exprs))) { - vec_free(exprs); - goto fold_leave; - } - - /* - * Blub: what sorts of unreffing and resizing of - * sy->out should I be doing here? - */ - sy->out[fid] = syexp(foldval->node.context, foldval); - vec_shrinkby(sy->out, paramcount); - vec_free(exprs); - - return true; - } - - fold_leave: - call = ast_call_new(sy->ops[vec_size(sy->ops)].ctx, fun); - - if (!call) - return false; - - if (fid+1 + paramcount != vec_size(sy->out)) { - parseerror(parser, "internal error: parameter count mismatch: (%lu+1+%lu), %lu", - (unsigned long)fid, (unsigned long)paramcount, (unsigned long)vec_size(sy->out)); - return false; - } - - for (i = 0; i < paramcount; ++i) - vec_push(call->params, sy->out[fid+1 + i].out); - vec_shrinkby(sy->out, paramcount); - (void)!ast_call_check_types(call, parser->function->vtype->expression.varparam); - if (parser->max_param_count < paramcount) - parser->max_param_count = paramcount; - - if (ast_istype(fun, ast_value)) { - funval = (ast_value*)fun; - 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, false); - } - } - - /* overwrite fid, the function, with a call */ - sy->out[fid] = syexp(call->expression.node.context, (ast_expression*)call); - - if (fun->vtype != TYPE_FUNCTION) { - parseerror(parser, "not a function (%s)", type_name[fun->vtype]); - return false; - } - - if (!fun->next) { - parseerror(parser, "could not determine function return type"); - return false; - } else { - ast_value *fval = (ast_istype(fun, ast_value) ? ((ast_value*)fun) : NULL); - - if (fun->flags & AST_FLAG_DEPRECATED) { - if (!fval) { - return !parsewarning(parser, WARN_DEPRECATED, - "call to function (which is marked deprecated)\n", - "-> it has been declared here: %s:%i", - ast_ctx(fun).file, ast_ctx(fun).line); - } - if (!fval->desc) { - return !parsewarning(parser, WARN_DEPRECATED, - "call to `%s` (which is marked deprecated)\n" - "-> `%s` declared here: %s:%i", - fval->name, fval->name, ast_ctx(fun).file, ast_ctx(fun).line); - } - return !parsewarning(parser, WARN_DEPRECATED, - "call to `%s` (deprecated: %s)\n" - "-> `%s` declared here: %s:%i", - fval->name, fval->desc, fval->name, ast_ctx(fun).file, - ast_ctx(fun).line); - } - - if (vec_size(fun->params) != paramcount && - !((fun->flags & AST_FLAG_VARIADIC) && - vec_size(fun->params) < paramcount)) - { - const char *fewmany = (vec_size(fun->params) > paramcount) ? "few" : "many"; - if (fval) - return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT, - "too %s parameters for call to %s: expected %i, got %i\n" - " -> `%s` has been declared here: %s:%i", - fewmany, fval->name, (int)vec_size(fun->params), (int)paramcount, - fval->name, ast_ctx(fun).file, (int)ast_ctx(fun).line); - else - return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT, - "too %s parameters for function call: expected %i, got %i\n" - " -> it has been declared here: %s:%i", - fewmany, (int)vec_size(fun->params), (int)paramcount, - ast_ctx(fun).file, (int)ast_ctx(fun).line); - } - } - - return true; -} - -static bool parser_close_paren(parser_t *parser, shunt *sy) -{ - if (!vec_size(sy->ops)) { - parseerror(parser, "unmatched closing paren"); - return false; - } - - while (vec_size(sy->ops)) { - if (vec_last(sy->ops).isparen) { - if (vec_last(sy->paren) == PAREN_FUNC) { - vec_pop(sy->paren); - if (!parser_close_call(parser, sy)) - return false; - break; - } - if (vec_last(sy->paren) == PAREN_EXPR) { - vec_pop(sy->paren); - if (!vec_size(sy->out)) { - compile_error(vec_last(sy->ops).ctx, "empty paren expression"); - vec_shrinkby(sy->ops, 1); - return false; - } - vec_shrinkby(sy->ops, 1); - break; - } - if (vec_last(sy->paren) == PAREN_INDEX) { - vec_pop(sy->paren); - /* pop off the parenthesis */ - vec_shrinkby(sy->ops, 1); - /* then apply the index operator */ - if (!parser_sy_apply_operator(parser, sy)) - return false; - break; - } - if (vec_last(sy->paren) == PAREN_TERNARY1) { - vec_last(sy->paren) = PAREN_TERNARY2; - /* pop off the parenthesis */ - vec_shrinkby(sy->ops, 1); - break; - } - compile_error(vec_last(sy->ops).ctx, "invalid parenthesis"); - return false; - } - if (!parser_sy_apply_operator(parser, sy)) - return false; - } - return true; -} - -static void parser_reclassify_token(parser_t *parser) -{ - size_t i; - if (parser->tok >= TOKEN_START) - return; - for (i = 0; i < operator_count; ++i) { - if (!strcmp(parser_tokval(parser), operators[i].op)) { - parser->tok = TOKEN_OPERATOR; - return; - } - } -} - -static ast_expression* parse_vararg_do(parser_t *parser) -{ - ast_expression *idx, *out; - ast_value *typevar; - ast_value *funtype = parser->function->vtype; - lex_ctx_t ctx = parser_ctx(parser); - - if (!parser->function->varargs) { - parseerror(parser, "function has no variable argument list"); - return NULL; - } - - if (!parser_next(parser) || parser->tok != '(') { - parseerror(parser, "expected parameter index and type in parenthesis"); - return NULL; - } - if (!parser_next(parser)) { - parseerror(parser, "error parsing parameter index"); - return NULL; - } - - idx = parse_expression_leave(parser, true, false, false); - if (!idx) - return NULL; - - if (parser->tok != ',') { - if (parser->tok != ')') { - ast_unref(idx); - parseerror(parser, "expected comma after parameter index"); - return NULL; - } - /* vararg piping: ...(start) */ - out = (ast_expression*)ast_argpipe_new(ctx, idx); - return out; - } - - if (!parser_next(parser) || (parser->tok != TOKEN_IDENT && parser->tok != TOKEN_TYPENAME)) { - ast_unref(idx); - parseerror(parser, "expected typename for vararg"); - return NULL; - } - - typevar = parse_typename(parser, NULL, NULL, NULL); - if (!typevar) { - ast_unref(idx); - return NULL; - } - - if (parser->tok != ')') { - ast_unref(idx); - ast_delete(typevar); - parseerror(parser, "expected closing paren"); - return NULL; - } - - if (funtype->expression.varparam && - !ast_compare_type((ast_expression*)typevar, (ast_expression*)funtype->expression.varparam)) - { - char ty1[1024]; - char ty2[1024]; - ast_type_to_string((ast_expression*)typevar, ty1, sizeof(ty1)); - ast_type_to_string((ast_expression*)funtype->expression.varparam, ty2, sizeof(ty2)); - compile_error(ast_ctx(typevar), - "function was declared to take varargs of type `%s`, requested type is: %s", - ty2, ty1); - } - - out = (ast_expression*)ast_array_index_new(ctx, (ast_expression*)(parser->function->varargs), idx); - ast_type_adopt(out, typevar); - ast_delete(typevar); - return out; -} - -static ast_expression* parse_vararg(parser_t *parser) -{ - bool old_noops = parser->lex->flags.noops; - - ast_expression *out; - - parser->lex->flags.noops = true; - out = parse_vararg_do(parser); - - parser->lex->flags.noops = old_noops; - return out; -} - -/* not to be exposed */ -bool ftepp_predef_exists(const char *name); -static bool parse_sya_operand(parser_t *parser, shunt *sy, bool with_labels) -{ - if (OPTS_FLAG(TRANSLATABLE_STRINGS) && - parser->tok == TOKEN_IDENT && - !strcmp(parser_tokval(parser), "_")) - { - /* a translatable string */ - ast_value *val; - - parser->lex->flags.noops = true; - if (!parser_next(parser) || parser->tok != '(') { - parseerror(parser, "use _(\"string\") to create a translatable string constant"); - return false; - } - parser->lex->flags.noops = false; - if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) { - parseerror(parser, "expected a constant string in translatable-string extension"); - return false; - } - val = (ast_value*)fold_constgen_string(parser->fold, parser_tokval(parser), true); - if (!val) - return false; - vec_push(sy->out, syexp(parser_ctx(parser), (ast_expression*)val)); - - if (!parser_next(parser) || parser->tok != ')') { - parseerror(parser, "expected closing paren after translatable string"); - return false; - } - return true; - } - else if (parser->tok == TOKEN_DOTS) - { - ast_expression *va; - if (!OPTS_FLAG(VARIADIC_ARGS)) { - parseerror(parser, "cannot access varargs (try -fvariadic-args)"); - return false; - } - va = parse_vararg(parser); - if (!va) - return false; - vec_push(sy->out, syexp(parser_ctx(parser), va)); - return true; - } - else if (parser->tok == TOKEN_FLOATCONST) { - 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), false); - if (!val) - return false; - vec_push(sy->out, syexp(parser_ctx(parser), val)); - return true; - } - else if (parser->tok == TOKEN_STRINGCONST) { - ast_expression *val = fold_constgen_string(parser->fold, parser_tokval(parser), false); - if (!val) - return false; - vec_push(sy->out, syexp(parser_ctx(parser), val)); - return true; - } - else if (parser->tok == TOKEN_VECTORCONST) { - ast_expression *val = fold_constgen_vector(parser->fold, parser_token(parser)->constval.v); - if (!val) - return false; - vec_push(sy->out, syexp(parser_ctx(parser), val)); - return true; - } - else if (parser->tok == TOKEN_IDENT) - { - const char *ctoken = parser_tokval(parser); - ast_expression *prev = vec_size(sy->out) ? vec_last(sy->out).out : NULL; - ast_expression *var; - /* a_vector.{x,y,z} */ - if (!vec_size(sy->ops) || - !vec_last(sy->ops).etype || - operators[vec_last(sy->ops).etype-1].id != opid1('.')) - { - /* When adding more intrinsics, fix the above condition */ - prev = NULL; - } - if (prev && prev->vtype == TYPE_VECTOR && ctoken[0] >= 'x' && ctoken[0] <= 'z' && !ctoken[1]) - { - var = (ast_expression*)parser->const_vec[ctoken[0]-'x']; - } else { - var = parser_find_var(parser, parser_tokval(parser)); - if (!var) - var = parser_find_field(parser, parser_tokval(parser)); - } - if (!var && with_labels) { - var = (ast_expression*)parser_find_label(parser, parser_tokval(parser)); - if (!with_labels) { - ast_label *lbl = ast_label_new(parser_ctx(parser), parser_tokval(parser), true); - var = (ast_expression*)lbl; - vec_push(parser->labels, lbl); - } - } - if (!var && !strcmp(parser_tokval(parser), "__FUNC__")) - var = (ast_expression*)fold_constgen_string(parser->fold, parser->function->name, false); - if (!var) { - /* - * now we try for the real intrinsic hashtable. If the string - * begins with __builtin, we simply skip past it, otherwise we - * use the identifier as is. - */ - if (!strncmp(parser_tokval(parser), "__builtin_", 10)) { - var = intrin_func(parser->intrin, parser_tokval(parser)); - } - - /* - * Try it again, intrin_func deals with the alias method as well - * the first one masks for __builtin though, we emit warning here. - */ - if (!var) { - if ((var = intrin_func(parser->intrin, parser_tokval(parser)))) { - (void)!compile_warning( - parser_ctx(parser), - WARN_BUILTINS, - "using implicitly defined builtin `__builtin_%s' for `%s'", - parser_tokval(parser), - parser_tokval(parser) - ); - } - } - - - if (!var) { - /* - * sometimes people use preprocessing predefs without enabling them - * i've done this thousands of times already myself. Lets check for - * it in the predef table. And diagnose it better :) - */ - if (!OPTS_FLAG(FTEPP_PREDEFS) && ftepp_predef_exists(parser_tokval(parser))) { - parseerror(parser, "unexpected identifier: %s (use -fftepp-predef to enable pre-defined macros)", parser_tokval(parser)); - return false; - } - - parseerror(parser, "unexpected identifier: %s", parser_tokval(parser)); - return false; - } - } - else - { - if (ast_istype(var, ast_value)) { - ((ast_value*)var)->uses++; - } - else if (ast_istype(var, ast_member)) { - ast_member *mem = (ast_member*)var; - if (ast_istype(mem->owner, ast_value)) - ((ast_value*)(mem->owner))->uses++; - } - } - vec_push(sy->out, syexp(parser_ctx(parser), var)); - return true; - } - parseerror(parser, "unexpected token `%s`", parser_tokval(parser)); - return false; -} - -static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels) -{ - ast_expression *expr = NULL; - shunt sy; - size_t i; - bool wantop = false; - /* only warn once about an assignment in a truth value because the current code - * would trigger twice on: if(a = b && ...), once for the if-truth-value, once for the && part - */ - bool warn_parenthesis = true; - - /* count the parens because an if starts with one, so the - * end of a condition is an unmatched closing paren - */ - int ternaries = 0; - - memset(&sy, 0, sizeof(sy)); - - parser->lex->flags.noops = false; - - parser_reclassify_token(parser); - - while (true) - { - if (parser->tok == TOKEN_TYPENAME) { - parseerror(parser, "unexpected typename `%s`", parser_tokval(parser)); - goto onerr; - } - - if (parser->tok == TOKEN_OPERATOR) - { - /* classify the operator */ - const oper_info *op; - const oper_info *olast = NULL; - size_t o; - for (o = 0; o < operator_count; ++o) { - if (((!(operators[o].flags & OP_PREFIX) == !!wantop)) && - /* !(operators[o].flags & OP_SUFFIX) && / * remove this */ - !strcmp(parser_tokval(parser), operators[o].op)) - { - break; - } - } - if (o == operator_count) { - compile_error(parser_ctx(parser), "unexpected operator: %s", parser_tokval(parser)); - goto onerr; - } - /* found an operator */ - op = &operators[o]; - - /* when declaring variables, a comma starts a new variable */ - if (op->id == opid1(',') && !vec_size(sy.paren) && stopatcomma) { - /* fixup the token */ - parser->tok = ','; - break; - } - - /* a colon without a pervious question mark cannot be a ternary */ - if (!ternaries && op->id == opid2(':','?')) { - parser->tok = ':'; - break; - } - - if (op->id == opid1(',')) { - if (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) { - (void)!parsewarning(parser, WARN_TERNARY_PRECEDENCE, "suggesting parenthesis around ternary expression"); - } - } - - if (vec_size(sy.ops) && !vec_last(sy.ops).isparen) - olast = &operators[vec_last(sy.ops).etype-1]; - - /* first only apply higher precedences, assoc_left+equal comes after we warn about precedence rules */ - while (olast && op->prec < olast->prec) - { - if (!parser_sy_apply_operator(parser, &sy)) - goto onerr; - if (vec_size(sy.ops) && !vec_last(sy.ops).isparen) - olast = &operators[vec_last(sy.ops).etype-1]; - else - olast = NULL; - } - -#define IsAssignOp(x) (\ - (x) == opid1('=') || \ - (x) == opid2('+','=') || \ - (x) == opid2('-','=') || \ - (x) == opid2('*','=') || \ - (x) == opid2('/','=') || \ - (x) == opid2('%','=') || \ - (x) == opid2('&','=') || \ - (x) == opid2('|','=') || \ - (x) == opid3('&','~','=') \ - ) - if (warn_parenthesis) { - if ( (olast && IsAssignOp(olast->id) && (op->id == opid2('&','&') || op->id == opid2('|','|'))) || - (olast && IsAssignOp(op->id) && (olast->id == opid2('&','&') || olast->id == opid2('|','|'))) || - (truthvalue && !vec_size(sy.paren) && IsAssignOp(op->id)) - ) - { - (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around assignment used as truth value"); - warn_parenthesis = false; - } - - if (olast && olast->id != op->id) { - if ((op->id == opid1('&') || op->id == opid1('|') || op->id == opid1('^')) && - (olast->id == opid1('&') || olast->id == opid1('|') || olast->id == opid1('^'))) - { - (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around bitwise operations"); - warn_parenthesis = false; - } - else if ((op->id == opid2('&','&') || op->id == opid2('|','|')) && - (olast->id == opid2('&','&') || olast->id == opid2('|','|'))) - { - (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around logical operations"); - warn_parenthesis = false; - } - } - } - - while (olast && ( - (op->prec < olast->prec) || - (op->assoc == ASSOC_LEFT && op->prec <= olast->prec) ) ) - { - if (!parser_sy_apply_operator(parser, &sy)) - goto onerr; - if (vec_size(sy.ops) && !vec_last(sy.ops).isparen) - olast = &operators[vec_last(sy.ops).etype-1]; - else - olast = NULL; - } - - if (op->id == opid1('(')) { - if (wantop) { - size_t sycount = vec_size(sy.out); - /* we expected an operator, this is the function-call operator */ - vec_push(sy.paren, PAREN_FUNC); - vec_push(sy.ops, syparen(parser_ctx(parser), sycount-1)); - vec_push(sy.argc, 0); - } else { - vec_push(sy.paren, PAREN_EXPR); - vec_push(sy.ops, syparen(parser_ctx(parser), 0)); - } - wantop = false; - } else if (op->id == opid1('[')) { - if (!wantop) { - parseerror(parser, "unexpected array subscript"); - goto onerr; - } - vec_push(sy.paren, PAREN_INDEX); - /* push both the operator and the paren, this makes life easier */ - vec_push(sy.ops, syop(parser_ctx(parser), op)); - vec_push(sy.ops, syparen(parser_ctx(parser), 0)); - wantop = false; - } else if (op->id == opid2('?',':')) { - vec_push(sy.ops, syop(parser_ctx(parser), op)); - vec_push(sy.ops, syparen(parser_ctx(parser), 0)); - wantop = false; - ++ternaries; - vec_push(sy.paren, PAREN_TERNARY1); - } else if (op->id == opid2(':','?')) { - if (!vec_size(sy.paren)) { - parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)"); - goto onerr; - } - if (vec_last(sy.paren) != PAREN_TERNARY1) { - parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)"); - goto onerr; - } - if (!parser_close_paren(parser, &sy)) - goto onerr; - vec_push(sy.ops, syop(parser_ctx(parser), op)); - wantop = false; - --ternaries; - } else { - vec_push(sy.ops, syop(parser_ctx(parser), op)); - wantop = !!(op->flags & OP_SUFFIX); - } - } - else if (parser->tok == ')') { - while (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) { - if (!parser_sy_apply_operator(parser, &sy)) - goto onerr; - } - if (!vec_size(sy.paren)) - break; - if (wantop) { - if (vec_last(sy.paren) == PAREN_TERNARY1) { - parseerror(parser, "mismatched parentheses (closing paren in ternary expression?)"); - goto onerr; - } - if (!parser_close_paren(parser, &sy)) - goto onerr; - } else { - /* must be a function call without parameters */ - if (vec_last(sy.paren) != PAREN_FUNC) { - parseerror(parser, "closing paren in invalid position"); - goto onerr; - } - if (!parser_close_paren(parser, &sy)) - goto onerr; - } - wantop = true; - } - else if (parser->tok == '(') { - parseerror(parser, "internal error: '(' should be classified as operator"); - goto onerr; - } - else if (parser->tok == '[') { - parseerror(parser, "internal error: '[' should be classified as operator"); - goto onerr; - } - else if (parser->tok == ']') { - while (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) { - if (!parser_sy_apply_operator(parser, &sy)) - goto onerr; - } - if (!vec_size(sy.paren)) - break; - if (vec_last(sy.paren) != PAREN_INDEX) { - parseerror(parser, "mismatched parentheses, unexpected ']'"); - goto onerr; - } - if (!parser_close_paren(parser, &sy)) - goto onerr; - wantop = true; - } - else if (!wantop) { - if (!parse_sya_operand(parser, &sy, with_labels)) - goto onerr; - wantop = true; - } - else { - /* in this case we might want to allow constant string concatenation */ - bool concatenated = false; - if (parser->tok == TOKEN_STRINGCONST && vec_size(sy.out)) { - ast_expression *lexpr = vec_last(sy.out).out; - if (ast_istype(lexpr, ast_value)) { - ast_value *last = (ast_value*)lexpr; - if (last->isimm == true && last->cvq == CV_CONST && - last->hasvalue && last->expression.vtype == TYPE_STRING) - { - char *newstr = NULL; - util_asprintf(&newstr, "%s%s", last->constval.vstring, parser_tokval(parser)); - vec_last(sy.out).out = (ast_expression*)fold_constgen_string(parser->fold, newstr, false); - mem_d(newstr); - concatenated = true; - } - } - } - if (!concatenated) { - parseerror(parser, "expected operator or end of statement"); - goto onerr; - } - } - - if (!parser_next(parser)) { - goto onerr; - } - if (parser->tok == ';' || - ((!vec_size(sy.paren) || (vec_size(sy.paren) == 1 && vec_last(sy.paren) == PAREN_TERNARY2)) && - (parser->tok == ']' || parser->tok == ')' || parser->tok == '}'))) - { - break; - } - } - - while (vec_size(sy.ops)) { - if (!parser_sy_apply_operator(parser, &sy)) - goto onerr; - } - - parser->lex->flags.noops = true; - if (vec_size(sy.out) != 1) { - parseerror(parser, "expression expected"); - expr = NULL; - } else - expr = sy.out[0].out; - vec_free(sy.out); - vec_free(sy.ops); - if (vec_size(sy.paren)) { - parseerror(parser, "internal error: vec_size(sy.paren) = %lu", (unsigned long)vec_size(sy.paren)); - return NULL; - } - vec_free(sy.paren); - vec_free(sy.argc); - return expr; - -onerr: - parser->lex->flags.noops = true; - for (i = 0; i < vec_size(sy.out); ++i) { - if (sy.out[i].out) - ast_unref(sy.out[i].out); - } - vec_free(sy.out); - vec_free(sy.ops); - vec_free(sy.paren); - vec_free(sy.argc); - return NULL; -} - -static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels) -{ - ast_expression *e = parse_expression_leave(parser, stopatcomma, false, with_labels); - if (!e) - return NULL; - if (parser->tok != ';') { - parseerror(parser, "semicolon expected after expression"); - ast_unref(e); - return NULL; - } - if (!parser_next(parser)) { - ast_unref(e); - return NULL; - } - return e; -} - -static void parser_enterblock(parser_t *parser) -{ - vec_push(parser->variables, util_htnew(PARSER_HT_SIZE)); - vec_push(parser->_blocklocals, vec_size(parser->_locals)); - vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE)); - vec_push(parser->_blocktypedefs, vec_size(parser->_typedefs)); - vec_push(parser->_block_ctx, parser_ctx(parser)); -} - -static bool parser_leaveblock(parser_t *parser) -{ - bool rv = true; - size_t locals, typedefs; - - if (vec_size(parser->variables) <= PARSER_HT_LOCALS) { - parseerror(parser, "internal error: parser_leaveblock with no block"); - return false; - } - - util_htdel(vec_last(parser->variables)); - - vec_pop(parser->variables); - if (!vec_size(parser->_blocklocals)) { - parseerror(parser, "internal error: parser_leaveblock with no block (2)"); - return false; - } - - locals = vec_last(parser->_blocklocals); - vec_pop(parser->_blocklocals); - while (vec_size(parser->_locals) != locals) { - ast_expression *e = vec_last(parser->_locals); - ast_value *v = (ast_value*)e; - vec_pop(parser->_locals); - if (ast_istype(e, ast_value) && !v->uses) { - if (compile_warning(ast_ctx(v), WARN_UNUSED_VARIABLE, "unused variable: `%s`", v->name)) - rv = false; - } - } - - typedefs = vec_last(parser->_blocktypedefs); - while (vec_size(parser->_typedefs) != typedefs) { - ast_delete(vec_last(parser->_typedefs)); - vec_pop(parser->_typedefs); - } - util_htdel(vec_last(parser->typedefs)); - vec_pop(parser->typedefs); - - vec_pop(parser->_block_ctx); - - return rv; -} - -static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e) -{ - vec_push(parser->_locals, e); - util_htset(vec_last(parser->variables), name, (void*)e); -} - -static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e) -{ - vec_push(parser->globals, e); - util_htset(parser->htglobals, name, e); -} - -static ast_expression* process_condition(parser_t *parser, ast_expression *cond, bool *_ifnot) -{ - bool ifnot = false; - ast_unary *unary; - ast_expression *prev; - - if (cond->vtype == TYPE_VOID || cond->vtype >= TYPE_VARIANT) { - char ty[1024]; - ast_type_to_string(cond, ty, sizeof(ty)); - compile_error(ast_ctx(cond), "invalid type for if() condition: %s", ty); - } - - if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && cond->vtype == TYPE_STRING) - { - prev = cond; - cond = (ast_expression*)ast_unary_new(ast_ctx(cond), INSTR_NOT_S, cond); - if (!cond) { - ast_unref(prev); - parseerror(parser, "internal error: failed to process condition"); - return NULL; - } - ifnot = !ifnot; - } - else if (OPTS_FLAG(CORRECT_LOGIC) && cond->vtype == TYPE_VECTOR) - { - /* vector types need to be cast to true booleans */ - ast_binary *bin = (ast_binary*)cond; - if (!OPTS_FLAG(PERL_LOGIC) || !ast_istype(cond, ast_binary) || !(bin->op == INSTR_AND || bin->op == INSTR_OR)) - { - /* in perl-logic, AND and OR take care of the -fcorrect-logic */ - prev = cond; - cond = (ast_expression*)ast_unary_new(ast_ctx(cond), INSTR_NOT_V, cond); - if (!cond) { - ast_unref(prev); - parseerror(parser, "internal error: failed to process condition"); - return NULL; - } - ifnot = !ifnot; - } - } - - unary = (ast_unary*)cond; - /* ast_istype dereferences cond, should test here for safety */ - while (cond && ast_istype(cond, ast_unary) && unary->op == INSTR_NOT_F) - { - cond = unary->operand; - unary->operand = NULL; - ast_delete(unary); - ifnot = !ifnot; - unary = (ast_unary*)cond; - } - - if (!cond) - parseerror(parser, "internal error: failed to process condition"); - - if (ifnot) *_ifnot = !*_ifnot; - return cond; -} - -static bool parse_if(parser_t *parser, ast_block *block, ast_expression **out) -{ - ast_ifthen *ifthen; - ast_expression *cond, *ontrue = NULL, *onfalse = NULL; - bool ifnot = false; - - lex_ctx_t ctx = parser_ctx(parser); - - (void)block; /* not touching */ - - /* skip the 'if', parse an optional 'not' and check for an opening paren */ - if (!parser_next(parser)) { - parseerror(parser, "expected condition or 'not'"); - return false; - } - if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "not")) { - ifnot = true; - if (!parser_next(parser)) { - parseerror(parser, "expected condition in parenthesis"); - return false; - } - } - if (parser->tok != '(') { - parseerror(parser, "expected 'if' condition in parenthesis"); - return false; - } - /* parse into the expression */ - if (!parser_next(parser)) { - parseerror(parser, "expected 'if' condition after opening paren"); - return false; - } - /* parse the condition */ - cond = parse_expression_leave(parser, false, true, false); - if (!cond) - return false; - /* closing paren */ - if (parser->tok != ')') { - parseerror(parser, "expected closing paren after 'if' condition"); - ast_unref(cond); - return false; - } - /* parse into the 'then' branch */ - if (!parser_next(parser)) { - parseerror(parser, "expected statement for on-true branch of 'if'"); - ast_unref(cond); - return false; - } - if (!parse_statement_or_block(parser, &ontrue)) { - ast_unref(cond); - return false; - } - if (!ontrue) - ontrue = (ast_expression*)ast_block_new(parser_ctx(parser)); - /* check for an else */ - if (!strcmp(parser_tokval(parser), "else")) { - /* parse into the 'else' branch */ - if (!parser_next(parser)) { - parseerror(parser, "expected on-false branch after 'else'"); - ast_delete(ontrue); - ast_unref(cond); - return false; - } - if (!parse_statement_or_block(parser, &onfalse)) { - ast_delete(ontrue); - ast_unref(cond); - return false; - } - } - - cond = process_condition(parser, cond, &ifnot); - if (!cond) { - if (ontrue) ast_delete(ontrue); - if (onfalse) ast_delete(onfalse); - return false; - } - - if (ifnot) - ifthen = ast_ifthen_new(ctx, cond, onfalse, ontrue); - else - ifthen = ast_ifthen_new(ctx, cond, ontrue, onfalse); - *out = (ast_expression*)ifthen; - return true; -} - -static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out); -static bool parse_while(parser_t *parser, ast_block *block, ast_expression **out) -{ - bool rv; - char *label = NULL; - - /* skip the 'while' and get the body */ - if (!parser_next(parser)) { - if (OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "expected loop label or 'while' condition in parenthesis"); - else - parseerror(parser, "expected 'while' condition in parenthesis"); - return false; - } - - if (parser->tok == ':') { - if (!OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "labeled loops not activated, try using -floop-labels"); - if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { - parseerror(parser, "expected loop label"); - return false; - } - label = util_strdup(parser_tokval(parser)); - if (!parser_next(parser)) { - mem_d(label); - parseerror(parser, "expected 'while' condition in parenthesis"); - return false; - } - } - - if (parser->tok != '(') { - parseerror(parser, "expected 'while' condition in parenthesis"); - return false; - } - - vec_push(parser->breaks, label); - vec_push(parser->continues, label); - - rv = parse_while_go(parser, block, out); - if (label) - mem_d(label); - if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) { - parseerror(parser, "internal error: label stack corrupted"); - rv = false; - ast_delete(*out); - *out = NULL; - } - else { - vec_pop(parser->breaks); - vec_pop(parser->continues); - } - return rv; -} - -static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out) -{ - ast_loop *aloop; - ast_expression *cond, *ontrue; - - bool ifnot = false; - - lex_ctx_t ctx = parser_ctx(parser); - - (void)block; /* not touching */ - - /* parse into the expression */ - if (!parser_next(parser)) { - parseerror(parser, "expected 'while' condition after opening paren"); - return false; - } - /* parse the condition */ - cond = parse_expression_leave(parser, false, true, false); - if (!cond) - return false; - /* closing paren */ - if (parser->tok != ')') { - parseerror(parser, "expected closing paren after 'while' condition"); - ast_unref(cond); - return false; - } - /* parse into the 'then' branch */ - if (!parser_next(parser)) { - parseerror(parser, "expected while-loop body"); - ast_unref(cond); - return false; - } - if (!parse_statement_or_block(parser, &ontrue)) { - ast_unref(cond); - return false; - } - - cond = process_condition(parser, cond, &ifnot); - if (!cond) { - ast_unref(ontrue); - return false; - } - aloop = ast_loop_new(ctx, NULL, cond, ifnot, NULL, false, NULL, ontrue); - *out = (ast_expression*)aloop; - return true; -} - -static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out); -static bool parse_dowhile(parser_t *parser, ast_block *block, ast_expression **out) -{ - bool rv; - char *label = NULL; - - /* skip the 'do' and get the body */ - if (!parser_next(parser)) { - if (OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "expected loop label or body"); - else - parseerror(parser, "expected loop body"); - return false; - } - - if (parser->tok == ':') { - if (!OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "labeled loops not activated, try using -floop-labels"); - if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { - parseerror(parser, "expected loop label"); - return false; - } - label = util_strdup(parser_tokval(parser)); - if (!parser_next(parser)) { - mem_d(label); - parseerror(parser, "expected loop body"); - return false; - } - } - - vec_push(parser->breaks, label); - vec_push(parser->continues, label); - - rv = parse_dowhile_go(parser, block, out); - if (label) - mem_d(label); - if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) { - parseerror(parser, "internal error: label stack corrupted"); - rv = false; - /* - * Test for NULL otherwise ast_delete dereferences null pointer - * and boom. - */ - if (*out) - ast_delete(*out); - *out = NULL; - } - else { - vec_pop(parser->breaks); - vec_pop(parser->continues); - } - return rv; -} - -static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out) -{ - ast_loop *aloop; - ast_expression *cond, *ontrue; - - bool ifnot = false; - - lex_ctx_t ctx = parser_ctx(parser); - - (void)block; /* not touching */ - - if (!parse_statement_or_block(parser, &ontrue)) - return false; - - /* expect the "while" */ - if (parser->tok != TOKEN_KEYWORD || - strcmp(parser_tokval(parser), "while")) - { - parseerror(parser, "expected 'while' and condition"); - ast_delete(ontrue); - return false; - } - - /* skip the 'while' and check for opening paren */ - if (!parser_next(parser) || parser->tok != '(') { - parseerror(parser, "expected 'while' condition in parenthesis"); - ast_delete(ontrue); - return false; - } - /* parse into the expression */ - if (!parser_next(parser)) { - parseerror(parser, "expected 'while' condition after opening paren"); - ast_delete(ontrue); - return false; - } - /* parse the condition */ - cond = parse_expression_leave(parser, false, true, false); - if (!cond) - return false; - /* closing paren */ - if (parser->tok != ')') { - parseerror(parser, "expected closing paren after 'while' condition"); - ast_delete(ontrue); - ast_unref(cond); - return false; - } - /* parse on */ - if (!parser_next(parser) || parser->tok != ';') { - parseerror(parser, "expected semicolon after condition"); - ast_delete(ontrue); - ast_unref(cond); - return false; - } - - if (!parser_next(parser)) { - parseerror(parser, "parse error"); - ast_delete(ontrue); - ast_unref(cond); - return false; - } - - cond = process_condition(parser, cond, &ifnot); - if (!cond) { - ast_delete(ontrue); - return false; - } - aloop = ast_loop_new(ctx, NULL, NULL, false, cond, ifnot, NULL, ontrue); - *out = (ast_expression*)aloop; - return true; -} - -static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out); -static bool parse_for(parser_t *parser, ast_block *block, ast_expression **out) -{ - bool rv; - char *label = NULL; - - /* skip the 'for' and check for opening paren */ - if (!parser_next(parser)) { - if (OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "expected loop label or 'for' expressions in parenthesis"); - else - parseerror(parser, "expected 'for' expressions in parenthesis"); - return false; - } - - if (parser->tok == ':') { - if (!OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "labeled loops not activated, try using -floop-labels"); - if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { - parseerror(parser, "expected loop label"); - return false; - } - label = util_strdup(parser_tokval(parser)); - if (!parser_next(parser)) { - mem_d(label); - parseerror(parser, "expected 'for' expressions in parenthesis"); - return false; - } - } - - if (parser->tok != '(') { - parseerror(parser, "expected 'for' expressions in parenthesis"); - return false; - } - - vec_push(parser->breaks, label); - vec_push(parser->continues, label); - - rv = parse_for_go(parser, block, out); - if (label) - mem_d(label); - if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) { - parseerror(parser, "internal error: label stack corrupted"); - rv = false; - ast_delete(*out); - *out = NULL; - } - else { - vec_pop(parser->breaks); - vec_pop(parser->continues); - } - return rv; -} -static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out) -{ - ast_loop *aloop; - ast_expression *initexpr, *cond, *increment, *ontrue; - ast_value *typevar; - - bool ifnot = false; - - lex_ctx_t ctx = parser_ctx(parser); - - parser_enterblock(parser); - - initexpr = NULL; - cond = NULL; - increment = NULL; - ontrue = NULL; - - /* parse into the expression */ - if (!parser_next(parser)) { - parseerror(parser, "expected 'for' initializer after opening paren"); - goto onerr; - } - - typevar = NULL; - 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, true, CV_VAR, typevar, false, false, 0, NULL)) - goto onerr; - } - else if (parser->tok != ';') - { - initexpr = parse_expression_leave(parser, false, false, false); - if (!initexpr) - goto onerr; - - /* move on to condition */ - if (parser->tok != ';') { - parseerror(parser, "expected semicolon after for-loop initializer"); - goto onerr; - } - - if (!parser_next(parser)) { - parseerror(parser, "expected for-loop condition"); - goto onerr; - } - } - - /* parse the condition */ - if (parser->tok != ';') { - cond = parse_expression_leave(parser, false, true, false); - if (!cond) - goto onerr; - } - - /* move on to incrementor */ - if (parser->tok != ';') { - parseerror(parser, "expected semicolon after for-loop initializer"); - goto onerr; - } - if (!parser_next(parser)) { - parseerror(parser, "expected for-loop condition"); - goto onerr; - } - - /* parse the incrementor */ - if (parser->tok != ')') { - lex_ctx_t condctx = parser_ctx(parser); - increment = parse_expression_leave(parser, false, false, false); - if (!increment) - goto onerr; - if (!ast_side_effects(increment)) { - if (compile_warning(condctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect")) - goto onerr; - } - } - - /* closing paren */ - if (parser->tok != ')') { - parseerror(parser, "expected closing paren after 'for-loop' incrementor"); - goto onerr; - } - /* parse into the 'then' branch */ - if (!parser_next(parser)) { - parseerror(parser, "expected for-loop body"); - goto onerr; - } - if (!parse_statement_or_block(parser, &ontrue)) - goto onerr; - - if (cond) { - cond = process_condition(parser, cond, &ifnot); - if (!cond) - goto onerr; - } - aloop = ast_loop_new(ctx, initexpr, cond, ifnot, NULL, false, increment, ontrue); - *out = (ast_expression*)aloop; - - if (!parser_leaveblock(parser)) { - ast_delete(aloop); - return false; - } - return true; -onerr: - if (initexpr) ast_unref(initexpr); - if (cond) ast_unref(cond); - if (increment) ast_unref(increment); - (void)!parser_leaveblock(parser); - return false; -} - -static bool parse_return(parser_t *parser, ast_block *block, ast_expression **out) -{ - ast_expression *exp = NULL; - ast_expression *var = NULL; - ast_return *ret = NULL; - ast_value *retval = parser->function->return_value; - ast_value *expected = parser->function->vtype; - - lex_ctx_t ctx = parser_ctx(parser); - - (void)block; /* not touching */ - - if (!parser_next(parser)) { - parseerror(parser, "expected return expression"); - return false; - } - - /* return assignments */ - if (parser->tok == '=') { - if (!OPTS_FLAG(RETURN_ASSIGNMENTS)) { - parseerror(parser, "return assignments not activated, try using -freturn-assigments"); - return false; - } - - if (type_store_instr[expected->expression.next->vtype] == VINSTR_END) { - char ty1[1024]; - ast_type_to_string(expected->expression.next, ty1, sizeof(ty1)); - parseerror(parser, "invalid return type: `%s'", ty1); - return false; - } - - if (!parser_next(parser)) { - parseerror(parser, "expected return assignment expression"); - return false; - } - - if (!(exp = parse_expression_leave(parser, false, false, false))) - return false; - - /* prepare the return value */ - if (!retval) { - retval = ast_value_new(ctx, "#LOCAL_RETURN", TYPE_VOID); - ast_type_adopt(retval, expected->expression.next); - parser->function->return_value = retval; - } - - if (!ast_compare_type(exp, (ast_expression*)retval)) { - char ty1[1024], ty2[1024]; - ast_type_to_string(exp, ty1, sizeof(ty1)); - ast_type_to_string(&retval->expression, ty2, sizeof(ty2)); - parseerror(parser, "invalid type for return value: `%s', expected `%s'", ty1, ty2); - } - - /* store to 'return' local variable */ - var = (ast_expression*)ast_store_new( - ctx, - type_store_instr[expected->expression.next->vtype], - (ast_expression*)retval, exp); - - if (!var) { - ast_unref(exp); - return false; - } - - if (parser->tok != ';') - parseerror(parser, "missing semicolon after return assignment"); - else if (!parser_next(parser)) - parseerror(parser, "parse error after return assignment"); - - *out = var; - return true; - } - - if (parser->tok != ';') { - exp = parse_expression(parser, false, false); - if (!exp) - return false; - - if (exp->vtype != TYPE_NIL && - exp->vtype != ((ast_expression*)expected)->next->vtype) - { - parseerror(parser, "return with invalid expression"); - } - - ret = ast_return_new(ctx, exp); - if (!ret) { - ast_unref(exp); - return false; - } - } else { - if (!parser_next(parser)) - parseerror(parser, "parse error"); - - if (!retval && expected->expression.next->vtype != TYPE_VOID) - { - (void)!parsewarning(parser, WARN_MISSING_RETURN_VALUES, "return without value"); - } - ret = ast_return_new(ctx, (ast_expression*)retval); - } - *out = (ast_expression*)ret; - return true; -} - -static bool parse_break_continue(parser_t *parser, ast_block *block, ast_expression **out, bool is_continue) -{ - size_t i; - unsigned int levels = 0; - lex_ctx_t ctx = parser_ctx(parser); - const char **loops = (is_continue ? parser->continues : parser->breaks); - - (void)block; /* not touching */ - if (!parser_next(parser)) { - parseerror(parser, "expected semicolon or loop label"); - return false; - } - - if (!vec_size(loops)) { - if (is_continue) - parseerror(parser, "`continue` can only be used inside loops"); - else - parseerror(parser, "`break` can only be used inside loops or switches"); - } - - if (parser->tok == TOKEN_IDENT) { - if (!OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "labeled loops not activated, try using -floop-labels"); - i = vec_size(loops); - while (i--) { - if (loops[i] && !strcmp(loops[i], parser_tokval(parser))) - break; - if (!i) { - parseerror(parser, "no such loop to %s: `%s`", - (is_continue ? "continue" : "break out of"), - parser_tokval(parser)); - return false; - } - ++levels; - } - if (!parser_next(parser)) { - parseerror(parser, "expected semicolon"); - return false; - } - } - - if (parser->tok != ';') { - parseerror(parser, "expected semicolon"); - return false; - } - - if (!parser_next(parser)) - parseerror(parser, "parse error"); - - *out = (ast_expression*)ast_breakcont_new(ctx, is_continue, levels); - return true; -} - -/* returns true when it was a variable qualifier, false otherwise! - * on error, cvq is set to CV_WRONG - */ -typedef struct { - const char *name; - size_t flag; -} attribute_t; - -static bool parse_qualifiers(parser_t *parser, bool with_local, int *cvq, bool *noref, bool *is_static, uint32_t *_flags, char **message) -{ - bool had_const = false; - bool had_var = false; - bool had_noref = false; - bool had_attrib = false; - bool had_static = false; - uint32_t flags = 0; - - static attribute_t attributes[] = { - { "noreturn", AST_FLAG_NORETURN }, - { "inline", AST_FLAG_INLINE }, - { "eraseable", AST_FLAG_ERASEABLE }, - { "accumulate", AST_FLAG_ACCUMULATE }, - { "last", AST_FLAG_FINAL_DECL } - }; - - *cvq = CV_NONE; - - for (;;) { - size_t i; - if (parser->tok == TOKEN_ATTRIBUTE_OPEN) { - had_attrib = true; - /* parse an attribute */ - if (!parser_next(parser)) { - parseerror(parser, "expected attribute after `[[`"); - *cvq = CV_WRONG; - return false; - } - - for (i = 0; i < GMQCC_ARRAY_COUNT(attributes); i++) { - if (!strcmp(parser_tokval(parser), attributes[i].name)) { - flags |= attributes[i].flag; - if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) { - parseerror(parser, "`%s` attribute has no parameters, expected `]]`", - attributes[i].name); - *cvq = CV_WRONG; - return false; - } - break; - } - } - - if (i != GMQCC_ARRAY_COUNT(attributes)) - goto leave; - - - if (!strcmp(parser_tokval(parser), "noref")) { - had_noref = true; - if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) { - parseerror(parser, "`noref` attribute has no parameters, expected `]]`"); - *cvq = CV_WRONG; - return false; - } - } - else if (!strcmp(parser_tokval(parser), "alias") && !(flags & AST_FLAG_ALIAS)) { - flags |= AST_FLAG_ALIAS; - *message = NULL; - - if (!parser_next(parser)) { - parseerror(parser, "parse error in attribute"); - goto argerr; - } - - if (parser->tok == '(') { - if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) { - parseerror(parser, "`alias` attribute missing parameter"); - goto argerr; - } - - *message = util_strdup(parser_tokval(parser)); - - if (!parser_next(parser)) { - parseerror(parser, "parse error in attribute"); - goto argerr; - } - - if (parser->tok != ')') { - parseerror(parser, "`alias` attribute expected `)` after parameter"); - goto argerr; - } - - if (!parser_next(parser)) { - parseerror(parser, "parse error in attribute"); - goto argerr; - } - } - - if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) { - parseerror(parser, "`alias` attribute expected `]]`"); - goto argerr; - } - } - else if (!strcmp(parser_tokval(parser), "deprecated") && !(flags & AST_FLAG_DEPRECATED)) { - flags |= AST_FLAG_DEPRECATED; - *message = NULL; - - if (!parser_next(parser)) { - parseerror(parser, "parse error in attribute"); - goto argerr; - } - - if (parser->tok == '(') { - if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) { - parseerror(parser, "`deprecated` attribute missing parameter"); - goto argerr; - } - - *message = util_strdup(parser_tokval(parser)); - - if (!parser_next(parser)) { - parseerror(parser, "parse error in attribute"); - goto argerr; - } - - if(parser->tok != ')') { - parseerror(parser, "`deprecated` attribute expected `)` after parameter"); - goto argerr; - } - - if (!parser_next(parser)) { - parseerror(parser, "parse error in attribute"); - goto argerr; - } - } - /* no message */ - if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) { - parseerror(parser, "`deprecated` attribute expected `]]`"); - - argerr: /* ugly */ - if (*message) mem_d(*message); - *message = NULL; - *cvq = CV_WRONG; - return false; - } - } - else if (!strcmp(parser_tokval(parser), "coverage") && !(flags & AST_FLAG_COVERAGE)) { - flags |= AST_FLAG_COVERAGE; - if (!parser_next(parser)) { - error_in_coverage: - parseerror(parser, "parse error in coverage attribute"); - *cvq = CV_WRONG; - return false; - } - if (parser->tok == '(') { - if (!parser_next(parser)) { - bad_coverage_arg: - parseerror(parser, "invalid parameter for coverage() attribute\n" - "valid are: block"); - *cvq = CV_WRONG; - return false; - } - if (parser->tok != ')') { - do { - if (parser->tok != TOKEN_IDENT) - goto bad_coverage_arg; - if (!strcmp(parser_tokval(parser), "block")) - flags |= AST_FLAG_BLOCK_COVERAGE; - else if (!strcmp(parser_tokval(parser), "none")) - flags &= ~(AST_FLAG_COVERAGE_MASK); - else - goto bad_coverage_arg; - if (!parser_next(parser)) - goto error_in_coverage; - if (parser->tok == ',') { - if (!parser_next(parser)) - goto error_in_coverage; - } - } while (parser->tok != ')'); - } - if (parser->tok != ')' || !parser_next(parser)) - goto error_in_coverage; - } else { - /* without parameter [[coverage]] equals [[coverage(block)]] */ - flags |= AST_FLAG_BLOCK_COVERAGE; - } - } - else - { - /* Skip tokens until we hit a ]] */ - (void)!parsewarning(parser, WARN_UNKNOWN_ATTRIBUTE, "unknown attribute starting with `%s`", parser_tokval(parser)); - while (parser->tok != TOKEN_ATTRIBUTE_CLOSE) { - if (!parser_next(parser)) { - parseerror(parser, "error inside attribute"); - *cvq = CV_WRONG; - return false; - } - } - } - } - else if (with_local && !strcmp(parser_tokval(parser), "static")) - had_static = true; - else if (!strcmp(parser_tokval(parser), "const")) - had_const = true; - else if (!strcmp(parser_tokval(parser), "var")) - had_var = true; - else if (with_local && !strcmp(parser_tokval(parser), "local")) - had_var = true; - else if (!strcmp(parser_tokval(parser), "noref")) - had_noref = true; - else if (!had_const && !had_var && !had_noref && !had_attrib && !had_static && !flags) { - return false; - } - else - break; - - leave: - if (!parser_next(parser)) - goto onerr; - } - if (had_const) - *cvq = CV_CONST; - else if (had_var) - *cvq = CV_VAR; - else - *cvq = CV_NONE; - *noref = had_noref; - *is_static = had_static; - *_flags = flags; - return true; -onerr: - parseerror(parser, "parse error after variable qualifier"); - *cvq = CV_WRONG; - return true; -} - -static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out); -static bool parse_switch(parser_t *parser, ast_block *block, ast_expression **out) -{ - bool rv; - char *label = NULL; - - /* skip the 'while' and get the body */ - if (!parser_next(parser)) { - if (OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "expected loop label or 'switch' operand in parenthesis"); - else - parseerror(parser, "expected 'switch' operand in parenthesis"); - return false; - } - - if (parser->tok == ':') { - if (!OPTS_FLAG(LOOP_LABELS)) - parseerror(parser, "labeled loops not activated, try using -floop-labels"); - if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { - parseerror(parser, "expected loop label"); - return false; - } - label = util_strdup(parser_tokval(parser)); - if (!parser_next(parser)) { - mem_d(label); - parseerror(parser, "expected 'switch' operand in parenthesis"); - return false; - } - } - - if (parser->tok != '(') { - parseerror(parser, "expected 'switch' operand in parenthesis"); - return false; - } - - vec_push(parser->breaks, label); - - rv = parse_switch_go(parser, block, out); - if (label) - mem_d(label); - if (vec_last(parser->breaks) != label) { - parseerror(parser, "internal error: label stack corrupted"); - rv = false; - ast_delete(*out); - *out = NULL; - } - else { - vec_pop(parser->breaks); - } - return rv; -} - -static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out) -{ - ast_expression *operand; - ast_value *opval; - ast_value *typevar; - ast_switch *switchnode; - ast_switch_case swcase; - - int cvq; - bool noref, is_static; - uint32_t qflags = 0; - - lex_ctx_t ctx = parser_ctx(parser); - - (void)block; /* not touching */ - (void)opval; - - /* parse into the expression */ - if (!parser_next(parser)) { - parseerror(parser, "expected switch operand"); - return false; - } - /* parse the operand */ - operand = parse_expression_leave(parser, false, false, false); - if (!operand) - return false; - - switchnode = ast_switch_new(ctx, operand); - - /* closing paren */ - if (parser->tok != ')') { - ast_delete(switchnode); - parseerror(parser, "expected closing paren after 'switch' operand"); - return false; - } - - /* parse over the opening paren */ - if (!parser_next(parser) || parser->tok != '{') { - ast_delete(switchnode); - parseerror(parser, "expected list of cases"); - return false; - } - - if (!parser_next(parser)) { - ast_delete(switchnode); - parseerror(parser, "expected 'case' or 'default'"); - return false; - } - - /* new block; allow some variables to be declared here */ - parser_enterblock(parser); - while (true) { - typevar = NULL; - 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, true, CV_NONE, typevar, false, false, 0, NULL)) { - ast_delete(switchnode); - return false; - } - continue; - } - if (parse_qualifiers(parser, true, &cvq, &noref, &is_static, &qflags, NULL)) - { - if (cvq == CV_WRONG) { - ast_delete(switchnode); - return false; - } - if (!parse_variable(parser, block, true, cvq, NULL, noref, is_static, qflags, NULL)) { - ast_delete(switchnode); - return false; - } - continue; - } - break; - } - - /* case list! */ - while (parser->tok != '}') { - ast_block *caseblock; - - if (!strcmp(parser_tokval(parser), "case")) { - if (!parser_next(parser)) { - ast_delete(switchnode); - parseerror(parser, "expected expression for case"); - return false; - } - swcase.value = parse_expression_leave(parser, false, false, false); - if (!swcase.value) { - ast_delete(switchnode); - parseerror(parser, "expected expression for case"); - return false; - } - if (!OPTS_FLAG(RELAXED_SWITCH)) { - if (!ast_istype(swcase.value, ast_value)) { /* || ((ast_value*)swcase.value)->cvq != CV_CONST) { */ - parseerror(parser, "case on non-constant values need to be explicitly enabled via -frelaxed-switch"); - ast_unref(operand); - return false; - } - } - } - else if (!strcmp(parser_tokval(parser), "default")) { - swcase.value = NULL; - if (!parser_next(parser)) { - ast_delete(switchnode); - parseerror(parser, "expected colon"); - return false; - } - } - else { - ast_delete(switchnode); - parseerror(parser, "expected 'case' or 'default'"); - return false; - } - - /* Now the colon and body */ - if (parser->tok != ':') { - if (swcase.value) ast_unref(swcase.value); - ast_delete(switchnode); - parseerror(parser, "expected colon"); - return false; - } - - if (!parser_next(parser)) { - if (swcase.value) ast_unref(swcase.value); - ast_delete(switchnode); - parseerror(parser, "expected statements or case"); - return false; - } - caseblock = ast_block_new(parser_ctx(parser)); - if (!caseblock) { - if (swcase.value) ast_unref(swcase.value); - ast_delete(switchnode); - return false; - } - swcase.code = (ast_expression*)caseblock; - vec_push(switchnode->cases, swcase); - while (true) { - ast_expression *expr; - if (parser->tok == '}') - break; - if (parser->tok == TOKEN_KEYWORD) { - if (!strcmp(parser_tokval(parser), "case") || - !strcmp(parser_tokval(parser), "default")) - { - break; - } - } - if (!parse_statement(parser, caseblock, &expr, true)) { - ast_delete(switchnode); - return false; - } - if (!expr) - continue; - if (!ast_block_add_expr(caseblock, expr)) { - ast_delete(switchnode); - return false; - } - } - } - - parser_leaveblock(parser); - - /* closing paren */ - if (parser->tok != '}') { - ast_delete(switchnode); - parseerror(parser, "expected closing paren of case list"); - return false; - } - if (!parser_next(parser)) { - ast_delete(switchnode); - parseerror(parser, "parse error after switch"); - return false; - } - *out = (ast_expression*)switchnode; - return true; -} - -/* parse computed goto sides */ -static ast_expression *parse_goto_computed(parser_t *parser, ast_expression **side) { - ast_expression *on_true; - ast_expression *on_false; - ast_expression *cond; - - if (!*side) - return NULL; - - if (ast_istype(*side, ast_ternary)) { - ast_ternary *tern = (ast_ternary*)*side; - on_true = parse_goto_computed(parser, &tern->on_true); - on_false = parse_goto_computed(parser, &tern->on_false); - - if (!on_true || !on_false) { - parseerror(parser, "expected label or expression in ternary"); - if (on_true) ast_unref(on_true); - if (on_false) ast_unref(on_false); - return NULL; - } - - cond = tern->cond; - tern->cond = NULL; - ast_delete(tern); - *side = NULL; - return (ast_expression*)ast_ifthen_new(parser_ctx(parser), cond, on_true, on_false); - } else if (ast_istype(*side, ast_label)) { - ast_goto *gt = ast_goto_new(parser_ctx(parser), ((ast_label*)*side)->name); - ast_goto_set_label(gt, ((ast_label*)*side)); - *side = NULL; - return (ast_expression*)gt; - } - return NULL; -} - -static bool parse_goto(parser_t *parser, ast_expression **out) -{ - ast_goto *gt = NULL; - ast_expression *lbl; - - if (!parser_next(parser)) - return false; - - if (parser->tok != TOKEN_IDENT) { - ast_expression *expression; - - /* could be an expression i.e computed goto :-) */ - if (parser->tok != '(') { - parseerror(parser, "expected label name after `goto`"); - return false; - } - - /* failed to parse expression for goto */ - if (!(expression = parse_expression(parser, false, true)) || - !(*out = parse_goto_computed(parser, &expression))) { - parseerror(parser, "invalid goto expression"); - if(expression) - ast_unref(expression); - return false; - } - - return true; - } - - /* not computed goto */ - gt = ast_goto_new(parser_ctx(parser), parser_tokval(parser)); - lbl = parser_find_label(parser, gt->name); - if (lbl) { - if (!ast_istype(lbl, ast_label)) { - parseerror(parser, "internal error: label is not an ast_label"); - ast_delete(gt); - return false; - } - ast_goto_set_label(gt, (ast_label*)lbl); - } - else - vec_push(parser->gotos, gt); - - if (!parser_next(parser) || parser->tok != ';') { - parseerror(parser, "semicolon expected after goto label"); - return false; - } - if (!parser_next(parser)) { - parseerror(parser, "parse error after goto"); - return false; - } - - *out = (ast_expression*)gt; - return true; -} - -static bool parse_skipwhite(parser_t *parser) -{ - do { - if (!parser_next(parser)) - return false; - } while (parser->tok == TOKEN_WHITE && parser->tok < TOKEN_ERROR); - return parser->tok < TOKEN_ERROR; -} - -static bool parse_eol(parser_t *parser) -{ - if (!parse_skipwhite(parser)) - return false; - return parser->tok == TOKEN_EOL; -} - -static bool parse_pragma_do(parser_t *parser) -{ - if (!parser_next(parser) || - parser->tok != TOKEN_IDENT || - strcmp(parser_tokval(parser), "pragma")) - { - parseerror(parser, "expected `pragma` keyword after `#`, got `%s`", parser_tokval(parser)); - return false; - } - if (!parse_skipwhite(parser) || parser->tok != TOKEN_IDENT) { - parseerror(parser, "expected pragma, got `%s`", parser_tokval(parser)); - return false; - } - - if (!strcmp(parser_tokval(parser), "noref")) { - if (!parse_skipwhite(parser) || parser->tok != TOKEN_INTCONST) { - parseerror(parser, "`noref` pragma requires an argument: 0 or 1"); - return false; - } - parser->noref = !!parser_token(parser)->constval.i; - if (!parse_eol(parser)) { - parseerror(parser, "parse error after `noref` pragma"); - return false; - } - } - else - { - (void)!parsewarning(parser, WARN_UNKNOWN_PRAGMAS, "ignoring #pragma %s", parser_tokval(parser)); - - /* skip to eol */ - while (!parse_eol(parser)) { - parser_next(parser); - } - - return true; - } - - return true; -} - -static bool parse_pragma(parser_t *parser) -{ - bool rv; - parser->lex->flags.preprocessing = true; - parser->lex->flags.mergelines = true; - rv = parse_pragma_do(parser); - if (parser->tok != TOKEN_EOL) { - parseerror(parser, "junk after pragma"); - rv = false; - } - parser->lex->flags.preprocessing = false; - parser->lex->flags.mergelines = false; - if (!parser_next(parser)) { - parseerror(parser, "parse error after pragma"); - rv = false; - } - return rv; -} - -static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases) -{ - bool noref, is_static; - int cvq = CV_NONE; - uint32_t qflags = 0; - ast_value *typevar = NULL; - char *vstring = NULL; - - *out = NULL; - - if (parser->tok == TOKEN_IDENT) - typevar = parser_find_typedef(parser, parser_tokval(parser), 0); - - if (typevar || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS) - { - /* local variable */ - if (!block) { - parseerror(parser, "cannot declare a variable from here"); - return false; - } - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { - if (parsewarning(parser, WARN_EXTENSIONS, "missing 'local' keyword when declaring a local variable")) - return false; - } - if (!parse_variable(parser, block, false, CV_NONE, typevar, false, false, 0, NULL)) - return false; - return true; - } - else if (parse_qualifiers(parser, !!block, &cvq, &noref, &is_static, &qflags, &vstring)) - { - if (cvq == CV_WRONG) - return false; - return parse_variable(parser, block, false, cvq, NULL, noref, is_static, qflags, vstring); - } - else if (parser->tok == TOKEN_KEYWORD) - { - if (!strcmp(parser_tokval(parser), "__builtin_debug_printtype")) - { - char ty[1024]; - ast_value *tdef; - - if (!parser_next(parser)) { - parseerror(parser, "parse error after __builtin_debug_printtype"); - return false; - } - - if (parser->tok == TOKEN_IDENT && (tdef = parser_find_typedef(parser, parser_tokval(parser), 0))) - { - ast_type_to_string((ast_expression*)tdef, ty, sizeof(ty)); - con_out("__builtin_debug_printtype: `%s`=`%s`\n", tdef->name, ty); - if (!parser_next(parser)) { - parseerror(parser, "parse error after __builtin_debug_printtype typename argument"); - return false; - } - } - else - { - if (!parse_statement(parser, block, out, allow_cases)) - return false; - if (!*out) - con_out("__builtin_debug_printtype: got no output node\n"); - else - { - ast_type_to_string(*out, ty, sizeof(ty)); - con_out("__builtin_debug_printtype: `%s`\n", ty); - } - } - return true; - } - else if (!strcmp(parser_tokval(parser), "return")) - { - return parse_return(parser, block, out); - } - else if (!strcmp(parser_tokval(parser), "if")) - { - return parse_if(parser, block, out); - } - else if (!strcmp(parser_tokval(parser), "while")) - { - return parse_while(parser, block, out); - } - else if (!strcmp(parser_tokval(parser), "do")) - { - return parse_dowhile(parser, block, out); - } - else if (!strcmp(parser_tokval(parser), "for")) - { - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { - if (parsewarning(parser, WARN_EXTENSIONS, "for loops are not recognized in the original Quake C standard, to enable try an alternate standard --std=?")) - return false; - } - return parse_for(parser, block, out); - } - else if (!strcmp(parser_tokval(parser), "break")) - { - return parse_break_continue(parser, block, out, false); - } - else if (!strcmp(parser_tokval(parser), "continue")) - { - return parse_break_continue(parser, block, out, true); - } - else if (!strcmp(parser_tokval(parser), "switch")) - { - return parse_switch(parser, block, out); - } - else if (!strcmp(parser_tokval(parser), "case") || - !strcmp(parser_tokval(parser), "default")) - { - if (!allow_cases) { - parseerror(parser, "unexpected 'case' label"); - return false; - } - return true; - } - else if (!strcmp(parser_tokval(parser), "goto")) - { - return parse_goto(parser, out); - } - else if (!strcmp(parser_tokval(parser), "typedef")) - { - if (!parser_next(parser)) { - parseerror(parser, "expected type definition after 'typedef'"); - return false; - } - return parse_typedef(parser); - } - parseerror(parser, "Unexpected keyword: `%s'", parser_tokval(parser)); - return false; - } - else if (parser->tok == '{') - { - ast_block *inner; - inner = parse_block(parser); - if (!inner) - return false; - *out = (ast_expression*)inner; - return true; - } - else if (parser->tok == ':') - { - size_t i; - ast_label *label; - if (!parser_next(parser)) { - parseerror(parser, "expected label name"); - return false; - } - if (parser->tok != TOKEN_IDENT) { - parseerror(parser, "label must be an identifier"); - return false; - } - label = (ast_label*)parser_find_label(parser, parser_tokval(parser)); - if (label) { - if (!label->undefined) { - parseerror(parser, "label `%s` already defined", label->name); - return false; - } - label->undefined = false; - } - else { - label = ast_label_new(parser_ctx(parser), parser_tokval(parser), false); - vec_push(parser->labels, label); - } - *out = (ast_expression*)label; - if (!parser_next(parser)) { - parseerror(parser, "parse error after label"); - return false; - } - for (i = 0; i < vec_size(parser->gotos); ++i) { - if (!strcmp(parser->gotos[i]->name, label->name)) { - ast_goto_set_label(parser->gotos[i], label); - vec_remove(parser->gotos, i, 1); - --i; - } - } - return true; - } - else if (parser->tok == ';') - { - if (!parser_next(parser)) { - parseerror(parser, "parse error after empty statement"); - return false; - } - return true; - } - else - { - lex_ctx_t ctx = parser_ctx(parser); - ast_expression *exp = parse_expression(parser, false, false); - if (!exp) - return false; - *out = exp; - if (!ast_side_effects(exp)) { - if (compile_warning(ctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect")) - return false; - } - return true; - } -} - -static bool parse_enum(parser_t *parser) -{ - bool flag = false; - bool reverse = false; - qcfloat_t num = 0; - ast_value **values = NULL; - ast_value *var = NULL; - ast_value *asvalue; - - ast_expression *old; - - if (!parser_next(parser) || (parser->tok != '{' && parser->tok != ':')) { - parseerror(parser, "expected `{` or `:` after `enum` keyword"); - return false; - } - - /* enumeration attributes (can add more later) */ - if (parser->tok == ':') { - if (!parser_next(parser) || parser->tok != TOKEN_IDENT){ - parseerror(parser, "expected `flag` or `reverse` for enumeration attribute"); - return false; - } - - /* attributes? */ - if (!strcmp(parser_tokval(parser), "flag")) { - num = 1; - flag = true; - } - else if (!strcmp(parser_tokval(parser), "reverse")) { - reverse = true; - } - else { - parseerror(parser, "invalid attribute `%s` for enumeration", parser_tokval(parser)); - return false; - } - - if (!parser_next(parser) || parser->tok != '{') { - parseerror(parser, "expected `{` after enum attribute "); - return false; - } - } - - while (true) { - if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { - if (parser->tok == '}') { - /* allow an empty enum */ - break; - } - parseerror(parser, "expected identifier or `}`"); - goto onerror; - } - - old = parser_find_field(parser, parser_tokval(parser)); - if (!old) - old = parser_find_global(parser, parser_tokval(parser)); - if (old) { - parseerror(parser, "value `%s` has already been declared here: %s:%i", - parser_tokval(parser), ast_ctx(old).file, ast_ctx(old).line); - goto onerror; - } - - var = ast_value_new(parser_ctx(parser), parser_tokval(parser), TYPE_FLOAT); - vec_push(values, var); - var->cvq = CV_CONST; - var->hasvalue = true; - - /* for flagged enumerations increment in POTs of TWO */ - var->constval.vfloat = (flag) ? (num *= 2) : (num ++); - parser_addglobal(parser, var->name, (ast_expression*)var); - - if (!parser_next(parser)) { - parseerror(parser, "expected `=`, `}` or comma after identifier"); - goto onerror; - } - - if (parser->tok == ',') - continue; - if (parser->tok == '}') - break; - if (parser->tok != '=') { - parseerror(parser, "expected `=`, `}` or comma after identifier"); - goto onerror; - } - - if (!parser_next(parser)) { - parseerror(parser, "expected expression after `=`"); - goto onerror; - } - - /* We got a value! */ - old = parse_expression_leave(parser, true, false, false); - asvalue = (ast_value*)old; - if (!ast_istype(old, ast_value) || asvalue->cvq != CV_CONST || !asvalue->hasvalue) { - compile_error(ast_ctx(var), "constant value or expression expected"); - goto onerror; - } - num = (var->constval.vfloat = asvalue->constval.vfloat) + 1; - - if (parser->tok == '}') - break; - if (parser->tok != ',') { - parseerror(parser, "expected `}` or comma after expression"); - goto onerror; - } - } - - /* patch them all (for reversed attribute) */ - if (reverse) { - size_t i; - for (i = 0; i < vec_size(values); i++) - values[i]->constval.vfloat = vec_size(values) - i - 1; - } - - if (parser->tok != '}') { - parseerror(parser, "internal error: breaking without `}`"); - goto onerror; - } - - if (!parser_next(parser) || parser->tok != ';') { - parseerror(parser, "expected semicolon after enumeration"); - goto onerror; - } - - if (!parser_next(parser)) { - parseerror(parser, "parse error after enumeration"); - goto onerror; - } - - vec_free(values); - return true; - -onerror: - vec_free(values); - return false; -} - -static bool parse_block_into(parser_t *parser, ast_block *block) -{ - bool retval = true; - - parser_enterblock(parser); - - if (!parser_next(parser)) { /* skip the '{' */ - parseerror(parser, "expected function body"); - goto cleanup; - } - - while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR) - { - ast_expression *expr = NULL; - if (parser->tok == '}') - break; - - if (!parse_statement(parser, block, &expr, false)) { - /* parseerror(parser, "parse error"); */ - block = NULL; - goto cleanup; - } - if (!expr) - continue; - if (!ast_block_add_expr(block, expr)) { - ast_delete(block); - block = NULL; - goto cleanup; - } - } - - if (parser->tok != '}') { - block = NULL; - } else { - (void)parser_next(parser); - } - -cleanup: - if (!parser_leaveblock(parser)) - retval = false; - return retval && !!block; -} - -static ast_block* parse_block(parser_t *parser) -{ - ast_block *block; - block = ast_block_new(parser_ctx(parser)); - if (!block) - return NULL; - if (!parse_block_into(parser, block)) { - ast_block_delete(block); - return NULL; - } - return block; -} - -static bool parse_statement_or_block(parser_t *parser, ast_expression **out) -{ - if (parser->tok == '{') { - *out = (ast_expression*)parse_block(parser); - return !!*out; - } - return parse_statement(parser, NULL, out, false); -} - -static bool create_vector_members(ast_value *var, ast_member **me) -{ - size_t i; - size_t len = strlen(var->name); - - for (i = 0; i < 3; ++i) { - char *name = (char*)mem_a(len+3); - memcpy(name, var->name, len); - name[len+0] = '_'; - name[len+1] = 'x'+i; - name[len+2] = 0; - me[i] = ast_member_new(ast_ctx(var), (ast_expression*)var, i, name); - mem_d(name); - if (!me[i]) - break; - } - if (i == 3) - return true; - - /* unroll */ - do { ast_member_delete(me[--i]); } while(i); - return false; -} - -static bool parse_function_body(parser_t *parser, ast_value *var) -{ - ast_block *block = NULL; - ast_function *func; - ast_function *old; - size_t parami; - - ast_expression *framenum = NULL; - ast_expression *nextthink = NULL; - /* None of the following have to be deleted */ - ast_expression *fld_think = NULL, *fld_nextthink = NULL, *fld_frame = NULL; - ast_expression *gbl_time = NULL, *gbl_self = NULL; - bool has_frame_think; - - bool retval = true; - - has_frame_think = false; - old = parser->function; - - if (var->expression.flags & AST_FLAG_ALIAS) { - parseerror(parser, "function aliases cannot have bodies"); - return false; - } - - if (vec_size(parser->gotos) || vec_size(parser->labels)) { - parseerror(parser, "gotos/labels leaking"); - return false; - } - - if (!OPTS_FLAG(VARIADIC_ARGS) && var->expression.flags & AST_FLAG_VARIADIC) { - if (parsewarning(parser, WARN_VARIADIC_FUNCTION, - "variadic function with implementation will not be able to access additional parameters (try -fvariadic-args)")) - { - return false; - } - } - - if (parser->tok == '[') { - /* got a frame definition: [ framenum, nextthink ] - * this translates to: - * self.frame = framenum; - * self.nextthink = time + 0.1; - * self.think = nextthink; - */ - nextthink = NULL; - - fld_think = parser_find_field(parser, "think"); - fld_nextthink = parser_find_field(parser, "nextthink"); - fld_frame = parser_find_field(parser, "frame"); - if (!fld_think || !fld_nextthink || !fld_frame) { - parseerror(parser, "cannot use [frame,think] notation without the required fields"); - parseerror(parser, "please declare the following entityfields: `frame`, `think`, `nextthink`"); - return false; - } - gbl_time = parser_find_global(parser, "time"); - gbl_self = parser_find_global(parser, "self"); - if (!gbl_time || !gbl_self) { - parseerror(parser, "cannot use [frame,think] notation without the required globals"); - parseerror(parser, "please declare the following globals: `time`, `self`"); - return false; - } - - if (!parser_next(parser)) - return false; - - framenum = parse_expression_leave(parser, true, false, false); - if (!framenum) { - parseerror(parser, "expected a framenumber constant in[frame,think] notation"); - return false; - } - if (!ast_istype(framenum, ast_value) || !( (ast_value*)framenum )->hasvalue) { - ast_unref(framenum); - parseerror(parser, "framenumber in [frame,think] notation must be a constant"); - return false; - } - - if (parser->tok != ',') { - ast_unref(framenum); - parseerror(parser, "expected comma after frame number in [frame,think] notation"); - parseerror(parser, "Got a %i\n", parser->tok); - return false; - } - - if (!parser_next(parser)) { - ast_unref(framenum); - return false; - } - - if (parser->tok == TOKEN_IDENT && !parser_find_var(parser, parser_tokval(parser))) - { - /* qc allows the use of not-yet-declared functions here - * - this automatically creates a prototype */ - ast_value *thinkfunc; - ast_expression *functype = fld_think->next; - - thinkfunc = ast_value_new(parser_ctx(parser), parser_tokval(parser), functype->vtype); - if (!thinkfunc) { /* || !ast_type_adopt(thinkfunc, functype)*/ - ast_unref(framenum); - parseerror(parser, "failed to create implicit prototype for `%s`", parser_tokval(parser)); - return false; - } - ast_type_adopt(thinkfunc, functype); - - if (!parser_next(parser)) { - ast_unref(framenum); - ast_delete(thinkfunc); - return false; - } - - parser_addglobal(parser, thinkfunc->name, (ast_expression*)thinkfunc); - - nextthink = (ast_expression*)thinkfunc; - - } else { - nextthink = parse_expression_leave(parser, true, false, false); - if (!nextthink) { - ast_unref(framenum); - parseerror(parser, "expected a think-function in [frame,think] notation"); - return false; - } - } - - if (!ast_istype(nextthink, ast_value)) { - parseerror(parser, "think-function in [frame,think] notation must be a constant"); - retval = false; - } - - if (retval && parser->tok != ']') { - parseerror(parser, "expected closing `]` for [frame,think] notation"); - retval = false; - } - - if (retval && !parser_next(parser)) { - retval = false; - } - - if (retval && parser->tok != '{') { - parseerror(parser, "a function body has to be declared after a [frame,think] declaration"); - retval = false; - } - - if (!retval) { - ast_unref(nextthink); - ast_unref(framenum); - return false; - } - - has_frame_think = true; - } - - block = ast_block_new(parser_ctx(parser)); - if (!block) { - parseerror(parser, "failed to allocate block"); - if (has_frame_think) { - ast_unref(nextthink); - ast_unref(framenum); - } - return false; - } - - if (has_frame_think) { - if (!OPTS_FLAG(EMULATE_STATE)) { - ast_state *state_op = ast_state_new(parser_ctx(parser), framenum, nextthink); - if (!ast_block_add_expr(block, (ast_expression*)state_op)) { - parseerror(parser, "failed to generate state op for [frame,think]"); - ast_unref(nextthink); - ast_unref(framenum); - ast_delete(block); - return false; - } - } else { - /* emulate OP_STATE in code: */ - lex_ctx_t ctx; - ast_expression *self_frame; - ast_expression *self_nextthink; - ast_expression *self_think; - ast_expression *time_plus_1; - ast_store *store_frame; - ast_store *store_nextthink; - ast_store *store_think; - - float frame_delta = 1.0f / (float)OPTS_OPTION_U32(OPTION_STATE_FPS); - - ctx = parser_ctx(parser); - self_frame = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_frame); - self_nextthink = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_nextthink); - self_think = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_think); - - time_plus_1 = (ast_expression*)ast_binary_new(ctx, INSTR_ADD_F, - gbl_time, (ast_expression*)fold_constgen_float(parser->fold, frame_delta, false)); - - 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 (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); - 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; - } - } - } - - if (var->hasvalue) { - if (!(var->expression.flags & AST_FLAG_ACCUMULATE)) { - parseerror(parser, "function `%s` declared with multiple bodies", var->name); - ast_block_delete(block); - goto enderr; - } - func = var->constval.vfunc; - - if (!func) { - parseerror(parser, "internal error: NULL function: `%s`", var->name); - ast_block_delete(block); - goto enderr; - } - } else { - func = ast_function_new(ast_ctx(var), var->name, var); - - if (!func) { - parseerror(parser, "failed to allocate function for `%s`", var->name); - ast_block_delete(block); - goto enderr; - } - vec_push(parser->functions, func); - } - - parser_enterblock(parser); - - for (parami = 0; parami < vec_size(var->expression.params); ++parami) { - size_t e; - ast_value *param = var->expression.params[parami]; - ast_member *me[3]; - - if (param->expression.vtype != TYPE_VECTOR && - (param->expression.vtype != TYPE_FIELD || - param->expression.next->vtype != TYPE_VECTOR)) - { - continue; - } - - if (!create_vector_members(param, me)) { - ast_block_delete(block); - goto enderrfn; - } - - for (e = 0; e < 3; ++e) { - parser_addlocal(parser, me[e]->name, (ast_expression*)me[e]); - ast_block_collect(block, (ast_expression*)me[e]); - } - } - - if (var->argcounter && !func->argc) { - ast_value *argc = ast_value_new(ast_ctx(var), var->argcounter, TYPE_FLOAT); - parser_addlocal(parser, argc->name, (ast_expression*)argc); - func->argc = argc; - } - - if (OPTS_FLAG(VARIADIC_ARGS) && var->expression.flags & AST_FLAG_VARIADIC && !func->varargs) { - char name[1024]; - ast_value *varargs = ast_value_new(ast_ctx(var), "reserved:va_args", TYPE_ARRAY); - varargs->expression.flags |= AST_FLAG_IS_VARARG; - varargs->expression.next = (ast_expression*)ast_value_new(ast_ctx(var), NULL, TYPE_VECTOR); - varargs->expression.count = 0; - util_snprintf(name, sizeof(name), "%s##va##SET", var->name); - if (!parser_create_array_setter_proto(parser, varargs, name)) { - ast_delete(varargs); - ast_block_delete(block); - goto enderrfn; - } - util_snprintf(name, sizeof(name), "%s##va##GET", var->name); - if (!parser_create_array_getter_proto(parser, varargs, varargs->expression.next, name)) { - ast_delete(varargs); - ast_block_delete(block); - goto enderrfn; - } - func->varargs = varargs; - func->fixedparams = (ast_value*)fold_constgen_float(parser->fold, vec_size(var->expression.params), false); - } - - parser->function = func; - if (!parse_block_into(parser, block)) { - ast_block_delete(block); - goto enderrfn; - } - - vec_push(func->blocks, block); - - parser->function = old; - if (!parser_leaveblock(parser)) - retval = false; - if (vec_size(parser->variables) != PARSER_HT_LOCALS) { - parseerror(parser, "internal error: local scopes left"); - retval = false; - } - - if (parser->tok == ';') - return parser_next(parser); - else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) - parseerror(parser, "missing semicolon after function body (mandatory with -std=qcc)"); - return retval; - -enderrfn: - (void)!parser_leaveblock(parser); - vec_pop(parser->functions); - ast_function_delete(func); - var->constval.vfunc = NULL; - -enderr: - parser->function = old; - return false; -} - -static ast_expression *array_accessor_split( - parser_t *parser, - ast_value *array, - ast_value *index, - size_t middle, - ast_expression *left, - ast_expression *right - ) -{ - ast_ifthen *ifthen; - ast_binary *cmp; - - lex_ctx_t ctx = ast_ctx(array); - - if (!left || !right) { - if (left) ast_delete(left); - if (right) ast_delete(right); - return NULL; - } - - cmp = ast_binary_new(ctx, INSTR_LT, - (ast_expression*)index, - (ast_expression*)fold_constgen_float(parser->fold, middle, false)); - if (!cmp) { - ast_delete(left); - ast_delete(right); - parseerror(parser, "internal error: failed to create comparison for array setter"); - return NULL; - } - - ifthen = ast_ifthen_new(ctx, (ast_expression*)cmp, left, right); - if (!ifthen) { - ast_delete(cmp); /* will delete left and right */ - parseerror(parser, "internal error: failed to create conditional jump for array setter"); - return NULL; - } - - return (ast_expression*)ifthen; -} - -static ast_expression *array_setter_node(parser_t *parser, ast_value *array, ast_value *index, ast_value *value, size_t from, size_t afterend) -{ - lex_ctx_t ctx = ast_ctx(array); - - if (from+1 == afterend) { - /* set this value */ - ast_block *block; - ast_return *ret; - ast_array_index *subscript; - ast_store *st; - int assignop = type_store_instr[value->expression.vtype]; - - 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, false)); - if (!subscript) - return NULL; - - st = ast_store_new(ctx, assignop, (ast_expression*)subscript, (ast_expression*)value); - if (!st) { - ast_delete(subscript); - return NULL; - } - - block = ast_block_new(ctx); - if (!block) { - ast_delete(st); - return NULL; - } - - if (!ast_block_add_expr(block, (ast_expression*)st)) { - ast_delete(block); - return NULL; - } - - ret = ast_return_new(ctx, NULL); - if (!ret) { - ast_delete(block); - return NULL; - } - - if (!ast_block_add_expr(block, (ast_expression*)ret)) { - ast_delete(block); - return NULL; - } - - return (ast_expression*)block; - } else { - ast_expression *left, *right; - size_t diff = afterend - from; - size_t middle = from + diff/2; - left = array_setter_node(parser, array, index, value, from, middle); - right = array_setter_node(parser, array, index, value, middle, afterend); - return array_accessor_split(parser, array, index, middle, left, right); - } -} - -static ast_expression *array_field_setter_node( - parser_t *parser, - ast_value *array, - ast_value *entity, - ast_value *index, - ast_value *value, - size_t from, - size_t afterend) -{ - lex_ctx_t ctx = ast_ctx(array); - - if (from+1 == afterend) { - /* set this value */ - ast_block *block; - ast_return *ret; - ast_entfield *entfield; - ast_array_index *subscript; - ast_store *st; - int assignop = type_storep_instr[value->expression.vtype]; - - 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, false)); - if (!subscript) - return NULL; - - subscript->expression.next = ast_type_copy(ast_ctx(subscript), (ast_expression*)subscript); - subscript->expression.vtype = TYPE_FIELD; - - entfield = ast_entfield_new_force(ctx, - (ast_expression*)entity, - (ast_expression*)subscript, - (ast_expression*)subscript); - if (!entfield) { - ast_delete(subscript); - return NULL; - } - - st = ast_store_new(ctx, assignop, (ast_expression*)entfield, (ast_expression*)value); - if (!st) { - ast_delete(entfield); - return NULL; - } - - block = ast_block_new(ctx); - if (!block) { - ast_delete(st); - return NULL; - } - - if (!ast_block_add_expr(block, (ast_expression*)st)) { - ast_delete(block); - return NULL; - } - - ret = ast_return_new(ctx, NULL); - if (!ret) { - ast_delete(block); - return NULL; - } - - if (!ast_block_add_expr(block, (ast_expression*)ret)) { - ast_delete(block); - return NULL; - } - - return (ast_expression*)block; - } else { - ast_expression *left, *right; - size_t diff = afterend - from; - size_t middle = from + diff/2; - left = array_field_setter_node(parser, array, entity, index, value, from, middle); - right = array_field_setter_node(parser, array, entity, index, value, middle, afterend); - return array_accessor_split(parser, array, index, middle, left, right); - } -} - -static ast_expression *array_getter_node(parser_t *parser, ast_value *array, ast_value *index, size_t from, size_t afterend) -{ - lex_ctx_t ctx = ast_ctx(array); - - if (from+1 == afterend) { - ast_return *ret; - ast_array_index *subscript; - - subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false)); - if (!subscript) - return NULL; - - ret = ast_return_new(ctx, (ast_expression*)subscript); - if (!ret) { - ast_delete(subscript); - return NULL; - } - - return (ast_expression*)ret; - } else { - ast_expression *left, *right; - size_t diff = afterend - from; - size_t middle = from + diff/2; - left = array_getter_node(parser, array, index, from, middle); - right = array_getter_node(parser, array, index, middle, afterend); - return array_accessor_split(parser, array, index, middle, left, right); - } -} - -static bool parser_create_array_accessor(parser_t *parser, ast_value *array, const char *funcname, ast_value **out) -{ - ast_function *func = NULL; - ast_value *fval = NULL; - ast_block *body = NULL; - - fval = ast_value_new(ast_ctx(array), funcname, TYPE_FUNCTION); - if (!fval) { - parseerror(parser, "failed to create accessor function value"); - return false; - } - fval->expression.flags &= ~(AST_FLAG_COVERAGE_MASK); - - func = ast_function_new(ast_ctx(array), funcname, fval); - if (!func) { - ast_delete(fval); - parseerror(parser, "failed to create accessor function node"); - return false; - } - - body = ast_block_new(ast_ctx(array)); - if (!body) { - parseerror(parser, "failed to create block for array accessor"); - ast_delete(fval); - ast_delete(func); - return false; - } - - vec_push(func->blocks, body); - *out = fval; - - vec_push(parser->accessors, fval); - - return true; -} - -static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname) -{ - ast_value *index = NULL; - ast_value *value = NULL; - ast_function *func; - ast_value *fval; - - if (!ast_istype(array->expression.next, ast_value)) { - parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type"); - return NULL; - } - - if (!parser_create_array_accessor(parser, array, funcname, &fval)) - return NULL; - func = fval->constval.vfunc; - fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "", TYPE_VOID); - - index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT); - value = ast_value_copy((ast_value*)array->expression.next); - - if (!index || !value) { - parseerror(parser, "failed to create locals for array accessor"); - goto cleanup; - } - (void)!ast_value_set_name(value, "value"); /* not important */ - vec_push(fval->expression.params, index); - vec_push(fval->expression.params, value); - - array->setter = fval; - return fval; -cleanup: - if (index) ast_delete(index); - if (value) ast_delete(value); - ast_delete(func); - ast_delete(fval); - return NULL; -} - -static bool parser_create_array_setter_impl(parser_t *parser, ast_value *array) -{ - ast_expression *root = NULL; - root = array_setter_node(parser, array, - array->setter->expression.params[0], - array->setter->expression.params[1], - 0, array->expression.count); - if (!root) { - parseerror(parser, "failed to build accessor search tree"); - return false; - } - if (!ast_block_add_expr(array->setter->constval.vfunc->blocks[0], root)) { - ast_delete(root); - return false; - } - return true; -} - -static bool parser_create_array_setter(parser_t *parser, ast_value *array, const char *funcname) -{ - if (!parser_create_array_setter_proto(parser, array, funcname)) - return false; - return parser_create_array_setter_impl(parser, array); -} - -static bool parser_create_array_field_setter(parser_t *parser, ast_value *array, const char *funcname) -{ - ast_expression *root = NULL; - ast_value *entity = NULL; - ast_value *index = NULL; - ast_value *value = NULL; - ast_function *func; - ast_value *fval; - - if (!ast_istype(array->expression.next, ast_value)) { - parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type"); - return false; - } - - if (!parser_create_array_accessor(parser, array, funcname, &fval)) - return false; - func = fval->constval.vfunc; - fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "", TYPE_VOID); - - entity = ast_value_new(ast_ctx(array), "entity", TYPE_ENTITY); - index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT); - value = ast_value_copy((ast_value*)array->expression.next); - if (!entity || !index || !value) { - parseerror(parser, "failed to create locals for array accessor"); - goto cleanup; - } - (void)!ast_value_set_name(value, "value"); /* not important */ - vec_push(fval->expression.params, entity); - vec_push(fval->expression.params, index); - vec_push(fval->expression.params, value); - - root = array_field_setter_node(parser, array, entity, index, value, 0, array->expression.count); - if (!root) { - parseerror(parser, "failed to build accessor search tree"); - goto cleanup; - } - - array->setter = fval; - return ast_block_add_expr(func->blocks[0], root); -cleanup: - if (entity) ast_delete(entity); - if (index) ast_delete(index); - if (value) ast_delete(value); - if (root) ast_delete(root); - ast_delete(func); - ast_delete(fval); - return false; -} - -static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname) -{ - ast_value *index = NULL; - ast_value *fval; - ast_function *func; - - /* NOTE: checking array->expression.next rather than elemtype since - * for fields elemtype is a temporary fieldtype. - */ - if (!ast_istype(array->expression.next, ast_value)) { - parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type"); - return NULL; - } - - if (!parser_create_array_accessor(parser, array, funcname, &fval)) - return NULL; - func = fval->constval.vfunc; - fval->expression.next = ast_type_copy(ast_ctx(array), elemtype); - - index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT); - - if (!index) { - parseerror(parser, "failed to create locals for array accessor"); - goto cleanup; - } - vec_push(fval->expression.params, index); - - array->getter = fval; - return fval; -cleanup: - if (index) ast_delete(index); - ast_delete(func); - ast_delete(fval); - return NULL; -} - -static bool parser_create_array_getter_impl(parser_t *parser, ast_value *array) -{ - ast_expression *root = NULL; - - root = array_getter_node(parser, array, array->getter->expression.params[0], 0, array->expression.count); - if (!root) { - parseerror(parser, "failed to build accessor search tree"); - return false; - } - if (!ast_block_add_expr(array->getter->constval.vfunc->blocks[0], root)) { - ast_delete(root); - return false; - } - return true; -} - -static bool parser_create_array_getter(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname) -{ - if (!parser_create_array_getter_proto(parser, array, elemtype, funcname)) - return false; - return parser_create_array_getter_impl(parser, array); -} - -static ast_value *parse_parameter_list(parser_t *parser, ast_value *var) -{ - lex_ctx_t ctx; - size_t i; - ast_value **params; - ast_value *param; - ast_value *fval; - bool first = true; - bool variadic = false; - ast_value *varparam = NULL; - char *argcounter = NULL; - - ctx = parser_ctx(parser); - - /* for the sake of less code we parse-in in this function */ - if (!parser_next(parser)) { - ast_delete(var); - parseerror(parser, "expected parameter list"); - return NULL; - } - - params = NULL; - - /* parse variables until we hit a closing paren */ - while (parser->tok != ')') { - bool is_varargs = false; - - if (!first) { - /* there must be commas between them */ - if (parser->tok != ',') { - parseerror(parser, "expected comma or end of parameter list"); - goto on_error; - } - if (!parser_next(parser)) { - parseerror(parser, "expected parameter"); - goto on_error; - } - } - first = false; - - param = parse_typename(parser, NULL, NULL, &is_varargs); - if (!param && !is_varargs) - goto on_error; - if (is_varargs) { - /* '...' indicates a varargs function */ - variadic = true; - if (parser->tok != ')' && parser->tok != TOKEN_IDENT) { - parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); - goto on_error; - } - if (parser->tok == TOKEN_IDENT) { - argcounter = util_strdup(parser_tokval(parser)); - if (!parser_next(parser) || parser->tok != ')') { - parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); - goto on_error; - } - } - } else { - vec_push(params, param); - if (param->expression.vtype >= TYPE_VARIANT) { - char tname[1024]; /* typename is reserved in C++ */ - ast_type_to_string((ast_expression*)param, tname, sizeof(tname)); - parseerror(parser, "type not supported as part of a parameter list: %s", tname); - goto on_error; - } - /* type-restricted varargs */ - if (parser->tok == TOKEN_DOTS) { - variadic = true; - varparam = vec_last(params); - vec_pop(params); - if (!parser_next(parser) || (parser->tok != ')' && parser->tok != TOKEN_IDENT)) { - parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); - goto on_error; - } - 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; - } - } - } - - if (vec_size(params) == 1 && params[0]->expression.vtype == TYPE_VOID) - vec_free(params); - - /* sanity check */ - if (vec_size(params) > 8 && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) - (void)!parsewarning(parser, WARN_EXTENSIONS, "more than 8 parameters are not supported by this standard"); - - /* parse-out */ - if (!parser_next(parser)) { - parseerror(parser, "parse error after typename"); - goto on_error; - } - - /* now turn 'var' into a function type */ - fval = ast_value_new(ctx, "", TYPE_FUNCTION); - fval->expression.next = (ast_expression*)var; - if (variadic) - fval->expression.flags |= AST_FLAG_VARIADIC; - var = fval; - - var->expression.params = params; - var->expression.varparam = (ast_expression*)varparam; - var->argcounter = argcounter; - params = NULL; - - return var; - -on_error: - if (argcounter) - mem_d(argcounter); - if (varparam) - ast_delete(varparam); - ast_delete(var); - for (i = 0; i < vec_size(params); ++i) - ast_delete(params[i]); - vec_free(params); - return NULL; -} - -static ast_value *parse_arraysize(parser_t *parser, ast_value *var) -{ - ast_expression *cexp; - ast_value *cval, *tmp; - lex_ctx_t ctx; - - ctx = parser_ctx(parser); - - if (!parser_next(parser)) { - ast_delete(var); - parseerror(parser, "expected array-size"); - return NULL; - } - - if (parser->tok != ']') { - cexp = parse_expression_leave(parser, true, false, false); - - if (!cexp || !ast_istype(cexp, ast_value)) { - if (cexp) - ast_unref(cexp); - ast_delete(var); - parseerror(parser, "expected array-size as constant positive integer"); - return NULL; - } - cval = (ast_value*)cexp; - } - else { - cexp = NULL; - cval = NULL; - } - - tmp = ast_value_new(ctx, "", TYPE_ARRAY); - tmp->expression.next = (ast_expression*)var; - var = tmp; - - if (cval) { - if (cval->expression.vtype == TYPE_INTEGER) - tmp->expression.count = cval->constval.vint; - else if (cval->expression.vtype == TYPE_FLOAT) - tmp->expression.count = cval->constval.vfloat; - else { - ast_unref(cexp); - ast_delete(var); - parseerror(parser, "array-size must be a positive integer constant"); - return NULL; - } - - ast_unref(cexp); - } else { - var->expression.count = -1; - var->expression.flags |= AST_FLAG_ARRAY_INIT; - } - - if (parser->tok != ']') { - ast_delete(var); - parseerror(parser, "expected ']' after array-size"); - return NULL; - } - if (!parser_next(parser)) { - ast_delete(var); - parseerror(parser, "error after parsing array size"); - return NULL; - } - return var; -} - -/* Parse a complete typename. - * for single-variables (ie. function parameters or typedefs) storebase should be NULL - * but when parsing variables separated by comma - * 'storebase' should point to where the base-type should be kept. - * The base type makes up every bit of type information which comes *before* the - * variable name. - * - * NOTE: The value must either be named, have a NULL name, or a name starting - * with '<'. In the first case, this will be the actual variable or type - * name, in the other cases it is assumed that the name will appear - * later, and an error is generated otherwise. - * - * The following will be parsed in its entirety: - * void() foo() - * The 'basetype' in this case is 'void()' - * and if there's a comma after it, say: - * void() foo(), bar - * then the type-information 'void()' can be stored in 'storebase' - */ -static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg) -{ - ast_value *var, *tmp; - lex_ctx_t ctx; - - const char *name = NULL; - bool isfield = false; - bool wasarray = false; - size_t morefields = 0; - - bool vararg = (parser->tok == TOKEN_DOTS); - - ctx = parser_ctx(parser); - - /* types may start with a dot */ - if (parser->tok == '.' || parser->tok == TOKEN_DOTS) { - isfield = true; - if (parser->tok == TOKEN_DOTS) - morefields += 2; - /* if we parsed a dot we need a typename now */ - if (!parser_next(parser)) { - parseerror(parser, "expected typename for field definition"); - return NULL; - } - - /* Further dots are handled seperately because they won't be part of the - * basetype - */ - while (true) { - if (parser->tok == '.') - ++morefields; - else if (parser->tok == TOKEN_DOTS) - morefields += 3; - else - break; - vararg = false; - if (!parser_next(parser)) { - parseerror(parser, "expected typename for field definition"); - return NULL; - } - } - } - if (parser->tok == TOKEN_IDENT) - cached_typedef = parser_find_typedef(parser, parser_tokval(parser), 0); - if (!cached_typedef && parser->tok != TOKEN_TYPENAME) { - if (vararg && is_vararg) { - *is_vararg = true; - return NULL; - } - parseerror(parser, "expected typename"); - return NULL; - } - - /* generate the basic type value */ - if (cached_typedef) { - var = ast_value_copy(cached_typedef); - ast_value_set_name(var, ""); - } else - var = ast_value_new(ctx, "", parser_token(parser)->constval.t); - - for (; morefields; --morefields) { - tmp = ast_value_new(ctx, "<.type>", TYPE_FIELD); - tmp->expression.next = (ast_expression*)var; - var = tmp; - } - - /* do not yet turn into a field - remember: - * .void() foo; is a field too - * .void()() foo; is a function - */ - - /* parse on */ - if (!parser_next(parser)) { - ast_delete(var); - parseerror(parser, "parse error after typename"); - return NULL; - } - - /* an opening paren now starts the parameter-list of a function - * this is where original-QC has parameter lists. - * We allow a single parameter list here. - * Much like fteqcc we don't allow `float()() x` - */ - if (parser->tok == '(') { - var = parse_parameter_list(parser, var); - if (!var) - return NULL; - } - - /* store the base if requested */ - if (storebase) { - *storebase = ast_value_copy(var); - if (isfield) { - tmp = ast_value_new(ctx, "", TYPE_FIELD); - tmp->expression.next = (ast_expression*)*storebase; - *storebase = tmp; - } - } - - /* there may be a name now */ - if (parser->tok == TOKEN_IDENT || parser->tok == TOKEN_KEYWORD) { - if (!strcmp(parser_tokval(parser), "break")) - (void)!parsewarning(parser, WARN_BREAKDEF, "break definition ignored (suggest removing it)"); - else if (parser->tok == TOKEN_KEYWORD) - goto leave; - - name = util_strdup(parser_tokval(parser)); - - /* parse on */ - if (!parser_next(parser)) { - ast_delete(var); - mem_d(name); - parseerror(parser, "error after variable or field declaration"); - return NULL; - } - } - - leave: - /* now this may be an array */ - if (parser->tok == '[') { - wasarray = true; - var = parse_arraysize(parser, var); - if (!var) { - if (name) mem_d(name); - return NULL; - } - } - - /* This is the point where we can turn it into a field */ - if (isfield) { - /* turn it into a field if desired */ - tmp = ast_value_new(ctx, "", TYPE_FIELD); - tmp->expression.next = (ast_expression*)var; - var = tmp; - } - - /* now there may be function parens again */ - if (parser->tok == '(' && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) - parseerror(parser, "C-style function syntax is not allowed in -std=qcc"); - if (parser->tok == '(' && wasarray) - parseerror(parser, "arrays as part of a return type is not supported"); - while (parser->tok == '(') { - var = parse_parameter_list(parser, var); - if (!var) { - if (name) mem_d(name); - return NULL; - } - } - - /* finally name it */ - if (name) { - if (!ast_value_set_name(var, name)) { - ast_delete(var); - mem_d(name); - parseerror(parser, "internal error: failed to set name"); - return NULL; - } - /* free the name, ast_value_set_name duplicates */ - mem_d(name); - } - - return var; -} - -static bool parse_typedef(parser_t *parser) -{ - ast_value *typevar, *oldtype; - ast_expression *old; - - typevar = parse_typename(parser, NULL, NULL, NULL); - - if (!typevar) - return false; - - /* while parsing types, the ast_value's get named '' */ - if (!typevar->name || typevar->name[0] == '<') { - parseerror(parser, "missing name in typedef"); - ast_delete(typevar); - return false; - } - - if ( (old = parser_find_var(parser, typevar->name)) ) { - parseerror(parser, "cannot define a type with the same name as a variable: %s\n" - " -> `%s` has been declared here: %s:%i", - typevar->name, ast_ctx(old).file, ast_ctx(old).line); - ast_delete(typevar); - return false; - } - - if ( (oldtype = parser_find_typedef(parser, typevar->name, vec_last(parser->_blocktypedefs))) ) { - parseerror(parser, "type `%s` has already been declared here: %s:%i", - typevar->name, ast_ctx(oldtype).file, ast_ctx(oldtype).line); - ast_delete(typevar); - return false; - } - - vec_push(parser->_typedefs, typevar); - util_htset(vec_last(parser->typedefs), typevar->name, typevar); - - if (parser->tok != ';') { - parseerror(parser, "expected semicolon after typedef"); - return false; - } - if (!parser_next(parser)) { - parseerror(parser, "parse error after typedef"); - return false; - } - - return true; -} - -static const char *cvq_to_str(int cvq) { - switch (cvq) { - case CV_NONE: return "none"; - case CV_VAR: return "`var`"; - case CV_CONST: return "`const`"; - default: return ""; - } -} - -static bool parser_check_qualifiers(parser_t *parser, const ast_value *var, const ast_value *proto) -{ - bool av, ao; - if (proto->cvq != var->cvq) { - if (!(proto->cvq == CV_CONST && var->cvq == CV_NONE && - !OPTS_FLAG(INITIALIZED_NONCONSTANTS) && - parser->tok == '=')) - { - return !parsewarning(parser, WARN_DIFFERENT_QUALIFIERS, - "`%s` declared with different qualifiers: %s\n" - " -> previous declaration here: %s:%i uses %s", - var->name, cvq_to_str(var->cvq), - ast_ctx(proto).file, ast_ctx(proto).line, - cvq_to_str(proto->cvq)); - } - } - av = (var ->expression.flags & AST_FLAG_NORETURN); - ao = (proto->expression.flags & AST_FLAG_NORETURN); - if (!av != !ao) { - return !parsewarning(parser, WARN_DIFFERENT_ATTRIBUTES, - "`%s` declared with different attributes%s\n" - " -> previous declaration here: %s:%i", - var->name, (av ? ": noreturn" : ""), - ast_ctx(proto).file, ast_ctx(proto).line, - (ao ? ": noreturn" : "")); - } - return true; -} - -static bool create_array_accessors(parser_t *parser, ast_value *var) -{ - char name[1024]; - util_snprintf(name, sizeof(name), "%s##SET", var->name); - if (!parser_create_array_setter(parser, var, name)) - return false; - util_snprintf(name, sizeof(name), "%s##GET", var->name); - if (!parser_create_array_getter(parser, var, var->expression.next, name)) - return false; - return true; -} - -static bool parse_array(parser_t *parser, ast_value *array) -{ - size_t i; - if (array->initlist) { - parseerror(parser, "array already initialized elsewhere"); - return false; - } - if (!parser_next(parser)) { - parseerror(parser, "parse error in array initializer"); - return false; - } - i = 0; - while (parser->tok != '}') { - ast_value *v = (ast_value*)parse_expression_leave(parser, true, false, false); - if (!v) - return false; - if (!ast_istype(v, ast_value) || !v->hasvalue || v->cvq != CV_CONST) { - ast_unref(v); - parseerror(parser, "initializing element must be a compile time constant"); - return false; - } - vec_push(array->initlist, v->constval); - if (v->expression.vtype == TYPE_STRING) { - array->initlist[i].vstring = util_strdupe(array->initlist[i].vstring); - ++i; - } - ast_unref(v); - if (parser->tok == '}') - break; - if (parser->tok != ',' || !parser_next(parser)) { - parseerror(parser, "expected comma or '}' in element list"); - return false; - } - } - if (!parser_next(parser) || parser->tok != ';') { - parseerror(parser, "expected semicolon after initializer, got %s"); - return false; - } - /* - if (!parser_next(parser)) { - parseerror(parser, "parse error after initializer"); - return false; - } - */ - - if (array->expression.flags & AST_FLAG_ARRAY_INIT) { - if (array->expression.count != (size_t)-1) { - parseerror(parser, "array `%s' has already been initialized with %u elements", - array->name, (unsigned)array->expression.count); - } - array->expression.count = vec_size(array->initlist); - if (!create_array_accessors(parser, array)) - return false; - } - return true; -} - -static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring) -{ - ast_value *var; - ast_value *proto; - ast_expression *old; - bool was_end; - size_t i; - - ast_value *basetype = NULL; - bool retval = true; - bool isparam = false; - bool isvector = false; - bool cleanvar = true; - 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"); - - /* get the first complete variable */ - var = parse_typename(parser, &basetype, cached_typedef, NULL); - if (!var) { - if (basetype) - ast_delete(basetype); - return false; - } - - /* while parsing types, the ast_value's get named '' */ - if (!var->name || var->name[0] == '<') { - parseerror(parser, "declaration does not declare anything"); - if (basetype) - ast_delete(basetype); - return false; - } - - while (true) { - proto = NULL; - wasarray = false; - - /* Part 0: finish the type */ - if (parser->tok == '(') { - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) - parseerror(parser, "C-style function syntax is not allowed in -std=qcc"); - var = parse_parameter_list(parser, var); - if (!var) { - retval = false; - goto cleanup; - } - } - /* we only allow 1-dimensional arrays */ - if (parser->tok == '[') { - wasarray = true; - var = parse_arraysize(parser, var); - if (!var) { - retval = false; - goto cleanup; - } - } - if (parser->tok == '(' && wasarray) { - parseerror(parser, "arrays as part of a return type is not supported"); - /* we'll still parse the type completely for now */ - } - /* for functions returning functions */ - while (parser->tok == '(') { - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) - parseerror(parser, "C-style function syntax is not allowed in -std=qcc"); - var = parse_parameter_list(parser, var); - if (!var) { - retval = false; - goto cleanup; - } - } - - var->cvq = qualifier; - if (qflags & AST_FLAG_COVERAGE) /* specified in QC, drop our default */ - var->expression.flags &= ~(AST_FLAG_COVERAGE_MASK); - var->expression.flags |= qflags; - - /* - * store the vstring back to var for alias and - * deprecation messages. - */ - if (var->expression.flags & AST_FLAG_DEPRECATED || - var->expression.flags & AST_FLAG_ALIAS) - var->desc = vstring; - - if (parser_find_global(parser, var->name) && var->expression.flags & AST_FLAG_ALIAS) { - parseerror(parser, "function aliases cannot be forward declared"); - retval = false; - goto cleanup; - } - - - /* Part 1: - * check for validity: (end_sys_..., multiple-definitions, prototypes, ...) - * Also: if there was a prototype, `var` will be deleted and set to `proto` which - * is then filled with the previous definition and the parameter-names replaced. - */ - if (!strcmp(var->name, "nil")) { - if (OPTS_FLAG(UNTYPED_NIL)) { - if (!localblock || !OPTS_FLAG(PERMISSIVE)) - parseerror(parser, "name `nil` not allowed (try -fpermissive)"); - } else - (void)!parsewarning(parser, WARN_RESERVED_NAMES, "variable name `nil` is reserved"); - } - if (!localblock) { - /* Deal with end_sys_ vars */ - was_end = false; - if (!strcmp(var->name, "end_sys_globals")) { - var->uses++; - parser->crc_globals = vec_size(parser->globals); - was_end = true; - } - else if (!strcmp(var->name, "end_sys_fields")) { - var->uses++; - parser->crc_fields = vec_size(parser->fields); - was_end = true; - } - if (was_end && var->expression.vtype == TYPE_FIELD) { - if (parsewarning(parser, WARN_END_SYS_FIELDS, - "global '%s' hint should not be a field", - parser_tokval(parser))) - { - retval = false; - goto cleanup; - } - } - - if (!nofields && var->expression.vtype == TYPE_FIELD) - { - /* deal with field declarations */ - old = parser_find_field(parser, var->name); - if (old) { - if (parsewarning(parser, WARN_FIELD_REDECLARED, "field `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, (int)ast_ctx(old).line)) - { - retval = false; - goto cleanup; - } - ast_delete(var); - var = NULL; - goto skipvar; - /* - parseerror(parser, "field `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, ast_ctx(old).line); - retval = false; - goto cleanup; - */ - } - if ((OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC || OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) && - (old = parser_find_global(parser, var->name))) - { - parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc"); - parseerror(parser, "field `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, ast_ctx(old).line); - retval = false; - goto cleanup; - } - } - else - { - /* deal with other globals */ - old = parser_find_global(parser, var->name); - if (old && var->expression.vtype == TYPE_FUNCTION && old->vtype == TYPE_FUNCTION) - { - /* This is a function which had a prototype */ - if (!ast_istype(old, ast_value)) { - parseerror(parser, "internal error: prototype is not an ast_value"); - retval = false; - goto cleanup; - } - proto = (ast_value*)old; - proto->desc = var->desc; - if (!ast_compare_type((ast_expression*)proto, (ast_expression*)var)) { - parseerror(parser, "conflicting types for `%s`, previous declaration was here: %s:%i", - proto->name, - ast_ctx(proto).file, ast_ctx(proto).line); - retval = false; - goto cleanup; - } - /* we need the new parameter-names */ - for (i = 0; i < vec_size(proto->expression.params); ++i) - ast_value_set_name(proto->expression.params[i], var->expression.params[i]->name); - if (!parser_check_qualifiers(parser, var, proto)) { - retval = false; - if (proto->desc) - mem_d(proto->desc); - proto = NULL; - goto cleanup; - } - proto->expression.flags |= var->expression.flags; - ast_delete(var); - var = proto; - } - else - { - /* other globals */ - if (old) { - if (parsewarning(parser, WARN_DOUBLE_DECLARATION, - "global `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, ast_ctx(old).line)) - { - retval = false; - goto cleanup; - } - if (old->flags & AST_FLAG_FINAL_DECL) { - parseerror(parser, "cannot redeclare variable `%s`, declared final here: %s:%i", - var->name, ast_ctx(old).file, ast_ctx(old).line); - retval = false; - goto cleanup; - } - proto = (ast_value*)old; - if (!ast_istype(old, ast_value)) { - parseerror(parser, "internal error: not an ast_value"); - retval = false; - proto = NULL; - goto cleanup; - } - if (!parser_check_qualifiers(parser, var, proto)) { - retval = false; - proto = NULL; - goto cleanup; - } - proto->expression.flags |= var->expression.flags; - /* copy the context for finals, - * so the error can show where it was actually made 'final' - */ - if (proto->expression.flags & AST_FLAG_FINAL_DECL) - ast_ctx(old) = ast_ctx(var); - ast_delete(var); - var = proto; - } - if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC && - (old = parser_find_field(parser, var->name))) - { - parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc"); - parseerror(parser, "global `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, ast_ctx(old).line); - retval = false; - goto cleanup; - } - } - } - } - else /* it's not a global */ - { - old = parser_find_local(parser, var->name, vec_size(parser->variables)-1, &isparam); - if (old && !isparam) { - parseerror(parser, "local `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, (int)ast_ctx(old).line); - retval = false; - goto cleanup; - } - /* doing this here as the above is just for a single scope */ - old = parser_find_local(parser, var->name, 0, &isparam); - if (old && isparam) { - if (parsewarning(parser, WARN_LOCAL_SHADOWS, - "local `%s` is shadowing a parameter", var->name)) - { - parseerror(parser, "local `%s` already declared here: %s:%i", - var->name, ast_ctx(old).file, (int)ast_ctx(old).line); - retval = false; - goto cleanup; - } - if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) { - ast_delete(var); - if (ast_istype(old, ast_value)) - var = proto = (ast_value*)old; - else { - var = NULL; - goto skipvar; - } - } - } - } - - /* in a noref section we simply bump the usecount */ - if (noref || parser->noref) - var->uses++; - - /* Part 2: - * Create the global/local, and deal with vector types. - */ - if (!proto) { - if (var->expression.vtype == TYPE_VECTOR) - isvector = true; - else if (var->expression.vtype == TYPE_FIELD && - var->expression.next->vtype == TYPE_VECTOR) - isvector = true; - - if (isvector) { - if (!create_vector_members(var, me)) { - retval = false; - goto cleanup; - } - } - - if (!localblock) { - /* deal with global variables, fields, functions */ - if (!nofields && var->expression.vtype == TYPE_FIELD && parser->tok != '=') { - var->isfield = true; - vec_push(parser->fields, (ast_expression*)var); - util_htset(parser->htfields, var->name, var); - if (isvector) { - for (i = 0; i < 3; ++i) { - vec_push(parser->fields, (ast_expression*)me[i]); - util_htset(parser->htfields, me[i]->name, me[i]); - } - } - } - else { - if (!(var->expression.flags & AST_FLAG_ALIAS)) { - parser_addglobal(parser, var->name, (ast_expression*)var); - if (isvector) { - for (i = 0; i < 3; ++i) { - parser_addglobal(parser, me[i]->name, (ast_expression*)me[i]); - } - } - } else { - ast_expression *find = parser_find_global(parser, var->desc); - - if (!find) { - compile_error(parser_ctx(parser), "undeclared variable `%s` for alias `%s`", var->desc, var->name); - return false; - } - - if (!ast_compare_type((ast_expression*)var, find)) { - char ty1[1024]; - char ty2[1024]; - - ast_type_to_string(find, ty1, sizeof(ty1)); - ast_type_to_string((ast_expression*)var, ty2, sizeof(ty2)); - - compile_error(parser_ctx(parser), "incompatible types `%s` and `%s` for alias `%s`", - ty1, ty2, var->name - ); - return false; - } - - util_htset(parser->aliases, var->name, find); - - /* generate aliases for vector components */ - if (isvector) { - char *buffer[3]; - - util_asprintf(&buffer[0], "%s_x", var->desc); - util_asprintf(&buffer[1], "%s_y", var->desc); - util_asprintf(&buffer[2], "%s_z", var->desc); - - util_htset(parser->aliases, me[0]->name, parser_find_global(parser, buffer[0])); - util_htset(parser->aliases, me[1]->name, parser_find_global(parser, buffer[1])); - util_htset(parser->aliases, me[2]->name, parser_find_global(parser, buffer[2])); - - mem_d(buffer[0]); - mem_d(buffer[1]); - mem_d(buffer[2]); - } - } - } - } else { - if (is_static) { - /* a static adds itself to be generated like any other global - * but is added to the local namespace instead - */ - char *defname = NULL; - size_t prefix_len, ln; - size_t sn, sn_size; - - ln = strlen(parser->function->name); - vec_append(defname, ln, parser->function->name); - - vec_append(defname, 2, "::"); - /* remember the length up to here */ - prefix_len = vec_size(defname); - - /* Add it to the local scope */ - util_htset(vec_last(parser->variables), var->name, (void*)var); - - /* now rename the global */ - ln = strlen(var->name); - vec_append(defname, ln, var->name); - /* if a variable of that name already existed, add the - * counter value. - * The counter is incremented either way. - */ - sn_size = vec_size(parser->function->static_names); - for (sn = 0; sn != sn_size; ++sn) { - if (strcmp(parser->function->static_names[sn], var->name) == 0) - break; - } - if (sn != sn_size) { - char *num = NULL; - int len = util_asprintf(&num, "#%u", parser->function->static_count); - vec_append(defname, len, num); - mem_d(num); - } - else - vec_push(parser->function->static_names, util_strdup(var->name)); - parser->function->static_count++; - ast_value_set_name(var, defname); - - /* push it to the to-be-generated globals */ - vec_push(parser->globals, (ast_expression*)var); - - /* same game for the vector members */ - if (isvector) { - for (i = 0; i < 3; ++i) { - util_htset(vec_last(parser->variables), me[i]->name, (void*)(me[i])); - - vec_shrinkto(defname, prefix_len); - ln = strlen(me[i]->name); - vec_append(defname, ln, me[i]->name); - ast_member_set_name(me[i], defname); - - vec_push(parser->globals, (ast_expression*)me[i]); - } - } - vec_free(defname); - } else { - vec_push(localblock->locals, var); - parser_addlocal(parser, var->name, (ast_expression*)var); - if (isvector) { - for (i = 0; i < 3; ++i) { - parser_addlocal(parser, me[i]->name, (ast_expression*)me[i]); - ast_block_collect(localblock, (ast_expression*)me[i]); - } - } - } - } - } - memcpy(last_me, me, sizeof(me)); - me[0] = me[1] = me[2] = NULL; - cleanvar = false; - /* Part 2.2 - * deal with arrays - */ - if (var->expression.vtype == TYPE_ARRAY) { - if (var->expression.count != (size_t)-1) { - if (!create_array_accessors(parser, var)) - goto cleanup; - } - } - else if (!localblock && !nofields && - var->expression.vtype == TYPE_FIELD && - var->expression.next->vtype == TYPE_ARRAY) - { - char name[1024]; - ast_expression *telem; - ast_value *tfield; - ast_value *array = (ast_value*)var->expression.next; - - if (!ast_istype(var->expression.next, ast_value)) { - parseerror(parser, "internal error: field element type must be an ast_value"); - goto cleanup; - } - - util_snprintf(name, sizeof(name), "%s##SETF", var->name); - if (!parser_create_array_field_setter(parser, array, name)) - goto cleanup; - - telem = ast_type_copy(ast_ctx(var), array->expression.next); - tfield = ast_value_new(ast_ctx(var), "<.type>", TYPE_FIELD); - tfield->expression.next = telem; - util_snprintf(name, sizeof(name), "%s##GETFP", var->name); - if (!parser_create_array_getter(parser, array, (ast_expression*)tfield, name)) { - ast_delete(tfield); - goto cleanup; - } - ast_delete(tfield); - } - -skipvar: - if (parser->tok == ';') { - ast_delete(basetype); - if (!parser_next(parser)) { - parseerror(parser, "error after variable declaration"); - return false; - } - return true; - } - - if (parser->tok == ',') - goto another; - - /* - if (!var || (!localblock && !nofields && basetype->expression.vtype == TYPE_FIELD)) { - */ - if (!var) { - parseerror(parser, "missing comma or semicolon while parsing variables"); - break; - } - - if (localblock && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { - if (parsewarning(parser, WARN_LOCAL_CONSTANTS, - "initializing expression turns variable `%s` into a constant in this standard", - var->name) ) - { - break; - } - } - - if (parser->tok != '{' || var->expression.vtype != TYPE_FUNCTION) { - if (parser->tok != '=') { - parseerror(parser, "missing semicolon or initializer, got: `%s`", parser_tokval(parser)); - break; - } - - if (!parser_next(parser)) { - parseerror(parser, "error parsing initializer"); - break; - } - } - else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { - parseerror(parser, "expected '=' before function body in this standard"); - } - - if (parser->tok == '#') { - ast_function *func = NULL; - ast_value *number = NULL; - float fractional; - float integral; - int builtin_num; - - if (localblock) { - parseerror(parser, "cannot declare builtins within functions"); - break; - } - if (var->expression.vtype != TYPE_FUNCTION) { - parseerror(parser, "unexpected builtin number, '%s' is not a function", var->name); - break; - } - if (!parser_next(parser)) { - parseerror(parser, "expected builtin number"); - break; - } - - if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS)) { - number = (ast_value*)parse_expression_leave(parser, true, false, false); - if (!number) { - parseerror(parser, "builtin number expected"); - break; - } - if (!ast_istype(number, ast_value) || !number->hasvalue || number->cvq != CV_CONST) - { - ast_unref(number); - parseerror(parser, "builtin number must be a compile time constant"); - break; - } - if (number->expression.vtype == TYPE_INTEGER) - builtin_num = number->constval.vint; - else if (number->expression.vtype == TYPE_FLOAT) - builtin_num = number->constval.vfloat; - else { - ast_unref(number); - parseerror(parser, "builtin number must be an integer constant"); - break; - } - ast_unref(number); - - fractional = modff(builtin_num, &integral); - if (builtin_num < 0 || fractional != 0) { - parseerror(parser, "builtin number must be an integer greater than zero"); - break; - } - - /* we only want the integral part anyways */ - builtin_num = integral; - } else if (parser->tok == TOKEN_INTCONST) { - builtin_num = parser_token(parser)->constval.i; - } else { - parseerror(parser, "builtin number must be a compile time constant"); - break; - } - - if (var->hasvalue) { - (void)!parsewarning(parser, WARN_DOUBLE_DECLARATION, - "builtin `%s` has already been defined\n" - " -> previous declaration here: %s:%i", - var->name, ast_ctx(var).file, (int)ast_ctx(var).line); - } - else - { - func = ast_function_new(ast_ctx(var), var->name, var); - if (!func) { - parseerror(parser, "failed to allocate function for `%s`", var->name); - break; - } - vec_push(parser->functions, func); - - func->builtin = -builtin_num-1; - } - - if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS) - ? (parser->tok != ',' && parser->tok != ';') - : (!parser_next(parser))) - { - parseerror(parser, "expected comma or semicolon"); - if (func) - ast_function_delete(func); - var->constval.vfunc = NULL; - break; - } - } - else if (var->expression.vtype == TYPE_ARRAY && parser->tok == '{') - { - if (localblock) { - /* Note that fteqcc and most others don't even *have* - * local arrays, so this is not a high priority. - */ - parseerror(parser, "TODO: initializers for local arrays"); - break; - } - - var->hasvalue = true; - if (!parse_array(parser, var)) - break; - } - else if (var->expression.vtype == TYPE_FUNCTION && (parser->tok == '{' || parser->tok == '[')) - { - if (localblock) { - parseerror(parser, "cannot declare functions within functions"); - break; - } - - if (proto) - ast_ctx(proto) = parser_ctx(parser); - - if (!parse_function_body(parser, var)) - break; - ast_delete(basetype); - for (i = 0; i < vec_size(parser->gotos); ++i) - parseerror(parser, "undefined label: `%s`", parser->gotos[i]->name); - vec_free(parser->gotos); - vec_free(parser->labels); - return true; - } 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; - - /* 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 && - (!cval || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield)) - ) - { - parseerror(parser, "initializer is non constant"); - } - else - { - if (!is_static && - !OPTS_FLAG(INITIALIZED_NONCONSTANTS) && - qualifier != CV_VAR) - { - var->cvq = CV_CONST; - } - if (cval == parser->nil) - var->expression.flags |= AST_FLAG_INITIALIZED; - else - { - var->hasvalue = true; - if (cval->expression.vtype == TYPE_STRING) - var->constval.vstring = parser_strdup(cval->constval.vstring); - else if (cval->expression.vtype == TYPE_FIELD) - var->constval.vfield = cval; - else - memcpy(&var->constval, &cval->constval, sizeof(var->constval)); - ast_unref(cval); - } - } - } else { - int cvq; - shunt sy = { NULL, NULL, NULL, NULL }; - cvq = var->cvq; - var->cvq = CV_NONE; - vec_push(sy.out, syexp(ast_ctx(var), (ast_expression*)var)); - vec_push(sy.out, syexp(ast_ctx(cexp), (ast_expression*)cexp)); - vec_push(sy.ops, syop(ast_ctx(var), parser->assign_op)); - if (!parser_sy_apply_operator(parser, &sy)) - ast_unref(cexp); - else { - if (vec_size(sy.out) != 1 && vec_size(sy.ops) != 0) - parseerror(parser, "internal error: leaked operands"); - if (!ast_block_add_expr(localblock, (ast_expression*)sy.out[0].out)) - break; - } - vec_free(sy.out); - vec_free(sy.ops); - vec_free(sy.argc); - var->cvq = cvq; - } - /* a constant initialized to an inexact value should be marked inexact: - * const float x = ; 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: - if (parser->tok == ',') { - if (!parser_next(parser)) { - parseerror(parser, "expected another variable"); - break; - } - - if (parser->tok != TOKEN_IDENT) { - parseerror(parser, "expected another variable"); - break; - } - var = ast_value_copy(basetype); - cleanvar = true; - ast_value_set_name(var, parser_tokval(parser)); - if (!parser_next(parser)) { - parseerror(parser, "error parsing variable declaration"); - break; - } - continue; - } - - if (parser->tok != ';') { - parseerror(parser, "missing semicolon after variables"); - break; - } - - if (!parser_next(parser)) { - parseerror(parser, "parse error after variable declaration"); - break; - } - - ast_delete(basetype); - return true; - } - - if (cleanvar && var) - ast_delete(var); - ast_delete(basetype); - return false; - -cleanup: - ast_delete(basetype); - if (cleanvar && var) - ast_delete(var); - if (me[0]) ast_member_delete(me[0]); - if (me[1]) ast_member_delete(me[1]); - if (me[2]) ast_member_delete(me[2]); - return retval; -} - -static bool parser_global_statement(parser_t *parser) -{ - int cvq = CV_WRONG; - bool noref = false; - bool is_static = false; - uint32_t qflags = 0; - ast_value *istype = NULL; - char *vstring = NULL; - - if (parser->tok == TOKEN_IDENT) - istype = parser_find_typedef(parser, parser_tokval(parser), 0); - - if (istype || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS) - { - return parse_variable(parser, NULL, false, CV_NONE, istype, false, false, 0, NULL); - } - else if (parse_qualifiers(parser, false, &cvq, &noref, &is_static, &qflags, &vstring)) - { - if (cvq == CV_WRONG) - return false; - return parse_variable(parser, NULL, false, cvq, NULL, noref, is_static, qflags, vstring); - } - else if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "enum")) - { - return parse_enum(parser); - } - else if (parser->tok == TOKEN_KEYWORD) - { - if (!strcmp(parser_tokval(parser), "typedef")) { - if (!parser_next(parser)) { - parseerror(parser, "expected type definition after 'typedef'"); - return false; - } - return parse_typedef(parser); - } - parseerror(parser, "unrecognized keyword `%s`", parser_tokval(parser)); - return false; - } - else if (parser->tok == '#') - { - return parse_pragma(parser); - } - else if (parser->tok == '$') - { - if (!parser_next(parser)) { - parseerror(parser, "parse error"); - return false; - } - } - else - { - parseerror(parser, "unexpected token: `%s`", parser->lex->tok.value); - return false; - } - return true; -} - -static uint16_t progdefs_crc_sum(uint16_t old, const char *str) -{ - return util_crc16(old, str, strlen(str)); -} - -static void progdefs_crc_file(const char *str) -{ - /* write to progdefs.h here */ - (void)str; -} - -static uint16_t progdefs_crc_both(uint16_t old, const char *str) -{ - old = progdefs_crc_sum(old, str); - progdefs_crc_file(str); - return old; -} - -static void generate_checksum(parser_t *parser, ir_builder *ir) -{ - uint16_t crc = 0xFFFF; - size_t i; - ast_value *value; - - crc = progdefs_crc_both(crc, "\n/* file generated by qcc, do not modify */\n\ntypedef struct\n{"); - crc = progdefs_crc_sum(crc, "\tint\tpad[28];\n"); - /* - progdefs_crc_file("\tint\tpad;\n"); - progdefs_crc_file("\tint\tofs_return[3];\n"); - progdefs_crc_file("\tint\tofs_parm0[3];\n"); - progdefs_crc_file("\tint\tofs_parm1[3];\n"); - progdefs_crc_file("\tint\tofs_parm2[3];\n"); - progdefs_crc_file("\tint\tofs_parm3[3];\n"); - progdefs_crc_file("\tint\tofs_parm4[3];\n"); - progdefs_crc_file("\tint\tofs_parm5[3];\n"); - progdefs_crc_file("\tint\tofs_parm6[3];\n"); - progdefs_crc_file("\tint\tofs_parm7[3];\n"); - */ - for (i = 0; i < parser->crc_globals; ++i) { - if (!ast_istype(parser->globals[i], ast_value)) - continue; - value = (ast_value*)(parser->globals[i]); - switch (value->expression.vtype) { - case TYPE_FLOAT: crc = progdefs_crc_both(crc, "\tfloat\t"); break; - case TYPE_VECTOR: crc = progdefs_crc_both(crc, "\tvec3_t\t"); break; - case TYPE_STRING: crc = progdefs_crc_both(crc, "\tstring_t\t"); break; - case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break; - default: - crc = progdefs_crc_both(crc, "\tint\t"); - break; - } - crc = progdefs_crc_both(crc, value->name); - crc = progdefs_crc_both(crc, ";\n"); - } - crc = progdefs_crc_both(crc, "} globalvars_t;\n\ntypedef struct\n{\n"); - for (i = 0; i < parser->crc_fields; ++i) { - if (!ast_istype(parser->fields[i], ast_value)) - continue; - value = (ast_value*)(parser->fields[i]); - switch (value->expression.next->vtype) { - case TYPE_FLOAT: crc = progdefs_crc_both(crc, "\tfloat\t"); break; - case TYPE_VECTOR: crc = progdefs_crc_both(crc, "\tvec3_t\t"); break; - case TYPE_STRING: crc = progdefs_crc_both(crc, "\tstring_t\t"); break; - case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break; - default: - crc = progdefs_crc_both(crc, "\tint\t"); - break; - } - crc = progdefs_crc_both(crc, value->name); - crc = progdefs_crc_both(crc, ";\n"); - } - crc = progdefs_crc_both(crc, "} entvars_t;\n\n"); - ir->code->crc = crc; -} - -parser_t *parser_create() -{ - parser_t *parser; - lex_ctx_t empty_ctx; - size_t i; - - parser = (parser_t*)mem_a(sizeof(parser_t)); - if (!parser) - return NULL; - - memset(parser, 0, sizeof(*parser)); - - for (i = 0; i < operator_count; ++i) { - if (operators[i].id == opid1('=')) { - parser->assign_op = operators+i; - break; - } - } - if (!parser->assign_op) { - con_err("internal error: initializing parser: failed to find assign operator\n"); - mem_d(parser); - return NULL; - } - - vec_push(parser->variables, parser->htfields = util_htnew(PARSER_HT_SIZE)); - vec_push(parser->variables, parser->htglobals = util_htnew(PARSER_HT_SIZE)); - vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE)); - vec_push(parser->_blocktypedefs, 0); - - parser->aliases = util_htnew(PARSER_HT_SIZE); - - empty_ctx.file = ""; - empty_ctx.line = 0; - empty_ctx.column = 0; - parser->nil = ast_value_new(empty_ctx, "nil", TYPE_NIL); - parser->nil->cvq = CV_CONST; - if (OPTS_FLAG(UNTYPED_NIL)) - util_htset(parser->htglobals, "nil", (void*)parser->nil); - - parser->max_param_count = 1; - - parser->const_vec[0] = ast_value_new(empty_ctx, "", TYPE_NOEXPR); - parser->const_vec[1] = ast_value_new(empty_ctx, "", TYPE_NOEXPR); - parser->const_vec[2] = ast_value_new(empty_ctx, "", TYPE_NOEXPR); - - if (OPTS_OPTION_BOOL(OPTION_ADD_INFO)) { - parser->reserved_version = ast_value_new(empty_ctx, "reserved:version", TYPE_STRING); - parser->reserved_version->cvq = CV_CONST; - parser->reserved_version->hasvalue = true; - parser->reserved_version->expression.flags |= AST_FLAG_INCLUDE_DEF; - parser->reserved_version->constval.vstring = util_strdup(GMQCC_FULL_VERSION_STRING); - } else { - parser->reserved_version = NULL; - } - - parser->fold = fold_init (parser); - parser->intrin = intrin_init(parser); - return parser; -} - -static bool parser_compile(parser_t *parser) -{ - /* initial lexer/parser state */ - parser->lex->flags.noops = true; - - if (parser_next(parser)) - { - while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR) - { - if (!parser_global_statement(parser)) { - if (parser->tok == TOKEN_EOF) - parseerror(parser, "unexpected end of file"); - else if (compile_errors) - parseerror(parser, "there have been errors, bailing out"); - lex_close(parser->lex); - parser->lex = NULL; - return false; - } - } - } else { - parseerror(parser, "parse error"); - lex_close(parser->lex); - parser->lex = NULL; - return false; - } - - lex_close(parser->lex); - parser->lex = NULL; - - return !compile_errors; -} - -bool parser_compile_file(parser_t *parser, const char *filename) -{ - parser->lex = lex_open(filename); - if (!parser->lex) { - con_err("failed to open file \"%s\"\n", filename); - return false; - } - return parser_compile(parser); -} - -bool parser_compile_string(parser_t *parser, const char *name, const char *str, size_t len) -{ - parser->lex = lex_open_string(str, len, name); - if (!parser->lex) { - con_err("failed to create lexer for string \"%s\"\n", name); - return false; - } - return parser_compile(parser); -} - -static void parser_remove_ast(parser_t *parser) -{ - size_t i; - if (parser->ast_cleaned) - return; - parser->ast_cleaned = true; - for (i = 0; i < vec_size(parser->accessors); ++i) { - ast_delete(parser->accessors[i]->constval.vfunc); - parser->accessors[i]->constval.vfunc = NULL; - ast_delete(parser->accessors[i]); - } - for (i = 0; i < vec_size(parser->functions); ++i) { - ast_delete(parser->functions[i]); - } - for (i = 0; i < vec_size(parser->fields); ++i) { - ast_delete(parser->fields[i]); - } - for (i = 0; i < vec_size(parser->globals); ++i) { - ast_delete(parser->globals[i]); - } - vec_free(parser->accessors); - vec_free(parser->functions); - vec_free(parser->globals); - vec_free(parser->fields); - - for (i = 0; i < vec_size(parser->variables); ++i) - util_htdel(parser->variables[i]); - vec_free(parser->variables); - vec_free(parser->_blocklocals); - vec_free(parser->_locals); - - for (i = 0; i < vec_size(parser->_typedefs); ++i) - ast_delete(parser->_typedefs[i]); - vec_free(parser->_typedefs); - for (i = 0; i < vec_size(parser->typedefs); ++i) - util_htdel(parser->typedefs[i]); - vec_free(parser->typedefs); - vec_free(parser->_blocktypedefs); - - vec_free(parser->_block_ctx); - - vec_free(parser->labels); - vec_free(parser->gotos); - vec_free(parser->breaks); - vec_free(parser->continues); - - ast_value_delete(parser->nil); - - ast_value_delete(parser->const_vec[0]); - ast_value_delete(parser->const_vec[1]); - ast_value_delete(parser->const_vec[2]); - - if (parser->reserved_version) - ast_value_delete(parser->reserved_version); - - util_htdel(parser->aliases); - fold_cleanup(parser->fold); - intrin_cleanup(parser->intrin); -} - -void parser_cleanup(parser_t *parser) -{ - parser_remove_ast(parser); - mem_d(parser); -} - -static bool parser_set_coverage_func(parser_t *parser, ir_builder *ir) { - size_t i; - ast_expression *expr; - ast_value *cov; - ast_function *func; - - if (!OPTS_OPTION_BOOL(OPTION_COVERAGE)) - return true; - - func = NULL; - for (i = 0; i != vec_size(parser->functions); ++i) { - if (!strcmp(parser->functions[i]->name, "coverage")) { - func = parser->functions[i]; - break; - } - } - if (!func) { - if (OPTS_OPTION_BOOL(OPTION_COVERAGE)) { - con_out("coverage support requested but no coverage() builtin declared\n"); - ir_builder_delete(ir); - return false; - } - return true; - } - - cov = func->vtype; - expr = (ast_expression*)cov; - - if (expr->vtype != TYPE_FUNCTION || vec_size(expr->params) != 0) { - char ty[1024]; - ast_type_to_string(expr, ty, sizeof(ty)); - con_out("invalid type for coverage(): %s\n", ty); - ir_builder_delete(ir); - return false; - } - - ir->coverage_func = func->ir_func->value; - return true; -} - -bool parser_finish(parser_t *parser, const char *output) -{ - size_t i; - ir_builder *ir; - bool retval = true; - - if (compile_errors) { - con_out("*** there were compile errors\n"); - return false; - } - - ir = ir_builder_new("gmqcc_out"); - if (!ir) { - con_out("failed to allocate builder\n"); - return false; - } - - for (i = 0; i < vec_size(parser->fields); ++i) { - ast_value *field; - bool hasvalue; - if (!ast_istype(parser->fields[i], ast_value)) - continue; - field = (ast_value*)parser->fields[i]; - hasvalue = field->hasvalue; - field->hasvalue = false; - if (!ast_global_codegen((ast_value*)field, ir, true)) { - con_out("failed to generate field %s\n", field->name); - ir_builder_delete(ir); - return false; - } - if (hasvalue) { - ir_value *ifld; - ast_expression *subtype; - field->hasvalue = true; - subtype = field->expression.next; - ifld = ir_builder_create_field(ir, field->name, subtype->vtype); - if (subtype->vtype == TYPE_FIELD) - ifld->fieldtype = subtype->next->vtype; - else if (subtype->vtype == TYPE_FUNCTION) - ifld->outtype = subtype->next->vtype; - (void)!ir_value_set_field(field->ir_v, ifld); - } - } - for (i = 0; i < vec_size(parser->globals); ++i) { - ast_value *asvalue; - if (!ast_istype(parser->globals[i], ast_value)) - continue; - asvalue = (ast_value*)(parser->globals[i]); - if (!asvalue->uses && !asvalue->hasvalue && asvalue->expression.vtype != TYPE_FUNCTION) { - retval = retval && !compile_warning(ast_ctx(asvalue), WARN_UNUSED_VARIABLE, - "unused global: `%s`", asvalue->name); - } - if (!ast_global_codegen(asvalue, ir, false)) { - con_out("failed to generate global %s\n", asvalue->name); - ir_builder_delete(ir); - return false; - } - } - /* Build function vararg accessor ast tree now before generating - * immediates, because the accessors may add new immediates - */ - for (i = 0; i < vec_size(parser->functions); ++i) { - ast_function *f = parser->functions[i]; - if (f->varargs) { - if (parser->max_param_count > vec_size(f->vtype->expression.params)) { - f->varargs->expression.count = parser->max_param_count - vec_size(f->vtype->expression.params); - if (!parser_create_array_setter_impl(parser, f->varargs)) { - con_out("failed to generate vararg setter for %s\n", f->name); - ir_builder_delete(ir); - return false; - } - if (!parser_create_array_getter_impl(parser, f->varargs)) { - con_out("failed to generate vararg getter for %s\n", f->name); - ir_builder_delete(ir); - return false; - } - } else { - ast_delete(f->varargs); - f->varargs = NULL; - } - } - } - /* Now we can generate immediates */ - if (!fold_generate(parser->fold, ir)) - return false; - - /* before generating any functions we need to set the coverage_func */ - if (!parser_set_coverage_func(parser, ir)) - return false; - - for (i = 0; i < vec_size(parser->globals); ++i) { - ast_value *asvalue; - if (!ast_istype(parser->globals[i], ast_value)) - continue; - asvalue = (ast_value*)(parser->globals[i]); - if (!(asvalue->expression.flags & AST_FLAG_INITIALIZED)) - { - if (asvalue->cvq == CV_CONST && !asvalue->hasvalue) - (void)!compile_warning(ast_ctx(asvalue), WARN_UNINITIALIZED_CONSTANT, - "uninitialized constant: `%s`", - asvalue->name); - else if ((asvalue->cvq == CV_NONE || asvalue->cvq == CV_CONST) && !asvalue->hasvalue) - (void)!compile_warning(ast_ctx(asvalue), WARN_UNINITIALIZED_GLOBAL, - "uninitialized global: `%s`", - asvalue->name); - } - if (!ast_generate_accessors(asvalue, ir)) { - ir_builder_delete(ir); - return false; - } - } - for (i = 0; i < vec_size(parser->fields); ++i) { - ast_value *asvalue; - asvalue = (ast_value*)(parser->fields[i]->next); - - if (!ast_istype((ast_expression*)asvalue, ast_value)) - continue; - if (asvalue->expression.vtype != TYPE_ARRAY) - continue; - if (!ast_generate_accessors(asvalue, ir)) { - ir_builder_delete(ir); - return false; - } - } - if (parser->reserved_version && - !ast_global_codegen(parser->reserved_version, ir, false)) - { - con_out("failed to generate reserved::version"); - ir_builder_delete(ir); - return false; - } - for (i = 0; i < vec_size(parser->functions); ++i) { - ast_function *f = parser->functions[i]; - if (!ast_function_codegen(f, ir)) { - con_out("failed to generate function %s\n", f->name); - ir_builder_delete(ir); - return false; - } - } - - generate_checksum(parser, ir); - - if (OPTS_OPTION_BOOL(OPTION_DUMP)) - ir_builder_dump(ir, con_out); - for (i = 0; i < vec_size(parser->functions); ++i) { - if (!ir_function_finalize(parser->functions[i]->ir_func)) { - con_out("failed to finalize function %s\n", parser->functions[i]->name); - ir_builder_delete(ir); - return false; - } - } - parser_remove_ast(parser); - - if (compile_Werrors) { - con_out("*** there were warnings treated as errors\n"); - compile_show_werrors(); - retval = false; - } - - if (retval) { - if (OPTS_OPTION_BOOL(OPTION_DUMPFIN)) - ir_builder_dump(ir, con_out); - - if (!ir_builder_generate(ir, output)) { - con_out("*** failed to generate output file\n"); - ir_builder_delete(ir); - return false; - } - } - ir_builder_delete(ir); - return retval; -} diff --git a/parser.cpp b/parser.cpp new file mode 100644 index 0000000..0370146 --- /dev/null +++ b/parser.cpp @@ -0,0 +1,6380 @@ +#include +#include + +#include "parser.h" + +#define PARSER_HT_LOCALS 2 +#define PARSER_HT_SIZE 512 +#define TYPEDEF_HT_SIZE 512 + +static void parser_enterblock(parser_t *parser); +static bool parser_leaveblock(parser_t *parser); +static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e); +static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e); +static bool parse_typedef(parser_t *parser); +static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring); +static ast_block* parse_block(parser_t *parser); +static bool parse_block_into(parser_t *parser, ast_block *block); +static bool parse_statement_or_block(parser_t *parser, ast_expression **out); +static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases); +static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels); +static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels); +static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname); +static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname); +static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg); + +static void parseerror(parser_t *parser, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vcompile_error(parser->lex->tok.ctx, fmt, ap); + va_end(ap); +} + +/* returns true if it counts as an error */ +static bool GMQCC_WARN parsewarning(parser_t *parser, int warntype, const char *fmt, ...) +{ + bool r; + va_list ap; + va_start(ap, fmt); + r = vcompile_warning(parser->lex->tok.ctx, warntype, fmt, ap); + va_end(ap); + return r; +} + +/********************************************************************** + * parsing + */ + +static bool parser_next(parser_t *parser) +{ + /* lex_do kills the previous token */ + parser->tok = lex_do(parser->lex); + if (parser->tok == TOKEN_EOF) + return true; + if (parser->tok >= TOKEN_ERROR) { + parseerror(parser, "lex error"); + return false; + } + return true; +} + +#define parser_tokval(p) ((p)->lex->tok.value) +#define parser_token(p) (&((p)->lex->tok)) + +char *parser_strdup(const char *str) +{ + if (str && !*str) { + /* actually dup empty strings */ + char *out = (char*)mem_a(1); + *out = 0; + return out; + } + return util_strdup(str); +} + +static ast_expression* parser_find_field(parser_t *parser, const char *name) +{ + return ( ast_expression*)util_htget(parser->htfields, name); +} + +static ast_expression* parser_find_label(parser_t *parser, const char *name) +{ + size_t i; + for(i = 0; i < vec_size(parser->labels); i++) + if (!strcmp(parser->labels[i]->name, name)) + return (ast_expression*)parser->labels[i]; + return NULL; +} + +ast_expression* parser_find_global(parser_t *parser, const char *name) +{ + ast_expression *var = (ast_expression*)util_htget(parser->aliases, parser_tokval(parser)); + if (var) + return var; + return (ast_expression*)util_htget(parser->htglobals, name); +} + +static ast_expression* parser_find_param(parser_t *parser, const char *name) +{ + size_t i; + ast_value *fun; + if (!parser->function) + return NULL; + fun = parser->function->vtype; + for (i = 0; i < vec_size(fun->expression.params); ++i) { + if (!strcmp(fun->expression.params[i]->name, name)) + return (ast_expression*)(fun->expression.params[i]); + } + return NULL; +} + +static ast_expression* parser_find_local(parser_t *parser, const char *name, size_t upto, bool *isparam) +{ + size_t i, hash; + ast_expression *e; + + hash = util_hthash(parser->htglobals, name); + + *isparam = false; + for (i = vec_size(parser->variables); i > upto;) { + --i; + if ( (e = (ast_expression*)util_htgeth(parser->variables[i], name, hash)) ) + return e; + } + *isparam = true; + return parser_find_param(parser, name); +} + +static ast_expression* parser_find_var(parser_t *parser, const char *name) +{ + bool dummy; + ast_expression *v; + v = parser_find_local(parser, name, 0, &dummy); + if (!v) v = parser_find_global(parser, name); + return v; +} + +static ast_value* parser_find_typedef(parser_t *parser, const char *name, size_t upto) +{ + size_t i, hash; + ast_value *e; + hash = util_hthash(parser->typedefs[0], name); + + for (i = vec_size(parser->typedefs); i > upto;) { + --i; + if ( (e = (ast_value*)util_htgeth(parser->typedefs[i], name, hash)) ) + return e; + } + return NULL; +} + +typedef struct +{ + size_t etype; /* 0 = expression, others are operators */ + bool isparen; + size_t off; + ast_expression *out; + ast_block *block; /* for commas and function calls */ + lex_ctx_t ctx; +} sy_elem; + +enum { + PAREN_EXPR, + PAREN_FUNC, + PAREN_INDEX, + PAREN_TERNARY1, + PAREN_TERNARY2 +}; +typedef struct +{ + sy_elem *out; + sy_elem *ops; + size_t *argc; + unsigned int *paren; +} shunt; + +static sy_elem syexp(lex_ctx_t ctx, ast_expression *v) { + sy_elem e; + e.etype = 0; + e.off = 0; + e.out = v; + e.block = NULL; + e.ctx = ctx; + e.isparen = false; + return e; +} + +static sy_elem syblock(lex_ctx_t ctx, ast_block *v) { + sy_elem e; + e.etype = 0; + e.off = 0; + e.out = (ast_expression*)v; + e.block = v; + e.ctx = ctx; + e.isparen = false; + return e; +} + +static sy_elem syop(lex_ctx_t ctx, const oper_info *op) { + sy_elem e; + e.etype = 1 + (op - operators); + e.off = 0; + e.out = NULL; + e.block = NULL; + e.ctx = ctx; + e.isparen = false; + return e; +} + +static sy_elem syparen(lex_ctx_t ctx, size_t off) { + sy_elem e; + e.etype = 0; + e.off = off; + e.out = NULL; + e.block = NULL; + e.ctx = ctx; + e.isparen = true; + return e; +} + +/* With regular precedence rules, ent.foo[n] is the same as (ent.foo)[n], + * so we need to rotate it to become ent.(foo[n]). + */ +static bool rotate_entfield_array_index_nodes(ast_expression **out) +{ + ast_array_index *index, *oldindex; + ast_entfield *entfield; + + ast_value *field; + ast_expression *sub; + ast_expression *entity; + + lex_ctx_t ctx = ast_ctx(*out); + + if (!ast_istype(*out, ast_array_index)) + return false; + index = (ast_array_index*)*out; + + if (!ast_istype(index->array, ast_entfield)) + return false; + entfield = (ast_entfield*)index->array; + + if (!ast_istype(entfield->field, ast_value)) + return false; + field = (ast_value*)entfield->field; + + sub = index->index; + entity = entfield->entity; + + oldindex = index; + + index = ast_array_index_new(ctx, (ast_expression*)field, sub); + entfield = ast_entfield_new(ctx, entity, (ast_expression*)index); + *out = (ast_expression*)entfield; + + oldindex->array = NULL; + oldindex->index = NULL; + ast_delete(oldindex); + + return true; +} + +static bool check_write_to(lex_ctx_t ctx, ast_expression *expr) +{ + if (ast_istype(expr, ast_value)) { + ast_value *val = (ast_value*)expr; + if (val->cvq == CV_CONST) { + if (val->name[0] == '#') { + compile_error(ctx, "invalid assignment to a literal constant"); + return false; + } + /* + * To work around quakeworld we must elide the error and make it + * a warning instead. + */ + if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) + compile_error(ctx, "assignment to constant `%s`", val->name); + else + (void)!compile_warning(ctx, WARN_CONST_OVERWRITE, "assignment to constant `%s`", val->name); + return false; + } + } + return true; +} + +static bool parser_sy_apply_operator(parser_t *parser, shunt *sy) +{ + const oper_info *op; + lex_ctx_t ctx; + ast_expression *out = NULL; + ast_expression *exprs[3]; + ast_block *blocks[3]; + ast_binstore *asbinstore; + size_t i, assignop, addop, subop; + qcint_t generated_op = 0; + + char ty1[1024]; + char ty2[1024]; + + if (!vec_size(sy->ops)) { + parseerror(parser, "internal error: missing operator"); + return false; + } + + if (vec_last(sy->ops).isparen) { + parseerror(parser, "unmatched parenthesis"); + return false; + } + + op = &operators[vec_last(sy->ops).etype - 1]; + ctx = vec_last(sy->ops).ctx; + + if (vec_size(sy->out) < op->operands) { + if (op->flags & OP_PREFIX) + compile_error(ctx, "expected expression after unary operator `%s`", op->op, (int)op->id); + else /* this should have errored previously already */ + compile_error(ctx, "expected expression after operator `%s`", op->op, (int)op->id); + return false; + } + + vec_shrinkby(sy->ops, 1); + + /* op(:?) has no input and no output */ + if (!op->operands) + return true; + + vec_shrinkby(sy->out, op->operands); + for (i = 0; i < op->operands; ++i) { + exprs[i] = sy->out[vec_size(sy->out)+i].out; + blocks[i] = sy->out[vec_size(sy->out)+i].block; + + if (exprs[i]->vtype == TYPE_NOEXPR && + !(i != 0 && op->id == opid2('?',':')) && + !(i == 1 && op->id == opid1('.'))) + { + if (ast_istype(exprs[i], ast_label)) + compile_error(ast_ctx(exprs[i]), "expected expression, got an unknown identifier"); + else + compile_error(ast_ctx(exprs[i]), "not an expression"); + (void)!compile_warning(ast_ctx(exprs[i]), WARN_DEBUG, "expression %u\n", (unsigned int)i); + } + } + + if (blocks[0] && !vec_size(blocks[0]->exprs) && op->id != opid1(',')) { + compile_error(ctx, "internal error: operator cannot be applied on empty blocks"); + return false; + } + +#define NotSameType(T) \ + (exprs[0]->vtype != exprs[1]->vtype || \ + exprs[0]->vtype != T) + + switch (op->id) + { + default: + compile_error(ctx, "internal error: unhandled operator: %s (%i)", op->op, (int)op->id); + return false; + + case opid1('.'): + if (exprs[0]->vtype == TYPE_VECTOR && + exprs[1]->vtype == TYPE_NOEXPR) + { + if (exprs[1] == (ast_expression*)parser->const_vec[0]) + out = (ast_expression*)ast_member_new(ctx, exprs[0], 0, NULL); + else if (exprs[1] == (ast_expression*)parser->const_vec[1]) + out = (ast_expression*)ast_member_new(ctx, exprs[0], 1, NULL); + else if (exprs[1] == (ast_expression*)parser->const_vec[2]) + out = (ast_expression*)ast_member_new(ctx, exprs[0], 2, NULL); + else { + compile_error(ctx, "access to invalid vector component"); + return false; + } + } + else if (exprs[0]->vtype == TYPE_ENTITY) { + if (exprs[1]->vtype != TYPE_FIELD) { + compile_error(ast_ctx(exprs[1]), "type error: right hand of member-operand should be an entity-field"); + return false; + } + out = (ast_expression*)ast_entfield_new(ctx, exprs[0], exprs[1]); + } + else if (exprs[0]->vtype == TYPE_VECTOR) { + compile_error(ast_ctx(exprs[1]), "vectors cannot be accessed this way"); + return false; + } + else { + compile_error(ast_ctx(exprs[1]), "type error: member-of operator on something that is not an entity or vector"); + return false; + } + break; + + case opid1('['): + if (exprs[0]->vtype != TYPE_ARRAY && + !(exprs[0]->vtype == TYPE_FIELD && + exprs[0]->next->vtype == TYPE_ARRAY)) + { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + compile_error(ast_ctx(exprs[0]), "cannot index value of type %s", ty1); + return false; + } + if (exprs[1]->vtype != TYPE_FLOAT) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + compile_error(ast_ctx(exprs[1]), "index must be of type float, not %s", ty1); + return false; + } + out = (ast_expression*)ast_array_index_new(ctx, exprs[0], exprs[1]); + rotate_entfield_array_index_nodes(&out); + break; + + case opid1(','): + if (vec_size(sy->paren) && vec_last(sy->paren) == PAREN_FUNC) { + vec_push(sy->out, syexp(ctx, exprs[0])); + vec_push(sy->out, syexp(ctx, exprs[1])); + vec_last(sy->argc)++; + return true; + } + if (blocks[0]) { + if (!ast_block_add_expr(blocks[0], exprs[1])) + return false; + } else { + blocks[0] = ast_block_new(ctx); + if (!ast_block_add_expr(blocks[0], exprs[0]) || + !ast_block_add_expr(blocks[0], exprs[1])) + { + return false; + } + } + ast_block_set_type(blocks[0], exprs[1]); + + vec_push(sy->out, syblock(ctx, blocks[0])); + return true; + + case opid2('+','P'): + out = exprs[0]; + break; + case opid2('-','P'): + if ((out = fold_op(parser->fold, op, exprs))) + break; + + if (exprs[0]->vtype != TYPE_FLOAT && + exprs[0]->vtype != TYPE_VECTOR) { + compile_error(ctx, "invalid types used in unary expression: cannot negate type %s", + type_name[exprs[0]->vtype]); + return false; + } + if (exprs[0]->vtype == TYPE_FLOAT) + out = (ast_expression*)ast_unary_new(ctx, VINSTR_NEG_F, exprs[0]); + else + out = (ast_expression*)ast_unary_new(ctx, VINSTR_NEG_V, exprs[0]); + break; + + case opid2('!','P'): + if (!(out = fold_op(parser->fold, op, exprs))) { + switch (exprs[0]->vtype) { + case TYPE_FLOAT: + out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, exprs[0]); + break; + case TYPE_VECTOR: + out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_V, exprs[0]); + break; + case TYPE_STRING: + if (OPTS_FLAG(TRUE_EMPTY_STRINGS)) + out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, exprs[0]); + else + out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_S, exprs[0]); + break; + /* we don't constant-fold NOT for these types */ + case TYPE_ENTITY: + out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_ENT, exprs[0]); + break; + case TYPE_FUNCTION: + out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_FNC, exprs[0]); + break; + default: + compile_error(ctx, "invalid types used in expression: cannot logically negate type %s", + type_name[exprs[0]->vtype]); + return false; + } + } + break; + + case opid1('+'): + if (exprs[0]->vtype != exprs[1]->vtype || + (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT) ) + { + compile_error(ctx, "invalid types used in expression: cannot add type %s and %s", + type_name[exprs[0]->vtype], + type_name[exprs[1]->vtype]); + return false; + } + if (!(out = fold_op(parser->fold, op, exprs))) { + switch (exprs[0]->vtype) { + case TYPE_FLOAT: + out = fold_binary(ctx, INSTR_ADD_F, exprs[0], exprs[1]); + break; + case TYPE_VECTOR: + out = fold_binary(ctx, INSTR_ADD_V, exprs[0], exprs[1]); + break; + default: + compile_error(ctx, "invalid types used in expression: cannot add type %s and %s", + type_name[exprs[0]->vtype], + type_name[exprs[1]->vtype]); + return false; + } + } + break; + case opid1('-'): + if (exprs[0]->vtype != exprs[1]->vtype || + (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT)) + { + compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s", + type_name[exprs[1]->vtype], + type_name[exprs[0]->vtype]); + return false; + } + if (!(out = fold_op(parser->fold, op, exprs))) { + switch (exprs[0]->vtype) { + case TYPE_FLOAT: + out = fold_binary(ctx, INSTR_SUB_F, exprs[0], exprs[1]); + break; + case TYPE_VECTOR: + out = fold_binary(ctx, INSTR_SUB_V, exprs[0], exprs[1]); + break; + default: + compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s", + type_name[exprs[1]->vtype], + type_name[exprs[0]->vtype]); + return false; + } + } + break; + case opid1('*'): + if (exprs[0]->vtype != exprs[1]->vtype && + !(exprs[0]->vtype == TYPE_VECTOR && + exprs[1]->vtype == TYPE_FLOAT) && + !(exprs[1]->vtype == TYPE_VECTOR && + exprs[0]->vtype == TYPE_FLOAT) + ) + { + compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s", + type_name[exprs[1]->vtype], + type_name[exprs[0]->vtype]); + return false; + } + if (!(out = fold_op(parser->fold, op, exprs))) { + switch (exprs[0]->vtype) { + case TYPE_FLOAT: + if (exprs[1]->vtype == TYPE_VECTOR) + out = fold_binary(ctx, INSTR_MUL_FV, exprs[0], exprs[1]); + else + out = fold_binary(ctx, INSTR_MUL_F, exprs[0], exprs[1]); + break; + case TYPE_VECTOR: + if (exprs[1]->vtype == TYPE_FLOAT) + out = fold_binary(ctx, INSTR_MUL_VF, exprs[0], exprs[1]); + else + out = fold_binary(ctx, INSTR_MUL_V, exprs[0], exprs[1]); + break; + default: + compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s", + type_name[exprs[1]->vtype], + type_name[exprs[0]->vtype]); + return false; + } + } + break; + + case opid1('/'): + if (exprs[1]->vtype != TYPE_FLOAT) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2); + return false; + } + if (!(out = fold_op(parser->fold, op, exprs))) { + if (exprs[0]->vtype == TYPE_FLOAT) + out = fold_binary(ctx, INSTR_DIV_F, exprs[0], exprs[1]); + else { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2); + return false; + } + } + break; + + case opid1('%'): + if (NotSameType(TYPE_FLOAT)) { + compile_error(ctx, "invalid types used in expression: cannot perform modulo operation between types %s and %s", + type_name[exprs[0]->vtype], + type_name[exprs[1]->vtype]); + return false; + } else if (!(out = fold_op(parser->fold, op, exprs))) { + /* generate a call to __builtin_mod */ + ast_expression *mod = intrin_func(parser->intrin, "mod"); + ast_call *call = NULL; + if (!mod) return false; /* can return null for missing floor */ + + call = ast_call_new(parser_ctx(parser), mod); + vec_push(call->params, exprs[0]); + vec_push(call->params, exprs[1]); + + out = (ast_expression*)call; + } + break; + + case opid2('%','='): + compile_error(ctx, "%= is unimplemented"); + return false; + + case opid1('|'): + case opid1('&'): + case opid1('^'): + if ( !(exprs[0]->vtype == TYPE_FLOAT && exprs[1]->vtype == TYPE_FLOAT) && + !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_FLOAT) && + !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_VECTOR)) + { + compile_error(ctx, "invalid types used in expression: cannot perform bit operations between types %s and %s", + type_name[exprs[0]->vtype], + type_name[exprs[1]->vtype]); + return false; + } + + if (!(out = fold_op(parser->fold, op, exprs))) { + /* + * IF the first expression is float, the following will be too + * since scalar ^ vector is not allowed. + */ + if (exprs[0]->vtype == TYPE_FLOAT) { + out = fold_binary(ctx, + (op->id == opid1('^') ? VINSTR_BITXOR : op->id == opid1('|') ? INSTR_BITOR : INSTR_BITAND), + exprs[0], exprs[1]); + } else { + /* + * The first is a vector: vector is allowed to bitop with vector and + * with scalar, branch here for the second operand. + */ + if (exprs[1]->vtype == TYPE_VECTOR) { + /* + * Bitop all the values of the vector components against the + * vectors components in question. + */ + out = fold_binary(ctx, + (op->id == opid1('^') ? VINSTR_BITXOR_V : op->id == opid1('|') ? VINSTR_BITOR_V : VINSTR_BITAND_V), + exprs[0], exprs[1]); + } else { + out = fold_binary(ctx, + (op->id == opid1('^') ? VINSTR_BITXOR_VF : op->id == opid1('|') ? VINSTR_BITOR_VF : VINSTR_BITAND_VF), + exprs[0], exprs[1]); + } + } + } + break; + + case opid2('<','<'): + case opid2('>','>'): + if (NotSameType(TYPE_FLOAT)) { + compile_error(ctx, "invalid types used in expression: cannot perform shift between types %s and %s", + type_name[exprs[0]->vtype], + type_name[exprs[1]->vtype]); + return false; + } + + if (!(out = fold_op(parser->fold, op, exprs))) { + ast_expression *shift = intrin_func(parser->intrin, (op->id == opid2('<','<')) ? "__builtin_lshift" : "__builtin_rshift"); + ast_call *call = ast_call_new(parser_ctx(parser), shift); + vec_push(call->params, exprs[0]); + vec_push(call->params, exprs[1]); + out = (ast_expression*)call; + } + break; + + case opid3('<','<','='): + case opid3('>','>','='): + if (NotSameType(TYPE_FLOAT)) { + compile_error(ctx, "invalid types used in expression: cannot perform shift operation between types %s and %s", + type_name[exprs[0]->vtype], + type_name[exprs[1]->vtype]); + return false; + } + + if(!(out = fold_op(parser->fold, op, exprs))) { + ast_expression *shift = intrin_func(parser->intrin, (op->id == opid3('<','<','=')) ? "__builtin_lshift" : "__builtin_rshift"); + ast_call *call = ast_call_new(parser_ctx(parser), shift); + vec_push(call->params, exprs[0]); + vec_push(call->params, exprs[1]); + out = (ast_expression*)ast_store_new( + parser_ctx(parser), + INSTR_STORE_F, + exprs[0], + (ast_expression*)call + ); + } + + break; + + case opid2('|','|'): + generated_op += 1; /* INSTR_OR */ + case opid2('&','&'): + generated_op += INSTR_AND; + if (!(out = fold_op(parser->fold, op, exprs))) { + if (OPTS_FLAG(PERL_LOGIC) && !ast_compare_type(exprs[0], exprs[1])) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types for logical operation with -fperl-logic: %s and %s", ty1, ty2); + return false; + } + for (i = 0; i < 2; ++i) { + if (OPTS_FLAG(CORRECT_LOGIC) && exprs[i]->vtype == TYPE_VECTOR) { + out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_V, exprs[i]); + if (!out) break; + out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, out); + if (!out) break; + exprs[i] = out; out = NULL; + if (OPTS_FLAG(PERL_LOGIC)) { + /* here we want to keep the right expressions' type */ + break; + } + } + else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && exprs[i]->vtype == TYPE_STRING) { + out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_S, exprs[i]); + if (!out) break; + out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, out); + if (!out) break; + exprs[i] = out; out = NULL; + if (OPTS_FLAG(PERL_LOGIC)) { + /* here we want to keep the right expressions' type */ + break; + } + } + } + out = fold_binary(ctx, generated_op, exprs[0], exprs[1]); + } + break; + + case opid2('?',':'): + if (vec_last(sy->paren) != PAREN_TERNARY2) { + compile_error(ctx, "mismatched parenthesis/ternary"); + return false; + } + vec_pop(sy->paren); + if (!ast_compare_type(exprs[1], exprs[2])) { + ast_type_to_string(exprs[1], ty1, sizeof(ty1)); + ast_type_to_string(exprs[2], ty2, sizeof(ty2)); + compile_error(ctx, "operands of ternary expression must have the same type, got %s and %s", ty1, ty2); + return false; + } + if (!(out = fold_op(parser->fold, op, exprs))) + out = (ast_expression*)ast_ternary_new(ctx, exprs[0], exprs[1], exprs[2]); + break; + + case opid2('*', '*'): + if (NotSameType(TYPE_FLOAT)) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in exponentiation: %s and %s", + ty1, ty2); + return false; + } + + if (!(out = fold_op(parser->fold, op, exprs))) { + ast_call *gencall = ast_call_new(parser_ctx(parser), intrin_func(parser->intrin, "pow")); + vec_push(gencall->params, exprs[0]); + vec_push(gencall->params, exprs[1]); + out = (ast_expression*)gencall; + } + break; + + case opid2('>', '<'): + if (NotSameType(TYPE_VECTOR)) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in cross product: %s and %s", + ty1, ty2); + return false; + } + + if (!(out = fold_op(parser->fold, op, exprs))) { + out = fold_binary( + parser_ctx(parser), + VINSTR_CROSS, + exprs[0], + exprs[1] + ); + } + + break; + + case opid3('<','=','>'): /* -1, 0, or 1 */ + if (NotSameType(TYPE_FLOAT)) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in comparision: %s and %s", + ty1, ty2); + + return false; + } + + if (!(out = fold_op(parser->fold, op, exprs))) { + /* This whole block is NOT fold_binary safe */ + ast_binary *eq = ast_binary_new(ctx, INSTR_EQ_F, exprs[0], exprs[1]); + + eq->refs = AST_REF_NONE; + + /* if (lt) { */ + out = (ast_expression*)ast_ternary_new(ctx, + (ast_expression*)ast_binary_new(ctx, INSTR_LT, exprs[0], exprs[1]), + /* out = -1 */ + (ast_expression*)parser->fold->imm_float[2], + /* } else { */ + /* if (eq) { */ + (ast_expression*)ast_ternary_new(ctx, (ast_expression*)eq, + /* out = 0 */ + (ast_expression*)parser->fold->imm_float[0], + /* } else { */ + /* out = 1 */ + (ast_expression*)parser->fold->imm_float[1] + /* } */ + ) + /* } */ + ); + + } + break; + + case opid1('>'): + generated_op += 1; /* INSTR_GT */ + case opid1('<'): + generated_op += 1; /* INSTR_LT */ + case opid2('>', '='): + generated_op += 1; /* INSTR_GE */ + case opid2('<', '='): + generated_op += INSTR_LE; + if (NotSameType(TYPE_FLOAT)) { + compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s", + type_name[exprs[0]->vtype], + type_name[exprs[1]->vtype]); + return false; + } + if (!(out = fold_op(parser->fold, op, exprs))) + out = fold_binary(ctx, generated_op, exprs[0], exprs[1]); + break; + case opid2('!', '='): + if (exprs[0]->vtype != exprs[1]->vtype) { + compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s", + type_name[exprs[0]->vtype], + type_name[exprs[1]->vtype]); + return false; + } + if (!(out = fold_op(parser->fold, op, exprs))) + out = fold_binary(ctx, type_ne_instr[exprs[0]->vtype], exprs[0], exprs[1]); + break; + case opid2('=', '='): + if (exprs[0]->vtype != exprs[1]->vtype) { + compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s", + type_name[exprs[0]->vtype], + type_name[exprs[1]->vtype]); + return false; + } + if (!(out = fold_op(parser->fold, op, exprs))) + out = fold_binary(ctx, type_eq_instr[exprs[0]->vtype], exprs[0], exprs[1]); + break; + + case opid1('='): + if (ast_istype(exprs[0], ast_entfield)) { + ast_expression *field = ((ast_entfield*)exprs[0])->field; + if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) && + exprs[0]->vtype == TYPE_FIELD && + exprs[0]->next->vtype == TYPE_VECTOR) + { + assignop = type_storep_instr[TYPE_VECTOR]; + } + else + assignop = type_storep_instr[exprs[0]->vtype]; + if (assignop == VINSTR_END || !ast_compare_type(field->next, exprs[1])) + { + ast_type_to_string(field->next, ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) && + field->next->vtype == TYPE_FUNCTION && + exprs[1]->vtype == TYPE_FUNCTION) + { + (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES, + "invalid types in assignment: cannot assign %s to %s", ty2, ty1); + } + else + compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1); + } + } + else + { + if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) && + exprs[0]->vtype == TYPE_FIELD && + exprs[0]->next->vtype == TYPE_VECTOR) + { + assignop = type_store_instr[TYPE_VECTOR]; + } + else { + assignop = type_store_instr[exprs[0]->vtype]; + } + + if (assignop == VINSTR_END) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1); + } + else if (!ast_compare_type(exprs[0], exprs[1])) + { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) && + exprs[0]->vtype == TYPE_FUNCTION && + exprs[1]->vtype == TYPE_FUNCTION) + { + (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES, + "invalid types in assignment: cannot assign %s to %s", ty2, ty1); + } + else + compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1); + } + } + (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'): + case opid3('-','-','P'): + /* prefix ++ */ + if (exprs[0]->vtype != TYPE_FLOAT) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + compile_error(ast_ctx(exprs[0]), "invalid type for prefix increment: %s", ty1); + return false; + } + if (op->id == opid3('+','+','P')) + addop = INSTR_ADD_F; + else + addop = INSTR_SUB_F; + (void)check_write_to(ast_ctx(exprs[0]), exprs[0]); + if (ast_istype(exprs[0], ast_entfield)) { + out = (ast_expression*)ast_binstore_new(ctx, INSTR_STOREP_F, addop, + exprs[0], + (ast_expression*)parser->fold->imm_float[1]); + } else { + out = (ast_expression*)ast_binstore_new(ctx, INSTR_STORE_F, addop, + exprs[0], + (ast_expression*)parser->fold->imm_float[1]); + } + break; + case opid3('S','+','+'): + case opid3('S','-','-'): + /* prefix ++ */ + if (exprs[0]->vtype != TYPE_FLOAT) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + compile_error(ast_ctx(exprs[0]), "invalid type for suffix increment: %s", ty1); + return false; + } + if (op->id == opid3('S','+','+')) { + addop = INSTR_ADD_F; + subop = INSTR_SUB_F; + } else { + addop = INSTR_SUB_F; + subop = INSTR_ADD_F; + } + (void)check_write_to(ast_ctx(exprs[0]), exprs[0]); + if (ast_istype(exprs[0], ast_entfield)) { + out = (ast_expression*)ast_binstore_new(ctx, INSTR_STOREP_F, addop, + exprs[0], + (ast_expression*)parser->fold->imm_float[1]); + } else { + out = (ast_expression*)ast_binstore_new(ctx, INSTR_STORE_F, addop, + exprs[0], + (ast_expression*)parser->fold->imm_float[1]); + } + if (!out) + return false; + out = fold_binary(ctx, subop, + out, + (ast_expression*)parser->fold->imm_float[1]); + + break; + case opid2('+','='): + case opid2('-','='): + if (exprs[0]->vtype != exprs[1]->vtype || + (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT) ) + { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s", + ty1, ty2); + return false; + } + (void)check_write_to(ctx, exprs[0]); + if (ast_istype(exprs[0], ast_entfield)) + assignop = type_storep_instr[exprs[0]->vtype]; + else + assignop = type_store_instr[exprs[0]->vtype]; + switch (exprs[0]->vtype) { + case TYPE_FLOAT: + out = (ast_expression*)ast_binstore_new(ctx, assignop, + (op->id == opid2('+','=') ? INSTR_ADD_F : INSTR_SUB_F), + exprs[0], exprs[1]); + break; + case TYPE_VECTOR: + out = (ast_expression*)ast_binstore_new(ctx, assignop, + (op->id == opid2('+','=') ? INSTR_ADD_V : INSTR_SUB_V), + exprs[0], exprs[1]); + break; + default: + compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s", + type_name[exprs[0]->vtype], + type_name[exprs[1]->vtype]); + return false; + }; + break; + case opid2('*','='): + case opid2('/','='): + if (exprs[1]->vtype != TYPE_FLOAT || + !(exprs[0]->vtype == TYPE_FLOAT || + exprs[0]->vtype == TYPE_VECTOR)) + { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in expression: %s and %s", + ty1, ty2); + return false; + } + (void)check_write_to(ctx, exprs[0]); + if (ast_istype(exprs[0], ast_entfield)) + assignop = type_storep_instr[exprs[0]->vtype]; + else + assignop = type_store_instr[exprs[0]->vtype]; + switch (exprs[0]->vtype) { + case TYPE_FLOAT: + out = (ast_expression*)ast_binstore_new(ctx, assignop, + (op->id == opid2('*','=') ? INSTR_MUL_F : INSTR_DIV_F), + exprs[0], exprs[1]); + break; + case TYPE_VECTOR: + if (op->id == opid2('*','=')) { + out = (ast_expression*)ast_binstore_new(ctx, assignop, INSTR_MUL_VF, + exprs[0], exprs[1]); + } else { + out = fold_binary(ctx, INSTR_DIV_F, + (ast_expression*)parser->fold->imm_float[1], + exprs[1]); + if (!out) { + compile_error(ctx, "internal error: failed to generate division"); + return false; + } + out = (ast_expression*)ast_binstore_new(ctx, assignop, INSTR_MUL_VF, + exprs[0], out); + } + break; + default: + compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s", + type_name[exprs[0]->vtype], + type_name[exprs[1]->vtype]); + return false; + }; + break; + case opid2('&','='): + case opid2('|','='): + case opid2('^','='): + if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in expression: %s and %s", + ty1, ty2); + return false; + } + (void)check_write_to(ctx, exprs[0]); + if (ast_istype(exprs[0], ast_entfield)) + assignop = type_storep_instr[exprs[0]->vtype]; + else + assignop = type_store_instr[exprs[0]->vtype]; + if (exprs[0]->vtype == TYPE_FLOAT) + out = (ast_expression*)ast_binstore_new(ctx, assignop, + (op->id == opid2('^','=') ? VINSTR_BITXOR : op->id == opid2('&','=') ? INSTR_BITAND : INSTR_BITOR), + exprs[0], exprs[1]); + else + out = (ast_expression*)ast_binstore_new(ctx, assignop, + (op->id == opid2('^','=') ? VINSTR_BITXOR_V : op->id == opid2('&','=') ? VINSTR_BITAND_V : VINSTR_BITOR_V), + exprs[0], exprs[1]); + break; + case opid3('&','~','='): + /* This is like: a &= ~(b); + * But QC has no bitwise-not, so we implement it as + * a -= a & (b); + */ + if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) { + ast_type_to_string(exprs[0], ty1, sizeof(ty1)); + ast_type_to_string(exprs[1], ty2, sizeof(ty2)); + compile_error(ctx, "invalid types used in expression: %s and %s", + ty1, ty2); + return false; + } + if (ast_istype(exprs[0], ast_entfield)) + assignop = type_storep_instr[exprs[0]->vtype]; + else + assignop = type_store_instr[exprs[0]->vtype]; + if (exprs[0]->vtype == TYPE_FLOAT) + out = fold_binary(ctx, INSTR_BITAND, exprs[0], exprs[1]); + else + out = fold_binary(ctx, VINSTR_BITAND_V, exprs[0], exprs[1]); + if (!out) + return false; + (void)check_write_to(ctx, exprs[0]); + if (exprs[0]->vtype == TYPE_FLOAT) + asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_F, exprs[0], out); + else + asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_V, exprs[0], out); + asbinstore->keep_dest = true; + 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)); + compile_error(ast_ctx(exprs[0]), "invalid type for bit not: %s", ty1); + return false; + } + if (!(out = fold_op(parser->fold, op, exprs))) { + if (exprs[0]->vtype == TYPE_FLOAT) { + out = fold_binary(ctx, INSTR_SUB_F, (ast_expression*)parser->fold->imm_float[2], exprs[0]); + } else { + out = fold_binary(ctx, INSTR_SUB_V, (ast_expression*)parser->fold->imm_vector[1], exprs[0]); + } + } + break; + } +#undef NotSameType + if (!out) { + compile_error(ctx, "failed to apply operator %s", op->op); + return false; + } + + vec_push(sy->out, syexp(ctx, out)); + return true; +} + +static bool parser_close_call(parser_t *parser, shunt *sy) +{ + /* was a function call */ + ast_expression *fun; + ast_value *funval = NULL; + ast_call *call; + + size_t fid; + size_t paramcount, i; + bool fold = true; + + fid = vec_last(sy->ops).off; + vec_shrinkby(sy->ops, 1); + + /* out[fid] is the function + * everything above is parameters... + */ + if (!vec_size(sy->argc)) { + parseerror(parser, "internal error: no argument counter available"); + return false; + } + + paramcount = vec_last(sy->argc); + vec_pop(sy->argc); + + if (vec_size(sy->out) < fid) { + parseerror(parser, "internal error: broken function call%lu < %lu+%lu\n", + (unsigned long)vec_size(sy->out), + (unsigned long)fid, + (unsigned long)paramcount); + return false; + } + + /* + * TODO handle this at the intrinsic level with an ast_intrinsic + * node and codegen. + */ + if ((fun = sy->out[fid].out) == intrin_debug_typestring(parser->intrin)) { + char ty[1024]; + if (fid+2 != vec_size(sy->out) || + vec_last(sy->out).block) + { + parseerror(parser, "intrinsic __builtin_debug_typestring requires exactly 1 parameter"); + return false; + } + ast_type_to_string(vec_last(sy->out).out, ty, sizeof(ty)); + ast_unref(vec_last(sy->out).out); + sy->out[fid] = syexp(ast_ctx(vec_last(sy->out).out), + (ast_expression*)fold_constgen_string(parser->fold, ty, false)); + vec_shrinkby(sy->out, 1); + return true; + } + + /* + * Now we need to determine if the function that is being called is + * an intrinsic so we can evaluate if the arguments to it are constant + * and than fruitfully fold them. + */ +#define fold_can_1(X) \ + (ast_istype(((ast_expression*)(X)), ast_value) && (X)->hasvalue && ((X)->cvq == CV_CONST) && \ + ((ast_expression*)(X))->vtype != TYPE_FUNCTION) + + if (fid + 1 < vec_size(sy->out)) + ++paramcount; + + for (i = 0; i < paramcount; ++i) { + if (!fold_can_1((ast_value*)sy->out[fid + 1 + i].out)) { + fold = false; + break; + } + } + + /* + * All is well which ends well, if we make it into here we can ignore the + * intrinsic call and just evaluate it i.e constant fold it. + */ + if (fold && ast_istype(fun, ast_value) && ((ast_value*)fun)->intrinsic) { + ast_expression **exprs = NULL; + ast_expression *foldval = NULL; + + for (i = 0; i < paramcount; i++) + vec_push(exprs, sy->out[fid+1 + i].out); + + if (!(foldval = intrin_fold(parser->intrin, (ast_value*)fun, exprs))) { + vec_free(exprs); + goto fold_leave; + } + + /* + * Blub: what sorts of unreffing and resizing of + * sy->out should I be doing here? + */ + sy->out[fid] = syexp(foldval->node.context, foldval); + vec_shrinkby(sy->out, paramcount); + vec_free(exprs); + + return true; + } + + fold_leave: + call = ast_call_new(sy->ops[vec_size(sy->ops)].ctx, fun); + + if (!call) + return false; + + if (fid+1 + paramcount != vec_size(sy->out)) { + parseerror(parser, "internal error: parameter count mismatch: (%lu+1+%lu), %lu", + (unsigned long)fid, (unsigned long)paramcount, (unsigned long)vec_size(sy->out)); + return false; + } + + for (i = 0; i < paramcount; ++i) + vec_push(call->params, sy->out[fid+1 + i].out); + vec_shrinkby(sy->out, paramcount); + (void)!ast_call_check_types(call, parser->function->vtype->expression.varparam); + if (parser->max_param_count < paramcount) + parser->max_param_count = paramcount; + + if (ast_istype(fun, ast_value)) { + funval = (ast_value*)fun; + 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, false); + } + } + + /* overwrite fid, the function, with a call */ + sy->out[fid] = syexp(call->expression.node.context, (ast_expression*)call); + + if (fun->vtype != TYPE_FUNCTION) { + parseerror(parser, "not a function (%s)", type_name[fun->vtype]); + return false; + } + + if (!fun->next) { + parseerror(parser, "could not determine function return type"); + return false; + } else { + ast_value *fval = (ast_istype(fun, ast_value) ? ((ast_value*)fun) : NULL); + + if (fun->flags & AST_FLAG_DEPRECATED) { + if (!fval) { + return !parsewarning(parser, WARN_DEPRECATED, + "call to function (which is marked deprecated)\n", + "-> it has been declared here: %s:%i", + ast_ctx(fun).file, ast_ctx(fun).line); + } + if (!fval->desc) { + return !parsewarning(parser, WARN_DEPRECATED, + "call to `%s` (which is marked deprecated)\n" + "-> `%s` declared here: %s:%i", + fval->name, fval->name, ast_ctx(fun).file, ast_ctx(fun).line); + } + return !parsewarning(parser, WARN_DEPRECATED, + "call to `%s` (deprecated: %s)\n" + "-> `%s` declared here: %s:%i", + fval->name, fval->desc, fval->name, ast_ctx(fun).file, + ast_ctx(fun).line); + } + + if (vec_size(fun->params) != paramcount && + !((fun->flags & AST_FLAG_VARIADIC) && + vec_size(fun->params) < paramcount)) + { + const char *fewmany = (vec_size(fun->params) > paramcount) ? "few" : "many"; + if (fval) + return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT, + "too %s parameters for call to %s: expected %i, got %i\n" + " -> `%s` has been declared here: %s:%i", + fewmany, fval->name, (int)vec_size(fun->params), (int)paramcount, + fval->name, ast_ctx(fun).file, (int)ast_ctx(fun).line); + else + return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT, + "too %s parameters for function call: expected %i, got %i\n" + " -> it has been declared here: %s:%i", + fewmany, (int)vec_size(fun->params), (int)paramcount, + ast_ctx(fun).file, (int)ast_ctx(fun).line); + } + } + + return true; +} + +static bool parser_close_paren(parser_t *parser, shunt *sy) +{ + if (!vec_size(sy->ops)) { + parseerror(parser, "unmatched closing paren"); + return false; + } + + while (vec_size(sy->ops)) { + if (vec_last(sy->ops).isparen) { + if (vec_last(sy->paren) == PAREN_FUNC) { + vec_pop(sy->paren); + if (!parser_close_call(parser, sy)) + return false; + break; + } + if (vec_last(sy->paren) == PAREN_EXPR) { + vec_pop(sy->paren); + if (!vec_size(sy->out)) { + compile_error(vec_last(sy->ops).ctx, "empty paren expression"); + vec_shrinkby(sy->ops, 1); + return false; + } + vec_shrinkby(sy->ops, 1); + break; + } + if (vec_last(sy->paren) == PAREN_INDEX) { + vec_pop(sy->paren); + /* pop off the parenthesis */ + vec_shrinkby(sy->ops, 1); + /* then apply the index operator */ + if (!parser_sy_apply_operator(parser, sy)) + return false; + break; + } + if (vec_last(sy->paren) == PAREN_TERNARY1) { + vec_last(sy->paren) = PAREN_TERNARY2; + /* pop off the parenthesis */ + vec_shrinkby(sy->ops, 1); + break; + } + compile_error(vec_last(sy->ops).ctx, "invalid parenthesis"); + return false; + } + if (!parser_sy_apply_operator(parser, sy)) + return false; + } + return true; +} + +static void parser_reclassify_token(parser_t *parser) +{ + size_t i; + if (parser->tok >= TOKEN_START) + return; + for (i = 0; i < operator_count; ++i) { + if (!strcmp(parser_tokval(parser), operators[i].op)) { + parser->tok = TOKEN_OPERATOR; + return; + } + } +} + +static ast_expression* parse_vararg_do(parser_t *parser) +{ + ast_expression *idx, *out; + ast_value *typevar; + ast_value *funtype = parser->function->vtype; + lex_ctx_t ctx = parser_ctx(parser); + + if (!parser->function->varargs) { + parseerror(parser, "function has no variable argument list"); + return NULL; + } + + if (!parser_next(parser) || parser->tok != '(') { + parseerror(parser, "expected parameter index and type in parenthesis"); + return NULL; + } + if (!parser_next(parser)) { + parseerror(parser, "error parsing parameter index"); + return NULL; + } + + idx = parse_expression_leave(parser, true, false, false); + if (!idx) + return NULL; + + if (parser->tok != ',') { + if (parser->tok != ')') { + ast_unref(idx); + parseerror(parser, "expected comma after parameter index"); + return NULL; + } + /* vararg piping: ...(start) */ + out = (ast_expression*)ast_argpipe_new(ctx, idx); + return out; + } + + if (!parser_next(parser) || (parser->tok != TOKEN_IDENT && parser->tok != TOKEN_TYPENAME)) { + ast_unref(idx); + parseerror(parser, "expected typename for vararg"); + return NULL; + } + + typevar = parse_typename(parser, NULL, NULL, NULL); + if (!typevar) { + ast_unref(idx); + return NULL; + } + + if (parser->tok != ')') { + ast_unref(idx); + ast_delete(typevar); + parseerror(parser, "expected closing paren"); + return NULL; + } + + if (funtype->expression.varparam && + !ast_compare_type((ast_expression*)typevar, (ast_expression*)funtype->expression.varparam)) + { + char ty1[1024]; + char ty2[1024]; + ast_type_to_string((ast_expression*)typevar, ty1, sizeof(ty1)); + ast_type_to_string((ast_expression*)funtype->expression.varparam, ty2, sizeof(ty2)); + compile_error(ast_ctx(typevar), + "function was declared to take varargs of type `%s`, requested type is: %s", + ty2, ty1); + } + + out = (ast_expression*)ast_array_index_new(ctx, (ast_expression*)(parser->function->varargs), idx); + ast_type_adopt(out, typevar); + ast_delete(typevar); + return out; +} + +static ast_expression* parse_vararg(parser_t *parser) +{ + bool old_noops = parser->lex->flags.noops; + + ast_expression *out; + + parser->lex->flags.noops = true; + out = parse_vararg_do(parser); + + parser->lex->flags.noops = old_noops; + return out; +} + +/* not to be exposed */ +bool ftepp_predef_exists(const char *name); +static bool parse_sya_operand(parser_t *parser, shunt *sy, bool with_labels) +{ + if (OPTS_FLAG(TRANSLATABLE_STRINGS) && + parser->tok == TOKEN_IDENT && + !strcmp(parser_tokval(parser), "_")) + { + /* a translatable string */ + ast_value *val; + + parser->lex->flags.noops = true; + if (!parser_next(parser) || parser->tok != '(') { + parseerror(parser, "use _(\"string\") to create a translatable string constant"); + return false; + } + parser->lex->flags.noops = false; + if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) { + parseerror(parser, "expected a constant string in translatable-string extension"); + return false; + } + val = (ast_value*)fold_constgen_string(parser->fold, parser_tokval(parser), true); + if (!val) + return false; + vec_push(sy->out, syexp(parser_ctx(parser), (ast_expression*)val)); + + if (!parser_next(parser) || parser->tok != ')') { + parseerror(parser, "expected closing paren after translatable string"); + return false; + } + return true; + } + else if (parser->tok == TOKEN_DOTS) + { + ast_expression *va; + if (!OPTS_FLAG(VARIADIC_ARGS)) { + parseerror(parser, "cannot access varargs (try -fvariadic-args)"); + return false; + } + va = parse_vararg(parser); + if (!va) + return false; + vec_push(sy->out, syexp(parser_ctx(parser), va)); + return true; + } + else if (parser->tok == TOKEN_FLOATCONST) { + 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), false); + if (!val) + return false; + vec_push(sy->out, syexp(parser_ctx(parser), val)); + return true; + } + else if (parser->tok == TOKEN_STRINGCONST) { + ast_expression *val = fold_constgen_string(parser->fold, parser_tokval(parser), false); + if (!val) + return false; + vec_push(sy->out, syexp(parser_ctx(parser), val)); + return true; + } + else if (parser->tok == TOKEN_VECTORCONST) { + ast_expression *val = fold_constgen_vector(parser->fold, parser_token(parser)->constval.v); + if (!val) + return false; + vec_push(sy->out, syexp(parser_ctx(parser), val)); + return true; + } + else if (parser->tok == TOKEN_IDENT) + { + const char *ctoken = parser_tokval(parser); + ast_expression *prev = vec_size(sy->out) ? vec_last(sy->out).out : NULL; + ast_expression *var; + /* a_vector.{x,y,z} */ + if (!vec_size(sy->ops) || + !vec_last(sy->ops).etype || + operators[vec_last(sy->ops).etype-1].id != opid1('.')) + { + /* When adding more intrinsics, fix the above condition */ + prev = NULL; + } + if (prev && prev->vtype == TYPE_VECTOR && ctoken[0] >= 'x' && ctoken[0] <= 'z' && !ctoken[1]) + { + var = (ast_expression*)parser->const_vec[ctoken[0]-'x']; + } else { + var = parser_find_var(parser, parser_tokval(parser)); + if (!var) + var = parser_find_field(parser, parser_tokval(parser)); + } + if (!var && with_labels) { + var = (ast_expression*)parser_find_label(parser, parser_tokval(parser)); + if (!with_labels) { + ast_label *lbl = ast_label_new(parser_ctx(parser), parser_tokval(parser), true); + var = (ast_expression*)lbl; + vec_push(parser->labels, lbl); + } + } + if (!var && !strcmp(parser_tokval(parser), "__FUNC__")) + var = (ast_expression*)fold_constgen_string(parser->fold, parser->function->name, false); + if (!var) { + /* + * now we try for the real intrinsic hashtable. If the string + * begins with __builtin, we simply skip past it, otherwise we + * use the identifier as is. + */ + if (!strncmp(parser_tokval(parser), "__builtin_", 10)) { + var = intrin_func(parser->intrin, parser_tokval(parser)); + } + + /* + * Try it again, intrin_func deals with the alias method as well + * the first one masks for __builtin though, we emit warning here. + */ + if (!var) { + if ((var = intrin_func(parser->intrin, parser_tokval(parser)))) { + (void)!compile_warning( + parser_ctx(parser), + WARN_BUILTINS, + "using implicitly defined builtin `__builtin_%s' for `%s'", + parser_tokval(parser), + parser_tokval(parser) + ); + } + } + + + if (!var) { + /* + * sometimes people use preprocessing predefs without enabling them + * i've done this thousands of times already myself. Lets check for + * it in the predef table. And diagnose it better :) + */ + if (!OPTS_FLAG(FTEPP_PREDEFS) && ftepp_predef_exists(parser_tokval(parser))) { + parseerror(parser, "unexpected identifier: %s (use -fftepp-predef to enable pre-defined macros)", parser_tokval(parser)); + return false; + } + + parseerror(parser, "unexpected identifier: %s", parser_tokval(parser)); + return false; + } + } + else + { + if (ast_istype(var, ast_value)) { + ((ast_value*)var)->uses++; + } + else if (ast_istype(var, ast_member)) { + ast_member *mem = (ast_member*)var; + if (ast_istype(mem->owner, ast_value)) + ((ast_value*)(mem->owner))->uses++; + } + } + vec_push(sy->out, syexp(parser_ctx(parser), var)); + return true; + } + parseerror(parser, "unexpected token `%s`", parser_tokval(parser)); + return false; +} + +static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels) +{ + ast_expression *expr = NULL; + shunt sy; + size_t i; + bool wantop = false; + /* only warn once about an assignment in a truth value because the current code + * would trigger twice on: if(a = b && ...), once for the if-truth-value, once for the && part + */ + bool warn_parenthesis = true; + + /* count the parens because an if starts with one, so the + * end of a condition is an unmatched closing paren + */ + int ternaries = 0; + + memset(&sy, 0, sizeof(sy)); + + parser->lex->flags.noops = false; + + parser_reclassify_token(parser); + + while (true) + { + if (parser->tok == TOKEN_TYPENAME) { + parseerror(parser, "unexpected typename `%s`", parser_tokval(parser)); + goto onerr; + } + + if (parser->tok == TOKEN_OPERATOR) + { + /* classify the operator */ + const oper_info *op; + const oper_info *olast = NULL; + size_t o; + for (o = 0; o < operator_count; ++o) { + if (((!(operators[o].flags & OP_PREFIX) == !!wantop)) && + /* !(operators[o].flags & OP_SUFFIX) && / * remove this */ + !strcmp(parser_tokval(parser), operators[o].op)) + { + break; + } + } + if (o == operator_count) { + compile_error(parser_ctx(parser), "unexpected operator: %s", parser_tokval(parser)); + goto onerr; + } + /* found an operator */ + op = &operators[o]; + + /* when declaring variables, a comma starts a new variable */ + if (op->id == opid1(',') && !vec_size(sy.paren) && stopatcomma) { + /* fixup the token */ + parser->tok = ','; + break; + } + + /* a colon without a pervious question mark cannot be a ternary */ + if (!ternaries && op->id == opid2(':','?')) { + parser->tok = ':'; + break; + } + + if (op->id == opid1(',')) { + if (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) { + (void)!parsewarning(parser, WARN_TERNARY_PRECEDENCE, "suggesting parenthesis around ternary expression"); + } + } + + if (vec_size(sy.ops) && !vec_last(sy.ops).isparen) + olast = &operators[vec_last(sy.ops).etype-1]; + + /* first only apply higher precedences, assoc_left+equal comes after we warn about precedence rules */ + while (olast && op->prec < olast->prec) + { + if (!parser_sy_apply_operator(parser, &sy)) + goto onerr; + if (vec_size(sy.ops) && !vec_last(sy.ops).isparen) + olast = &operators[vec_last(sy.ops).etype-1]; + else + olast = NULL; + } + +#define IsAssignOp(x) (\ + (x) == opid1('=') || \ + (x) == opid2('+','=') || \ + (x) == opid2('-','=') || \ + (x) == opid2('*','=') || \ + (x) == opid2('/','=') || \ + (x) == opid2('%','=') || \ + (x) == opid2('&','=') || \ + (x) == opid2('|','=') || \ + (x) == opid3('&','~','=') \ + ) + if (warn_parenthesis) { + if ( (olast && IsAssignOp(olast->id) && (op->id == opid2('&','&') || op->id == opid2('|','|'))) || + (olast && IsAssignOp(op->id) && (olast->id == opid2('&','&') || olast->id == opid2('|','|'))) || + (truthvalue && !vec_size(sy.paren) && IsAssignOp(op->id)) + ) + { + (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around assignment used as truth value"); + warn_parenthesis = false; + } + + if (olast && olast->id != op->id) { + if ((op->id == opid1('&') || op->id == opid1('|') || op->id == opid1('^')) && + (olast->id == opid1('&') || olast->id == opid1('|') || olast->id == opid1('^'))) + { + (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around bitwise operations"); + warn_parenthesis = false; + } + else if ((op->id == opid2('&','&') || op->id == opid2('|','|')) && + (olast->id == opid2('&','&') || olast->id == opid2('|','|'))) + { + (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around logical operations"); + warn_parenthesis = false; + } + } + } + + while (olast && ( + (op->prec < olast->prec) || + (op->assoc == ASSOC_LEFT && op->prec <= olast->prec) ) ) + { + if (!parser_sy_apply_operator(parser, &sy)) + goto onerr; + if (vec_size(sy.ops) && !vec_last(sy.ops).isparen) + olast = &operators[vec_last(sy.ops).etype-1]; + else + olast = NULL; + } + + if (op->id == opid1('(')) { + if (wantop) { + size_t sycount = vec_size(sy.out); + /* we expected an operator, this is the function-call operator */ + vec_push(sy.paren, PAREN_FUNC); + vec_push(sy.ops, syparen(parser_ctx(parser), sycount-1)); + vec_push(sy.argc, 0); + } else { + vec_push(sy.paren, PAREN_EXPR); + vec_push(sy.ops, syparen(parser_ctx(parser), 0)); + } + wantop = false; + } else if (op->id == opid1('[')) { + if (!wantop) { + parseerror(parser, "unexpected array subscript"); + goto onerr; + } + vec_push(sy.paren, PAREN_INDEX); + /* push both the operator and the paren, this makes life easier */ + vec_push(sy.ops, syop(parser_ctx(parser), op)); + vec_push(sy.ops, syparen(parser_ctx(parser), 0)); + wantop = false; + } else if (op->id == opid2('?',':')) { + vec_push(sy.ops, syop(parser_ctx(parser), op)); + vec_push(sy.ops, syparen(parser_ctx(parser), 0)); + wantop = false; + ++ternaries; + vec_push(sy.paren, PAREN_TERNARY1); + } else if (op->id == opid2(':','?')) { + if (!vec_size(sy.paren)) { + parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)"); + goto onerr; + } + if (vec_last(sy.paren) != PAREN_TERNARY1) { + parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)"); + goto onerr; + } + if (!parser_close_paren(parser, &sy)) + goto onerr; + vec_push(sy.ops, syop(parser_ctx(parser), op)); + wantop = false; + --ternaries; + } else { + vec_push(sy.ops, syop(parser_ctx(parser), op)); + wantop = !!(op->flags & OP_SUFFIX); + } + } + else if (parser->tok == ')') { + while (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) { + if (!parser_sy_apply_operator(parser, &sy)) + goto onerr; + } + if (!vec_size(sy.paren)) + break; + if (wantop) { + if (vec_last(sy.paren) == PAREN_TERNARY1) { + parseerror(parser, "mismatched parentheses (closing paren in ternary expression?)"); + goto onerr; + } + if (!parser_close_paren(parser, &sy)) + goto onerr; + } else { + /* must be a function call without parameters */ + if (vec_last(sy.paren) != PAREN_FUNC) { + parseerror(parser, "closing paren in invalid position"); + goto onerr; + } + if (!parser_close_paren(parser, &sy)) + goto onerr; + } + wantop = true; + } + else if (parser->tok == '(') { + parseerror(parser, "internal error: '(' should be classified as operator"); + goto onerr; + } + else if (parser->tok == '[') { + parseerror(parser, "internal error: '[' should be classified as operator"); + goto onerr; + } + else if (parser->tok == ']') { + while (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) { + if (!parser_sy_apply_operator(parser, &sy)) + goto onerr; + } + if (!vec_size(sy.paren)) + break; + if (vec_last(sy.paren) != PAREN_INDEX) { + parseerror(parser, "mismatched parentheses, unexpected ']'"); + goto onerr; + } + if (!parser_close_paren(parser, &sy)) + goto onerr; + wantop = true; + } + else if (!wantop) { + if (!parse_sya_operand(parser, &sy, with_labels)) + goto onerr; + wantop = true; + } + else { + /* in this case we might want to allow constant string concatenation */ + bool concatenated = false; + if (parser->tok == TOKEN_STRINGCONST && vec_size(sy.out)) { + ast_expression *lexpr = vec_last(sy.out).out; + if (ast_istype(lexpr, ast_value)) { + ast_value *last = (ast_value*)lexpr; + if (last->isimm == true && last->cvq == CV_CONST && + last->hasvalue && last->expression.vtype == TYPE_STRING) + { + char *newstr = NULL; + util_asprintf(&newstr, "%s%s", last->constval.vstring, parser_tokval(parser)); + vec_last(sy.out).out = (ast_expression*)fold_constgen_string(parser->fold, newstr, false); + mem_d(newstr); + concatenated = true; + } + } + } + if (!concatenated) { + parseerror(parser, "expected operator or end of statement"); + goto onerr; + } + } + + if (!parser_next(parser)) { + goto onerr; + } + if (parser->tok == ';' || + ((!vec_size(sy.paren) || (vec_size(sy.paren) == 1 && vec_last(sy.paren) == PAREN_TERNARY2)) && + (parser->tok == ']' || parser->tok == ')' || parser->tok == '}'))) + { + break; + } + } + + while (vec_size(sy.ops)) { + if (!parser_sy_apply_operator(parser, &sy)) + goto onerr; + } + + parser->lex->flags.noops = true; + if (vec_size(sy.out) != 1) { + parseerror(parser, "expression expected"); + expr = NULL; + } else + expr = sy.out[0].out; + vec_free(sy.out); + vec_free(sy.ops); + if (vec_size(sy.paren)) { + parseerror(parser, "internal error: vec_size(sy.paren) = %lu", (unsigned long)vec_size(sy.paren)); + return NULL; + } + vec_free(sy.paren); + vec_free(sy.argc); + return expr; + +onerr: + parser->lex->flags.noops = true; + for (i = 0; i < vec_size(sy.out); ++i) { + if (sy.out[i].out) + ast_unref(sy.out[i].out); + } + vec_free(sy.out); + vec_free(sy.ops); + vec_free(sy.paren); + vec_free(sy.argc); + return NULL; +} + +static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels) +{ + ast_expression *e = parse_expression_leave(parser, stopatcomma, false, with_labels); + if (!e) + return NULL; + if (parser->tok != ';') { + parseerror(parser, "semicolon expected after expression"); + ast_unref(e); + return NULL; + } + if (!parser_next(parser)) { + ast_unref(e); + return NULL; + } + return e; +} + +static void parser_enterblock(parser_t *parser) +{ + vec_push(parser->variables, util_htnew(PARSER_HT_SIZE)); + vec_push(parser->_blocklocals, vec_size(parser->_locals)); + vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE)); + vec_push(parser->_blocktypedefs, vec_size(parser->_typedefs)); + vec_push(parser->_block_ctx, parser_ctx(parser)); +} + +static bool parser_leaveblock(parser_t *parser) +{ + bool rv = true; + size_t locals, typedefs; + + if (vec_size(parser->variables) <= PARSER_HT_LOCALS) { + parseerror(parser, "internal error: parser_leaveblock with no block"); + return false; + } + + util_htdel(vec_last(parser->variables)); + + vec_pop(parser->variables); + if (!vec_size(parser->_blocklocals)) { + parseerror(parser, "internal error: parser_leaveblock with no block (2)"); + return false; + } + + locals = vec_last(parser->_blocklocals); + vec_pop(parser->_blocklocals); + while (vec_size(parser->_locals) != locals) { + ast_expression *e = vec_last(parser->_locals); + ast_value *v = (ast_value*)e; + vec_pop(parser->_locals); + if (ast_istype(e, ast_value) && !v->uses) { + if (compile_warning(ast_ctx(v), WARN_UNUSED_VARIABLE, "unused variable: `%s`", v->name)) + rv = false; + } + } + + typedefs = vec_last(parser->_blocktypedefs); + while (vec_size(parser->_typedefs) != typedefs) { + ast_delete(vec_last(parser->_typedefs)); + vec_pop(parser->_typedefs); + } + util_htdel(vec_last(parser->typedefs)); + vec_pop(parser->typedefs); + + vec_pop(parser->_block_ctx); + + return rv; +} + +static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e) +{ + vec_push(parser->_locals, e); + util_htset(vec_last(parser->variables), name, (void*)e); +} + +static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e) +{ + vec_push(parser->globals, e); + util_htset(parser->htglobals, name, e); +} + +static ast_expression* process_condition(parser_t *parser, ast_expression *cond, bool *_ifnot) +{ + bool ifnot = false; + ast_unary *unary; + ast_expression *prev; + + if (cond->vtype == TYPE_VOID || cond->vtype >= TYPE_VARIANT) { + char ty[1024]; + ast_type_to_string(cond, ty, sizeof(ty)); + compile_error(ast_ctx(cond), "invalid type for if() condition: %s", ty); + } + + if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && cond->vtype == TYPE_STRING) + { + prev = cond; + cond = (ast_expression*)ast_unary_new(ast_ctx(cond), INSTR_NOT_S, cond); + if (!cond) { + ast_unref(prev); + parseerror(parser, "internal error: failed to process condition"); + return NULL; + } + ifnot = !ifnot; + } + else if (OPTS_FLAG(CORRECT_LOGIC) && cond->vtype == TYPE_VECTOR) + { + /* vector types need to be cast to true booleans */ + ast_binary *bin = (ast_binary*)cond; + if (!OPTS_FLAG(PERL_LOGIC) || !ast_istype(cond, ast_binary) || !(bin->op == INSTR_AND || bin->op == INSTR_OR)) + { + /* in perl-logic, AND and OR take care of the -fcorrect-logic */ + prev = cond; + cond = (ast_expression*)ast_unary_new(ast_ctx(cond), INSTR_NOT_V, cond); + if (!cond) { + ast_unref(prev); + parseerror(parser, "internal error: failed to process condition"); + return NULL; + } + ifnot = !ifnot; + } + } + + unary = (ast_unary*)cond; + /* ast_istype dereferences cond, should test here for safety */ + while (cond && ast_istype(cond, ast_unary) && unary->op == INSTR_NOT_F) + { + cond = unary->operand; + unary->operand = NULL; + ast_delete(unary); + ifnot = !ifnot; + unary = (ast_unary*)cond; + } + + if (!cond) + parseerror(parser, "internal error: failed to process condition"); + + if (ifnot) *_ifnot = !*_ifnot; + return cond; +} + +static bool parse_if(parser_t *parser, ast_block *block, ast_expression **out) +{ + ast_ifthen *ifthen; + ast_expression *cond, *ontrue = NULL, *onfalse = NULL; + bool ifnot = false; + + lex_ctx_t ctx = parser_ctx(parser); + + (void)block; /* not touching */ + + /* skip the 'if', parse an optional 'not' and check for an opening paren */ + if (!parser_next(parser)) { + parseerror(parser, "expected condition or 'not'"); + return false; + } + if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "not")) { + ifnot = true; + if (!parser_next(parser)) { + parseerror(parser, "expected condition in parenthesis"); + return false; + } + } + if (parser->tok != '(') { + parseerror(parser, "expected 'if' condition in parenthesis"); + return false; + } + /* parse into the expression */ + if (!parser_next(parser)) { + parseerror(parser, "expected 'if' condition after opening paren"); + return false; + } + /* parse the condition */ + cond = parse_expression_leave(parser, false, true, false); + if (!cond) + return false; + /* closing paren */ + if (parser->tok != ')') { + parseerror(parser, "expected closing paren after 'if' condition"); + ast_unref(cond); + return false; + } + /* parse into the 'then' branch */ + if (!parser_next(parser)) { + parseerror(parser, "expected statement for on-true branch of 'if'"); + ast_unref(cond); + return false; + } + if (!parse_statement_or_block(parser, &ontrue)) { + ast_unref(cond); + return false; + } + if (!ontrue) + ontrue = (ast_expression*)ast_block_new(parser_ctx(parser)); + /* check for an else */ + if (!strcmp(parser_tokval(parser), "else")) { + /* parse into the 'else' branch */ + if (!parser_next(parser)) { + parseerror(parser, "expected on-false branch after 'else'"); + ast_delete(ontrue); + ast_unref(cond); + return false; + } + if (!parse_statement_or_block(parser, &onfalse)) { + ast_delete(ontrue); + ast_unref(cond); + return false; + } + } + + cond = process_condition(parser, cond, &ifnot); + if (!cond) { + if (ontrue) ast_delete(ontrue); + if (onfalse) ast_delete(onfalse); + return false; + } + + if (ifnot) + ifthen = ast_ifthen_new(ctx, cond, onfalse, ontrue); + else + ifthen = ast_ifthen_new(ctx, cond, ontrue, onfalse); + *out = (ast_expression*)ifthen; + return true; +} + +static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out); +static bool parse_while(parser_t *parser, ast_block *block, ast_expression **out) +{ + bool rv; + char *label = NULL; + + /* skip the 'while' and get the body */ + if (!parser_next(parser)) { + if (OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "expected loop label or 'while' condition in parenthesis"); + else + parseerror(parser, "expected 'while' condition in parenthesis"); + return false; + } + + if (parser->tok == ':') { + if (!OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "labeled loops not activated, try using -floop-labels"); + if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { + parseerror(parser, "expected loop label"); + return false; + } + label = util_strdup(parser_tokval(parser)); + if (!parser_next(parser)) { + mem_d(label); + parseerror(parser, "expected 'while' condition in parenthesis"); + return false; + } + } + + if (parser->tok != '(') { + parseerror(parser, "expected 'while' condition in parenthesis"); + return false; + } + + vec_push(parser->breaks, label); + vec_push(parser->continues, label); + + rv = parse_while_go(parser, block, out); + if (label) + mem_d(label); + if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) { + parseerror(parser, "internal error: label stack corrupted"); + rv = false; + ast_delete(*out); + *out = NULL; + } + else { + vec_pop(parser->breaks); + vec_pop(parser->continues); + } + return rv; +} + +static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out) +{ + ast_loop *aloop; + ast_expression *cond, *ontrue; + + bool ifnot = false; + + lex_ctx_t ctx = parser_ctx(parser); + + (void)block; /* not touching */ + + /* parse into the expression */ + if (!parser_next(parser)) { + parseerror(parser, "expected 'while' condition after opening paren"); + return false; + } + /* parse the condition */ + cond = parse_expression_leave(parser, false, true, false); + if (!cond) + return false; + /* closing paren */ + if (parser->tok != ')') { + parseerror(parser, "expected closing paren after 'while' condition"); + ast_unref(cond); + return false; + } + /* parse into the 'then' branch */ + if (!parser_next(parser)) { + parseerror(parser, "expected while-loop body"); + ast_unref(cond); + return false; + } + if (!parse_statement_or_block(parser, &ontrue)) { + ast_unref(cond); + return false; + } + + cond = process_condition(parser, cond, &ifnot); + if (!cond) { + ast_unref(ontrue); + return false; + } + aloop = ast_loop_new(ctx, NULL, cond, ifnot, NULL, false, NULL, ontrue); + *out = (ast_expression*)aloop; + return true; +} + +static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out); +static bool parse_dowhile(parser_t *parser, ast_block *block, ast_expression **out) +{ + bool rv; + char *label = NULL; + + /* skip the 'do' and get the body */ + if (!parser_next(parser)) { + if (OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "expected loop label or body"); + else + parseerror(parser, "expected loop body"); + return false; + } + + if (parser->tok == ':') { + if (!OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "labeled loops not activated, try using -floop-labels"); + if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { + parseerror(parser, "expected loop label"); + return false; + } + label = util_strdup(parser_tokval(parser)); + if (!parser_next(parser)) { + mem_d(label); + parseerror(parser, "expected loop body"); + return false; + } + } + + vec_push(parser->breaks, label); + vec_push(parser->continues, label); + + rv = parse_dowhile_go(parser, block, out); + if (label) + mem_d(label); + if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) { + parseerror(parser, "internal error: label stack corrupted"); + rv = false; + /* + * Test for NULL otherwise ast_delete dereferences null pointer + * and boom. + */ + if (*out) + ast_delete(*out); + *out = NULL; + } + else { + vec_pop(parser->breaks); + vec_pop(parser->continues); + } + return rv; +} + +static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out) +{ + ast_loop *aloop; + ast_expression *cond, *ontrue; + + bool ifnot = false; + + lex_ctx_t ctx = parser_ctx(parser); + + (void)block; /* not touching */ + + if (!parse_statement_or_block(parser, &ontrue)) + return false; + + /* expect the "while" */ + if (parser->tok != TOKEN_KEYWORD || + strcmp(parser_tokval(parser), "while")) + { + parseerror(parser, "expected 'while' and condition"); + ast_delete(ontrue); + return false; + } + + /* skip the 'while' and check for opening paren */ + if (!parser_next(parser) || parser->tok != '(') { + parseerror(parser, "expected 'while' condition in parenthesis"); + ast_delete(ontrue); + return false; + } + /* parse into the expression */ + if (!parser_next(parser)) { + parseerror(parser, "expected 'while' condition after opening paren"); + ast_delete(ontrue); + return false; + } + /* parse the condition */ + cond = parse_expression_leave(parser, false, true, false); + if (!cond) + return false; + /* closing paren */ + if (parser->tok != ')') { + parseerror(parser, "expected closing paren after 'while' condition"); + ast_delete(ontrue); + ast_unref(cond); + return false; + } + /* parse on */ + if (!parser_next(parser) || parser->tok != ';') { + parseerror(parser, "expected semicolon after condition"); + ast_delete(ontrue); + ast_unref(cond); + return false; + } + + if (!parser_next(parser)) { + parseerror(parser, "parse error"); + ast_delete(ontrue); + ast_unref(cond); + return false; + } + + cond = process_condition(parser, cond, &ifnot); + if (!cond) { + ast_delete(ontrue); + return false; + } + aloop = ast_loop_new(ctx, NULL, NULL, false, cond, ifnot, NULL, ontrue); + *out = (ast_expression*)aloop; + return true; +} + +static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out); +static bool parse_for(parser_t *parser, ast_block *block, ast_expression **out) +{ + bool rv; + char *label = NULL; + + /* skip the 'for' and check for opening paren */ + if (!parser_next(parser)) { + if (OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "expected loop label or 'for' expressions in parenthesis"); + else + parseerror(parser, "expected 'for' expressions in parenthesis"); + return false; + } + + if (parser->tok == ':') { + if (!OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "labeled loops not activated, try using -floop-labels"); + if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { + parseerror(parser, "expected loop label"); + return false; + } + label = util_strdup(parser_tokval(parser)); + if (!parser_next(parser)) { + mem_d(label); + parseerror(parser, "expected 'for' expressions in parenthesis"); + return false; + } + } + + if (parser->tok != '(') { + parseerror(parser, "expected 'for' expressions in parenthesis"); + return false; + } + + vec_push(parser->breaks, label); + vec_push(parser->continues, label); + + rv = parse_for_go(parser, block, out); + if (label) + mem_d(label); + if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) { + parseerror(parser, "internal error: label stack corrupted"); + rv = false; + ast_delete(*out); + *out = NULL; + } + else { + vec_pop(parser->breaks); + vec_pop(parser->continues); + } + return rv; +} +static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out) +{ + ast_loop *aloop; + ast_expression *initexpr, *cond, *increment, *ontrue; + ast_value *typevar; + + bool ifnot = false; + + lex_ctx_t ctx = parser_ctx(parser); + + parser_enterblock(parser); + + initexpr = NULL; + cond = NULL; + increment = NULL; + ontrue = NULL; + + /* parse into the expression */ + if (!parser_next(parser)) { + parseerror(parser, "expected 'for' initializer after opening paren"); + goto onerr; + } + + typevar = NULL; + 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, true, CV_VAR, typevar, false, false, 0, NULL)) + goto onerr; + } + else if (parser->tok != ';') + { + initexpr = parse_expression_leave(parser, false, false, false); + if (!initexpr) + goto onerr; + + /* move on to condition */ + if (parser->tok != ';') { + parseerror(parser, "expected semicolon after for-loop initializer"); + goto onerr; + } + + if (!parser_next(parser)) { + parseerror(parser, "expected for-loop condition"); + goto onerr; + } + } + + /* parse the condition */ + if (parser->tok != ';') { + cond = parse_expression_leave(parser, false, true, false); + if (!cond) + goto onerr; + } + + /* move on to incrementor */ + if (parser->tok != ';') { + parseerror(parser, "expected semicolon after for-loop initializer"); + goto onerr; + } + if (!parser_next(parser)) { + parseerror(parser, "expected for-loop condition"); + goto onerr; + } + + /* parse the incrementor */ + if (parser->tok != ')') { + lex_ctx_t condctx = parser_ctx(parser); + increment = parse_expression_leave(parser, false, false, false); + if (!increment) + goto onerr; + if (!ast_side_effects(increment)) { + if (compile_warning(condctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect")) + goto onerr; + } + } + + /* closing paren */ + if (parser->tok != ')') { + parseerror(parser, "expected closing paren after 'for-loop' incrementor"); + goto onerr; + } + /* parse into the 'then' branch */ + if (!parser_next(parser)) { + parseerror(parser, "expected for-loop body"); + goto onerr; + } + if (!parse_statement_or_block(parser, &ontrue)) + goto onerr; + + if (cond) { + cond = process_condition(parser, cond, &ifnot); + if (!cond) + goto onerr; + } + aloop = ast_loop_new(ctx, initexpr, cond, ifnot, NULL, false, increment, ontrue); + *out = (ast_expression*)aloop; + + if (!parser_leaveblock(parser)) { + ast_delete(aloop); + return false; + } + return true; +onerr: + if (initexpr) ast_unref(initexpr); + if (cond) ast_unref(cond); + if (increment) ast_unref(increment); + (void)!parser_leaveblock(parser); + return false; +} + +static bool parse_return(parser_t *parser, ast_block *block, ast_expression **out) +{ + ast_expression *exp = NULL; + ast_expression *var = NULL; + ast_return *ret = NULL; + ast_value *retval = parser->function->return_value; + ast_value *expected = parser->function->vtype; + + lex_ctx_t ctx = parser_ctx(parser); + + (void)block; /* not touching */ + + if (!parser_next(parser)) { + parseerror(parser, "expected return expression"); + return false; + } + + /* return assignments */ + if (parser->tok == '=') { + if (!OPTS_FLAG(RETURN_ASSIGNMENTS)) { + parseerror(parser, "return assignments not activated, try using -freturn-assigments"); + return false; + } + + if (type_store_instr[expected->expression.next->vtype] == VINSTR_END) { + char ty1[1024]; + ast_type_to_string(expected->expression.next, ty1, sizeof(ty1)); + parseerror(parser, "invalid return type: `%s'", ty1); + return false; + } + + if (!parser_next(parser)) { + parseerror(parser, "expected return assignment expression"); + return false; + } + + if (!(exp = parse_expression_leave(parser, false, false, false))) + return false; + + /* prepare the return value */ + if (!retval) { + retval = ast_value_new(ctx, "#LOCAL_RETURN", TYPE_VOID); + ast_type_adopt(retval, expected->expression.next); + parser->function->return_value = retval; + } + + if (!ast_compare_type(exp, (ast_expression*)retval)) { + char ty1[1024], ty2[1024]; + ast_type_to_string(exp, ty1, sizeof(ty1)); + ast_type_to_string(&retval->expression, ty2, sizeof(ty2)); + parseerror(parser, "invalid type for return value: `%s', expected `%s'", ty1, ty2); + } + + /* store to 'return' local variable */ + var = (ast_expression*)ast_store_new( + ctx, + type_store_instr[expected->expression.next->vtype], + (ast_expression*)retval, exp); + + if (!var) { + ast_unref(exp); + return false; + } + + if (parser->tok != ';') + parseerror(parser, "missing semicolon after return assignment"); + else if (!parser_next(parser)) + parseerror(parser, "parse error after return assignment"); + + *out = var; + return true; + } + + if (parser->tok != ';') { + exp = parse_expression(parser, false, false); + if (!exp) + return false; + + if (exp->vtype != TYPE_NIL && + exp->vtype != ((ast_expression*)expected)->next->vtype) + { + parseerror(parser, "return with invalid expression"); + } + + ret = ast_return_new(ctx, exp); + if (!ret) { + ast_unref(exp); + return false; + } + } else { + if (!parser_next(parser)) + parseerror(parser, "parse error"); + + if (!retval && expected->expression.next->vtype != TYPE_VOID) + { + (void)!parsewarning(parser, WARN_MISSING_RETURN_VALUES, "return without value"); + } + ret = ast_return_new(ctx, (ast_expression*)retval); + } + *out = (ast_expression*)ret; + return true; +} + +static bool parse_break_continue(parser_t *parser, ast_block *block, ast_expression **out, bool is_continue) +{ + size_t i; + unsigned int levels = 0; + lex_ctx_t ctx = parser_ctx(parser); + const char **loops = (is_continue ? parser->continues : parser->breaks); + + (void)block; /* not touching */ + if (!parser_next(parser)) { + parseerror(parser, "expected semicolon or loop label"); + return false; + } + + if (!vec_size(loops)) { + if (is_continue) + parseerror(parser, "`continue` can only be used inside loops"); + else + parseerror(parser, "`break` can only be used inside loops or switches"); + } + + if (parser->tok == TOKEN_IDENT) { + if (!OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "labeled loops not activated, try using -floop-labels"); + i = vec_size(loops); + while (i--) { + if (loops[i] && !strcmp(loops[i], parser_tokval(parser))) + break; + if (!i) { + parseerror(parser, "no such loop to %s: `%s`", + (is_continue ? "continue" : "break out of"), + parser_tokval(parser)); + return false; + } + ++levels; + } + if (!parser_next(parser)) { + parseerror(parser, "expected semicolon"); + return false; + } + } + + if (parser->tok != ';') { + parseerror(parser, "expected semicolon"); + return false; + } + + if (!parser_next(parser)) + parseerror(parser, "parse error"); + + *out = (ast_expression*)ast_breakcont_new(ctx, is_continue, levels); + return true; +} + +/* returns true when it was a variable qualifier, false otherwise! + * on error, cvq is set to CV_WRONG + */ +typedef struct { + const char *name; + size_t flag; +} attribute_t; + +static bool parse_qualifiers(parser_t *parser, bool with_local, int *cvq, bool *noref, bool *is_static, uint32_t *_flags, char **message) +{ + bool had_const = false; + bool had_var = false; + bool had_noref = false; + bool had_attrib = false; + bool had_static = false; + uint32_t flags = 0; + + static attribute_t attributes[] = { + { "noreturn", AST_FLAG_NORETURN }, + { "inline", AST_FLAG_INLINE }, + { "eraseable", AST_FLAG_ERASEABLE }, + { "accumulate", AST_FLAG_ACCUMULATE }, + { "last", AST_FLAG_FINAL_DECL } + }; + + *cvq = CV_NONE; + + for (;;) { + size_t i; + if (parser->tok == TOKEN_ATTRIBUTE_OPEN) { + had_attrib = true; + /* parse an attribute */ + if (!parser_next(parser)) { + parseerror(parser, "expected attribute after `[[`"); + *cvq = CV_WRONG; + return false; + } + + for (i = 0; i < GMQCC_ARRAY_COUNT(attributes); i++) { + if (!strcmp(parser_tokval(parser), attributes[i].name)) { + flags |= attributes[i].flag; + if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) { + parseerror(parser, "`%s` attribute has no parameters, expected `]]`", + attributes[i].name); + *cvq = CV_WRONG; + return false; + } + break; + } + } + + if (i != GMQCC_ARRAY_COUNT(attributes)) + goto leave; + + + if (!strcmp(parser_tokval(parser), "noref")) { + had_noref = true; + if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) { + parseerror(parser, "`noref` attribute has no parameters, expected `]]`"); + *cvq = CV_WRONG; + return false; + } + } + else if (!strcmp(parser_tokval(parser), "alias") && !(flags & AST_FLAG_ALIAS)) { + flags |= AST_FLAG_ALIAS; + *message = NULL; + + if (!parser_next(parser)) { + parseerror(parser, "parse error in attribute"); + goto argerr; + } + + if (parser->tok == '(') { + if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) { + parseerror(parser, "`alias` attribute missing parameter"); + goto argerr; + } + + *message = util_strdup(parser_tokval(parser)); + + if (!parser_next(parser)) { + parseerror(parser, "parse error in attribute"); + goto argerr; + } + + if (parser->tok != ')') { + parseerror(parser, "`alias` attribute expected `)` after parameter"); + goto argerr; + } + + if (!parser_next(parser)) { + parseerror(parser, "parse error in attribute"); + goto argerr; + } + } + + if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) { + parseerror(parser, "`alias` attribute expected `]]`"); + goto argerr; + } + } + else if (!strcmp(parser_tokval(parser), "deprecated") && !(flags & AST_FLAG_DEPRECATED)) { + flags |= AST_FLAG_DEPRECATED; + *message = NULL; + + if (!parser_next(parser)) { + parseerror(parser, "parse error in attribute"); + goto argerr; + } + + if (parser->tok == '(') { + if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) { + parseerror(parser, "`deprecated` attribute missing parameter"); + goto argerr; + } + + *message = util_strdup(parser_tokval(parser)); + + if (!parser_next(parser)) { + parseerror(parser, "parse error in attribute"); + goto argerr; + } + + if(parser->tok != ')') { + parseerror(parser, "`deprecated` attribute expected `)` after parameter"); + goto argerr; + } + + if (!parser_next(parser)) { + parseerror(parser, "parse error in attribute"); + goto argerr; + } + } + /* no message */ + if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) { + parseerror(parser, "`deprecated` attribute expected `]]`"); + + argerr: /* ugly */ + if (*message) mem_d(*message); + *message = NULL; + *cvq = CV_WRONG; + return false; + } + } + else if (!strcmp(parser_tokval(parser), "coverage") && !(flags & AST_FLAG_COVERAGE)) { + flags |= AST_FLAG_COVERAGE; + if (!parser_next(parser)) { + error_in_coverage: + parseerror(parser, "parse error in coverage attribute"); + *cvq = CV_WRONG; + return false; + } + if (parser->tok == '(') { + if (!parser_next(parser)) { + bad_coverage_arg: + parseerror(parser, "invalid parameter for coverage() attribute\n" + "valid are: block"); + *cvq = CV_WRONG; + return false; + } + if (parser->tok != ')') { + do { + if (parser->tok != TOKEN_IDENT) + goto bad_coverage_arg; + if (!strcmp(parser_tokval(parser), "block")) + flags |= AST_FLAG_BLOCK_COVERAGE; + else if (!strcmp(parser_tokval(parser), "none")) + flags &= ~(AST_FLAG_COVERAGE_MASK); + else + goto bad_coverage_arg; + if (!parser_next(parser)) + goto error_in_coverage; + if (parser->tok == ',') { + if (!parser_next(parser)) + goto error_in_coverage; + } + } while (parser->tok != ')'); + } + if (parser->tok != ')' || !parser_next(parser)) + goto error_in_coverage; + } else { + /* without parameter [[coverage]] equals [[coverage(block)]] */ + flags |= AST_FLAG_BLOCK_COVERAGE; + } + } + else + { + /* Skip tokens until we hit a ]] */ + (void)!parsewarning(parser, WARN_UNKNOWN_ATTRIBUTE, "unknown attribute starting with `%s`", parser_tokval(parser)); + while (parser->tok != TOKEN_ATTRIBUTE_CLOSE) { + if (!parser_next(parser)) { + parseerror(parser, "error inside attribute"); + *cvq = CV_WRONG; + return false; + } + } + } + } + else if (with_local && !strcmp(parser_tokval(parser), "static")) + had_static = true; + else if (!strcmp(parser_tokval(parser), "const")) + had_const = true; + else if (!strcmp(parser_tokval(parser), "var")) + had_var = true; + else if (with_local && !strcmp(parser_tokval(parser), "local")) + had_var = true; + else if (!strcmp(parser_tokval(parser), "noref")) + had_noref = true; + else if (!had_const && !had_var && !had_noref && !had_attrib && !had_static && !flags) { + return false; + } + else + break; + + leave: + if (!parser_next(parser)) + goto onerr; + } + if (had_const) + *cvq = CV_CONST; + else if (had_var) + *cvq = CV_VAR; + else + *cvq = CV_NONE; + *noref = had_noref; + *is_static = had_static; + *_flags = flags; + return true; +onerr: + parseerror(parser, "parse error after variable qualifier"); + *cvq = CV_WRONG; + return true; +} + +static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out); +static bool parse_switch(parser_t *parser, ast_block *block, ast_expression **out) +{ + bool rv; + char *label = NULL; + + /* skip the 'while' and get the body */ + if (!parser_next(parser)) { + if (OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "expected loop label or 'switch' operand in parenthesis"); + else + parseerror(parser, "expected 'switch' operand in parenthesis"); + return false; + } + + if (parser->tok == ':') { + if (!OPTS_FLAG(LOOP_LABELS)) + parseerror(parser, "labeled loops not activated, try using -floop-labels"); + if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { + parseerror(parser, "expected loop label"); + return false; + } + label = util_strdup(parser_tokval(parser)); + if (!parser_next(parser)) { + mem_d(label); + parseerror(parser, "expected 'switch' operand in parenthesis"); + return false; + } + } + + if (parser->tok != '(') { + parseerror(parser, "expected 'switch' operand in parenthesis"); + return false; + } + + vec_push(parser->breaks, label); + + rv = parse_switch_go(parser, block, out); + if (label) + mem_d(label); + if (vec_last(parser->breaks) != label) { + parseerror(parser, "internal error: label stack corrupted"); + rv = false; + ast_delete(*out); + *out = NULL; + } + else { + vec_pop(parser->breaks); + } + return rv; +} + +static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out) +{ + ast_expression *operand; + ast_value *opval; + ast_value *typevar; + ast_switch *switchnode; + ast_switch_case swcase; + + int cvq; + bool noref, is_static; + uint32_t qflags = 0; + + lex_ctx_t ctx = parser_ctx(parser); + + (void)block; /* not touching */ + (void)opval; + + /* parse into the expression */ + if (!parser_next(parser)) { + parseerror(parser, "expected switch operand"); + return false; + } + /* parse the operand */ + operand = parse_expression_leave(parser, false, false, false); + if (!operand) + return false; + + switchnode = ast_switch_new(ctx, operand); + + /* closing paren */ + if (parser->tok != ')') { + ast_delete(switchnode); + parseerror(parser, "expected closing paren after 'switch' operand"); + return false; + } + + /* parse over the opening paren */ + if (!parser_next(parser) || parser->tok != '{') { + ast_delete(switchnode); + parseerror(parser, "expected list of cases"); + return false; + } + + if (!parser_next(parser)) { + ast_delete(switchnode); + parseerror(parser, "expected 'case' or 'default'"); + return false; + } + + /* new block; allow some variables to be declared here */ + parser_enterblock(parser); + while (true) { + typevar = NULL; + 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, true, CV_NONE, typevar, false, false, 0, NULL)) { + ast_delete(switchnode); + return false; + } + continue; + } + if (parse_qualifiers(parser, true, &cvq, &noref, &is_static, &qflags, NULL)) + { + if (cvq == CV_WRONG) { + ast_delete(switchnode); + return false; + } + if (!parse_variable(parser, block, true, cvq, NULL, noref, is_static, qflags, NULL)) { + ast_delete(switchnode); + return false; + } + continue; + } + break; + } + + /* case list! */ + while (parser->tok != '}') { + ast_block *caseblock; + + if (!strcmp(parser_tokval(parser), "case")) { + if (!parser_next(parser)) { + ast_delete(switchnode); + parseerror(parser, "expected expression for case"); + return false; + } + swcase.value = parse_expression_leave(parser, false, false, false); + if (!swcase.value) { + ast_delete(switchnode); + parseerror(parser, "expected expression for case"); + return false; + } + if (!OPTS_FLAG(RELAXED_SWITCH)) { + if (!ast_istype(swcase.value, ast_value)) { /* || ((ast_value*)swcase.value)->cvq != CV_CONST) { */ + parseerror(parser, "case on non-constant values need to be explicitly enabled via -frelaxed-switch"); + ast_unref(operand); + return false; + } + } + } + else if (!strcmp(parser_tokval(parser), "default")) { + swcase.value = NULL; + if (!parser_next(parser)) { + ast_delete(switchnode); + parseerror(parser, "expected colon"); + return false; + } + } + else { + ast_delete(switchnode); + parseerror(parser, "expected 'case' or 'default'"); + return false; + } + + /* Now the colon and body */ + if (parser->tok != ':') { + if (swcase.value) ast_unref(swcase.value); + ast_delete(switchnode); + parseerror(parser, "expected colon"); + return false; + } + + if (!parser_next(parser)) { + if (swcase.value) ast_unref(swcase.value); + ast_delete(switchnode); + parseerror(parser, "expected statements or case"); + return false; + } + caseblock = ast_block_new(parser_ctx(parser)); + if (!caseblock) { + if (swcase.value) ast_unref(swcase.value); + ast_delete(switchnode); + return false; + } + swcase.code = (ast_expression*)caseblock; + vec_push(switchnode->cases, swcase); + while (true) { + ast_expression *expr; + if (parser->tok == '}') + break; + if (parser->tok == TOKEN_KEYWORD) { + if (!strcmp(parser_tokval(parser), "case") || + !strcmp(parser_tokval(parser), "default")) + { + break; + } + } + if (!parse_statement(parser, caseblock, &expr, true)) { + ast_delete(switchnode); + return false; + } + if (!expr) + continue; + if (!ast_block_add_expr(caseblock, expr)) { + ast_delete(switchnode); + return false; + } + } + } + + parser_leaveblock(parser); + + /* closing paren */ + if (parser->tok != '}') { + ast_delete(switchnode); + parseerror(parser, "expected closing paren of case list"); + return false; + } + if (!parser_next(parser)) { + ast_delete(switchnode); + parseerror(parser, "parse error after switch"); + return false; + } + *out = (ast_expression*)switchnode; + return true; +} + +/* parse computed goto sides */ +static ast_expression *parse_goto_computed(parser_t *parser, ast_expression **side) { + ast_expression *on_true; + ast_expression *on_false; + ast_expression *cond; + + if (!*side) + return NULL; + + if (ast_istype(*side, ast_ternary)) { + ast_ternary *tern = (ast_ternary*)*side; + on_true = parse_goto_computed(parser, &tern->on_true); + on_false = parse_goto_computed(parser, &tern->on_false); + + if (!on_true || !on_false) { + parseerror(parser, "expected label or expression in ternary"); + if (on_true) ast_unref(on_true); + if (on_false) ast_unref(on_false); + return NULL; + } + + cond = tern->cond; + tern->cond = NULL; + ast_delete(tern); + *side = NULL; + return (ast_expression*)ast_ifthen_new(parser_ctx(parser), cond, on_true, on_false); + } else if (ast_istype(*side, ast_label)) { + ast_goto *gt = ast_goto_new(parser_ctx(parser), ((ast_label*)*side)->name); + ast_goto_set_label(gt, ((ast_label*)*side)); + *side = NULL; + return (ast_expression*)gt; + } + return NULL; +} + +static bool parse_goto(parser_t *parser, ast_expression **out) +{ + ast_goto *gt = NULL; + ast_expression *lbl; + + if (!parser_next(parser)) + return false; + + if (parser->tok != TOKEN_IDENT) { + ast_expression *expression; + + /* could be an expression i.e computed goto :-) */ + if (parser->tok != '(') { + parseerror(parser, "expected label name after `goto`"); + return false; + } + + /* failed to parse expression for goto */ + if (!(expression = parse_expression(parser, false, true)) || + !(*out = parse_goto_computed(parser, &expression))) { + parseerror(parser, "invalid goto expression"); + if(expression) + ast_unref(expression); + return false; + } + + return true; + } + + /* not computed goto */ + gt = ast_goto_new(parser_ctx(parser), parser_tokval(parser)); + lbl = parser_find_label(parser, gt->name); + if (lbl) { + if (!ast_istype(lbl, ast_label)) { + parseerror(parser, "internal error: label is not an ast_label"); + ast_delete(gt); + return false; + } + ast_goto_set_label(gt, (ast_label*)lbl); + } + else + vec_push(parser->gotos, gt); + + if (!parser_next(parser) || parser->tok != ';') { + parseerror(parser, "semicolon expected after goto label"); + return false; + } + if (!parser_next(parser)) { + parseerror(parser, "parse error after goto"); + return false; + } + + *out = (ast_expression*)gt; + return true; +} + +static bool parse_skipwhite(parser_t *parser) +{ + do { + if (!parser_next(parser)) + return false; + } while (parser->tok == TOKEN_WHITE && parser->tok < TOKEN_ERROR); + return parser->tok < TOKEN_ERROR; +} + +static bool parse_eol(parser_t *parser) +{ + if (!parse_skipwhite(parser)) + return false; + return parser->tok == TOKEN_EOL; +} + +static bool parse_pragma_do(parser_t *parser) +{ + if (!parser_next(parser) || + parser->tok != TOKEN_IDENT || + strcmp(parser_tokval(parser), "pragma")) + { + parseerror(parser, "expected `pragma` keyword after `#`, got `%s`", parser_tokval(parser)); + return false; + } + if (!parse_skipwhite(parser) || parser->tok != TOKEN_IDENT) { + parseerror(parser, "expected pragma, got `%s`", parser_tokval(parser)); + return false; + } + + if (!strcmp(parser_tokval(parser), "noref")) { + if (!parse_skipwhite(parser) || parser->tok != TOKEN_INTCONST) { + parseerror(parser, "`noref` pragma requires an argument: 0 or 1"); + return false; + } + parser->noref = !!parser_token(parser)->constval.i; + if (!parse_eol(parser)) { + parseerror(parser, "parse error after `noref` pragma"); + return false; + } + } + else + { + (void)!parsewarning(parser, WARN_UNKNOWN_PRAGMAS, "ignoring #pragma %s", parser_tokval(parser)); + + /* skip to eol */ + while (!parse_eol(parser)) { + parser_next(parser); + } + + return true; + } + + return true; +} + +static bool parse_pragma(parser_t *parser) +{ + bool rv; + parser->lex->flags.preprocessing = true; + parser->lex->flags.mergelines = true; + rv = parse_pragma_do(parser); + if (parser->tok != TOKEN_EOL) { + parseerror(parser, "junk after pragma"); + rv = false; + } + parser->lex->flags.preprocessing = false; + parser->lex->flags.mergelines = false; + if (!parser_next(parser)) { + parseerror(parser, "parse error after pragma"); + rv = false; + } + return rv; +} + +static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases) +{ + bool noref, is_static; + int cvq = CV_NONE; + uint32_t qflags = 0; + ast_value *typevar = NULL; + char *vstring = NULL; + + *out = NULL; + + if (parser->tok == TOKEN_IDENT) + typevar = parser_find_typedef(parser, parser_tokval(parser), 0); + + if (typevar || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS) + { + /* local variable */ + if (!block) { + parseerror(parser, "cannot declare a variable from here"); + return false; + } + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { + if (parsewarning(parser, WARN_EXTENSIONS, "missing 'local' keyword when declaring a local variable")) + return false; + } + if (!parse_variable(parser, block, false, CV_NONE, typevar, false, false, 0, NULL)) + return false; + return true; + } + else if (parse_qualifiers(parser, !!block, &cvq, &noref, &is_static, &qflags, &vstring)) + { + if (cvq == CV_WRONG) + return false; + return parse_variable(parser, block, false, cvq, NULL, noref, is_static, qflags, vstring); + } + else if (parser->tok == TOKEN_KEYWORD) + { + if (!strcmp(parser_tokval(parser), "__builtin_debug_printtype")) + { + char ty[1024]; + ast_value *tdef; + + if (!parser_next(parser)) { + parseerror(parser, "parse error after __builtin_debug_printtype"); + return false; + } + + if (parser->tok == TOKEN_IDENT && (tdef = parser_find_typedef(parser, parser_tokval(parser), 0))) + { + ast_type_to_string((ast_expression*)tdef, ty, sizeof(ty)); + con_out("__builtin_debug_printtype: `%s`=`%s`\n", tdef->name, ty); + if (!parser_next(parser)) { + parseerror(parser, "parse error after __builtin_debug_printtype typename argument"); + return false; + } + } + else + { + if (!parse_statement(parser, block, out, allow_cases)) + return false; + if (!*out) + con_out("__builtin_debug_printtype: got no output node\n"); + else + { + ast_type_to_string(*out, ty, sizeof(ty)); + con_out("__builtin_debug_printtype: `%s`\n", ty); + } + } + return true; + } + else if (!strcmp(parser_tokval(parser), "return")) + { + return parse_return(parser, block, out); + } + else if (!strcmp(parser_tokval(parser), "if")) + { + return parse_if(parser, block, out); + } + else if (!strcmp(parser_tokval(parser), "while")) + { + return parse_while(parser, block, out); + } + else if (!strcmp(parser_tokval(parser), "do")) + { + return parse_dowhile(parser, block, out); + } + else if (!strcmp(parser_tokval(parser), "for")) + { + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { + if (parsewarning(parser, WARN_EXTENSIONS, "for loops are not recognized in the original Quake C standard, to enable try an alternate standard --std=?")) + return false; + } + return parse_for(parser, block, out); + } + else if (!strcmp(parser_tokval(parser), "break")) + { + return parse_break_continue(parser, block, out, false); + } + else if (!strcmp(parser_tokval(parser), "continue")) + { + return parse_break_continue(parser, block, out, true); + } + else if (!strcmp(parser_tokval(parser), "switch")) + { + return parse_switch(parser, block, out); + } + else if (!strcmp(parser_tokval(parser), "case") || + !strcmp(parser_tokval(parser), "default")) + { + if (!allow_cases) { + parseerror(parser, "unexpected 'case' label"); + return false; + } + return true; + } + else if (!strcmp(parser_tokval(parser), "goto")) + { + return parse_goto(parser, out); + } + else if (!strcmp(parser_tokval(parser), "typedef")) + { + if (!parser_next(parser)) { + parseerror(parser, "expected type definition after 'typedef'"); + return false; + } + return parse_typedef(parser); + } + parseerror(parser, "Unexpected keyword: `%s'", parser_tokval(parser)); + return false; + } + else if (parser->tok == '{') + { + ast_block *inner; + inner = parse_block(parser); + if (!inner) + return false; + *out = (ast_expression*)inner; + return true; + } + else if (parser->tok == ':') + { + size_t i; + ast_label *label; + if (!parser_next(parser)) { + parseerror(parser, "expected label name"); + return false; + } + if (parser->tok != TOKEN_IDENT) { + parseerror(parser, "label must be an identifier"); + return false; + } + label = (ast_label*)parser_find_label(parser, parser_tokval(parser)); + if (label) { + if (!label->undefined) { + parseerror(parser, "label `%s` already defined", label->name); + return false; + } + label->undefined = false; + } + else { + label = ast_label_new(parser_ctx(parser), parser_tokval(parser), false); + vec_push(parser->labels, label); + } + *out = (ast_expression*)label; + if (!parser_next(parser)) { + parseerror(parser, "parse error after label"); + return false; + } + for (i = 0; i < vec_size(parser->gotos); ++i) { + if (!strcmp(parser->gotos[i]->name, label->name)) { + ast_goto_set_label(parser->gotos[i], label); + vec_remove(parser->gotos, i, 1); + --i; + } + } + return true; + } + else if (parser->tok == ';') + { + if (!parser_next(parser)) { + parseerror(parser, "parse error after empty statement"); + return false; + } + return true; + } + else + { + lex_ctx_t ctx = parser_ctx(parser); + ast_expression *exp = parse_expression(parser, false, false); + if (!exp) + return false; + *out = exp; + if (!ast_side_effects(exp)) { + if (compile_warning(ctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect")) + return false; + } + return true; + } +} + +static bool parse_enum(parser_t *parser) +{ + bool flag = false; + bool reverse = false; + qcfloat_t num = 0; + ast_value **values = NULL; + ast_value *var = NULL; + ast_value *asvalue; + + ast_expression *old; + + if (!parser_next(parser) || (parser->tok != '{' && parser->tok != ':')) { + parseerror(parser, "expected `{` or `:` after `enum` keyword"); + return false; + } + + /* enumeration attributes (can add more later) */ + if (parser->tok == ':') { + if (!parser_next(parser) || parser->tok != TOKEN_IDENT){ + parseerror(parser, "expected `flag` or `reverse` for enumeration attribute"); + return false; + } + + /* attributes? */ + if (!strcmp(parser_tokval(parser), "flag")) { + num = 1; + flag = true; + } + else if (!strcmp(parser_tokval(parser), "reverse")) { + reverse = true; + } + else { + parseerror(parser, "invalid attribute `%s` for enumeration", parser_tokval(parser)); + return false; + } + + if (!parser_next(parser) || parser->tok != '{') { + parseerror(parser, "expected `{` after enum attribute "); + return false; + } + } + + while (true) { + if (!parser_next(parser) || parser->tok != TOKEN_IDENT) { + if (parser->tok == '}') { + /* allow an empty enum */ + break; + } + parseerror(parser, "expected identifier or `}`"); + goto onerror; + } + + old = parser_find_field(parser, parser_tokval(parser)); + if (!old) + old = parser_find_global(parser, parser_tokval(parser)); + if (old) { + parseerror(parser, "value `%s` has already been declared here: %s:%i", + parser_tokval(parser), ast_ctx(old).file, ast_ctx(old).line); + goto onerror; + } + + var = ast_value_new(parser_ctx(parser), parser_tokval(parser), TYPE_FLOAT); + vec_push(values, var); + var->cvq = CV_CONST; + var->hasvalue = true; + + /* for flagged enumerations increment in POTs of TWO */ + var->constval.vfloat = (flag) ? (num *= 2) : (num ++); + parser_addglobal(parser, var->name, (ast_expression*)var); + + if (!parser_next(parser)) { + parseerror(parser, "expected `=`, `}` or comma after identifier"); + goto onerror; + } + + if (parser->tok == ',') + continue; + if (parser->tok == '}') + break; + if (parser->tok != '=') { + parseerror(parser, "expected `=`, `}` or comma after identifier"); + goto onerror; + } + + if (!parser_next(parser)) { + parseerror(parser, "expected expression after `=`"); + goto onerror; + } + + /* We got a value! */ + old = parse_expression_leave(parser, true, false, false); + asvalue = (ast_value*)old; + if (!ast_istype(old, ast_value) || asvalue->cvq != CV_CONST || !asvalue->hasvalue) { + compile_error(ast_ctx(var), "constant value or expression expected"); + goto onerror; + } + num = (var->constval.vfloat = asvalue->constval.vfloat) + 1; + + if (parser->tok == '}') + break; + if (parser->tok != ',') { + parseerror(parser, "expected `}` or comma after expression"); + goto onerror; + } + } + + /* patch them all (for reversed attribute) */ + if (reverse) { + size_t i; + for (i = 0; i < vec_size(values); i++) + values[i]->constval.vfloat = vec_size(values) - i - 1; + } + + if (parser->tok != '}') { + parseerror(parser, "internal error: breaking without `}`"); + goto onerror; + } + + if (!parser_next(parser) || parser->tok != ';') { + parseerror(parser, "expected semicolon after enumeration"); + goto onerror; + } + + if (!parser_next(parser)) { + parseerror(parser, "parse error after enumeration"); + goto onerror; + } + + vec_free(values); + return true; + +onerror: + vec_free(values); + return false; +} + +static bool parse_block_into(parser_t *parser, ast_block *block) +{ + bool retval = true; + + parser_enterblock(parser); + + if (!parser_next(parser)) { /* skip the '{' */ + parseerror(parser, "expected function body"); + goto cleanup; + } + + while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR) + { + ast_expression *expr = NULL; + if (parser->tok == '}') + break; + + if (!parse_statement(parser, block, &expr, false)) { + /* parseerror(parser, "parse error"); */ + block = NULL; + goto cleanup; + } + if (!expr) + continue; + if (!ast_block_add_expr(block, expr)) { + ast_delete(block); + block = NULL; + goto cleanup; + } + } + + if (parser->tok != '}') { + block = NULL; + } else { + (void)parser_next(parser); + } + +cleanup: + if (!parser_leaveblock(parser)) + retval = false; + return retval && !!block; +} + +static ast_block* parse_block(parser_t *parser) +{ + ast_block *block; + block = ast_block_new(parser_ctx(parser)); + if (!block) + return NULL; + if (!parse_block_into(parser, block)) { + ast_block_delete(block); + return NULL; + } + return block; +} + +static bool parse_statement_or_block(parser_t *parser, ast_expression **out) +{ + if (parser->tok == '{') { + *out = (ast_expression*)parse_block(parser); + return !!*out; + } + return parse_statement(parser, NULL, out, false); +} + +static bool create_vector_members(ast_value *var, ast_member **me) +{ + size_t i; + size_t len = strlen(var->name); + + for (i = 0; i < 3; ++i) { + char *name = (char*)mem_a(len+3); + memcpy(name, var->name, len); + name[len+0] = '_'; + name[len+1] = 'x'+i; + name[len+2] = 0; + me[i] = ast_member_new(ast_ctx(var), (ast_expression*)var, i, name); + mem_d(name); + if (!me[i]) + break; + } + if (i == 3) + return true; + + /* unroll */ + do { ast_member_delete(me[--i]); } while(i); + return false; +} + +static bool parse_function_body(parser_t *parser, ast_value *var) +{ + ast_block *block = NULL; + ast_function *func; + ast_function *old; + size_t parami; + + ast_expression *framenum = NULL; + ast_expression *nextthink = NULL; + /* None of the following have to be deleted */ + ast_expression *fld_think = NULL, *fld_nextthink = NULL, *fld_frame = NULL; + ast_expression *gbl_time = NULL, *gbl_self = NULL; + bool has_frame_think; + + bool retval = true; + + has_frame_think = false; + old = parser->function; + + if (var->expression.flags & AST_FLAG_ALIAS) { + parseerror(parser, "function aliases cannot have bodies"); + return false; + } + + if (vec_size(parser->gotos) || vec_size(parser->labels)) { + parseerror(parser, "gotos/labels leaking"); + return false; + } + + if (!OPTS_FLAG(VARIADIC_ARGS) && var->expression.flags & AST_FLAG_VARIADIC) { + if (parsewarning(parser, WARN_VARIADIC_FUNCTION, + "variadic function with implementation will not be able to access additional parameters (try -fvariadic-args)")) + { + return false; + } + } + + if (parser->tok == '[') { + /* got a frame definition: [ framenum, nextthink ] + * this translates to: + * self.frame = framenum; + * self.nextthink = time + 0.1; + * self.think = nextthink; + */ + nextthink = NULL; + + fld_think = parser_find_field(parser, "think"); + fld_nextthink = parser_find_field(parser, "nextthink"); + fld_frame = parser_find_field(parser, "frame"); + if (!fld_think || !fld_nextthink || !fld_frame) { + parseerror(parser, "cannot use [frame,think] notation without the required fields"); + parseerror(parser, "please declare the following entityfields: `frame`, `think`, `nextthink`"); + return false; + } + gbl_time = parser_find_global(parser, "time"); + gbl_self = parser_find_global(parser, "self"); + if (!gbl_time || !gbl_self) { + parseerror(parser, "cannot use [frame,think] notation without the required globals"); + parseerror(parser, "please declare the following globals: `time`, `self`"); + return false; + } + + if (!parser_next(parser)) + return false; + + framenum = parse_expression_leave(parser, true, false, false); + if (!framenum) { + parseerror(parser, "expected a framenumber constant in[frame,think] notation"); + return false; + } + if (!ast_istype(framenum, ast_value) || !( (ast_value*)framenum )->hasvalue) { + ast_unref(framenum); + parseerror(parser, "framenumber in [frame,think] notation must be a constant"); + return false; + } + + if (parser->tok != ',') { + ast_unref(framenum); + parseerror(parser, "expected comma after frame number in [frame,think] notation"); + parseerror(parser, "Got a %i\n", parser->tok); + return false; + } + + if (!parser_next(parser)) { + ast_unref(framenum); + return false; + } + + if (parser->tok == TOKEN_IDENT && !parser_find_var(parser, parser_tokval(parser))) + { + /* qc allows the use of not-yet-declared functions here + * - this automatically creates a prototype */ + ast_value *thinkfunc; + ast_expression *functype = fld_think->next; + + thinkfunc = ast_value_new(parser_ctx(parser), parser_tokval(parser), functype->vtype); + if (!thinkfunc) { /* || !ast_type_adopt(thinkfunc, functype)*/ + ast_unref(framenum); + parseerror(parser, "failed to create implicit prototype for `%s`", parser_tokval(parser)); + return false; + } + ast_type_adopt(thinkfunc, functype); + + if (!parser_next(parser)) { + ast_unref(framenum); + ast_delete(thinkfunc); + return false; + } + + parser_addglobal(parser, thinkfunc->name, (ast_expression*)thinkfunc); + + nextthink = (ast_expression*)thinkfunc; + + } else { + nextthink = parse_expression_leave(parser, true, false, false); + if (!nextthink) { + ast_unref(framenum); + parseerror(parser, "expected a think-function in [frame,think] notation"); + return false; + } + } + + if (!ast_istype(nextthink, ast_value)) { + parseerror(parser, "think-function in [frame,think] notation must be a constant"); + retval = false; + } + + if (retval && parser->tok != ']') { + parseerror(parser, "expected closing `]` for [frame,think] notation"); + retval = false; + } + + if (retval && !parser_next(parser)) { + retval = false; + } + + if (retval && parser->tok != '{') { + parseerror(parser, "a function body has to be declared after a [frame,think] declaration"); + retval = false; + } + + if (!retval) { + ast_unref(nextthink); + ast_unref(framenum); + return false; + } + + has_frame_think = true; + } + + block = ast_block_new(parser_ctx(parser)); + if (!block) { + parseerror(parser, "failed to allocate block"); + if (has_frame_think) { + ast_unref(nextthink); + ast_unref(framenum); + } + return false; + } + + if (has_frame_think) { + if (!OPTS_FLAG(EMULATE_STATE)) { + ast_state *state_op = ast_state_new(parser_ctx(parser), framenum, nextthink); + if (!ast_block_add_expr(block, (ast_expression*)state_op)) { + parseerror(parser, "failed to generate state op for [frame,think]"); + ast_unref(nextthink); + ast_unref(framenum); + ast_delete(block); + return false; + } + } else { + /* emulate OP_STATE in code: */ + lex_ctx_t ctx; + ast_expression *self_frame; + ast_expression *self_nextthink; + ast_expression *self_think; + ast_expression *time_plus_1; + ast_store *store_frame; + ast_store *store_nextthink; + ast_store *store_think; + + float frame_delta = 1.0f / (float)OPTS_OPTION_U32(OPTION_STATE_FPS); + + ctx = parser_ctx(parser); + self_frame = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_frame); + self_nextthink = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_nextthink); + self_think = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_think); + + time_plus_1 = (ast_expression*)ast_binary_new(ctx, INSTR_ADD_F, + gbl_time, (ast_expression*)fold_constgen_float(parser->fold, frame_delta, false)); + + 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 (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); + 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; + } + } + } + + if (var->hasvalue) { + if (!(var->expression.flags & AST_FLAG_ACCUMULATE)) { + parseerror(parser, "function `%s` declared with multiple bodies", var->name); + ast_block_delete(block); + goto enderr; + } + func = var->constval.vfunc; + + if (!func) { + parseerror(parser, "internal error: NULL function: `%s`", var->name); + ast_block_delete(block); + goto enderr; + } + } else { + func = ast_function_new(ast_ctx(var), var->name, var); + + if (!func) { + parseerror(parser, "failed to allocate function for `%s`", var->name); + ast_block_delete(block); + goto enderr; + } + vec_push(parser->functions, func); + } + + parser_enterblock(parser); + + for (parami = 0; parami < vec_size(var->expression.params); ++parami) { + size_t e; + ast_value *param = var->expression.params[parami]; + ast_member *me[3]; + + if (param->expression.vtype != TYPE_VECTOR && + (param->expression.vtype != TYPE_FIELD || + param->expression.next->vtype != TYPE_VECTOR)) + { + continue; + } + + if (!create_vector_members(param, me)) { + ast_block_delete(block); + goto enderrfn; + } + + for (e = 0; e < 3; ++e) { + parser_addlocal(parser, me[e]->name, (ast_expression*)me[e]); + ast_block_collect(block, (ast_expression*)me[e]); + } + } + + if (var->argcounter && !func->argc) { + ast_value *argc = ast_value_new(ast_ctx(var), var->argcounter, TYPE_FLOAT); + parser_addlocal(parser, argc->name, (ast_expression*)argc); + func->argc = argc; + } + + if (OPTS_FLAG(VARIADIC_ARGS) && var->expression.flags & AST_FLAG_VARIADIC && !func->varargs) { + char name[1024]; + ast_value *varargs = ast_value_new(ast_ctx(var), "reserved:va_args", TYPE_ARRAY); + varargs->expression.flags |= AST_FLAG_IS_VARARG; + varargs->expression.next = (ast_expression*)ast_value_new(ast_ctx(var), NULL, TYPE_VECTOR); + varargs->expression.count = 0; + util_snprintf(name, sizeof(name), "%s##va##SET", var->name); + if (!parser_create_array_setter_proto(parser, varargs, name)) { + ast_delete(varargs); + ast_block_delete(block); + goto enderrfn; + } + util_snprintf(name, sizeof(name), "%s##va##GET", var->name); + if (!parser_create_array_getter_proto(parser, varargs, varargs->expression.next, name)) { + ast_delete(varargs); + ast_block_delete(block); + goto enderrfn; + } + func->varargs = varargs; + func->fixedparams = (ast_value*)fold_constgen_float(parser->fold, vec_size(var->expression.params), false); + } + + parser->function = func; + if (!parse_block_into(parser, block)) { + ast_block_delete(block); + goto enderrfn; + } + + vec_push(func->blocks, block); + + parser->function = old; + if (!parser_leaveblock(parser)) + retval = false; + if (vec_size(parser->variables) != PARSER_HT_LOCALS) { + parseerror(parser, "internal error: local scopes left"); + retval = false; + } + + if (parser->tok == ';') + return parser_next(parser); + else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) + parseerror(parser, "missing semicolon after function body (mandatory with -std=qcc)"); + return retval; + +enderrfn: + (void)!parser_leaveblock(parser); + vec_pop(parser->functions); + ast_function_delete(func); + var->constval.vfunc = NULL; + +enderr: + parser->function = old; + return false; +} + +static ast_expression *array_accessor_split( + parser_t *parser, + ast_value *array, + ast_value *index, + size_t middle, + ast_expression *left, + ast_expression *right + ) +{ + ast_ifthen *ifthen; + ast_binary *cmp; + + lex_ctx_t ctx = ast_ctx(array); + + if (!left || !right) { + if (left) ast_delete(left); + if (right) ast_delete(right); + return NULL; + } + + cmp = ast_binary_new(ctx, INSTR_LT, + (ast_expression*)index, + (ast_expression*)fold_constgen_float(parser->fold, middle, false)); + if (!cmp) { + ast_delete(left); + ast_delete(right); + parseerror(parser, "internal error: failed to create comparison for array setter"); + return NULL; + } + + ifthen = ast_ifthen_new(ctx, (ast_expression*)cmp, left, right); + if (!ifthen) { + ast_delete(cmp); /* will delete left and right */ + parseerror(parser, "internal error: failed to create conditional jump for array setter"); + return NULL; + } + + return (ast_expression*)ifthen; +} + +static ast_expression *array_setter_node(parser_t *parser, ast_value *array, ast_value *index, ast_value *value, size_t from, size_t afterend) +{ + lex_ctx_t ctx = ast_ctx(array); + + if (from+1 == afterend) { + /* set this value */ + ast_block *block; + ast_return *ret; + ast_array_index *subscript; + ast_store *st; + int assignop = type_store_instr[value->expression.vtype]; + + 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, false)); + if (!subscript) + return NULL; + + st = ast_store_new(ctx, assignop, (ast_expression*)subscript, (ast_expression*)value); + if (!st) { + ast_delete(subscript); + return NULL; + } + + block = ast_block_new(ctx); + if (!block) { + ast_delete(st); + return NULL; + } + + if (!ast_block_add_expr(block, (ast_expression*)st)) { + ast_delete(block); + return NULL; + } + + ret = ast_return_new(ctx, NULL); + if (!ret) { + ast_delete(block); + return NULL; + } + + if (!ast_block_add_expr(block, (ast_expression*)ret)) { + ast_delete(block); + return NULL; + } + + return (ast_expression*)block; + } else { + ast_expression *left, *right; + size_t diff = afterend - from; + size_t middle = from + diff/2; + left = array_setter_node(parser, array, index, value, from, middle); + right = array_setter_node(parser, array, index, value, middle, afterend); + return array_accessor_split(parser, array, index, middle, left, right); + } +} + +static ast_expression *array_field_setter_node( + parser_t *parser, + ast_value *array, + ast_value *entity, + ast_value *index, + ast_value *value, + size_t from, + size_t afterend) +{ + lex_ctx_t ctx = ast_ctx(array); + + if (from+1 == afterend) { + /* set this value */ + ast_block *block; + ast_return *ret; + ast_entfield *entfield; + ast_array_index *subscript; + ast_store *st; + int assignop = type_storep_instr[value->expression.vtype]; + + 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, false)); + if (!subscript) + return NULL; + + subscript->expression.next = ast_type_copy(ast_ctx(subscript), (ast_expression*)subscript); + subscript->expression.vtype = TYPE_FIELD; + + entfield = ast_entfield_new_force(ctx, + (ast_expression*)entity, + (ast_expression*)subscript, + (ast_expression*)subscript); + if (!entfield) { + ast_delete(subscript); + return NULL; + } + + st = ast_store_new(ctx, assignop, (ast_expression*)entfield, (ast_expression*)value); + if (!st) { + ast_delete(entfield); + return NULL; + } + + block = ast_block_new(ctx); + if (!block) { + ast_delete(st); + return NULL; + } + + if (!ast_block_add_expr(block, (ast_expression*)st)) { + ast_delete(block); + return NULL; + } + + ret = ast_return_new(ctx, NULL); + if (!ret) { + ast_delete(block); + return NULL; + } + + if (!ast_block_add_expr(block, (ast_expression*)ret)) { + ast_delete(block); + return NULL; + } + + return (ast_expression*)block; + } else { + ast_expression *left, *right; + size_t diff = afterend - from; + size_t middle = from + diff/2; + left = array_field_setter_node(parser, array, entity, index, value, from, middle); + right = array_field_setter_node(parser, array, entity, index, value, middle, afterend); + return array_accessor_split(parser, array, index, middle, left, right); + } +} + +static ast_expression *array_getter_node(parser_t *parser, ast_value *array, ast_value *index, size_t from, size_t afterend) +{ + lex_ctx_t ctx = ast_ctx(array); + + if (from+1 == afterend) { + ast_return *ret; + ast_array_index *subscript; + + subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false)); + if (!subscript) + return NULL; + + ret = ast_return_new(ctx, (ast_expression*)subscript); + if (!ret) { + ast_delete(subscript); + return NULL; + } + + return (ast_expression*)ret; + } else { + ast_expression *left, *right; + size_t diff = afterend - from; + size_t middle = from + diff/2; + left = array_getter_node(parser, array, index, from, middle); + right = array_getter_node(parser, array, index, middle, afterend); + return array_accessor_split(parser, array, index, middle, left, right); + } +} + +static bool parser_create_array_accessor(parser_t *parser, ast_value *array, const char *funcname, ast_value **out) +{ + ast_function *func = NULL; + ast_value *fval = NULL; + ast_block *body = NULL; + + fval = ast_value_new(ast_ctx(array), funcname, TYPE_FUNCTION); + if (!fval) { + parseerror(parser, "failed to create accessor function value"); + return false; + } + fval->expression.flags &= ~(AST_FLAG_COVERAGE_MASK); + + func = ast_function_new(ast_ctx(array), funcname, fval); + if (!func) { + ast_delete(fval); + parseerror(parser, "failed to create accessor function node"); + return false; + } + + body = ast_block_new(ast_ctx(array)); + if (!body) { + parseerror(parser, "failed to create block for array accessor"); + ast_delete(fval); + ast_delete(func); + return false; + } + + vec_push(func->blocks, body); + *out = fval; + + vec_push(parser->accessors, fval); + + return true; +} + +static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname) +{ + ast_value *index = NULL; + ast_value *value = NULL; + ast_function *func; + ast_value *fval; + + if (!ast_istype(array->expression.next, ast_value)) { + parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type"); + return NULL; + } + + if (!parser_create_array_accessor(parser, array, funcname, &fval)) + return NULL; + func = fval->constval.vfunc; + fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "", TYPE_VOID); + + index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT); + value = ast_value_copy((ast_value*)array->expression.next); + + if (!index || !value) { + parseerror(parser, "failed to create locals for array accessor"); + goto cleanup; + } + (void)!ast_value_set_name(value, "value"); /* not important */ + vec_push(fval->expression.params, index); + vec_push(fval->expression.params, value); + + array->setter = fval; + return fval; +cleanup: + if (index) ast_delete(index); + if (value) ast_delete(value); + ast_delete(func); + ast_delete(fval); + return NULL; +} + +static bool parser_create_array_setter_impl(parser_t *parser, ast_value *array) +{ + ast_expression *root = NULL; + root = array_setter_node(parser, array, + array->setter->expression.params[0], + array->setter->expression.params[1], + 0, array->expression.count); + if (!root) { + parseerror(parser, "failed to build accessor search tree"); + return false; + } + if (!ast_block_add_expr(array->setter->constval.vfunc->blocks[0], root)) { + ast_delete(root); + return false; + } + return true; +} + +static bool parser_create_array_setter(parser_t *parser, ast_value *array, const char *funcname) +{ + if (!parser_create_array_setter_proto(parser, array, funcname)) + return false; + return parser_create_array_setter_impl(parser, array); +} + +static bool parser_create_array_field_setter(parser_t *parser, ast_value *array, const char *funcname) +{ + ast_expression *root = NULL; + ast_value *entity = NULL; + ast_value *index = NULL; + ast_value *value = NULL; + ast_function *func; + ast_value *fval; + + if (!ast_istype(array->expression.next, ast_value)) { + parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type"); + return false; + } + + if (!parser_create_array_accessor(parser, array, funcname, &fval)) + return false; + func = fval->constval.vfunc; + fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "", TYPE_VOID); + + entity = ast_value_new(ast_ctx(array), "entity", TYPE_ENTITY); + index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT); + value = ast_value_copy((ast_value*)array->expression.next); + if (!entity || !index || !value) { + parseerror(parser, "failed to create locals for array accessor"); + goto cleanup; + } + (void)!ast_value_set_name(value, "value"); /* not important */ + vec_push(fval->expression.params, entity); + vec_push(fval->expression.params, index); + vec_push(fval->expression.params, value); + + root = array_field_setter_node(parser, array, entity, index, value, 0, array->expression.count); + if (!root) { + parseerror(parser, "failed to build accessor search tree"); + goto cleanup; + } + + array->setter = fval; + return ast_block_add_expr(func->blocks[0], root); +cleanup: + if (entity) ast_delete(entity); + if (index) ast_delete(index); + if (value) ast_delete(value); + if (root) ast_delete(root); + ast_delete(func); + ast_delete(fval); + return false; +} + +static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname) +{ + ast_value *index = NULL; + ast_value *fval; + ast_function *func; + + /* NOTE: checking array->expression.next rather than elemtype since + * for fields elemtype is a temporary fieldtype. + */ + if (!ast_istype(array->expression.next, ast_value)) { + parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type"); + return NULL; + } + + if (!parser_create_array_accessor(parser, array, funcname, &fval)) + return NULL; + func = fval->constval.vfunc; + fval->expression.next = ast_type_copy(ast_ctx(array), elemtype); + + index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT); + + if (!index) { + parseerror(parser, "failed to create locals for array accessor"); + goto cleanup; + } + vec_push(fval->expression.params, index); + + array->getter = fval; + return fval; +cleanup: + if (index) ast_delete(index); + ast_delete(func); + ast_delete(fval); + return NULL; +} + +static bool parser_create_array_getter_impl(parser_t *parser, ast_value *array) +{ + ast_expression *root = NULL; + + root = array_getter_node(parser, array, array->getter->expression.params[0], 0, array->expression.count); + if (!root) { + parseerror(parser, "failed to build accessor search tree"); + return false; + } + if (!ast_block_add_expr(array->getter->constval.vfunc->blocks[0], root)) { + ast_delete(root); + return false; + } + return true; +} + +static bool parser_create_array_getter(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname) +{ + if (!parser_create_array_getter_proto(parser, array, elemtype, funcname)) + return false; + return parser_create_array_getter_impl(parser, array); +} + +static ast_value *parse_parameter_list(parser_t *parser, ast_value *var) +{ + lex_ctx_t ctx; + size_t i; + ast_value **params; + ast_value *param; + ast_value *fval; + bool first = true; + bool variadic = false; + ast_value *varparam = NULL; + char *argcounter = NULL; + + ctx = parser_ctx(parser); + + /* for the sake of less code we parse-in in this function */ + if (!parser_next(parser)) { + ast_delete(var); + parseerror(parser, "expected parameter list"); + return NULL; + } + + params = NULL; + + /* parse variables until we hit a closing paren */ + while (parser->tok != ')') { + bool is_varargs = false; + + if (!first) { + /* there must be commas between them */ + if (parser->tok != ',') { + parseerror(parser, "expected comma or end of parameter list"); + goto on_error; + } + if (!parser_next(parser)) { + parseerror(parser, "expected parameter"); + goto on_error; + } + } + first = false; + + param = parse_typename(parser, NULL, NULL, &is_varargs); + if (!param && !is_varargs) + goto on_error; + if (is_varargs) { + /* '...' indicates a varargs function */ + variadic = true; + if (parser->tok != ')' && parser->tok != TOKEN_IDENT) { + parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); + goto on_error; + } + if (parser->tok == TOKEN_IDENT) { + argcounter = util_strdup(parser_tokval(parser)); + if (!parser_next(parser) || parser->tok != ')') { + parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); + goto on_error; + } + } + } else { + vec_push(params, param); + if (param->expression.vtype >= TYPE_VARIANT) { + char tname[1024]; /* typename is reserved in C++ */ + ast_type_to_string((ast_expression*)param, tname, sizeof(tname)); + parseerror(parser, "type not supported as part of a parameter list: %s", tname); + goto on_error; + } + /* type-restricted varargs */ + if (parser->tok == TOKEN_DOTS) { + variadic = true; + varparam = vec_last(params); + vec_pop(params); + if (!parser_next(parser) || (parser->tok != ')' && parser->tok != TOKEN_IDENT)) { + parseerror(parser, "`...` must be the last parameter of a variadic function declaration"); + goto on_error; + } + 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; + } + } + } + + if (vec_size(params) == 1 && params[0]->expression.vtype == TYPE_VOID) + vec_free(params); + + /* sanity check */ + if (vec_size(params) > 8 && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) + (void)!parsewarning(parser, WARN_EXTENSIONS, "more than 8 parameters are not supported by this standard"); + + /* parse-out */ + if (!parser_next(parser)) { + parseerror(parser, "parse error after typename"); + goto on_error; + } + + /* now turn 'var' into a function type */ + fval = ast_value_new(ctx, "", TYPE_FUNCTION); + fval->expression.next = (ast_expression*)var; + if (variadic) + fval->expression.flags |= AST_FLAG_VARIADIC; + var = fval; + + var->expression.params = params; + var->expression.varparam = (ast_expression*)varparam; + var->argcounter = argcounter; + params = NULL; + + return var; + +on_error: + if (argcounter) + mem_d(argcounter); + if (varparam) + ast_delete(varparam); + ast_delete(var); + for (i = 0; i < vec_size(params); ++i) + ast_delete(params[i]); + vec_free(params); + return NULL; +} + +static ast_value *parse_arraysize(parser_t *parser, ast_value *var) +{ + ast_expression *cexp; + ast_value *cval, *tmp; + lex_ctx_t ctx; + + ctx = parser_ctx(parser); + + if (!parser_next(parser)) { + ast_delete(var); + parseerror(parser, "expected array-size"); + return NULL; + } + + if (parser->tok != ']') { + cexp = parse_expression_leave(parser, true, false, false); + + if (!cexp || !ast_istype(cexp, ast_value)) { + if (cexp) + ast_unref(cexp); + ast_delete(var); + parseerror(parser, "expected array-size as constant positive integer"); + return NULL; + } + cval = (ast_value*)cexp; + } + else { + cexp = NULL; + cval = NULL; + } + + tmp = ast_value_new(ctx, "", TYPE_ARRAY); + tmp->expression.next = (ast_expression*)var; + var = tmp; + + if (cval) { + if (cval->expression.vtype == TYPE_INTEGER) + tmp->expression.count = cval->constval.vint; + else if (cval->expression.vtype == TYPE_FLOAT) + tmp->expression.count = cval->constval.vfloat; + else { + ast_unref(cexp); + ast_delete(var); + parseerror(parser, "array-size must be a positive integer constant"); + return NULL; + } + + ast_unref(cexp); + } else { + var->expression.count = -1; + var->expression.flags |= AST_FLAG_ARRAY_INIT; + } + + if (parser->tok != ']') { + ast_delete(var); + parseerror(parser, "expected ']' after array-size"); + return NULL; + } + if (!parser_next(parser)) { + ast_delete(var); + parseerror(parser, "error after parsing array size"); + return NULL; + } + return var; +} + +/* Parse a complete typename. + * for single-variables (ie. function parameters or typedefs) storebase should be NULL + * but when parsing variables separated by comma + * 'storebase' should point to where the base-type should be kept. + * The base type makes up every bit of type information which comes *before* the + * variable name. + * + * NOTE: The value must either be named, have a NULL name, or a name starting + * with '<'. In the first case, this will be the actual variable or type + * name, in the other cases it is assumed that the name will appear + * later, and an error is generated otherwise. + * + * The following will be parsed in its entirety: + * void() foo() + * The 'basetype' in this case is 'void()' + * and if there's a comma after it, say: + * void() foo(), bar + * then the type-information 'void()' can be stored in 'storebase' + */ +static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg) +{ + ast_value *var, *tmp; + lex_ctx_t ctx; + + const char *name = NULL; + bool isfield = false; + bool wasarray = false; + size_t morefields = 0; + + bool vararg = (parser->tok == TOKEN_DOTS); + + ctx = parser_ctx(parser); + + /* types may start with a dot */ + if (parser->tok == '.' || parser->tok == TOKEN_DOTS) { + isfield = true; + if (parser->tok == TOKEN_DOTS) + morefields += 2; + /* if we parsed a dot we need a typename now */ + if (!parser_next(parser)) { + parseerror(parser, "expected typename for field definition"); + return NULL; + } + + /* Further dots are handled seperately because they won't be part of the + * basetype + */ + while (true) { + if (parser->tok == '.') + ++morefields; + else if (parser->tok == TOKEN_DOTS) + morefields += 3; + else + break; + vararg = false; + if (!parser_next(parser)) { + parseerror(parser, "expected typename for field definition"); + return NULL; + } + } + } + if (parser->tok == TOKEN_IDENT) + cached_typedef = parser_find_typedef(parser, parser_tokval(parser), 0); + if (!cached_typedef && parser->tok != TOKEN_TYPENAME) { + if (vararg && is_vararg) { + *is_vararg = true; + return NULL; + } + parseerror(parser, "expected typename"); + return NULL; + } + + /* generate the basic type value */ + if (cached_typedef) { + var = ast_value_copy(cached_typedef); + ast_value_set_name(var, ""); + } else + var = ast_value_new(ctx, "", parser_token(parser)->constval.t); + + for (; morefields; --morefields) { + tmp = ast_value_new(ctx, "<.type>", TYPE_FIELD); + tmp->expression.next = (ast_expression*)var; + var = tmp; + } + + /* do not yet turn into a field - remember: + * .void() foo; is a field too + * .void()() foo; is a function + */ + + /* parse on */ + if (!parser_next(parser)) { + ast_delete(var); + parseerror(parser, "parse error after typename"); + return NULL; + } + + /* an opening paren now starts the parameter-list of a function + * this is where original-QC has parameter lists. + * We allow a single parameter list here. + * Much like fteqcc we don't allow `float()() x` + */ + if (parser->tok == '(') { + var = parse_parameter_list(parser, var); + if (!var) + return NULL; + } + + /* store the base if requested */ + if (storebase) { + *storebase = ast_value_copy(var); + if (isfield) { + tmp = ast_value_new(ctx, "", TYPE_FIELD); + tmp->expression.next = (ast_expression*)*storebase; + *storebase = tmp; + } + } + + /* there may be a name now */ + if (parser->tok == TOKEN_IDENT || parser->tok == TOKEN_KEYWORD) { + if (!strcmp(parser_tokval(parser), "break")) + (void)!parsewarning(parser, WARN_BREAKDEF, "break definition ignored (suggest removing it)"); + else if (parser->tok == TOKEN_KEYWORD) + goto leave; + + name = util_strdup(parser_tokval(parser)); + + /* parse on */ + if (!parser_next(parser)) { + ast_delete(var); + mem_d(name); + parseerror(parser, "error after variable or field declaration"); + return NULL; + } + } + + leave: + /* now this may be an array */ + if (parser->tok == '[') { + wasarray = true; + var = parse_arraysize(parser, var); + if (!var) { + if (name) mem_d(name); + return NULL; + } + } + + /* This is the point where we can turn it into a field */ + if (isfield) { + /* turn it into a field if desired */ + tmp = ast_value_new(ctx, "", TYPE_FIELD); + tmp->expression.next = (ast_expression*)var; + var = tmp; + } + + /* now there may be function parens again */ + if (parser->tok == '(' && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) + parseerror(parser, "C-style function syntax is not allowed in -std=qcc"); + if (parser->tok == '(' && wasarray) + parseerror(parser, "arrays as part of a return type is not supported"); + while (parser->tok == '(') { + var = parse_parameter_list(parser, var); + if (!var) { + if (name) mem_d(name); + return NULL; + } + } + + /* finally name it */ + if (name) { + if (!ast_value_set_name(var, name)) { + ast_delete(var); + mem_d(name); + parseerror(parser, "internal error: failed to set name"); + return NULL; + } + /* free the name, ast_value_set_name duplicates */ + mem_d(name); + } + + return var; +} + +static bool parse_typedef(parser_t *parser) +{ + ast_value *typevar, *oldtype; + ast_expression *old; + + typevar = parse_typename(parser, NULL, NULL, NULL); + + if (!typevar) + return false; + + /* while parsing types, the ast_value's get named '' */ + if (!typevar->name || typevar->name[0] == '<') { + parseerror(parser, "missing name in typedef"); + ast_delete(typevar); + return false; + } + + if ( (old = parser_find_var(parser, typevar->name)) ) { + parseerror(parser, "cannot define a type with the same name as a variable: %s\n" + " -> `%s` has been declared here: %s:%i", + typevar->name, ast_ctx(old).file, ast_ctx(old).line); + ast_delete(typevar); + return false; + } + + if ( (oldtype = parser_find_typedef(parser, typevar->name, vec_last(parser->_blocktypedefs))) ) { + parseerror(parser, "type `%s` has already been declared here: %s:%i", + typevar->name, ast_ctx(oldtype).file, ast_ctx(oldtype).line); + ast_delete(typevar); + return false; + } + + vec_push(parser->_typedefs, typevar); + util_htset(vec_last(parser->typedefs), typevar->name, typevar); + + if (parser->tok != ';') { + parseerror(parser, "expected semicolon after typedef"); + return false; + } + if (!parser_next(parser)) { + parseerror(parser, "parse error after typedef"); + return false; + } + + return true; +} + +static const char *cvq_to_str(int cvq) { + switch (cvq) { + case CV_NONE: return "none"; + case CV_VAR: return "`var`"; + case CV_CONST: return "`const`"; + default: return ""; + } +} + +static bool parser_check_qualifiers(parser_t *parser, const ast_value *var, const ast_value *proto) +{ + bool av, ao; + if (proto->cvq != var->cvq) { + if (!(proto->cvq == CV_CONST && var->cvq == CV_NONE && + !OPTS_FLAG(INITIALIZED_NONCONSTANTS) && + parser->tok == '=')) + { + return !parsewarning(parser, WARN_DIFFERENT_QUALIFIERS, + "`%s` declared with different qualifiers: %s\n" + " -> previous declaration here: %s:%i uses %s", + var->name, cvq_to_str(var->cvq), + ast_ctx(proto).file, ast_ctx(proto).line, + cvq_to_str(proto->cvq)); + } + } + av = (var ->expression.flags & AST_FLAG_NORETURN); + ao = (proto->expression.flags & AST_FLAG_NORETURN); + if (!av != !ao) { + return !parsewarning(parser, WARN_DIFFERENT_ATTRIBUTES, + "`%s` declared with different attributes%s\n" + " -> previous declaration here: %s:%i", + var->name, (av ? ": noreturn" : ""), + ast_ctx(proto).file, ast_ctx(proto).line, + (ao ? ": noreturn" : "")); + } + return true; +} + +static bool create_array_accessors(parser_t *parser, ast_value *var) +{ + char name[1024]; + util_snprintf(name, sizeof(name), "%s##SET", var->name); + if (!parser_create_array_setter(parser, var, name)) + return false; + util_snprintf(name, sizeof(name), "%s##GET", var->name); + if (!parser_create_array_getter(parser, var, var->expression.next, name)) + return false; + return true; +} + +static bool parse_array(parser_t *parser, ast_value *array) +{ + size_t i; + if (array->initlist) { + parseerror(parser, "array already initialized elsewhere"); + return false; + } + if (!parser_next(parser)) { + parseerror(parser, "parse error in array initializer"); + return false; + } + i = 0; + while (parser->tok != '}') { + ast_value *v = (ast_value*)parse_expression_leave(parser, true, false, false); + if (!v) + return false; + if (!ast_istype(v, ast_value) || !v->hasvalue || v->cvq != CV_CONST) { + ast_unref(v); + parseerror(parser, "initializing element must be a compile time constant"); + return false; + } + vec_push(array->initlist, v->constval); + if (v->expression.vtype == TYPE_STRING) { + array->initlist[i].vstring = util_strdupe(array->initlist[i].vstring); + ++i; + } + ast_unref(v); + if (parser->tok == '}') + break; + if (parser->tok != ',' || !parser_next(parser)) { + parseerror(parser, "expected comma or '}' in element list"); + return false; + } + } + if (!parser_next(parser) || parser->tok != ';') { + parseerror(parser, "expected semicolon after initializer, got %s"); + return false; + } + /* + if (!parser_next(parser)) { + parseerror(parser, "parse error after initializer"); + return false; + } + */ + + if (array->expression.flags & AST_FLAG_ARRAY_INIT) { + if (array->expression.count != (size_t)-1) { + parseerror(parser, "array `%s' has already been initialized with %u elements", + array->name, (unsigned)array->expression.count); + } + array->expression.count = vec_size(array->initlist); + if (!create_array_accessors(parser, array)) + return false; + } + return true; +} + +static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring) +{ + ast_value *var; + ast_value *proto; + ast_expression *old; + bool was_end; + size_t i; + + ast_value *basetype = NULL; + bool retval = true; + bool isparam = false; + bool isvector = false; + bool cleanvar = true; + 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"); + + /* get the first complete variable */ + var = parse_typename(parser, &basetype, cached_typedef, NULL); + if (!var) { + if (basetype) + ast_delete(basetype); + return false; + } + + /* while parsing types, the ast_value's get named '' */ + if (!var->name || var->name[0] == '<') { + parseerror(parser, "declaration does not declare anything"); + if (basetype) + ast_delete(basetype); + return false; + } + + while (true) { + proto = NULL; + wasarray = false; + + /* Part 0: finish the type */ + if (parser->tok == '(') { + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) + parseerror(parser, "C-style function syntax is not allowed in -std=qcc"); + var = parse_parameter_list(parser, var); + if (!var) { + retval = false; + goto cleanup; + } + } + /* we only allow 1-dimensional arrays */ + if (parser->tok == '[') { + wasarray = true; + var = parse_arraysize(parser, var); + if (!var) { + retval = false; + goto cleanup; + } + } + if (parser->tok == '(' && wasarray) { + parseerror(parser, "arrays as part of a return type is not supported"); + /* we'll still parse the type completely for now */ + } + /* for functions returning functions */ + while (parser->tok == '(') { + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) + parseerror(parser, "C-style function syntax is not allowed in -std=qcc"); + var = parse_parameter_list(parser, var); + if (!var) { + retval = false; + goto cleanup; + } + } + + var->cvq = qualifier; + if (qflags & AST_FLAG_COVERAGE) /* specified in QC, drop our default */ + var->expression.flags &= ~(AST_FLAG_COVERAGE_MASK); + var->expression.flags |= qflags; + + /* + * store the vstring back to var for alias and + * deprecation messages. + */ + if (var->expression.flags & AST_FLAG_DEPRECATED || + var->expression.flags & AST_FLAG_ALIAS) + var->desc = vstring; + + if (parser_find_global(parser, var->name) && var->expression.flags & AST_FLAG_ALIAS) { + parseerror(parser, "function aliases cannot be forward declared"); + retval = false; + goto cleanup; + } + + + /* Part 1: + * check for validity: (end_sys_..., multiple-definitions, prototypes, ...) + * Also: if there was a prototype, `var` will be deleted and set to `proto` which + * is then filled with the previous definition and the parameter-names replaced. + */ + if (!strcmp(var->name, "nil")) { + if (OPTS_FLAG(UNTYPED_NIL)) { + if (!localblock || !OPTS_FLAG(PERMISSIVE)) + parseerror(parser, "name `nil` not allowed (try -fpermissive)"); + } else + (void)!parsewarning(parser, WARN_RESERVED_NAMES, "variable name `nil` is reserved"); + } + if (!localblock) { + /* Deal with end_sys_ vars */ + was_end = false; + if (!strcmp(var->name, "end_sys_globals")) { + var->uses++; + parser->crc_globals = vec_size(parser->globals); + was_end = true; + } + else if (!strcmp(var->name, "end_sys_fields")) { + var->uses++; + parser->crc_fields = vec_size(parser->fields); + was_end = true; + } + if (was_end && var->expression.vtype == TYPE_FIELD) { + if (parsewarning(parser, WARN_END_SYS_FIELDS, + "global '%s' hint should not be a field", + parser_tokval(parser))) + { + retval = false; + goto cleanup; + } + } + + if (!nofields && var->expression.vtype == TYPE_FIELD) + { + /* deal with field declarations */ + old = parser_find_field(parser, var->name); + if (old) { + if (parsewarning(parser, WARN_FIELD_REDECLARED, "field `%s` already declared here: %s:%i", + var->name, ast_ctx(old).file, (int)ast_ctx(old).line)) + { + retval = false; + goto cleanup; + } + ast_delete(var); + var = NULL; + goto skipvar; + /* + parseerror(parser, "field `%s` already declared here: %s:%i", + var->name, ast_ctx(old).file, ast_ctx(old).line); + retval = false; + goto cleanup; + */ + } + if ((OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC || OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) && + (old = parser_find_global(parser, var->name))) + { + parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc"); + parseerror(parser, "field `%s` already declared here: %s:%i", + var->name, ast_ctx(old).file, ast_ctx(old).line); + retval = false; + goto cleanup; + } + } + else + { + /* deal with other globals */ + old = parser_find_global(parser, var->name); + if (old && var->expression.vtype == TYPE_FUNCTION && old->vtype == TYPE_FUNCTION) + { + /* This is a function which had a prototype */ + if (!ast_istype(old, ast_value)) { + parseerror(parser, "internal error: prototype is not an ast_value"); + retval = false; + goto cleanup; + } + proto = (ast_value*)old; + proto->desc = var->desc; + if (!ast_compare_type((ast_expression*)proto, (ast_expression*)var)) { + parseerror(parser, "conflicting types for `%s`, previous declaration was here: %s:%i", + proto->name, + ast_ctx(proto).file, ast_ctx(proto).line); + retval = false; + goto cleanup; + } + /* we need the new parameter-names */ + for (i = 0; i < vec_size(proto->expression.params); ++i) + ast_value_set_name(proto->expression.params[i], var->expression.params[i]->name); + if (!parser_check_qualifiers(parser, var, proto)) { + retval = false; + if (proto->desc) + mem_d(proto->desc); + proto = NULL; + goto cleanup; + } + proto->expression.flags |= var->expression.flags; + ast_delete(var); + var = proto; + } + else + { + /* other globals */ + if (old) { + if (parsewarning(parser, WARN_DOUBLE_DECLARATION, + "global `%s` already declared here: %s:%i", + var->name, ast_ctx(old).file, ast_ctx(old).line)) + { + retval = false; + goto cleanup; + } + if (old->flags & AST_FLAG_FINAL_DECL) { + parseerror(parser, "cannot redeclare variable `%s`, declared final here: %s:%i", + var->name, ast_ctx(old).file, ast_ctx(old).line); + retval = false; + goto cleanup; + } + proto = (ast_value*)old; + if (!ast_istype(old, ast_value)) { + parseerror(parser, "internal error: not an ast_value"); + retval = false; + proto = NULL; + goto cleanup; + } + if (!parser_check_qualifiers(parser, var, proto)) { + retval = false; + proto = NULL; + goto cleanup; + } + proto->expression.flags |= var->expression.flags; + /* copy the context for finals, + * so the error can show where it was actually made 'final' + */ + if (proto->expression.flags & AST_FLAG_FINAL_DECL) + ast_ctx(old) = ast_ctx(var); + ast_delete(var); + var = proto; + } + if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC && + (old = parser_find_field(parser, var->name))) + { + parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc"); + parseerror(parser, "global `%s` already declared here: %s:%i", + var->name, ast_ctx(old).file, ast_ctx(old).line); + retval = false; + goto cleanup; + } + } + } + } + else /* it's not a global */ + { + old = parser_find_local(parser, var->name, vec_size(parser->variables)-1, &isparam); + if (old && !isparam) { + parseerror(parser, "local `%s` already declared here: %s:%i", + var->name, ast_ctx(old).file, (int)ast_ctx(old).line); + retval = false; + goto cleanup; + } + /* doing this here as the above is just for a single scope */ + old = parser_find_local(parser, var->name, 0, &isparam); + if (old && isparam) { + if (parsewarning(parser, WARN_LOCAL_SHADOWS, + "local `%s` is shadowing a parameter", var->name)) + { + parseerror(parser, "local `%s` already declared here: %s:%i", + var->name, ast_ctx(old).file, (int)ast_ctx(old).line); + retval = false; + goto cleanup; + } + if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) { + ast_delete(var); + if (ast_istype(old, ast_value)) + var = proto = (ast_value*)old; + else { + var = NULL; + goto skipvar; + } + } + } + } + + /* in a noref section we simply bump the usecount */ + if (noref || parser->noref) + var->uses++; + + /* Part 2: + * Create the global/local, and deal with vector types. + */ + if (!proto) { + if (var->expression.vtype == TYPE_VECTOR) + isvector = true; + else if (var->expression.vtype == TYPE_FIELD && + var->expression.next->vtype == TYPE_VECTOR) + isvector = true; + + if (isvector) { + if (!create_vector_members(var, me)) { + retval = false; + goto cleanup; + } + } + + if (!localblock) { + /* deal with global variables, fields, functions */ + if (!nofields && var->expression.vtype == TYPE_FIELD && parser->tok != '=') { + var->isfield = true; + vec_push(parser->fields, (ast_expression*)var); + util_htset(parser->htfields, var->name, var); + if (isvector) { + for (i = 0; i < 3; ++i) { + vec_push(parser->fields, (ast_expression*)me[i]); + util_htset(parser->htfields, me[i]->name, me[i]); + } + } + } + else { + if (!(var->expression.flags & AST_FLAG_ALIAS)) { + parser_addglobal(parser, var->name, (ast_expression*)var); + if (isvector) { + for (i = 0; i < 3; ++i) { + parser_addglobal(parser, me[i]->name, (ast_expression*)me[i]); + } + } + } else { + ast_expression *find = parser_find_global(parser, var->desc); + + if (!find) { + compile_error(parser_ctx(parser), "undeclared variable `%s` for alias `%s`", var->desc, var->name); + return false; + } + + if (!ast_compare_type((ast_expression*)var, find)) { + char ty1[1024]; + char ty2[1024]; + + ast_type_to_string(find, ty1, sizeof(ty1)); + ast_type_to_string((ast_expression*)var, ty2, sizeof(ty2)); + + compile_error(parser_ctx(parser), "incompatible types `%s` and `%s` for alias `%s`", + ty1, ty2, var->name + ); + return false; + } + + util_htset(parser->aliases, var->name, find); + + /* generate aliases for vector components */ + if (isvector) { + char *buffer[3]; + + util_asprintf(&buffer[0], "%s_x", var->desc); + util_asprintf(&buffer[1], "%s_y", var->desc); + util_asprintf(&buffer[2], "%s_z", var->desc); + + util_htset(parser->aliases, me[0]->name, parser_find_global(parser, buffer[0])); + util_htset(parser->aliases, me[1]->name, parser_find_global(parser, buffer[1])); + util_htset(parser->aliases, me[2]->name, parser_find_global(parser, buffer[2])); + + mem_d(buffer[0]); + mem_d(buffer[1]); + mem_d(buffer[2]); + } + } + } + } else { + if (is_static) { + /* a static adds itself to be generated like any other global + * but is added to the local namespace instead + */ + char *defname = NULL; + size_t prefix_len, ln; + size_t sn, sn_size; + + ln = strlen(parser->function->name); + vec_append(defname, ln, parser->function->name); + + vec_append(defname, 2, "::"); + /* remember the length up to here */ + prefix_len = vec_size(defname); + + /* Add it to the local scope */ + util_htset(vec_last(parser->variables), var->name, (void*)var); + + /* now rename the global */ + ln = strlen(var->name); + vec_append(defname, ln, var->name); + /* if a variable of that name already existed, add the + * counter value. + * The counter is incremented either way. + */ + sn_size = vec_size(parser->function->static_names); + for (sn = 0; sn != sn_size; ++sn) { + if (strcmp(parser->function->static_names[sn], var->name) == 0) + break; + } + if (sn != sn_size) { + char *num = NULL; + int len = util_asprintf(&num, "#%u", parser->function->static_count); + vec_append(defname, len, num); + mem_d(num); + } + else + vec_push(parser->function->static_names, util_strdup(var->name)); + parser->function->static_count++; + ast_value_set_name(var, defname); + + /* push it to the to-be-generated globals */ + vec_push(parser->globals, (ast_expression*)var); + + /* same game for the vector members */ + if (isvector) { + for (i = 0; i < 3; ++i) { + util_htset(vec_last(parser->variables), me[i]->name, (void*)(me[i])); + + vec_shrinkto(defname, prefix_len); + ln = strlen(me[i]->name); + vec_append(defname, ln, me[i]->name); + ast_member_set_name(me[i], defname); + + vec_push(parser->globals, (ast_expression*)me[i]); + } + } + vec_free(defname); + } else { + vec_push(localblock->locals, var); + parser_addlocal(parser, var->name, (ast_expression*)var); + if (isvector) { + for (i = 0; i < 3; ++i) { + parser_addlocal(parser, me[i]->name, (ast_expression*)me[i]); + ast_block_collect(localblock, (ast_expression*)me[i]); + } + } + } + } + } + memcpy(last_me, me, sizeof(me)); + me[0] = me[1] = me[2] = NULL; + cleanvar = false; + /* Part 2.2 + * deal with arrays + */ + if (var->expression.vtype == TYPE_ARRAY) { + if (var->expression.count != (size_t)-1) { + if (!create_array_accessors(parser, var)) + goto cleanup; + } + } + else if (!localblock && !nofields && + var->expression.vtype == TYPE_FIELD && + var->expression.next->vtype == TYPE_ARRAY) + { + char name[1024]; + ast_expression *telem; + ast_value *tfield; + ast_value *array = (ast_value*)var->expression.next; + + if (!ast_istype(var->expression.next, ast_value)) { + parseerror(parser, "internal error: field element type must be an ast_value"); + goto cleanup; + } + + util_snprintf(name, sizeof(name), "%s##SETF", var->name); + if (!parser_create_array_field_setter(parser, array, name)) + goto cleanup; + + telem = ast_type_copy(ast_ctx(var), array->expression.next); + tfield = ast_value_new(ast_ctx(var), "<.type>", TYPE_FIELD); + tfield->expression.next = telem; + util_snprintf(name, sizeof(name), "%s##GETFP", var->name); + if (!parser_create_array_getter(parser, array, (ast_expression*)tfield, name)) { + ast_delete(tfield); + goto cleanup; + } + ast_delete(tfield); + } + +skipvar: + if (parser->tok == ';') { + ast_delete(basetype); + if (!parser_next(parser)) { + parseerror(parser, "error after variable declaration"); + return false; + } + return true; + } + + if (parser->tok == ',') + goto another; + + /* + if (!var || (!localblock && !nofields && basetype->expression.vtype == TYPE_FIELD)) { + */ + if (!var) { + parseerror(parser, "missing comma or semicolon while parsing variables"); + break; + } + + if (localblock && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { + if (parsewarning(parser, WARN_LOCAL_CONSTANTS, + "initializing expression turns variable `%s` into a constant in this standard", + var->name) ) + { + break; + } + } + + if (parser->tok != '{' || var->expression.vtype != TYPE_FUNCTION) { + if (parser->tok != '=') { + parseerror(parser, "missing semicolon or initializer, got: `%s`", parser_tokval(parser)); + break; + } + + if (!parser_next(parser)) { + parseerror(parser, "error parsing initializer"); + break; + } + } + else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) { + parseerror(parser, "expected '=' before function body in this standard"); + } + + if (parser->tok == '#') { + ast_function *func = NULL; + ast_value *number = NULL; + float fractional; + float integral; + int builtin_num; + + if (localblock) { + parseerror(parser, "cannot declare builtins within functions"); + break; + } + if (var->expression.vtype != TYPE_FUNCTION) { + parseerror(parser, "unexpected builtin number, '%s' is not a function", var->name); + break; + } + if (!parser_next(parser)) { + parseerror(parser, "expected builtin number"); + break; + } + + if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS)) { + number = (ast_value*)parse_expression_leave(parser, true, false, false); + if (!number) { + parseerror(parser, "builtin number expected"); + break; + } + if (!ast_istype(number, ast_value) || !number->hasvalue || number->cvq != CV_CONST) + { + ast_unref(number); + parseerror(parser, "builtin number must be a compile time constant"); + break; + } + if (number->expression.vtype == TYPE_INTEGER) + builtin_num = number->constval.vint; + else if (number->expression.vtype == TYPE_FLOAT) + builtin_num = number->constval.vfloat; + else { + ast_unref(number); + parseerror(parser, "builtin number must be an integer constant"); + break; + } + ast_unref(number); + + fractional = modff(builtin_num, &integral); + if (builtin_num < 0 || fractional != 0) { + parseerror(parser, "builtin number must be an integer greater than zero"); + break; + } + + /* we only want the integral part anyways */ + builtin_num = integral; + } else if (parser->tok == TOKEN_INTCONST) { + builtin_num = parser_token(parser)->constval.i; + } else { + parseerror(parser, "builtin number must be a compile time constant"); + break; + } + + if (var->hasvalue) { + (void)!parsewarning(parser, WARN_DOUBLE_DECLARATION, + "builtin `%s` has already been defined\n" + " -> previous declaration here: %s:%i", + var->name, ast_ctx(var).file, (int)ast_ctx(var).line); + } + else + { + func = ast_function_new(ast_ctx(var), var->name, var); + if (!func) { + parseerror(parser, "failed to allocate function for `%s`", var->name); + break; + } + vec_push(parser->functions, func); + + func->builtin = -builtin_num-1; + } + + if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS) + ? (parser->tok != ',' && parser->tok != ';') + : (!parser_next(parser))) + { + parseerror(parser, "expected comma or semicolon"); + if (func) + ast_function_delete(func); + var->constval.vfunc = NULL; + break; + } + } + else if (var->expression.vtype == TYPE_ARRAY && parser->tok == '{') + { + if (localblock) { + /* Note that fteqcc and most others don't even *have* + * local arrays, so this is not a high priority. + */ + parseerror(parser, "TODO: initializers for local arrays"); + break; + } + + var->hasvalue = true; + if (!parse_array(parser, var)) + break; + } + else if (var->expression.vtype == TYPE_FUNCTION && (parser->tok == '{' || parser->tok == '[')) + { + if (localblock) { + parseerror(parser, "cannot declare functions within functions"); + break; + } + + if (proto) + ast_ctx(proto) = parser_ctx(parser); + + if (!parse_function_body(parser, var)) + break; + ast_delete(basetype); + for (i = 0; i < vec_size(parser->gotos); ++i) + parseerror(parser, "undefined label: `%s`", parser->gotos[i]->name); + vec_free(parser->gotos); + vec_free(parser->labels); + return true; + } 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; + + /* 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 && + (!cval || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield)) + ) + { + parseerror(parser, "initializer is non constant"); + } + else + { + if (!is_static && + !OPTS_FLAG(INITIALIZED_NONCONSTANTS) && + qualifier != CV_VAR) + { + var->cvq = CV_CONST; + } + if (cval == parser->nil) + var->expression.flags |= AST_FLAG_INITIALIZED; + else + { + var->hasvalue = true; + if (cval->expression.vtype == TYPE_STRING) + var->constval.vstring = parser_strdup(cval->constval.vstring); + else if (cval->expression.vtype == TYPE_FIELD) + var->constval.vfield = cval; + else + memcpy(&var->constval, &cval->constval, sizeof(var->constval)); + ast_unref(cval); + } + } + } else { + int cvq; + shunt sy = { NULL, NULL, NULL, NULL }; + cvq = var->cvq; + var->cvq = CV_NONE; + vec_push(sy.out, syexp(ast_ctx(var), (ast_expression*)var)); + vec_push(sy.out, syexp(ast_ctx(cexp), (ast_expression*)cexp)); + vec_push(sy.ops, syop(ast_ctx(var), parser->assign_op)); + if (!parser_sy_apply_operator(parser, &sy)) + ast_unref(cexp); + else { + if (vec_size(sy.out) != 1 && vec_size(sy.ops) != 0) + parseerror(parser, "internal error: leaked operands"); + if (!ast_block_add_expr(localblock, (ast_expression*)sy.out[0].out)) + break; + } + vec_free(sy.out); + vec_free(sy.ops); + vec_free(sy.argc); + var->cvq = cvq; + } + /* a constant initialized to an inexact value should be marked inexact: + * const float x = ; 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: + if (parser->tok == ',') { + if (!parser_next(parser)) { + parseerror(parser, "expected another variable"); + break; + } + + if (parser->tok != TOKEN_IDENT) { + parseerror(parser, "expected another variable"); + break; + } + var = ast_value_copy(basetype); + cleanvar = true; + ast_value_set_name(var, parser_tokval(parser)); + if (!parser_next(parser)) { + parseerror(parser, "error parsing variable declaration"); + break; + } + continue; + } + + if (parser->tok != ';') { + parseerror(parser, "missing semicolon after variables"); + break; + } + + if (!parser_next(parser)) { + parseerror(parser, "parse error after variable declaration"); + break; + } + + ast_delete(basetype); + return true; + } + + if (cleanvar && var) + ast_delete(var); + ast_delete(basetype); + return false; + +cleanup: + ast_delete(basetype); + if (cleanvar && var) + ast_delete(var); + if (me[0]) ast_member_delete(me[0]); + if (me[1]) ast_member_delete(me[1]); + if (me[2]) ast_member_delete(me[2]); + return retval; +} + +static bool parser_global_statement(parser_t *parser) +{ + int cvq = CV_WRONG; + bool noref = false; + bool is_static = false; + uint32_t qflags = 0; + ast_value *istype = NULL; + char *vstring = NULL; + + if (parser->tok == TOKEN_IDENT) + istype = parser_find_typedef(parser, parser_tokval(parser), 0); + + if (istype || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS) + { + return parse_variable(parser, NULL, false, CV_NONE, istype, false, false, 0, NULL); + } + else if (parse_qualifiers(parser, false, &cvq, &noref, &is_static, &qflags, &vstring)) + { + if (cvq == CV_WRONG) + return false; + return parse_variable(parser, NULL, false, cvq, NULL, noref, is_static, qflags, vstring); + } + else if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "enum")) + { + return parse_enum(parser); + } + else if (parser->tok == TOKEN_KEYWORD) + { + if (!strcmp(parser_tokval(parser), "typedef")) { + if (!parser_next(parser)) { + parseerror(parser, "expected type definition after 'typedef'"); + return false; + } + return parse_typedef(parser); + } + parseerror(parser, "unrecognized keyword `%s`", parser_tokval(parser)); + return false; + } + else if (parser->tok == '#') + { + return parse_pragma(parser); + } + else if (parser->tok == '$') + { + if (!parser_next(parser)) { + parseerror(parser, "parse error"); + return false; + } + } + else + { + parseerror(parser, "unexpected token: `%s`", parser->lex->tok.value); + return false; + } + return true; +} + +static uint16_t progdefs_crc_sum(uint16_t old, const char *str) +{ + return util_crc16(old, str, strlen(str)); +} + +static void progdefs_crc_file(const char *str) +{ + /* write to progdefs.h here */ + (void)str; +} + +static uint16_t progdefs_crc_both(uint16_t old, const char *str) +{ + old = progdefs_crc_sum(old, str); + progdefs_crc_file(str); + return old; +} + +static void generate_checksum(parser_t *parser, ir_builder *ir) +{ + uint16_t crc = 0xFFFF; + size_t i; + ast_value *value; + + crc = progdefs_crc_both(crc, "\n/* file generated by qcc, do not modify */\n\ntypedef struct\n{"); + crc = progdefs_crc_sum(crc, "\tint\tpad[28];\n"); + /* + progdefs_crc_file("\tint\tpad;\n"); + progdefs_crc_file("\tint\tofs_return[3];\n"); + progdefs_crc_file("\tint\tofs_parm0[3];\n"); + progdefs_crc_file("\tint\tofs_parm1[3];\n"); + progdefs_crc_file("\tint\tofs_parm2[3];\n"); + progdefs_crc_file("\tint\tofs_parm3[3];\n"); + progdefs_crc_file("\tint\tofs_parm4[3];\n"); + progdefs_crc_file("\tint\tofs_parm5[3];\n"); + progdefs_crc_file("\tint\tofs_parm6[3];\n"); + progdefs_crc_file("\tint\tofs_parm7[3];\n"); + */ + for (i = 0; i < parser->crc_globals; ++i) { + if (!ast_istype(parser->globals[i], ast_value)) + continue; + value = (ast_value*)(parser->globals[i]); + switch (value->expression.vtype) { + case TYPE_FLOAT: crc = progdefs_crc_both(crc, "\tfloat\t"); break; + case TYPE_VECTOR: crc = progdefs_crc_both(crc, "\tvec3_t\t"); break; + case TYPE_STRING: crc = progdefs_crc_both(crc, "\tstring_t\t"); break; + case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break; + default: + crc = progdefs_crc_both(crc, "\tint\t"); + break; + } + crc = progdefs_crc_both(crc, value->name); + crc = progdefs_crc_both(crc, ";\n"); + } + crc = progdefs_crc_both(crc, "} globalvars_t;\n\ntypedef struct\n{\n"); + for (i = 0; i < parser->crc_fields; ++i) { + if (!ast_istype(parser->fields[i], ast_value)) + continue; + value = (ast_value*)(parser->fields[i]); + switch (value->expression.next->vtype) { + case TYPE_FLOAT: crc = progdefs_crc_both(crc, "\tfloat\t"); break; + case TYPE_VECTOR: crc = progdefs_crc_both(crc, "\tvec3_t\t"); break; + case TYPE_STRING: crc = progdefs_crc_both(crc, "\tstring_t\t"); break; + case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break; + default: + crc = progdefs_crc_both(crc, "\tint\t"); + break; + } + crc = progdefs_crc_both(crc, value->name); + crc = progdefs_crc_both(crc, ";\n"); + } + crc = progdefs_crc_both(crc, "} entvars_t;\n\n"); + ir->code->crc = crc; +} + +parser_t *parser_create() +{ + parser_t *parser; + lex_ctx_t empty_ctx; + size_t i; + + parser = (parser_t*)mem_a(sizeof(parser_t)); + if (!parser) + return NULL; + + memset(parser, 0, sizeof(*parser)); + + for (i = 0; i < operator_count; ++i) { + if (operators[i].id == opid1('=')) { + parser->assign_op = operators+i; + break; + } + } + if (!parser->assign_op) { + con_err("internal error: initializing parser: failed to find assign operator\n"); + mem_d(parser); + return NULL; + } + + vec_push(parser->variables, parser->htfields = util_htnew(PARSER_HT_SIZE)); + vec_push(parser->variables, parser->htglobals = util_htnew(PARSER_HT_SIZE)); + vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE)); + vec_push(parser->_blocktypedefs, 0); + + parser->aliases = util_htnew(PARSER_HT_SIZE); + + empty_ctx.file = ""; + empty_ctx.line = 0; + empty_ctx.column = 0; + parser->nil = ast_value_new(empty_ctx, "nil", TYPE_NIL); + parser->nil->cvq = CV_CONST; + if (OPTS_FLAG(UNTYPED_NIL)) + util_htset(parser->htglobals, "nil", (void*)parser->nil); + + parser->max_param_count = 1; + + parser->const_vec[0] = ast_value_new(empty_ctx, "", TYPE_NOEXPR); + parser->const_vec[1] = ast_value_new(empty_ctx, "", TYPE_NOEXPR); + parser->const_vec[2] = ast_value_new(empty_ctx, "", TYPE_NOEXPR); + + if (OPTS_OPTION_BOOL(OPTION_ADD_INFO)) { + parser->reserved_version = ast_value_new(empty_ctx, "reserved:version", TYPE_STRING); + parser->reserved_version->cvq = CV_CONST; + parser->reserved_version->hasvalue = true; + parser->reserved_version->expression.flags |= AST_FLAG_INCLUDE_DEF; + parser->reserved_version->constval.vstring = util_strdup(GMQCC_FULL_VERSION_STRING); + } else { + parser->reserved_version = NULL; + } + + parser->fold = fold_init (parser); + parser->intrin = intrin_init(parser); + return parser; +} + +static bool parser_compile(parser_t *parser) +{ + /* initial lexer/parser state */ + parser->lex->flags.noops = true; + + if (parser_next(parser)) + { + while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR) + { + if (!parser_global_statement(parser)) { + if (parser->tok == TOKEN_EOF) + parseerror(parser, "unexpected end of file"); + else if (compile_errors) + parseerror(parser, "there have been errors, bailing out"); + lex_close(parser->lex); + parser->lex = NULL; + return false; + } + } + } else { + parseerror(parser, "parse error"); + lex_close(parser->lex); + parser->lex = NULL; + return false; + } + + lex_close(parser->lex); + parser->lex = NULL; + + return !compile_errors; +} + +bool parser_compile_file(parser_t *parser, const char *filename) +{ + parser->lex = lex_open(filename); + if (!parser->lex) { + con_err("failed to open file \"%s\"\n", filename); + return false; + } + return parser_compile(parser); +} + +bool parser_compile_string(parser_t *parser, const char *name, const char *str, size_t len) +{ + parser->lex = lex_open_string(str, len, name); + if (!parser->lex) { + con_err("failed to create lexer for string \"%s\"\n", name); + return false; + } + return parser_compile(parser); +} + +static void parser_remove_ast(parser_t *parser) +{ + size_t i; + if (parser->ast_cleaned) + return; + parser->ast_cleaned = true; + for (i = 0; i < vec_size(parser->accessors); ++i) { + ast_delete(parser->accessors[i]->constval.vfunc); + parser->accessors[i]->constval.vfunc = NULL; + ast_delete(parser->accessors[i]); + } + for (i = 0; i < vec_size(parser->functions); ++i) { + ast_delete(parser->functions[i]); + } + for (i = 0; i < vec_size(parser->fields); ++i) { + ast_delete(parser->fields[i]); + } + for (i = 0; i < vec_size(parser->globals); ++i) { + ast_delete(parser->globals[i]); + } + vec_free(parser->accessors); + vec_free(parser->functions); + vec_free(parser->globals); + vec_free(parser->fields); + + for (i = 0; i < vec_size(parser->variables); ++i) + util_htdel(parser->variables[i]); + vec_free(parser->variables); + vec_free(parser->_blocklocals); + vec_free(parser->_locals); + + for (i = 0; i < vec_size(parser->_typedefs); ++i) + ast_delete(parser->_typedefs[i]); + vec_free(parser->_typedefs); + for (i = 0; i < vec_size(parser->typedefs); ++i) + util_htdel(parser->typedefs[i]); + vec_free(parser->typedefs); + vec_free(parser->_blocktypedefs); + + vec_free(parser->_block_ctx); + + vec_free(parser->labels); + vec_free(parser->gotos); + vec_free(parser->breaks); + vec_free(parser->continues); + + ast_value_delete(parser->nil); + + ast_value_delete(parser->const_vec[0]); + ast_value_delete(parser->const_vec[1]); + ast_value_delete(parser->const_vec[2]); + + if (parser->reserved_version) + ast_value_delete(parser->reserved_version); + + util_htdel(parser->aliases); + fold_cleanup(parser->fold); + intrin_cleanup(parser->intrin); +} + +void parser_cleanup(parser_t *parser) +{ + parser_remove_ast(parser); + mem_d(parser); +} + +static bool parser_set_coverage_func(parser_t *parser, ir_builder *ir) { + size_t i; + ast_expression *expr; + ast_value *cov; + ast_function *func; + + if (!OPTS_OPTION_BOOL(OPTION_COVERAGE)) + return true; + + func = NULL; + for (i = 0; i != vec_size(parser->functions); ++i) { + if (!strcmp(parser->functions[i]->name, "coverage")) { + func = parser->functions[i]; + break; + } + } + if (!func) { + if (OPTS_OPTION_BOOL(OPTION_COVERAGE)) { + con_out("coverage support requested but no coverage() builtin declared\n"); + ir_builder_delete(ir); + return false; + } + return true; + } + + cov = func->vtype; + expr = (ast_expression*)cov; + + if (expr->vtype != TYPE_FUNCTION || vec_size(expr->params) != 0) { + char ty[1024]; + ast_type_to_string(expr, ty, sizeof(ty)); + con_out("invalid type for coverage(): %s\n", ty); + ir_builder_delete(ir); + return false; + } + + ir->coverage_func = func->ir_func->value; + return true; +} + +bool parser_finish(parser_t *parser, const char *output) +{ + size_t i; + ir_builder *ir; + bool retval = true; + + if (compile_errors) { + con_out("*** there were compile errors\n"); + return false; + } + + ir = ir_builder_new("gmqcc_out"); + if (!ir) { + con_out("failed to allocate builder\n"); + return false; + } + + for (i = 0; i < vec_size(parser->fields); ++i) { + ast_value *field; + bool hasvalue; + if (!ast_istype(parser->fields[i], ast_value)) + continue; + field = (ast_value*)parser->fields[i]; + hasvalue = field->hasvalue; + field->hasvalue = false; + if (!ast_global_codegen((ast_value*)field, ir, true)) { + con_out("failed to generate field %s\n", field->name); + ir_builder_delete(ir); + return false; + } + if (hasvalue) { + ir_value *ifld; + ast_expression *subtype; + field->hasvalue = true; + subtype = field->expression.next; + ifld = ir_builder_create_field(ir, field->name, subtype->vtype); + if (subtype->vtype == TYPE_FIELD) + ifld->fieldtype = subtype->next->vtype; + else if (subtype->vtype == TYPE_FUNCTION) + ifld->outtype = subtype->next->vtype; + (void)!ir_value_set_field(field->ir_v, ifld); + } + } + for (i = 0; i < vec_size(parser->globals); ++i) { + ast_value *asvalue; + if (!ast_istype(parser->globals[i], ast_value)) + continue; + asvalue = (ast_value*)(parser->globals[i]); + if (!asvalue->uses && !asvalue->hasvalue && asvalue->expression.vtype != TYPE_FUNCTION) { + retval = retval && !compile_warning(ast_ctx(asvalue), WARN_UNUSED_VARIABLE, + "unused global: `%s`", asvalue->name); + } + if (!ast_global_codegen(asvalue, ir, false)) { + con_out("failed to generate global %s\n", asvalue->name); + ir_builder_delete(ir); + return false; + } + } + /* Build function vararg accessor ast tree now before generating + * immediates, because the accessors may add new immediates + */ + for (i = 0; i < vec_size(parser->functions); ++i) { + ast_function *f = parser->functions[i]; + if (f->varargs) { + if (parser->max_param_count > vec_size(f->vtype->expression.params)) { + f->varargs->expression.count = parser->max_param_count - vec_size(f->vtype->expression.params); + if (!parser_create_array_setter_impl(parser, f->varargs)) { + con_out("failed to generate vararg setter for %s\n", f->name); + ir_builder_delete(ir); + return false; + } + if (!parser_create_array_getter_impl(parser, f->varargs)) { + con_out("failed to generate vararg getter for %s\n", f->name); + ir_builder_delete(ir); + return false; + } + } else { + ast_delete(f->varargs); + f->varargs = NULL; + } + } + } + /* Now we can generate immediates */ + if (!fold_generate(parser->fold, ir)) + return false; + + /* before generating any functions we need to set the coverage_func */ + if (!parser_set_coverage_func(parser, ir)) + return false; + + for (i = 0; i < vec_size(parser->globals); ++i) { + ast_value *asvalue; + if (!ast_istype(parser->globals[i], ast_value)) + continue; + asvalue = (ast_value*)(parser->globals[i]); + if (!(asvalue->expression.flags & AST_FLAG_INITIALIZED)) + { + if (asvalue->cvq == CV_CONST && !asvalue->hasvalue) + (void)!compile_warning(ast_ctx(asvalue), WARN_UNINITIALIZED_CONSTANT, + "uninitialized constant: `%s`", + asvalue->name); + else if ((asvalue->cvq == CV_NONE || asvalue->cvq == CV_CONST) && !asvalue->hasvalue) + (void)!compile_warning(ast_ctx(asvalue), WARN_UNINITIALIZED_GLOBAL, + "uninitialized global: `%s`", + asvalue->name); + } + if (!ast_generate_accessors(asvalue, ir)) { + ir_builder_delete(ir); + return false; + } + } + for (i = 0; i < vec_size(parser->fields); ++i) { + ast_value *asvalue; + asvalue = (ast_value*)(parser->fields[i]->next); + + if (!ast_istype((ast_expression*)asvalue, ast_value)) + continue; + if (asvalue->expression.vtype != TYPE_ARRAY) + continue; + if (!ast_generate_accessors(asvalue, ir)) { + ir_builder_delete(ir); + return false; + } + } + if (parser->reserved_version && + !ast_global_codegen(parser->reserved_version, ir, false)) + { + con_out("failed to generate reserved::version"); + ir_builder_delete(ir); + return false; + } + for (i = 0; i < vec_size(parser->functions); ++i) { + ast_function *f = parser->functions[i]; + if (!ast_function_codegen(f, ir)) { + con_out("failed to generate function %s\n", f->name); + ir_builder_delete(ir); + return false; + } + } + + generate_checksum(parser, ir); + + if (OPTS_OPTION_BOOL(OPTION_DUMP)) + ir_builder_dump(ir, con_out); + for (i = 0; i < vec_size(parser->functions); ++i) { + if (!ir_function_finalize(parser->functions[i]->ir_func)) { + con_out("failed to finalize function %s\n", parser->functions[i]->name); + ir_builder_delete(ir); + return false; + } + } + parser_remove_ast(parser); + + if (compile_Werrors) { + con_out("*** there were warnings treated as errors\n"); + compile_show_werrors(); + retval = false; + } + + if (retval) { + if (OPTS_OPTION_BOOL(OPTION_DUMPFIN)) + ir_builder_dump(ir, con_out); + + if (!ir_builder_generate(ir, output)) { + con_out("*** failed to generate output file\n"); + ir_builder_delete(ir); + return false; + } + } + ir_builder_delete(ir); + return retval; +} diff --git a/stat.c b/stat.c deleted file mode 100644 index 1e171e8..0000000 --- a/stat.c +++ /dev/null @@ -1,249 +0,0 @@ -#include -#include - -#include "gmqcc.h" - -/* - * strdup does it's own malloc, we need to track malloc. We don't want - * to overwrite malloc though, infact, we can't really hook it at all - * without library specific assumptions. So we re implement strdup. - */ -char *stat_mem_strdup(const char *src, bool empty) { - size_t len = 0; - char *ptr = NULL; - - if (!src) - return NULL; - - len = strlen(src); - if ((!empty ? len : true) && (ptr = (char*)mem_a(len + 1))) { - memcpy(ptr, src, len); - ptr[len] = '\0'; - } - - return ptr; -} - -/* - * The reallocate function for resizing vectors. - */ -void _util_vec_grow(void **a, size_t i, size_t s) { - vector_t *d = vec_meta(*a); - size_t m = 0; - void *p = NULL; - - if (*a) { - m = 2 * d->allocated + i; - p = mem_r(d, s * m + sizeof(vector_t)); - } else { - m = i + 1; - p = mem_a(s * m + sizeof(vector_t)); - ((vector_t*)p)->used = 0; - } - - d = (vector_t*)p; - d->allocated = m; - *a = d + 1; -} - -void _util_vec_delete(void *data) { - mem_d(vec_meta(data)); -} - -/* - * Hash table for generic data, based on dynamic memory allocations - * all around. This is the internal interface, please look for - * EXPOSED INTERFACE comment below - */ -typedef struct hash_node_t { - char *key; /* the key for this node in table */ - void *value; /* pointer to the data as void* */ - struct hash_node_t *next; /* next node (linked list) */ -} hash_node_t; - -size_t hash(const char *key); - -size_t util_hthash(hash_table_t *ht, const char *key) { - return hash(key) % ht->size; -} - -static hash_node_t *_util_htnewpair(const char *key, void *value) { - hash_node_t *node; - if (!(node = (hash_node_t*)mem_a(sizeof(hash_node_t)))) - return NULL; - - if (!(node->key = util_strdupe(key))) { - mem_d(node); - return NULL; - } - - node->value = value; - node->next = NULL; - - return node; -} - -/* - * EXPOSED INTERFACE for the hashtable implementation - * util_htnew(size) -- to make a new hashtable - * util_htset(table, key, value, sizeof(value)) -- to set something in the table - * util_htget(table, key) -- to get something from the table - * util_htdel(table) -- to delete the table - */ -hash_table_t *util_htnew(size_t size) { - hash_table_t *hashtable = NULL; - - if (size < 1) - return NULL; - - if (!(hashtable = (hash_table_t*)mem_a(sizeof(hash_table_t)))) - return NULL; - - if (!(hashtable->table = (hash_node_t**)mem_a(sizeof(hash_node_t*) * size))) { - mem_d(hashtable); - return NULL; - } - - hashtable->size = size; - memset(hashtable->table, 0, sizeof(hash_node_t*) * size); - - return hashtable; -} - -void util_htseth(hash_table_t *ht, const char *key, size_t bin, void *value) { - hash_node_t *newnode = NULL; - hash_node_t *next = NULL; - hash_node_t *last = NULL; - - next = ht->table[bin]; - - while (next && next->key && strcmp(key, next->key) > 0) - last = next, next = next->next; - - /* already in table, do a replace */ - if (next && next->key && strcmp(key, next->key) == 0) { - next->value = value; - } else { - /* not found, grow a pair man :P */ - newnode = _util_htnewpair(key, value); - if (next == ht->table[bin]) { - newnode->next = next; - ht->table[bin] = newnode; - } else if (!next) { - last->next = newnode; - } else { - newnode->next = next; - last->next = newnode; - } - } -} - -void util_htset(hash_table_t *ht, const char *key, void *value) { - util_htseth(ht, key, util_hthash(ht, key), value); -} - -void *util_htgeth(hash_table_t *ht, const char *key, size_t bin) { - hash_node_t *pair = ht->table[bin]; - - while (pair && pair->key && strcmp(key, pair->key) > 0) - pair = pair->next; - - if (!pair || !pair->key || strcmp(key, pair->key) != 0) - return NULL; - - return pair->value; -} - -void *util_htget(hash_table_t *ht, const char *key) { - return util_htgeth(ht, key, util_hthash(ht, key)); -} - -void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin); -void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin) { - hash_node_t *pair; - size_t len, keylen; - int cmp; - - keylen = strlen(key); - - pair = ht->table[bin]; - while (pair && pair->key) { - len = strlen(pair->key); - if (len < keylen) { - pair = pair->next; - continue; - } - if (keylen == len) { - cmp = strcmp(key, pair->key); - if (cmp == 0) - return pair->value; - if (cmp < 0) - return NULL; - pair = pair->next; - continue; - } - cmp = strcmp(key, pair->key + len - keylen); - if (cmp == 0) { - uintptr_t up = (uintptr_t)pair->value; - up += len - keylen; - return (void*)up; - } - pair = pair->next; - } - return NULL; -} - -/* - * Free all allocated data in a hashtable, this is quite the amount - * of work. - */ -void util_htrem(hash_table_t *ht, void (*callback)(void *data)) { - size_t i = 0; - - for (; i < ht->size; ++i) { - hash_node_t *n = ht->table[i]; - hash_node_t *p; - - /* free in list */ - while (n) { - if (n->key) - mem_d(n->key); - if (callback) - callback(n->value); - p = n; - n = p->next; - mem_d(p); - } - - } - /* free table */ - mem_d(ht->table); - mem_d(ht); -} - -void util_htrmh(hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*)) { - hash_node_t **pair = &ht->table[bin]; - hash_node_t *tmp; - - while (*pair && (*pair)->key && strcmp(key, (*pair)->key) > 0) - pair = &(*pair)->next; - - tmp = *pair; - if (!tmp || !tmp->key || strcmp(key, tmp->key) != 0) - return; - - if (cb) - (*cb)(tmp->value); - - *pair = tmp->next; - mem_d(tmp->key); - mem_d(tmp); -} - -void util_htrm(hash_table_t *ht, const char *key, void (*cb)(void*)) { - util_htrmh(ht, key, util_hthash(ht, key), cb); -} - -void util_htdel(hash_table_t *ht) { - util_htrem(ht, NULL); -} diff --git a/stat.cpp b/stat.cpp new file mode 100644 index 0000000..1e171e8 --- /dev/null +++ b/stat.cpp @@ -0,0 +1,249 @@ +#include +#include + +#include "gmqcc.h" + +/* + * strdup does it's own malloc, we need to track malloc. We don't want + * to overwrite malloc though, infact, we can't really hook it at all + * without library specific assumptions. So we re implement strdup. + */ +char *stat_mem_strdup(const char *src, bool empty) { + size_t len = 0; + char *ptr = NULL; + + if (!src) + return NULL; + + len = strlen(src); + if ((!empty ? len : true) && (ptr = (char*)mem_a(len + 1))) { + memcpy(ptr, src, len); + ptr[len] = '\0'; + } + + return ptr; +} + +/* + * The reallocate function for resizing vectors. + */ +void _util_vec_grow(void **a, size_t i, size_t s) { + vector_t *d = vec_meta(*a); + size_t m = 0; + void *p = NULL; + + if (*a) { + m = 2 * d->allocated + i; + p = mem_r(d, s * m + sizeof(vector_t)); + } else { + m = i + 1; + p = mem_a(s * m + sizeof(vector_t)); + ((vector_t*)p)->used = 0; + } + + d = (vector_t*)p; + d->allocated = m; + *a = d + 1; +} + +void _util_vec_delete(void *data) { + mem_d(vec_meta(data)); +} + +/* + * Hash table for generic data, based on dynamic memory allocations + * all around. This is the internal interface, please look for + * EXPOSED INTERFACE comment below + */ +typedef struct hash_node_t { + char *key; /* the key for this node in table */ + void *value; /* pointer to the data as void* */ + struct hash_node_t *next; /* next node (linked list) */ +} hash_node_t; + +size_t hash(const char *key); + +size_t util_hthash(hash_table_t *ht, const char *key) { + return hash(key) % ht->size; +} + +static hash_node_t *_util_htnewpair(const char *key, void *value) { + hash_node_t *node; + if (!(node = (hash_node_t*)mem_a(sizeof(hash_node_t)))) + return NULL; + + if (!(node->key = util_strdupe(key))) { + mem_d(node); + return NULL; + } + + node->value = value; + node->next = NULL; + + return node; +} + +/* + * EXPOSED INTERFACE for the hashtable implementation + * util_htnew(size) -- to make a new hashtable + * util_htset(table, key, value, sizeof(value)) -- to set something in the table + * util_htget(table, key) -- to get something from the table + * util_htdel(table) -- to delete the table + */ +hash_table_t *util_htnew(size_t size) { + hash_table_t *hashtable = NULL; + + if (size < 1) + return NULL; + + if (!(hashtable = (hash_table_t*)mem_a(sizeof(hash_table_t)))) + return NULL; + + if (!(hashtable->table = (hash_node_t**)mem_a(sizeof(hash_node_t*) * size))) { + mem_d(hashtable); + return NULL; + } + + hashtable->size = size; + memset(hashtable->table, 0, sizeof(hash_node_t*) * size); + + return hashtable; +} + +void util_htseth(hash_table_t *ht, const char *key, size_t bin, void *value) { + hash_node_t *newnode = NULL; + hash_node_t *next = NULL; + hash_node_t *last = NULL; + + next = ht->table[bin]; + + while (next && next->key && strcmp(key, next->key) > 0) + last = next, next = next->next; + + /* already in table, do a replace */ + if (next && next->key && strcmp(key, next->key) == 0) { + next->value = value; + } else { + /* not found, grow a pair man :P */ + newnode = _util_htnewpair(key, value); + if (next == ht->table[bin]) { + newnode->next = next; + ht->table[bin] = newnode; + } else if (!next) { + last->next = newnode; + } else { + newnode->next = next; + last->next = newnode; + } + } +} + +void util_htset(hash_table_t *ht, const char *key, void *value) { + util_htseth(ht, key, util_hthash(ht, key), value); +} + +void *util_htgeth(hash_table_t *ht, const char *key, size_t bin) { + hash_node_t *pair = ht->table[bin]; + + while (pair && pair->key && strcmp(key, pair->key) > 0) + pair = pair->next; + + if (!pair || !pair->key || strcmp(key, pair->key) != 0) + return NULL; + + return pair->value; +} + +void *util_htget(hash_table_t *ht, const char *key) { + return util_htgeth(ht, key, util_hthash(ht, key)); +} + +void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin); +void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin) { + hash_node_t *pair; + size_t len, keylen; + int cmp; + + keylen = strlen(key); + + pair = ht->table[bin]; + while (pair && pair->key) { + len = strlen(pair->key); + if (len < keylen) { + pair = pair->next; + continue; + } + if (keylen == len) { + cmp = strcmp(key, pair->key); + if (cmp == 0) + return pair->value; + if (cmp < 0) + return NULL; + pair = pair->next; + continue; + } + cmp = strcmp(key, pair->key + len - keylen); + if (cmp == 0) { + uintptr_t up = (uintptr_t)pair->value; + up += len - keylen; + return (void*)up; + } + pair = pair->next; + } + return NULL; +} + +/* + * Free all allocated data in a hashtable, this is quite the amount + * of work. + */ +void util_htrem(hash_table_t *ht, void (*callback)(void *data)) { + size_t i = 0; + + for (; i < ht->size; ++i) { + hash_node_t *n = ht->table[i]; + hash_node_t *p; + + /* free in list */ + while (n) { + if (n->key) + mem_d(n->key); + if (callback) + callback(n->value); + p = n; + n = p->next; + mem_d(p); + } + + } + /* free table */ + mem_d(ht->table); + mem_d(ht); +} + +void util_htrmh(hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*)) { + hash_node_t **pair = &ht->table[bin]; + hash_node_t *tmp; + + while (*pair && (*pair)->key && strcmp(key, (*pair)->key) > 0) + pair = &(*pair)->next; + + tmp = *pair; + if (!tmp || !tmp->key || strcmp(key, tmp->key) != 0) + return; + + if (cb) + (*cb)(tmp->value); + + *pair = tmp->next; + mem_d(tmp->key); + mem_d(tmp); +} + +void util_htrm(hash_table_t *ht, const char *key, void (*cb)(void*)) { + util_htrmh(ht, key, util_hthash(ht, key), cb); +} + +void util_htdel(hash_table_t *ht) { + util_htrem(ht, NULL); +} diff --git a/test.c b/test.c deleted file mode 100644 index 2c985f0..0000000 --- a/test.c +++ /dev/null @@ -1,1311 +0,0 @@ -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include "gmqcc.h" - -static const char *task_bins[] = { - "./gmqcc", - "./qcvm" -}; - -typedef struct { - FILE *handles[3]; - int pipes[3]; - int stderr_fd; - int stdout_fd; - int pid; -} popen_t; - -static FILE **task_popen(const char *command, const char *mode) { - int inhandle [2]; - int outhandle [2]; - int errhandle [2]; - int trypipe; - - popen_t *data = (popen_t*)mem_a(sizeof(popen_t)); - - /* - * Parse the command now into a list for execv, this is a pain - * in the ass. - */ - char *line = (char*)command; - char **argv = NULL; - { - - while (*line != '\0') { - while (*line == ' ' || *line == '\t' || *line == '\n') - *line++ = '\0'; - vec_push(argv, line); - - while (*line != '\0' && *line != ' ' && - *line != '\t' && *line != '\n') line++; - } - vec_push(argv, (char *)0); - } - - - if ((trypipe = pipe(inhandle)) < 0) goto task_popen_error_0; - if ((trypipe = pipe(outhandle)) < 0) goto task_popen_error_1; - if ((trypipe = pipe(errhandle)) < 0) goto task_popen_error_2; - - if ((data->pid = fork()) > 0) { - /* parent */ - close(inhandle [0]); - close(outhandle [1]); - close(errhandle [1]); - data->pipes[0] = inhandle [1]; - data->pipes[1] = outhandle[0]; - data->pipes[2] = errhandle[0]; - data->handles[0] = fdopen(inhandle [1], "w"); - data->handles[1] = fdopen(outhandle[0], mode); - data->handles[2] = fdopen(errhandle[0], mode); - - /* sigh */ - vec_free(argv); - return data->handles; - } else if (data->pid == 0) { - /* child */ - close(inhandle [1]); - close(outhandle[0]); - close(errhandle[0]); - - /* see piping documentation for this sillyness :P */ - dup2(inhandle [0], 0); - dup2(outhandle[1], 1); - dup2(errhandle[1], 2); - - execvp(*argv, argv); - exit(EXIT_FAILURE); - } else { - /* fork failed */ - goto task_popen_error_3; - } - -task_popen_error_3: close(errhandle[0]), close(errhandle[1]); -task_popen_error_2: close(outhandle[0]), close(outhandle[1]); -task_popen_error_1: close(inhandle [0]), close(inhandle [1]); -task_popen_error_0: - - vec_free(argv); - return NULL; -} - -static int task_pclose(FILE **handles) { - popen_t *data = (popen_t*)handles; - int status = 0; - - close(data->pipes[0]); /* stdin */ - close(data->pipes[1]); /* stdout */ - close(data->pipes[2]); /* stderr */ - - waitpid(data->pid, &status, 0); - - mem_d(data); - - return status; -} - -#define TASK_COMPILE 0 -#define TASK_EXECUTE 1 -/* - * Task template system: - * templates are rules for a specific test, used to create a "task" that - * is executed with those set of rules (arguments, and what not). Tests - * that don't have a template with them cannot become tasks, since without - * the information for that test there is no way to properly "test" them. - * Rules for these templates are described in a template file, using a - * task template language. - * - * The language is a basic finite statemachine, top-down single-line - * description language. - * - * The languge is composed entierly of "tags" which describe a string of - * text for a task. Think of it much like a configuration file. Except - * it's been designed to allow flexibility and future support for prodecual - * semantics. - * - * The following "tags" are suported by the language - * - * D: - * Used to set a description of the current test, this must be - * provided, this tag is NOT optional. - * - * T: - * Used to set the procedure for the given task, there are two - * options for this: - * -compile - * This simply performs compilation only - * -execute - * This will perform compilation and execution - * -fail - * This will perform compilation, but requires - * the compilation to fail in order to succeed. - * - * This must be provided, this tag is NOT optional. - * - * C: - * Used to set the compilation flags for the given task, this - * must be provided, this tag is NOT optional. - * - * F: Used to set some test suite flags, currently the only option - * is -no-defs (to including of defs.qh) - * - * E: - * Used to set the execution flags for the given task. This tag - * must be provided if T == -execute, otherwise it's erroneous - * as compilation only takes place. - * - * M: - * Used to describe a string of text that should be matched from - * the output of executing the task. If this doesn't match the - * task fails. This tag must be provided if T == -execute, otherwise - * it's erroneous as compilation only takes place. - * - * I: - * Used to specify the INPUT source file to operate on, this must be - * provided, this tag is NOT optional - * - * - * Notes: - * These tags have one-time use, using them more than once will result - * in template compilation errors. - * - * Lines beginning with # or // in the template file are comments and - * are ignored by the template parser. - * - * Whitespace is optional, with exception to the colon ':' between the - * tag and it's assignment value/ - * - * The template compiler will detect erronrous tags (optional tags - * that need not be set), as well as missing tags, and error accordingly - * this will result in the task failing. - */ -typedef struct { - char *description; - char *compileflags; - char *executeflags; - char *proceduretype; - char *sourcefile; - char *tempfilename; - char **comparematch; - char *rulesfile; - char *testflags; -} task_template_t; - -/* - * This is very much like a compiler code generator :-). This generates - * a value from some data observed from the compiler. - */ -static bool task_template_generate(task_template_t *tmpl, char tag, const char *file, size_t line, char *value, size_t *pad) { - size_t desclen = 0; - size_t filelen = 0; - char **destval = NULL; - - if (!tmpl) - return false; - - switch(tag) { - case 'D': destval = &tmpl->description; break; - case 'T': destval = &tmpl->proceduretype; break; - case 'C': destval = &tmpl->compileflags; break; - case 'E': destval = &tmpl->executeflags; break; - case 'I': destval = &tmpl->sourcefile; break; - case 'F': destval = &tmpl->testflags; break; - default: - con_printmsg(LVL_ERROR, __FILE__, __LINE__, 0, "internal error", - "invalid tag `%c:` during code generation\n", - tag - ); - return false; - } - - /* - * Ensure if for the given tag, there already exists a - * assigned value. - */ - if (*destval) { - con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "compile error", - "tag `%c:` already assigned value: %s\n", - tag, *destval - ); - return false; - } - - /* - * Strip any whitespace that might exist in the value for assignments - * like "D: foo" - */ - if (value && *value && (*value == ' ' || *value == '\t')) - value++; - else if (!value) - exit(EXIT_FAILURE); - - /* - * Value will contain a newline character at the end, we need to strip - * this otherwise kaboom, seriously, kaboom :P - */ - if (strchr(value, '\n')) - *strrchr(value, '\n')='\0'; - - /* - * Now allocate and set the actual value for the specific tag. Which - * was properly selected and can be accessed with *destval. - */ - *destval = util_strdup(value); - - - if (*destval == tmpl->description) { - /* - * Create some padding for the description to align the - * printing of the rules file. - */ - if ((desclen = strlen(tmpl->description)) > pad[0]) - pad[0] = desclen; - } - - if ((filelen = strlen(file)) > pad[2]) - pad[2] = filelen; - - return true; -} - -static bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size_t *pad) { - char *data = NULL; - char *back = NULL; - size_t size = 0; - size_t line = 1; - - if (!tmpl) - return false; - - /* top down parsing */ - while (util_getline(&back, &size, fp) != EOF) { - /* skip whitespace */ - data = back; - if (*data && (*data == ' ' || *data == '\t')) - data++; - - switch (*data) { - /* - * Handle comments inside task tmpl files. We're strict - * about the language for fun :-) - */ - case '/': - if (data[1] != '/') { - con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error", - "invalid character `/`, perhaps you meant `//` ?"); - - mem_d(back); - return false; - } - case '#': - break; - - /* - * Empty newlines are acceptable as well, so we handle that here - * despite being just odd since there should't be that many - * empty lines to begin with. - */ - case '\r': - case '\n': - break; - - - /* - * Now begin the actual "tag" stuff. This works as you expect - * it to. - */ - case 'D': - case 'T': - case 'C': - case 'E': - case 'I': - case 'F': - if (data[1] != ':') { - con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error", - "expected `:` after `%c`", - *data - ); - goto failure; - } - if (!task_template_generate(tmpl, *data, file, line, &data[3], pad)) { - con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl compile error", - "failed to generate for given task\n" - ); - goto failure; - } - break; - - /* - * Match requires it's own system since we allow multiple M's - * for multi-line matching. - */ - case 'M': - { - char *value = &data[3]; - if (data[1] != ':') { - con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error", - "expected `:` after `%c`", - *data - ); - goto failure; - } - - /* - * Value will contain a newline character at the end, we need to strip - * this otherwise kaboom, seriously, kaboom :P - */ - if (strrchr(value, '\n')) - *strrchr(value, '\n')='\0'; - else /* cppcheck: possible null pointer dereference */ - exit(EXIT_FAILURE); - - vec_push(tmpl->comparematch, util_strdup(value)); - - break; - } - - default: - con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error", - "invalid tag `%c`", *data - ); - goto failure; - /* no break required */ - } - - /* update line and free old sata */ - line++; - mem_d(back); - back = NULL; - } - if (back) - mem_d(back); - return true; - -failure: - mem_d (back); - return false; -} - -/* - * Nullifies the template data: used during initialization of a new - * template and free. - */ -static void task_template_nullify(task_template_t *tmpl) { - if (!tmpl) - return; - - tmpl->description = NULL; - tmpl->proceduretype = NULL; - tmpl->compileflags = NULL; - tmpl->executeflags = NULL; - tmpl->comparematch = NULL; - tmpl->sourcefile = NULL; - tmpl->tempfilename = NULL; - tmpl->rulesfile = NULL; - tmpl->testflags = NULL; -} - -static task_template_t *task_template_compile(const char *file, const char *dir, size_t *pad) { - /* a page should be enough */ - char fullfile[4096]; - size_t filepadd = 0; - FILE *tempfile = NULL; - task_template_t *tmpl = NULL; - - util_snprintf(fullfile, sizeof(fullfile), "%s/%s", dir, file); - - tempfile = fopen(fullfile, "r"); - tmpl = (task_template_t*)mem_a(sizeof(task_template_t)); - task_template_nullify(tmpl); - - /* - * Create some padding for the printing to align the - * printing of the rules file to the console. - */ - if ((filepadd = strlen(fullfile)) > pad[1]) - pad[1] = filepadd; - - tmpl->rulesfile = util_strdup(fullfile); - - /* - * Esnure the file even exists for the task, this is pretty useless - * to even do. - */ - if (!tempfile) { - con_err("template file: %s does not exist or invalid permissions\n", - file - ); - goto failure; - } - - if (!task_template_parse(file, tmpl, tempfile, pad)) { - con_err("template parse error: error during parsing\n"); - goto failure; - } - - /* - * Regardless procedure type, the following tags must exist: - * D - * T - * C - * I - */ - if (!tmpl->description) { - con_err("template compile error: %s missing `D:` tag\n", file); - goto failure; - } - if (!tmpl->proceduretype) { - con_err("template compile error: %s missing `T:` tag\n", file); - goto failure; - } - if (!tmpl->compileflags) { - con_err("template compile error: %s missing `C:` tag\n", file); - goto failure; - } - if (!tmpl->sourcefile) { - con_err("template compile error: %s missing `I:` tag\n", file); - goto failure; - } - - /* - * Now lets compile the template, compilation is really just - * the process of validating the input. - */ - if (!strcmp(tmpl->proceduretype, "-compile")) { - if (tmpl->executeflags) - con_err("template compile warning: %s erroneous tag `E:` when only compiling\n", file); - if (tmpl->comparematch) - con_err("template compile warning: %s erroneous tag `M:` when only compiling\n", file); - goto success; - } else if (!strcmp(tmpl->proceduretype, "-execute")) { - if (!tmpl->executeflags) { - /* default to $null */ - tmpl->executeflags = util_strdup("$null"); - } - if (!tmpl->comparematch) { - con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file); - goto failure; - } - } else if (!strcmp(tmpl->proceduretype, "-fail")) { - if (tmpl->executeflags) - con_err("template compile warning: %s erroneous tag `E:` when only failing\n", file); - if (tmpl->comparematch) - con_err("template compile warning: %s erroneous tag `M:` when only failing\n", file); - } else if (!strcmp(tmpl->proceduretype, "-diagnostic")) { - if (tmpl->executeflags) - con_err("template compile warning: %s erroneous tag `E:` when only diagnostic\n", file); - if (!tmpl->comparematch) { - con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file); - goto failure; - } - } else if (!strcmp(tmpl->proceduretype, "-pp")) { - if (tmpl->executeflags) - con_err("template compile warning: %s erroneous tag `E:` when only preprocessing\n", file); - if (!tmpl->comparematch) { - con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file); - goto failure; - } - } else { - con_err("template compile error: %s invalid procedure type: %s\n", file, tmpl->proceduretype); - goto failure; - } - -success: - fclose(tempfile); - return tmpl; - -failure: - /* - * The file might not exist and we jump here when that doesn't happen - * so the check to see if it's not null here is required. - */ - if (tempfile) - fclose(tempfile); - mem_d (tmpl); - - return NULL; -} - -static void task_template_destroy(task_template_t *tmpl) { - if (!tmpl) - return; - - if (tmpl->description) mem_d(tmpl->description); - if (tmpl->proceduretype) mem_d(tmpl->proceduretype); - if (tmpl->compileflags) mem_d(tmpl->compileflags); - if (tmpl->executeflags) mem_d(tmpl->executeflags); - if (tmpl->sourcefile) mem_d(tmpl->sourcefile); - if (tmpl->rulesfile) mem_d(tmpl->rulesfile); - if (tmpl->testflags) mem_d(tmpl->testflags); - - /* - * Delete all allocated string for task tmpl then destroy the - * main vector. - */ - { - size_t i = 0; - for (; i < vec_size(tmpl->comparematch); i++) - mem_d(tmpl->comparematch[i]); - - vec_free(tmpl->comparematch); - } - - /* - * Nullify all the template members otherwise NULL comparision - * checks will fail if tmpl pointer is reused. - */ - mem_d(tmpl->tempfilename); - mem_d(tmpl); -} - -/* - * Now comes the task manager, this system allows adding tasks in and out - * of a task list. This is the executor of the tasks essentially as well. - */ -typedef struct { - task_template_t *tmpl; - FILE **runhandles; - FILE *stderrlog; - FILE *stdoutlog; - char *stdoutlogfile; - char *stderrlogfile; - bool compiled; -} task_t; - -static task_t *task_tasks = NULL; - -/* - * Read a directory and searches for all template files in it - * which is later used to run all tests. - */ -static bool task_propagate(const char *curdir, size_t *pad, const char *defs) { - bool success = true; - DIR *dir; - struct dirent *files; - struct stat directory; - char buffer[4096]; - size_t found = 0; - char **directories = NULL; - char *claim = util_strdup(curdir); - size_t i; - - vec_push(directories, claim); - dir = opendir(claim); - - /* - * Generate a list of subdirectories since we'll be checking them too - * for tmpl files. - */ - while ((files = readdir(dir))) { - util_asprintf(&claim, "%s/%s", curdir, files->d_name); - if (stat(claim, &directory) == -1) { - closedir(dir); - mem_d(claim); - return false; - } - - if (S_ISDIR(directory.st_mode) && files->d_name[0] != '.') { - vec_push(directories, claim); - } else { - mem_d(claim); - claim = NULL; - } - } - closedir(dir); - - /* - * Now do all the work, by touching all the directories inside - * test as well and compile the task templates into data we can - * use to run the tests. - */ - for (i = 0; i < vec_size(directories); i++) { - dir = opendir(directories[i]); - - while ((files = readdir(dir))) { - util_snprintf(buffer, sizeof(buffer), "%s/%s", directories[i], files->d_name); - if (stat(buffer, &directory) == -1) { - con_err("internal error: stat failed, aborting\n"); - abort(); - } - - if (S_ISDIR(directory.st_mode)) - continue; - - /* - * We made it here, which concludes the file/directory is not - * actually a directory, so it must be a file :) - */ - if (strcmp(files->d_name + strlen(files->d_name) - 5, ".tmpl") == 0) { - task_template_t *tmpl = task_template_compile(files->d_name, directories[i], pad); - char buf[4096]; /* one page should be enough */ - const char *qcflags = NULL; - task_t task; - - found ++; - if (!tmpl) { - con_err("error compiling task template: %s\n", files->d_name); - success = false; - continue; - } - /* - * Generate a temportary file name for the output binary - * so we don't trample over an existing one. - */ - tmpl->tempfilename = NULL; - util_asprintf(&tmpl->tempfilename, "%s/TMPDAT.%s.dat", directories[i], files->d_name); - - /* - * Additional QCFLAGS enviroment variable may be used - * to test compile flags for all tests. This needs to be - * BEFORE other flags (so that the .tmpl can override them) - */ - qcflags = getenv("QCFLAGS"); - - /* - * Generate the command required to open a pipe to a process - * which will be refered to with a handle in the task for - * reading the data from the pipe. - */ - if (strcmp(tmpl->proceduretype, "-pp")) { - if (qcflags) { - if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) { - util_snprintf(buf, sizeof(buf), "%s %s/%s %s %s -o %s", - task_bins[TASK_COMPILE], - directories[i], - tmpl->sourcefile, - qcflags, - tmpl->compileflags, - tmpl->tempfilename - ); - } else { - util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s %s -o %s", - task_bins[TASK_COMPILE], - curdir, - defs, - directories[i], - tmpl->sourcefile, - qcflags, - tmpl->compileflags, - tmpl->tempfilename - ); - } - } else { - if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) { - util_snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s", - task_bins[TASK_COMPILE], - directories[i], - tmpl->sourcefile, - tmpl->compileflags, - tmpl->tempfilename - ); - } else { - util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s -o %s", - task_bins[TASK_COMPILE], - curdir, - defs, - directories[i], - tmpl->sourcefile, - tmpl->compileflags, - tmpl->tempfilename - ); - } - } - } 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 %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 %s -o %s", - task_bins[TASK_COMPILE], - curdir, - defs, - directories[i], - tmpl->sourcefile, - tmpl->compileflags, - tmpl->tempfilename - ); - } - } - - /* - * The task template was compiled, now lets create a task from - * the template data which has now been propagated. - */ - task.tmpl = tmpl; - if (!(task.runhandles = task_popen(buf, "r"))) { - con_err("error opening pipe to process for test: %s\n", tmpl->description); - success = false; - continue; - } - - /* - * Open up some file desciptors for logging the stdout/stderr - * to our own. - */ - util_snprintf(buf, sizeof(buf), "%s.stdout", tmpl->tempfilename); - task.stdoutlogfile = util_strdup(buf); - if (!(task.stdoutlog = fopen(buf, "w"))) { - con_err("error opening %s for stdout\n", buf); - continue; - } - - util_snprintf(buf, sizeof(buf), "%s.stderr", tmpl->tempfilename); - task.stderrlogfile = util_strdup(buf); - if (!(task.stderrlog = fopen(buf, "w"))) { - con_err("error opening %s for stderr\n", buf); - continue; - } - vec_push(task_tasks, task); - } - } - closedir(dir); - mem_d(directories[i]); /* free claimed memory */ - } - vec_free(directories); - - return success; -} - -/* - * Task precleanup removes any existing temporary files or log files - * left behind from a previous invoke of the test-suite. - */ -static void task_precleanup(const char *curdir) { - DIR *dir; - struct dirent *files; - char buffer[4096]; - - dir = opendir(curdir); - - while ((files = readdir(dir))) { - if (strstr(files->d_name, "TMP") || - strstr(files->d_name, ".stdout") || - strstr(files->d_name, ".stderr") || - strstr(files->d_name, ".dat")) - { - util_snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name); - if (remove(buffer)) - con_err("error removing temporary file: %s\n", buffer); - } - } - - closedir(dir); -} - -static void task_destroy(void) { - /* - * Free all the data in the task list and finally the list itself - * then proceed to cleanup anything else outside the program like - * temporary files. - */ - size_t i; - for (i = 0; i < vec_size(task_tasks); i++) { - /* - * Close any open handles to files or processes here. It's mighty - * annoying to have to do all this cleanup work. - */ - if (task_tasks[i].stdoutlog) fclose(task_tasks[i].stdoutlog); - if (task_tasks[i].stderrlog) fclose(task_tasks[i].stderrlog); - - /* - * Only remove the log files if the test actually compiled otherwise - * forget about it (or if it didn't compile, and the procedure type - * was set to -fail (meaning it shouldn't compile) .. stil remove) - */ - if (task_tasks[i].compiled || !strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) { - if (remove(task_tasks[i].stdoutlogfile)) - con_err("error removing stdout log file: %s\n", task_tasks[i].stdoutlogfile); - if (remove(task_tasks[i].stderrlogfile)) - con_err("error removing stderr log file: %s\n", task_tasks[i].stderrlogfile); - - (void)!remove(task_tasks[i].tmpl->tempfilename); - } - - /* free util_strdup data for log files */ - mem_d(task_tasks[i].stdoutlogfile); - mem_d(task_tasks[i].stderrlogfile); - - task_template_destroy(task_tasks[i].tmpl); - } - vec_free(task_tasks); -} - -/* - * This executes the QCVM task for a specificly compiled progs.dat - * using the template passed into it for call-flags and user defined - * messages IF the procedure type is -execute, otherwise it matches - * the preprocessor output. - */ -static bool task_trymatch(size_t i, char ***line) { - bool success = true; - bool process = true; - int retval = EXIT_SUCCESS; - FILE *execute; - char buffer[4096]; - task_template_t *tmpl = task_tasks[i].tmpl; - - memset (buffer,0,sizeof(buffer)); - - if (!strcmp(tmpl->proceduretype, "-execute")) { - /* - * Drop the execution flags for the QCVM if none where - * actually specified. - */ - if (!strcmp(tmpl->executeflags, "$null")) { - util_snprintf(buffer, sizeof(buffer), "%s %s", - task_bins[TASK_EXECUTE], - tmpl->tempfilename - ); - } else { - util_snprintf(buffer, sizeof(buffer), "%s %s %s", - task_bins[TASK_EXECUTE], - tmpl->executeflags, - tmpl->tempfilename - ); - } - - execute = popen(buffer, "r"); - if (!execute) - return false; - } else if (!strcmp(tmpl->proceduretype, "-pp")) { - /* - * we're preprocessing, which means we need to read int - * the produced file and do some really weird shit. - */ - if (!(execute = fopen(tmpl->tempfilename, "r"))) - return false; - process = false; - } else { - /* - * we're testing diagnostic output, which means it will be - * in runhandles[2] (stderr) since that is where the compiler - * puts it's errors. - */ - if (!(execute = fopen(task_tasks[i].stderrlogfile, "r"))) - return false; - process = false; - } - - /* - * Now lets read the lines and compare them to the matches we expect - * and handle accordingly. - */ - { - char *data = NULL; - size_t size = 0; - size_t compare = 0; - - while (util_getline(&data, &size, execute) != EOF) { - if (!strcmp(data, "No main function found\n")) { - con_err("test failure: `%s` (No main function found) [%s]\n", - tmpl->description, - tmpl->rulesfile - ); - if (!process) - fclose(execute); - else - pclose((FILE*)execute); - return false; - } - - /* - * Trim newlines from data since they will just break our - * ability to properly validate matches. - */ - if (strrchr(data, '\n')) - *strrchr(data, '\n') = '\0'; - - /* - * We remove the file/directory and stuff from the error - * match messages when testing diagnostics. - */ - if(!strcmp(tmpl->proceduretype, "-diagnostic")) { - if (strstr(data, "there have been errors, bailing out")) - continue; /* ignore it */ - if (strstr(data, ": error: ")) { - char *claim = util_strdup(data + (strstr(data, ": error: ") - data) + 9); - mem_d(data); - data = claim; - } - } - - /* - * We need to ignore null lines for when -pp is used (preprocessor), since - * the preprocessor is likely to create empty newlines in certain macro - * instantations, otherwise it's in the wrong nature to ignore empty newlines. - */ - if (!strcmp(tmpl->proceduretype, "-pp") && !*data) - continue; - - if (vec_size(tmpl->comparematch) > compare) { - if (strcmp(data, tmpl->comparematch[compare++])) { - success = false; - } - } else { - success = false; - } - - /* - * Copy to output vector for diagnostics if execution match - * fails. - */ - vec_push(*line, data); - - /* reset */ - data = NULL; - size = 0; - } - - if (compare != vec_size(tmpl->comparematch)) - success = false; - - mem_d(data); - data = NULL; - } - - if (process) - retval = pclose((FILE*)execute); - else - fclose(execute); - - return success && retval == EXIT_SUCCESS; -} - -static const char *task_type(task_template_t *tmpl) { - if (!strcmp(tmpl->proceduretype, "-pp")) - return "type: preprocessor"; - if (!strcmp(tmpl->proceduretype, "-execute")) - return "type: execution"; - if (!strcmp(tmpl->proceduretype, "-compile")) - return "type: compile"; - if (!strcmp(tmpl->proceduretype, "-diagnostic")) - return "type: diagnostic"; - return "type: fail"; -} - -/* - * This schedualizes all tasks and actually runs them individually - * this is generally easy for just -compile variants. For compile and - * execution this takes more work since a task needs to be generated - * from thin air and executed INLINE. - */ -#include -static size_t task_schedualize(size_t *pad) { - char space[2][64]; - bool execute = false; - char *data = NULL; - char **match = NULL; - size_t size = 0; - 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)); - - for (; i < vec_size(task_tasks); i++) { - memset(space[1], 0, sizeof(space[1])); - util_snprintf(space[1], sizeof(space[1]), "%d", (int)(i + 1)); - - con_out("test #%u %*s", i + 1, strlen(space[0]) - strlen(space[1]), ""); - - /* - * Generate a task from thin air if it requires execution in - * the QCVM. - */ - - /* diagnostic is not executed, but compare tested instead, like preproessor */ - execute = !! (!strcmp(task_tasks[i].tmpl->proceduretype, "-execute")) || - (!strcmp(task_tasks[i].tmpl->proceduretype, "-pp")) || - (!strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")); - - /* - * We assume it compiled before we actually compiled :). On error - * we change the value - */ - task_tasks[i].compiled = true; - - /* - * Read data from stdout first and pipe that stuff into a log file - * then we do the same for stderr. - */ - while (util_getline(&data, &size, task_tasks[i].runhandles[1]) != EOF) { - fputs(data, task_tasks[i].stdoutlog); - - if (strstr(data, "failed to open file")) { - task_tasks[i].compiled = false; - execute = false; - } - } - while (util_getline(&data, &size, task_tasks[i].runhandles[2]) != EOF) { - /* - * If a string contains an error we just dissalow execution - * of it in the vm. - * - * TODO: make this more percise, e.g if we print a warning - * that refers to a variable named error, or something like - * that .. then this will blowup :P - */ - if (strstr(data, "error") && strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")) { - execute = false; - task_tasks[i].compiled = false; - } - - fputs(data, task_tasks[i].stderrlog); - fflush(task_tasks[i].stderrlog); /* fast flush for read */ - } - - if (!task_tasks[i].compiled && strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) { - 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]), - task_tasks[i].tmpl->rulesfile, - (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(failed to compile)") - pad[2]), - "(failed to compile)" - ); - failed++; - continue; - } - - 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]), - task_tasks[i].tmpl->rulesfile, - (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(compiler didn't return exit success)") - pad[2]), - "(compiler didn't return exit success)" - ); - failed++; - continue; - } - - if (!execute) { - con_out("succeeded: `%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]), - task_tasks[i].tmpl->rulesfile, - (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl)) - pad[2]), - task_type(task_tasks[i].tmpl) - - ); - continue; - } - - /* - * If we made it here that concludes the task is to be executed - * in the virtual machine (or the preprocessor output needs to - * be matched). - */ - if (!task_trymatch(i, &match)) { - size_t d = 0; - - 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]), - task_tasks[i].tmpl->rulesfile, - (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen( - (strcmp(task_tasks[i].tmpl->proceduretype, "-pp")) - ? "(invalid results from execution)" - : (strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")) - ? "(invalid results from preprocessing)" - : "(invalid results from compiler diagnsotics)" - ) - pad[2]), - (strcmp(task_tasks[i].tmpl->proceduretype, "-pp")) - ? "(invalid results from execution)" - : (strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")) - ? "(invalid results from preprocessing)" - : "(invalid results from compiler diagnsotics)" - ); - - /* - * Print nicely formatted expected match lists to console error - * handler for the all the given matches in the template file and - * what was actually returned from executing. - */ - con_out(" Expected From %u Matches: (got %u Matches)\n", - vec_size(task_tasks[i].tmpl->comparematch), - vec_size(match) - ); - for (; d < vec_size(task_tasks[i].tmpl->comparematch); d++) { - char *select = task_tasks[i].tmpl->comparematch[d]; - size_t length = 60 - strlen(select); - - con_out(" Expected: \"%s\"", select); - while (length --) - con_out(" "); - con_out("| Got: \"%s\"\n", (d >= vec_size(match)) ? "<>" : match[d]); - } - - /* - * Print the non-expected out (since we are simply not expecting it) - * This will help track down bugs in template files that fail to match - * something. - */ - if (vec_size(match) > vec_size(task_tasks[i].tmpl->comparematch)) { - for (d = 0; d < vec_size(match) - vec_size(task_tasks[i].tmpl->comparematch); d++) { - con_out(" Expected: Nothing | Got: \"%s\"\n", - match[d + vec_size(task_tasks[i].tmpl->comparematch)] - ); - } - } - - - for (j = 0; j < vec_size(match); j++) - mem_d(match[j]); - vec_free(match); - failed++; - continue; - } - - for (j = 0; j < vec_size(match); j++) - mem_d(match[j]); - vec_free(match); - - con_out("succeeded: `%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]), - task_tasks[i].tmpl->rulesfile, - (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl))- pad[2]), - task_type(task_tasks[i].tmpl) - - ); - } - mem_d(data); - return failed; -} - -/* - * This is the heart of the whole test-suite process. This cleans up - * any existing temporary files left behind as well as log files left - * behind. Then it propagates a list of tests from `curdir` by scaning - * it for template files and compiling them into tasks, in which it - * schedualizes them (executes them) and actually reports errors and - * what not. It then proceeds to destroy the tasks and return memory - * it's the engine :) - * - * It returns true of tests could be propagated, otherwise it returns - * false. - * - * It expects con_init() was called before hand. - */ -static GMQCC_WARN bool test_perform(const char *curdir, const char *defs) { - size_t failed = false; - static const char *default_defs = "defs.qh"; - - size_t pad[] = { - /* test ### [succeed/fail]: `description` [tests/template.tmpl] [type] */ - 0, 0, 0 - }; - - /* - * If the default definition file isn't set to anything. We will - * use the default_defs here, which is "defs.qc" - */ - if (!defs) { - defs = default_defs; - } - - - task_precleanup(curdir); - if (!task_propagate(curdir, pad, defs)) { - con_err("error: failed to propagate tasks\n"); - task_destroy(); - return false; - } - /* - * If we made it here all tasks where propagated from their resultant - * template file. So we can start the FILO scheduler, this has been - * designed in the most thread-safe way possible for future threading - * it's designed to prevent lock contention, and possible syncronization - * issues. - */ - failed = task_schedualize(pad); - if (failed) - con_out("%u out of %u tests failed\n", failed, vec_size(task_tasks)); - task_destroy(); - - return (failed) ? false : true; -} - -/* - * Fancy GCC-like LONG parsing allows things like --opt=param with - * assignment operator. This is used for redirecting stdout/stderr - * console to specific files of your choice. - */ -static bool parsecmd(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) { - int argc = *argc_; - char **argv = *argv_; - - size_t len = strlen(optname); - - if (strncmp(argv[0]+ds, optname, len)) - return false; - - /* it's --optname, check how the parameter is supplied */ - if (argv[0][ds+len] == '=') { - *out = argv[0]+ds+len+1; - return true; - } - - if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */ - return false; - - /* using --opt param */ - *out = argv[1]; - --*argc_; - ++*argv_; - return true; -} - -int main(int argc, char **argv) { - bool succeed = false; - char *defs = NULL; - - con_init(); - - /* - * Command line option parsing commences now We only need to support - * a few things in the test suite. - */ - while (argc > 1) { - ++argv; - --argc; - - if (argv[0][0] == '-') { - if (parsecmd("defs", &argc, &argv, &defs, 1, false)) - continue; - - if (!strcmp(argv[0]+1, "debug")) { - OPTS_OPTION_BOOL(OPTION_DEBUG) = true; - continue; - } - if (!strcmp(argv[0]+1, "nocolor")) { - con_color(0); - continue; - } - - con_err("invalid argument %s\n", argv[0]+1); - return -1; - } - } - succeed = test_perform("tests", defs); - - return (succeed) ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..2c985f0 --- /dev/null +++ b/test.cpp @@ -0,0 +1,1311 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "gmqcc.h" + +static const char *task_bins[] = { + "./gmqcc", + "./qcvm" +}; + +typedef struct { + FILE *handles[3]; + int pipes[3]; + int stderr_fd; + int stdout_fd; + int pid; +} popen_t; + +static FILE **task_popen(const char *command, const char *mode) { + int inhandle [2]; + int outhandle [2]; + int errhandle [2]; + int trypipe; + + popen_t *data = (popen_t*)mem_a(sizeof(popen_t)); + + /* + * Parse the command now into a list for execv, this is a pain + * in the ass. + */ + char *line = (char*)command; + char **argv = NULL; + { + + while (*line != '\0') { + while (*line == ' ' || *line == '\t' || *line == '\n') + *line++ = '\0'; + vec_push(argv, line); + + while (*line != '\0' && *line != ' ' && + *line != '\t' && *line != '\n') line++; + } + vec_push(argv, (char *)0); + } + + + if ((trypipe = pipe(inhandle)) < 0) goto task_popen_error_0; + if ((trypipe = pipe(outhandle)) < 0) goto task_popen_error_1; + if ((trypipe = pipe(errhandle)) < 0) goto task_popen_error_2; + + if ((data->pid = fork()) > 0) { + /* parent */ + close(inhandle [0]); + close(outhandle [1]); + close(errhandle [1]); + data->pipes[0] = inhandle [1]; + data->pipes[1] = outhandle[0]; + data->pipes[2] = errhandle[0]; + data->handles[0] = fdopen(inhandle [1], "w"); + data->handles[1] = fdopen(outhandle[0], mode); + data->handles[2] = fdopen(errhandle[0], mode); + + /* sigh */ + vec_free(argv); + return data->handles; + } else if (data->pid == 0) { + /* child */ + close(inhandle [1]); + close(outhandle[0]); + close(errhandle[0]); + + /* see piping documentation for this sillyness :P */ + dup2(inhandle [0], 0); + dup2(outhandle[1], 1); + dup2(errhandle[1], 2); + + execvp(*argv, argv); + exit(EXIT_FAILURE); + } else { + /* fork failed */ + goto task_popen_error_3; + } + +task_popen_error_3: close(errhandle[0]), close(errhandle[1]); +task_popen_error_2: close(outhandle[0]), close(outhandle[1]); +task_popen_error_1: close(inhandle [0]), close(inhandle [1]); +task_popen_error_0: + + vec_free(argv); + return NULL; +} + +static int task_pclose(FILE **handles) { + popen_t *data = (popen_t*)handles; + int status = 0; + + close(data->pipes[0]); /* stdin */ + close(data->pipes[1]); /* stdout */ + close(data->pipes[2]); /* stderr */ + + waitpid(data->pid, &status, 0); + + mem_d(data); + + return status; +} + +#define TASK_COMPILE 0 +#define TASK_EXECUTE 1 +/* + * Task template system: + * templates are rules for a specific test, used to create a "task" that + * is executed with those set of rules (arguments, and what not). Tests + * that don't have a template with them cannot become tasks, since without + * the information for that test there is no way to properly "test" them. + * Rules for these templates are described in a template file, using a + * task template language. + * + * The language is a basic finite statemachine, top-down single-line + * description language. + * + * The languge is composed entierly of "tags" which describe a string of + * text for a task. Think of it much like a configuration file. Except + * it's been designed to allow flexibility and future support for prodecual + * semantics. + * + * The following "tags" are suported by the language + * + * D: + * Used to set a description of the current test, this must be + * provided, this tag is NOT optional. + * + * T: + * Used to set the procedure for the given task, there are two + * options for this: + * -compile + * This simply performs compilation only + * -execute + * This will perform compilation and execution + * -fail + * This will perform compilation, but requires + * the compilation to fail in order to succeed. + * + * This must be provided, this tag is NOT optional. + * + * C: + * Used to set the compilation flags for the given task, this + * must be provided, this tag is NOT optional. + * + * F: Used to set some test suite flags, currently the only option + * is -no-defs (to including of defs.qh) + * + * E: + * Used to set the execution flags for the given task. This tag + * must be provided if T == -execute, otherwise it's erroneous + * as compilation only takes place. + * + * M: + * Used to describe a string of text that should be matched from + * the output of executing the task. If this doesn't match the + * task fails. This tag must be provided if T == -execute, otherwise + * it's erroneous as compilation only takes place. + * + * I: + * Used to specify the INPUT source file to operate on, this must be + * provided, this tag is NOT optional + * + * + * Notes: + * These tags have one-time use, using them more than once will result + * in template compilation errors. + * + * Lines beginning with # or // in the template file are comments and + * are ignored by the template parser. + * + * Whitespace is optional, with exception to the colon ':' between the + * tag and it's assignment value/ + * + * The template compiler will detect erronrous tags (optional tags + * that need not be set), as well as missing tags, and error accordingly + * this will result in the task failing. + */ +typedef struct { + char *description; + char *compileflags; + char *executeflags; + char *proceduretype; + char *sourcefile; + char *tempfilename; + char **comparematch; + char *rulesfile; + char *testflags; +} task_template_t; + +/* + * This is very much like a compiler code generator :-). This generates + * a value from some data observed from the compiler. + */ +static bool task_template_generate(task_template_t *tmpl, char tag, const char *file, size_t line, char *value, size_t *pad) { + size_t desclen = 0; + size_t filelen = 0; + char **destval = NULL; + + if (!tmpl) + return false; + + switch(tag) { + case 'D': destval = &tmpl->description; break; + case 'T': destval = &tmpl->proceduretype; break; + case 'C': destval = &tmpl->compileflags; break; + case 'E': destval = &tmpl->executeflags; break; + case 'I': destval = &tmpl->sourcefile; break; + case 'F': destval = &tmpl->testflags; break; + default: + con_printmsg(LVL_ERROR, __FILE__, __LINE__, 0, "internal error", + "invalid tag `%c:` during code generation\n", + tag + ); + return false; + } + + /* + * Ensure if for the given tag, there already exists a + * assigned value. + */ + if (*destval) { + con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "compile error", + "tag `%c:` already assigned value: %s\n", + tag, *destval + ); + return false; + } + + /* + * Strip any whitespace that might exist in the value for assignments + * like "D: foo" + */ + if (value && *value && (*value == ' ' || *value == '\t')) + value++; + else if (!value) + exit(EXIT_FAILURE); + + /* + * Value will contain a newline character at the end, we need to strip + * this otherwise kaboom, seriously, kaboom :P + */ + if (strchr(value, '\n')) + *strrchr(value, '\n')='\0'; + + /* + * Now allocate and set the actual value for the specific tag. Which + * was properly selected and can be accessed with *destval. + */ + *destval = util_strdup(value); + + + if (*destval == tmpl->description) { + /* + * Create some padding for the description to align the + * printing of the rules file. + */ + if ((desclen = strlen(tmpl->description)) > pad[0]) + pad[0] = desclen; + } + + if ((filelen = strlen(file)) > pad[2]) + pad[2] = filelen; + + return true; +} + +static bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size_t *pad) { + char *data = NULL; + char *back = NULL; + size_t size = 0; + size_t line = 1; + + if (!tmpl) + return false; + + /* top down parsing */ + while (util_getline(&back, &size, fp) != EOF) { + /* skip whitespace */ + data = back; + if (*data && (*data == ' ' || *data == '\t')) + data++; + + switch (*data) { + /* + * Handle comments inside task tmpl files. We're strict + * about the language for fun :-) + */ + case '/': + if (data[1] != '/') { + con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error", + "invalid character `/`, perhaps you meant `//` ?"); + + mem_d(back); + return false; + } + case '#': + break; + + /* + * Empty newlines are acceptable as well, so we handle that here + * despite being just odd since there should't be that many + * empty lines to begin with. + */ + case '\r': + case '\n': + break; + + + /* + * Now begin the actual "tag" stuff. This works as you expect + * it to. + */ + case 'D': + case 'T': + case 'C': + case 'E': + case 'I': + case 'F': + if (data[1] != ':') { + con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error", + "expected `:` after `%c`", + *data + ); + goto failure; + } + if (!task_template_generate(tmpl, *data, file, line, &data[3], pad)) { + con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl compile error", + "failed to generate for given task\n" + ); + goto failure; + } + break; + + /* + * Match requires it's own system since we allow multiple M's + * for multi-line matching. + */ + case 'M': + { + char *value = &data[3]; + if (data[1] != ':') { + con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error", + "expected `:` after `%c`", + *data + ); + goto failure; + } + + /* + * Value will contain a newline character at the end, we need to strip + * this otherwise kaboom, seriously, kaboom :P + */ + if (strrchr(value, '\n')) + *strrchr(value, '\n')='\0'; + else /* cppcheck: possible null pointer dereference */ + exit(EXIT_FAILURE); + + vec_push(tmpl->comparematch, util_strdup(value)); + + break; + } + + default: + con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error", + "invalid tag `%c`", *data + ); + goto failure; + /* no break required */ + } + + /* update line and free old sata */ + line++; + mem_d(back); + back = NULL; + } + if (back) + mem_d(back); + return true; + +failure: + mem_d (back); + return false; +} + +/* + * Nullifies the template data: used during initialization of a new + * template and free. + */ +static void task_template_nullify(task_template_t *tmpl) { + if (!tmpl) + return; + + tmpl->description = NULL; + tmpl->proceduretype = NULL; + tmpl->compileflags = NULL; + tmpl->executeflags = NULL; + tmpl->comparematch = NULL; + tmpl->sourcefile = NULL; + tmpl->tempfilename = NULL; + tmpl->rulesfile = NULL; + tmpl->testflags = NULL; +} + +static task_template_t *task_template_compile(const char *file, const char *dir, size_t *pad) { + /* a page should be enough */ + char fullfile[4096]; + size_t filepadd = 0; + FILE *tempfile = NULL; + task_template_t *tmpl = NULL; + + util_snprintf(fullfile, sizeof(fullfile), "%s/%s", dir, file); + + tempfile = fopen(fullfile, "r"); + tmpl = (task_template_t*)mem_a(sizeof(task_template_t)); + task_template_nullify(tmpl); + + /* + * Create some padding for the printing to align the + * printing of the rules file to the console. + */ + if ((filepadd = strlen(fullfile)) > pad[1]) + pad[1] = filepadd; + + tmpl->rulesfile = util_strdup(fullfile); + + /* + * Esnure the file even exists for the task, this is pretty useless + * to even do. + */ + if (!tempfile) { + con_err("template file: %s does not exist or invalid permissions\n", + file + ); + goto failure; + } + + if (!task_template_parse(file, tmpl, tempfile, pad)) { + con_err("template parse error: error during parsing\n"); + goto failure; + } + + /* + * Regardless procedure type, the following tags must exist: + * D + * T + * C + * I + */ + if (!tmpl->description) { + con_err("template compile error: %s missing `D:` tag\n", file); + goto failure; + } + if (!tmpl->proceduretype) { + con_err("template compile error: %s missing `T:` tag\n", file); + goto failure; + } + if (!tmpl->compileflags) { + con_err("template compile error: %s missing `C:` tag\n", file); + goto failure; + } + if (!tmpl->sourcefile) { + con_err("template compile error: %s missing `I:` tag\n", file); + goto failure; + } + + /* + * Now lets compile the template, compilation is really just + * the process of validating the input. + */ + if (!strcmp(tmpl->proceduretype, "-compile")) { + if (tmpl->executeflags) + con_err("template compile warning: %s erroneous tag `E:` when only compiling\n", file); + if (tmpl->comparematch) + con_err("template compile warning: %s erroneous tag `M:` when only compiling\n", file); + goto success; + } else if (!strcmp(tmpl->proceduretype, "-execute")) { + if (!tmpl->executeflags) { + /* default to $null */ + tmpl->executeflags = util_strdup("$null"); + } + if (!tmpl->comparematch) { + con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file); + goto failure; + } + } else if (!strcmp(tmpl->proceduretype, "-fail")) { + if (tmpl->executeflags) + con_err("template compile warning: %s erroneous tag `E:` when only failing\n", file); + if (tmpl->comparematch) + con_err("template compile warning: %s erroneous tag `M:` when only failing\n", file); + } else if (!strcmp(tmpl->proceduretype, "-diagnostic")) { + if (tmpl->executeflags) + con_err("template compile warning: %s erroneous tag `E:` when only diagnostic\n", file); + if (!tmpl->comparematch) { + con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file); + goto failure; + } + } else if (!strcmp(tmpl->proceduretype, "-pp")) { + if (tmpl->executeflags) + con_err("template compile warning: %s erroneous tag `E:` when only preprocessing\n", file); + if (!tmpl->comparematch) { + con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file); + goto failure; + } + } else { + con_err("template compile error: %s invalid procedure type: %s\n", file, tmpl->proceduretype); + goto failure; + } + +success: + fclose(tempfile); + return tmpl; + +failure: + /* + * The file might not exist and we jump here when that doesn't happen + * so the check to see if it's not null here is required. + */ + if (tempfile) + fclose(tempfile); + mem_d (tmpl); + + return NULL; +} + +static void task_template_destroy(task_template_t *tmpl) { + if (!tmpl) + return; + + if (tmpl->description) mem_d(tmpl->description); + if (tmpl->proceduretype) mem_d(tmpl->proceduretype); + if (tmpl->compileflags) mem_d(tmpl->compileflags); + if (tmpl->executeflags) mem_d(tmpl->executeflags); + if (tmpl->sourcefile) mem_d(tmpl->sourcefile); + if (tmpl->rulesfile) mem_d(tmpl->rulesfile); + if (tmpl->testflags) mem_d(tmpl->testflags); + + /* + * Delete all allocated string for task tmpl then destroy the + * main vector. + */ + { + size_t i = 0; + for (; i < vec_size(tmpl->comparematch); i++) + mem_d(tmpl->comparematch[i]); + + vec_free(tmpl->comparematch); + } + + /* + * Nullify all the template members otherwise NULL comparision + * checks will fail if tmpl pointer is reused. + */ + mem_d(tmpl->tempfilename); + mem_d(tmpl); +} + +/* + * Now comes the task manager, this system allows adding tasks in and out + * of a task list. This is the executor of the tasks essentially as well. + */ +typedef struct { + task_template_t *tmpl; + FILE **runhandles; + FILE *stderrlog; + FILE *stdoutlog; + char *stdoutlogfile; + char *stderrlogfile; + bool compiled; +} task_t; + +static task_t *task_tasks = NULL; + +/* + * Read a directory and searches for all template files in it + * which is later used to run all tests. + */ +static bool task_propagate(const char *curdir, size_t *pad, const char *defs) { + bool success = true; + DIR *dir; + struct dirent *files; + struct stat directory; + char buffer[4096]; + size_t found = 0; + char **directories = NULL; + char *claim = util_strdup(curdir); + size_t i; + + vec_push(directories, claim); + dir = opendir(claim); + + /* + * Generate a list of subdirectories since we'll be checking them too + * for tmpl files. + */ + while ((files = readdir(dir))) { + util_asprintf(&claim, "%s/%s", curdir, files->d_name); + if (stat(claim, &directory) == -1) { + closedir(dir); + mem_d(claim); + return false; + } + + if (S_ISDIR(directory.st_mode) && files->d_name[0] != '.') { + vec_push(directories, claim); + } else { + mem_d(claim); + claim = NULL; + } + } + closedir(dir); + + /* + * Now do all the work, by touching all the directories inside + * test as well and compile the task templates into data we can + * use to run the tests. + */ + for (i = 0; i < vec_size(directories); i++) { + dir = opendir(directories[i]); + + while ((files = readdir(dir))) { + util_snprintf(buffer, sizeof(buffer), "%s/%s", directories[i], files->d_name); + if (stat(buffer, &directory) == -1) { + con_err("internal error: stat failed, aborting\n"); + abort(); + } + + if (S_ISDIR(directory.st_mode)) + continue; + + /* + * We made it here, which concludes the file/directory is not + * actually a directory, so it must be a file :) + */ + if (strcmp(files->d_name + strlen(files->d_name) - 5, ".tmpl") == 0) { + task_template_t *tmpl = task_template_compile(files->d_name, directories[i], pad); + char buf[4096]; /* one page should be enough */ + const char *qcflags = NULL; + task_t task; + + found ++; + if (!tmpl) { + con_err("error compiling task template: %s\n", files->d_name); + success = false; + continue; + } + /* + * Generate a temportary file name for the output binary + * so we don't trample over an existing one. + */ + tmpl->tempfilename = NULL; + util_asprintf(&tmpl->tempfilename, "%s/TMPDAT.%s.dat", directories[i], files->d_name); + + /* + * Additional QCFLAGS enviroment variable may be used + * to test compile flags for all tests. This needs to be + * BEFORE other flags (so that the .tmpl can override them) + */ + qcflags = getenv("QCFLAGS"); + + /* + * Generate the command required to open a pipe to a process + * which will be refered to with a handle in the task for + * reading the data from the pipe. + */ + if (strcmp(tmpl->proceduretype, "-pp")) { + if (qcflags) { + if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) { + util_snprintf(buf, sizeof(buf), "%s %s/%s %s %s -o %s", + task_bins[TASK_COMPILE], + directories[i], + tmpl->sourcefile, + qcflags, + tmpl->compileflags, + tmpl->tempfilename + ); + } else { + util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s %s -o %s", + task_bins[TASK_COMPILE], + curdir, + defs, + directories[i], + tmpl->sourcefile, + qcflags, + tmpl->compileflags, + tmpl->tempfilename + ); + } + } else { + if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) { + util_snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s", + task_bins[TASK_COMPILE], + directories[i], + tmpl->sourcefile, + tmpl->compileflags, + tmpl->tempfilename + ); + } else { + util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s -o %s", + task_bins[TASK_COMPILE], + curdir, + defs, + directories[i], + tmpl->sourcefile, + tmpl->compileflags, + tmpl->tempfilename + ); + } + } + } 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 %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 %s -o %s", + task_bins[TASK_COMPILE], + curdir, + defs, + directories[i], + tmpl->sourcefile, + tmpl->compileflags, + tmpl->tempfilename + ); + } + } + + /* + * The task template was compiled, now lets create a task from + * the template data which has now been propagated. + */ + task.tmpl = tmpl; + if (!(task.runhandles = task_popen(buf, "r"))) { + con_err("error opening pipe to process for test: %s\n", tmpl->description); + success = false; + continue; + } + + /* + * Open up some file desciptors for logging the stdout/stderr + * to our own. + */ + util_snprintf(buf, sizeof(buf), "%s.stdout", tmpl->tempfilename); + task.stdoutlogfile = util_strdup(buf); + if (!(task.stdoutlog = fopen(buf, "w"))) { + con_err("error opening %s for stdout\n", buf); + continue; + } + + util_snprintf(buf, sizeof(buf), "%s.stderr", tmpl->tempfilename); + task.stderrlogfile = util_strdup(buf); + if (!(task.stderrlog = fopen(buf, "w"))) { + con_err("error opening %s for stderr\n", buf); + continue; + } + vec_push(task_tasks, task); + } + } + closedir(dir); + mem_d(directories[i]); /* free claimed memory */ + } + vec_free(directories); + + return success; +} + +/* + * Task precleanup removes any existing temporary files or log files + * left behind from a previous invoke of the test-suite. + */ +static void task_precleanup(const char *curdir) { + DIR *dir; + struct dirent *files; + char buffer[4096]; + + dir = opendir(curdir); + + while ((files = readdir(dir))) { + if (strstr(files->d_name, "TMP") || + strstr(files->d_name, ".stdout") || + strstr(files->d_name, ".stderr") || + strstr(files->d_name, ".dat")) + { + util_snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name); + if (remove(buffer)) + con_err("error removing temporary file: %s\n", buffer); + } + } + + closedir(dir); +} + +static void task_destroy(void) { + /* + * Free all the data in the task list and finally the list itself + * then proceed to cleanup anything else outside the program like + * temporary files. + */ + size_t i; + for (i = 0; i < vec_size(task_tasks); i++) { + /* + * Close any open handles to files or processes here. It's mighty + * annoying to have to do all this cleanup work. + */ + if (task_tasks[i].stdoutlog) fclose(task_tasks[i].stdoutlog); + if (task_tasks[i].stderrlog) fclose(task_tasks[i].stderrlog); + + /* + * Only remove the log files if the test actually compiled otherwise + * forget about it (or if it didn't compile, and the procedure type + * was set to -fail (meaning it shouldn't compile) .. stil remove) + */ + if (task_tasks[i].compiled || !strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) { + if (remove(task_tasks[i].stdoutlogfile)) + con_err("error removing stdout log file: %s\n", task_tasks[i].stdoutlogfile); + if (remove(task_tasks[i].stderrlogfile)) + con_err("error removing stderr log file: %s\n", task_tasks[i].stderrlogfile); + + (void)!remove(task_tasks[i].tmpl->tempfilename); + } + + /* free util_strdup data for log files */ + mem_d(task_tasks[i].stdoutlogfile); + mem_d(task_tasks[i].stderrlogfile); + + task_template_destroy(task_tasks[i].tmpl); + } + vec_free(task_tasks); +} + +/* + * This executes the QCVM task for a specificly compiled progs.dat + * using the template passed into it for call-flags and user defined + * messages IF the procedure type is -execute, otherwise it matches + * the preprocessor output. + */ +static bool task_trymatch(size_t i, char ***line) { + bool success = true; + bool process = true; + int retval = EXIT_SUCCESS; + FILE *execute; + char buffer[4096]; + task_template_t *tmpl = task_tasks[i].tmpl; + + memset (buffer,0,sizeof(buffer)); + + if (!strcmp(tmpl->proceduretype, "-execute")) { + /* + * Drop the execution flags for the QCVM if none where + * actually specified. + */ + if (!strcmp(tmpl->executeflags, "$null")) { + util_snprintf(buffer, sizeof(buffer), "%s %s", + task_bins[TASK_EXECUTE], + tmpl->tempfilename + ); + } else { + util_snprintf(buffer, sizeof(buffer), "%s %s %s", + task_bins[TASK_EXECUTE], + tmpl->executeflags, + tmpl->tempfilename + ); + } + + execute = popen(buffer, "r"); + if (!execute) + return false; + } else if (!strcmp(tmpl->proceduretype, "-pp")) { + /* + * we're preprocessing, which means we need to read int + * the produced file and do some really weird shit. + */ + if (!(execute = fopen(tmpl->tempfilename, "r"))) + return false; + process = false; + } else { + /* + * we're testing diagnostic output, which means it will be + * in runhandles[2] (stderr) since that is where the compiler + * puts it's errors. + */ + if (!(execute = fopen(task_tasks[i].stderrlogfile, "r"))) + return false; + process = false; + } + + /* + * Now lets read the lines and compare them to the matches we expect + * and handle accordingly. + */ + { + char *data = NULL; + size_t size = 0; + size_t compare = 0; + + while (util_getline(&data, &size, execute) != EOF) { + if (!strcmp(data, "No main function found\n")) { + con_err("test failure: `%s` (No main function found) [%s]\n", + tmpl->description, + tmpl->rulesfile + ); + if (!process) + fclose(execute); + else + pclose((FILE*)execute); + return false; + } + + /* + * Trim newlines from data since they will just break our + * ability to properly validate matches. + */ + if (strrchr(data, '\n')) + *strrchr(data, '\n') = '\0'; + + /* + * We remove the file/directory and stuff from the error + * match messages when testing diagnostics. + */ + if(!strcmp(tmpl->proceduretype, "-diagnostic")) { + if (strstr(data, "there have been errors, bailing out")) + continue; /* ignore it */ + if (strstr(data, ": error: ")) { + char *claim = util_strdup(data + (strstr(data, ": error: ") - data) + 9); + mem_d(data); + data = claim; + } + } + + /* + * We need to ignore null lines for when -pp is used (preprocessor), since + * the preprocessor is likely to create empty newlines in certain macro + * instantations, otherwise it's in the wrong nature to ignore empty newlines. + */ + if (!strcmp(tmpl->proceduretype, "-pp") && !*data) + continue; + + if (vec_size(tmpl->comparematch) > compare) { + if (strcmp(data, tmpl->comparematch[compare++])) { + success = false; + } + } else { + success = false; + } + + /* + * Copy to output vector for diagnostics if execution match + * fails. + */ + vec_push(*line, data); + + /* reset */ + data = NULL; + size = 0; + } + + if (compare != vec_size(tmpl->comparematch)) + success = false; + + mem_d(data); + data = NULL; + } + + if (process) + retval = pclose((FILE*)execute); + else + fclose(execute); + + return success && retval == EXIT_SUCCESS; +} + +static const char *task_type(task_template_t *tmpl) { + if (!strcmp(tmpl->proceduretype, "-pp")) + return "type: preprocessor"; + if (!strcmp(tmpl->proceduretype, "-execute")) + return "type: execution"; + if (!strcmp(tmpl->proceduretype, "-compile")) + return "type: compile"; + if (!strcmp(tmpl->proceduretype, "-diagnostic")) + return "type: diagnostic"; + return "type: fail"; +} + +/* + * This schedualizes all tasks and actually runs them individually + * this is generally easy for just -compile variants. For compile and + * execution this takes more work since a task needs to be generated + * from thin air and executed INLINE. + */ +#include +static size_t task_schedualize(size_t *pad) { + char space[2][64]; + bool execute = false; + char *data = NULL; + char **match = NULL; + size_t size = 0; + 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)); + + for (; i < vec_size(task_tasks); i++) { + memset(space[1], 0, sizeof(space[1])); + util_snprintf(space[1], sizeof(space[1]), "%d", (int)(i + 1)); + + con_out("test #%u %*s", i + 1, strlen(space[0]) - strlen(space[1]), ""); + + /* + * Generate a task from thin air if it requires execution in + * the QCVM. + */ + + /* diagnostic is not executed, but compare tested instead, like preproessor */ + execute = !! (!strcmp(task_tasks[i].tmpl->proceduretype, "-execute")) || + (!strcmp(task_tasks[i].tmpl->proceduretype, "-pp")) || + (!strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")); + + /* + * We assume it compiled before we actually compiled :). On error + * we change the value + */ + task_tasks[i].compiled = true; + + /* + * Read data from stdout first and pipe that stuff into a log file + * then we do the same for stderr. + */ + while (util_getline(&data, &size, task_tasks[i].runhandles[1]) != EOF) { + fputs(data, task_tasks[i].stdoutlog); + + if (strstr(data, "failed to open file")) { + task_tasks[i].compiled = false; + execute = false; + } + } + while (util_getline(&data, &size, task_tasks[i].runhandles[2]) != EOF) { + /* + * If a string contains an error we just dissalow execution + * of it in the vm. + * + * TODO: make this more percise, e.g if we print a warning + * that refers to a variable named error, or something like + * that .. then this will blowup :P + */ + if (strstr(data, "error") && strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")) { + execute = false; + task_tasks[i].compiled = false; + } + + fputs(data, task_tasks[i].stderrlog); + fflush(task_tasks[i].stderrlog); /* fast flush for read */ + } + + if (!task_tasks[i].compiled && strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) { + 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]), + task_tasks[i].tmpl->rulesfile, + (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(failed to compile)") - pad[2]), + "(failed to compile)" + ); + failed++; + continue; + } + + 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]), + task_tasks[i].tmpl->rulesfile, + (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(compiler didn't return exit success)") - pad[2]), + "(compiler didn't return exit success)" + ); + failed++; + continue; + } + + if (!execute) { + con_out("succeeded: `%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]), + task_tasks[i].tmpl->rulesfile, + (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl)) - pad[2]), + task_type(task_tasks[i].tmpl) + + ); + continue; + } + + /* + * If we made it here that concludes the task is to be executed + * in the virtual machine (or the preprocessor output needs to + * be matched). + */ + if (!task_trymatch(i, &match)) { + size_t d = 0; + + 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]), + task_tasks[i].tmpl->rulesfile, + (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen( + (strcmp(task_tasks[i].tmpl->proceduretype, "-pp")) + ? "(invalid results from execution)" + : (strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")) + ? "(invalid results from preprocessing)" + : "(invalid results from compiler diagnsotics)" + ) - pad[2]), + (strcmp(task_tasks[i].tmpl->proceduretype, "-pp")) + ? "(invalid results from execution)" + : (strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")) + ? "(invalid results from preprocessing)" + : "(invalid results from compiler diagnsotics)" + ); + + /* + * Print nicely formatted expected match lists to console error + * handler for the all the given matches in the template file and + * what was actually returned from executing. + */ + con_out(" Expected From %u Matches: (got %u Matches)\n", + vec_size(task_tasks[i].tmpl->comparematch), + vec_size(match) + ); + for (; d < vec_size(task_tasks[i].tmpl->comparematch); d++) { + char *select = task_tasks[i].tmpl->comparematch[d]; + size_t length = 60 - strlen(select); + + con_out(" Expected: \"%s\"", select); + while (length --) + con_out(" "); + con_out("| Got: \"%s\"\n", (d >= vec_size(match)) ? "<>" : match[d]); + } + + /* + * Print the non-expected out (since we are simply not expecting it) + * This will help track down bugs in template files that fail to match + * something. + */ + if (vec_size(match) > vec_size(task_tasks[i].tmpl->comparematch)) { + for (d = 0; d < vec_size(match) - vec_size(task_tasks[i].tmpl->comparematch); d++) { + con_out(" Expected: Nothing | Got: \"%s\"\n", + match[d + vec_size(task_tasks[i].tmpl->comparematch)] + ); + } + } + + + for (j = 0; j < vec_size(match); j++) + mem_d(match[j]); + vec_free(match); + failed++; + continue; + } + + for (j = 0; j < vec_size(match); j++) + mem_d(match[j]); + vec_free(match); + + con_out("succeeded: `%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]), + task_tasks[i].tmpl->rulesfile, + (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl))- pad[2]), + task_type(task_tasks[i].tmpl) + + ); + } + mem_d(data); + return failed; +} + +/* + * This is the heart of the whole test-suite process. This cleans up + * any existing temporary files left behind as well as log files left + * behind. Then it propagates a list of tests from `curdir` by scaning + * it for template files and compiling them into tasks, in which it + * schedualizes them (executes them) and actually reports errors and + * what not. It then proceeds to destroy the tasks and return memory + * it's the engine :) + * + * It returns true of tests could be propagated, otherwise it returns + * false. + * + * It expects con_init() was called before hand. + */ +static GMQCC_WARN bool test_perform(const char *curdir, const char *defs) { + size_t failed = false; + static const char *default_defs = "defs.qh"; + + size_t pad[] = { + /* test ### [succeed/fail]: `description` [tests/template.tmpl] [type] */ + 0, 0, 0 + }; + + /* + * If the default definition file isn't set to anything. We will + * use the default_defs here, which is "defs.qc" + */ + if (!defs) { + defs = default_defs; + } + + + task_precleanup(curdir); + if (!task_propagate(curdir, pad, defs)) { + con_err("error: failed to propagate tasks\n"); + task_destroy(); + return false; + } + /* + * If we made it here all tasks where propagated from their resultant + * template file. So we can start the FILO scheduler, this has been + * designed in the most thread-safe way possible for future threading + * it's designed to prevent lock contention, and possible syncronization + * issues. + */ + failed = task_schedualize(pad); + if (failed) + con_out("%u out of %u tests failed\n", failed, vec_size(task_tasks)); + task_destroy(); + + return (failed) ? false : true; +} + +/* + * Fancy GCC-like LONG parsing allows things like --opt=param with + * assignment operator. This is used for redirecting stdout/stderr + * console to specific files of your choice. + */ +static bool parsecmd(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) { + int argc = *argc_; + char **argv = *argv_; + + size_t len = strlen(optname); + + if (strncmp(argv[0]+ds, optname, len)) + return false; + + /* it's --optname, check how the parameter is supplied */ + if (argv[0][ds+len] == '=') { + *out = argv[0]+ds+len+1; + return true; + } + + if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */ + return false; + + /* using --opt param */ + *out = argv[1]; + --*argc_; + ++*argv_; + return true; +} + +int main(int argc, char **argv) { + bool succeed = false; + char *defs = NULL; + + con_init(); + + /* + * Command line option parsing commences now We only need to support + * a few things in the test suite. + */ + while (argc > 1) { + ++argv; + --argc; + + if (argv[0][0] == '-') { + if (parsecmd("defs", &argc, &argv, &defs, 1, false)) + continue; + + if (!strcmp(argv[0]+1, "debug")) { + OPTS_OPTION_BOOL(OPTION_DEBUG) = true; + continue; + } + if (!strcmp(argv[0]+1, "nocolor")) { + con_color(0); + continue; + } + + con_err("invalid argument %s\n", argv[0]+1); + return -1; + } + } + succeed = test_perform("tests", defs); + + return (succeed) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/utf8.c b/utf8.c deleted file mode 100644 index 622363c..0000000 --- a/utf8.c +++ /dev/null @@ -1,129 +0,0 @@ -#include "gmqcc.h" - -/* - * Based on the flexible and economical utf8 decoder: - * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ - * - * This is slightly more economical, the fastest way to decode utf8 is - * with a lookup table as in: - * - * first 1-byte lookup - * if that fails, 2-byte lookup - * if that fails, 3-byte lookup - * if that fails, 4-byte lookup - * - * The following table can be generated with some interval trickery. - * consider an interval [a, b): - * - * a must be 0x80 or b must be 0xc0, lower 3 bits - * are clear, thus: - * interval(a,b) = ((uint32_t)((a==0x80?0x40-b:-a)<<23)) - * - * The failstate can be represented as interval(0x80,0x80), it's - * odd to see but this is a full state machine. - * - * The table than maps the corresponding sections as a serise of - * intervals. - * - * In this table the transition values are pre-multiplied with 16 to - * save a shift instruction for every byte, we throw away fillers - * which makes the table smaller. - * - * The first section of the table handles bytes with leading C - * The second section of the table handles bytes with leading D - * The third section of the table handles bytes with leading E - * The last section of the table handles bytes with leading F - * - * The values themselfs in the table are arranged so that when you - * left shift them by 6 to shift continuation characters into place, the - * new top bits tell you: - * - * 1 - if you keep going - * 2 - the range of valid values for the next byte - */ -static const uint32_t utf8_tab[] = { - 0xC0000002, 0xC0000003, 0xC0000004, 0xC0000005, 0xC0000006, - 0xC0000007, 0xC0000008, 0xC0000009, 0xC000000A, 0xC000000B, - 0xC000000C, 0xC000000D, 0xC000000E, 0xC000000F, 0xC0000010, - 0xC0000011, 0xC0000012, 0xC0000013, 0xC0000014, 0xC0000015, - 0xC0000016, 0xC0000017, 0xC0000018, 0xC0000019, 0xC000001A, - 0xC000001B, 0xC000001C, 0xC000001D, 0xC000001E, 0xC000001F, - 0xB3000000, 0xC3000001, 0xC3000002, 0xC3000003, 0xC3000004, - 0xC3000005, 0xC3000006, 0xC3000007, 0xC3000008, 0xC3000009, - 0xC300000A, 0xC300000B, 0xC300000C, 0xD300000D, 0xC300000E, - 0xC300000F, 0xBB0C0000, 0xC30C0001, 0xC30C0002, 0xC30C0003, - 0xD30C0004 -}; - -int utf8_from(char *s, utf8ch_t ch) { - if (!s) - return 0; - - if ((unsigned)ch < 0x80) { - *s = ch; - return 1; - } else if ((unsigned)ch < 0x800) { - *s++ = 0xC0 | (ch >> 6); - *s = 0x80 | (ch & 0x3F); - return 2; - } else if ((unsigned)ch < 0xD800 || (unsigned)ch - 0xE000 < 0x2000) { - *s++ = 0xE0 | (ch >> 12); - *s++ = 0x80 | ((ch >> 6) & 0x3F); - *s = 0x80 | (ch & 0x3F); - return 3; - } else if ((unsigned)ch - 0x10000 < 0x100000) { - *s++ = 0xF0 | (ch >> 18); - *s++ = 0x80 | ((ch >> 12) & 0x3F); - *s++ = 0x80 | ((ch >> 6) & 0x3F); - *s = 0x80 | (ch & 0x3F); - return 4; - } - return 0; -} - -int utf8_to(utf8ch_t *i, const unsigned char *s, size_t n) { - unsigned c,j; - - if (!s || !n) - return 0; - - /* This is consistent with mbtowc behaviour. */ - if (!i) - i = (utf8ch_t*)(void*)&i; - - if (*s < 0x80) - return !!(*i = *s); - if (*s-0xC2U > 0x32) - return 0; - - c = utf8_tab[*s++-0xC2U]; - - /* - * Avoid excessive checks against n. - * - * When shifting state `n-1` times does not clear the high bit, - * then the value of `n` won't satisfy the condition to read a - * character as it will be insufficent. - */ - if (n < 4 && ((c<<(6*n-6)) & (1U << 31))) - return 0; - - /* - * The upper 6 state bits are negitive integer offset to a bound-check - * next byte equivlant to: ((b-0x80)+(b+offset))&~0x3f - */ - if ((((*s>>3)-0x10)|((*s>>3)+((int32_t)c>>26))) & ~7) - return 0; - - for (j=2; j<3; j++) { - if (!((c = c<<6 | (*s++-0x80))&(1U<<31))) { - *i = c; - return j; - } - if (*s-0x80U >= 0x40) - return 0; - } - - *i = c<<6 | (*s++-0x80); - return 4; -} diff --git a/utf8.cpp b/utf8.cpp new file mode 100644 index 0000000..622363c --- /dev/null +++ b/utf8.cpp @@ -0,0 +1,129 @@ +#include "gmqcc.h" + +/* + * Based on the flexible and economical utf8 decoder: + * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + * + * This is slightly more economical, the fastest way to decode utf8 is + * with a lookup table as in: + * + * first 1-byte lookup + * if that fails, 2-byte lookup + * if that fails, 3-byte lookup + * if that fails, 4-byte lookup + * + * The following table can be generated with some interval trickery. + * consider an interval [a, b): + * + * a must be 0x80 or b must be 0xc0, lower 3 bits + * are clear, thus: + * interval(a,b) = ((uint32_t)((a==0x80?0x40-b:-a)<<23)) + * + * The failstate can be represented as interval(0x80,0x80), it's + * odd to see but this is a full state machine. + * + * The table than maps the corresponding sections as a serise of + * intervals. + * + * In this table the transition values are pre-multiplied with 16 to + * save a shift instruction for every byte, we throw away fillers + * which makes the table smaller. + * + * The first section of the table handles bytes with leading C + * The second section of the table handles bytes with leading D + * The third section of the table handles bytes with leading E + * The last section of the table handles bytes with leading F + * + * The values themselfs in the table are arranged so that when you + * left shift them by 6 to shift continuation characters into place, the + * new top bits tell you: + * + * 1 - if you keep going + * 2 - the range of valid values for the next byte + */ +static const uint32_t utf8_tab[] = { + 0xC0000002, 0xC0000003, 0xC0000004, 0xC0000005, 0xC0000006, + 0xC0000007, 0xC0000008, 0xC0000009, 0xC000000A, 0xC000000B, + 0xC000000C, 0xC000000D, 0xC000000E, 0xC000000F, 0xC0000010, + 0xC0000011, 0xC0000012, 0xC0000013, 0xC0000014, 0xC0000015, + 0xC0000016, 0xC0000017, 0xC0000018, 0xC0000019, 0xC000001A, + 0xC000001B, 0xC000001C, 0xC000001D, 0xC000001E, 0xC000001F, + 0xB3000000, 0xC3000001, 0xC3000002, 0xC3000003, 0xC3000004, + 0xC3000005, 0xC3000006, 0xC3000007, 0xC3000008, 0xC3000009, + 0xC300000A, 0xC300000B, 0xC300000C, 0xD300000D, 0xC300000E, + 0xC300000F, 0xBB0C0000, 0xC30C0001, 0xC30C0002, 0xC30C0003, + 0xD30C0004 +}; + +int utf8_from(char *s, utf8ch_t ch) { + if (!s) + return 0; + + if ((unsigned)ch < 0x80) { + *s = ch; + return 1; + } else if ((unsigned)ch < 0x800) { + *s++ = 0xC0 | (ch >> 6); + *s = 0x80 | (ch & 0x3F); + return 2; + } else if ((unsigned)ch < 0xD800 || (unsigned)ch - 0xE000 < 0x2000) { + *s++ = 0xE0 | (ch >> 12); + *s++ = 0x80 | ((ch >> 6) & 0x3F); + *s = 0x80 | (ch & 0x3F); + return 3; + } else if ((unsigned)ch - 0x10000 < 0x100000) { + *s++ = 0xF0 | (ch >> 18); + *s++ = 0x80 | ((ch >> 12) & 0x3F); + *s++ = 0x80 | ((ch >> 6) & 0x3F); + *s = 0x80 | (ch & 0x3F); + return 4; + } + return 0; +} + +int utf8_to(utf8ch_t *i, const unsigned char *s, size_t n) { + unsigned c,j; + + if (!s || !n) + return 0; + + /* This is consistent with mbtowc behaviour. */ + if (!i) + i = (utf8ch_t*)(void*)&i; + + if (*s < 0x80) + return !!(*i = *s); + if (*s-0xC2U > 0x32) + return 0; + + c = utf8_tab[*s++-0xC2U]; + + /* + * Avoid excessive checks against n. + * + * When shifting state `n-1` times does not clear the high bit, + * then the value of `n` won't satisfy the condition to read a + * character as it will be insufficent. + */ + if (n < 4 && ((c<<(6*n-6)) & (1U << 31))) + return 0; + + /* + * The upper 6 state bits are negitive integer offset to a bound-check + * next byte equivlant to: ((b-0x80)+(b+offset))&~0x3f + */ + if ((((*s>>3)-0x10)|((*s>>3)+((int32_t)c>>26))) & ~7) + return 0; + + for (j=2; j<3; j++) { + if (!((c = c<<6 | (*s++-0x80))&(1U<<31))) { + *i = c; + return j; + } + if (*s-0x80U >= 0x40) + return 0; + } + + *i = c<<6 | (*s++-0x80); + return 4; +} diff --git a/util.c b/util.c deleted file mode 100644 index ceed449..0000000 --- a/util.c +++ /dev/null @@ -1,732 +0,0 @@ -#include -#include -#include "gmqcc.h" - -const char *util_instr_str[VINSTR_END] = { - "DONE", "MUL_F", "MUL_V", "MUL_FV", - "MUL_VF", "DIV_F", "ADD_F", "ADD_V", - "SUB_F", "SUB_V", "EQ_F", "EQ_V", - "EQ_S", "EQ_E", "EQ_FNC", "NE_F", - "NE_V", "NE_S", "NE_E", "NE_FNC", - "LE", "GE", "LT", "GT", - "LOAD_F", "LOAD_V", "LOAD_S", "LOAD_ENT", - "LOAD_FLD", "LOAD_FNC", "ADDRESS", "STORE_F", - "STORE_V", "STORE_S", "STORE_ENT", "STORE_FLD", - "STORE_FNC", "STOREP_F", "STOREP_V", "STOREP_S", - "STOREP_ENT", "STOREP_FLD", "STOREP_FNC", "RETURN", - "NOT_F", "NOT_V", "NOT_S", "NOT_ENT", - "NOT_FNC", "IF", "IFNOT", "CALL0", - "CALL1", "CALL2", "CALL3", "CALL4", - "CALL5", "CALL6", "CALL7", "CALL8", - "STATE", "GOTO", "AND", "OR", - "BITAND", "BITOR" -}; - -/* - * only required if big endian .. otherwise no need to swap - * data. - */ -#if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_BIG || PLATFORM_BYTE_ORDER == -1 - static GMQCC_INLINE void util_swap16(uint16_t *d, size_t l) { - while (l--) { - d[l] = (d[l] << 8) | (d[l] >> 8); - } - } - - static GMQCC_INLINE void util_swap32(uint32_t *d, size_t l) { - while (l--) { - uint32_t v; - v = ((d[l] << 8) & 0xFF00FF00) | ((d[l] >> 8) & 0x00FF00FF); - d[l] = (v << 16) | (v >> 16); - } - } - - /* Some strange system doesn't like constants that big, AND doesn't recognize an ULL suffix - * so let's go the safe way - */ - static GMQCC_INLINE void util_swap64(uint32_t *d, size_t l) { - while (l--) { - uint64_t v; - v = ((d[l] << 8) & 0xFF00FF00FF00FF00) | ((d[l] >> 8) & 0x00FF00FF00FF00FF); - v = ((v << 16) & 0xFFFF0000FFFF0000) | ((v >> 16) & 0x0000FFFF0000FFFF); - d[l] = (v << 32) | (v >> 32); - } - } -#endif - -void util_endianswap(void *_data, size_t count, unsigned int typesize) { -# if PLATFORM_BYTE_ORDER == -1 /* runtime check */ - if (*((char*)&typesize)) - return; -#else - -# if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_LITTLE - /* prevent unused warnings */ - (void) _data; - (void) count; - (void) typesize; - return; -# else - switch (typesize) { - case 1: return; - case 2: - util_swap16((uint16_t*)_data, count); - return; - case 4: - util_swap32((uint32_t*)_data, count); - return; - case 8: - util_swap64((uint32_t*)_data, count); - return; - - default: - con_err ("util_endianswap: I don't know how to swap a %u byte structure!\n", typesize); - exit(EXIT_FAILURE); /* please blow the fuck up! */ - } -# endif -#endif -} - -void util_swap_header(prog_header_t *code_header) { - util_endianswap(&code_header->version, 1, sizeof(code_header->version)); - util_endianswap(&code_header->crc16, 1, sizeof(code_header->crc16)); - util_endianswap(&code_header->statements.offset, 1, sizeof(code_header->statements.offset)); - util_endianswap(&code_header->statements.length, 1, sizeof(code_header->statements.length)); - util_endianswap(&code_header->defs.offset, 1, sizeof(code_header->defs.offset)); - util_endianswap(&code_header->defs.length, 1, sizeof(code_header->defs.length)); - util_endianswap(&code_header->fields.offset, 1, sizeof(code_header->fields.offset)); - util_endianswap(&code_header->fields.length, 1, sizeof(code_header->fields.length)); - util_endianswap(&code_header->functions.offset, 1, sizeof(code_header->functions.offset)); - util_endianswap(&code_header->functions.length, 1, sizeof(code_header->functions.length)); - util_endianswap(&code_header->strings.offset, 1, sizeof(code_header->strings.offset)); - util_endianswap(&code_header->strings.length, 1, sizeof(code_header->strings.length)); - util_endianswap(&code_header->globals.offset, 1, sizeof(code_header->globals.offset)); - util_endianswap(&code_header->globals.length, 1, sizeof(code_header->globals.length)); - util_endianswap(&code_header->entfield, 1, sizeof(code_header->entfield)); -} - -void util_swap_statements(prog_section_statement_t *statements) { - size_t i; - - for (i = 0; i < vec_size(statements); ++i) { - util_endianswap(&statements[i].opcode, 1, sizeof(statements[i].opcode)); - util_endianswap(&statements[i].o1, 1, sizeof(statements[i].o1)); - util_endianswap(&statements[i].o2, 1, sizeof(statements[i].o2)); - util_endianswap(&statements[i].o3, 1, sizeof(statements[i].o3)); - } -} - -void util_swap_defs_fields(prog_section_both_t *section) { - size_t i; - - for (i = 0; i < vec_size(section); ++i) { - util_endianswap(§ion[i].type, 1, sizeof(section[i].type)); - util_endianswap(§ion[i].offset, 1, sizeof(section[i].offset)); - util_endianswap(§ion[i].name, 1, sizeof(section[i].name)); - } -} - -void util_swap_functions(prog_section_function_t *functions) { - size_t i; - - for (i = 0; i < vec_size(functions); ++i) { - util_endianswap(&functions[i].entry, 1, sizeof(functions[i].entry)); - util_endianswap(&functions[i].firstlocal, 1, sizeof(functions[i].firstlocal)); - util_endianswap(&functions[i].locals, 1, sizeof(functions[i].locals)); - util_endianswap(&functions[i].profile, 1, sizeof(functions[i].profile)); - util_endianswap(&functions[i].name, 1, sizeof(functions[i].name)); - util_endianswap(&functions[i].file, 1, sizeof(functions[i].file)); - util_endianswap(&functions[i].nargs, 1, sizeof(functions[i].nargs)); - /* Don't swap argsize[] - it's just a byte array, which Quake uses only as such. */ - } -} - -void util_swap_globals(int32_t *globals) { - util_endianswap(globals, vec_size(globals), sizeof(int32_t)); -} - -/* -* Based On: -* Slicing-by-8 algorithms by Michael E. -* Kounavis and Frank L. Berry from Intel Corp. -* http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf -* -* This code was made to be slightly less confusing with macros, which -* I suppose is somewhat ironic. -* -* The code had to be changed for non reflected on the output register -* since that's the method Quake uses. -* -* The code also had to be changed for CRC16, which is slightly harder -* since the CRC32 method in the original Intel paper used a different -* bit order convention. -* -* Notes about the table: -* - It's exactly 4K in size -* - 64 elements fit in a cache line -* - can do 8 iterations unrolled 8 times for free -* - The first 256 elements of the table are standard CRC16 table -* -* Table can be generated with the following utility: -*/ -#if 0 -#include -#include -int main(void) { - for (unsigned i = 0; i < 0x100; ++i) { - uint16_t x = i << 8; - for (int j = 0; j < 8; ++j) - x = (x << 1) ^ ((x & 0x8000) ? 0x1021 : 0); - tab[0][i] = x; - } - for (unsigned i = 0; i < 0x100; ++i) { - uint16_t c = tab[0][i]; - for (unsigned j = 1; j < 8; ++j) { - c = tab[0][c >> 8] ^ (c << 8); - tab[j][i] = c; - } - } - printf("static const uint16_t util_crc16_table[8][256] = {"); - for (int i = 0; i < 8; ++i) { - printf("{\n"); - for (int j = 0; j < 0x100; ++j) { - printf((j & 7) ? " " : " "); - printf((j != 0x100-1) ? "0x%04X," : "0x%04X", tab[i][j]); - if ((j & 7) == 7) - printf("\n"); - } - printf((i != 7) ? "}," : "}"); - } - printf("};\n"); - return 0; -} -#endif -/* - * Non-Reflective version is present as well as a reference. - * - * TODO: - * combine the crc16 into u32s and mask off low high for byte order - * to make the arrays smaller. - */ - -static const uint16_t util_crc16_table[8][256] = {{ - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, - 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, - 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, - 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, - 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, - 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, - 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, - 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, - 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, - 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, - 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, - 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, - 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, - 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, - 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, - 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, - 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, - 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, - 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, - 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, - 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, - 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, - 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, - 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, - 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, - 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, - 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, - 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, - 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, - 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, - 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, - 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 -},{ - 0x0000, 0x3331, 0x6662, 0x5553, 0xCCC4, 0xFFF5, 0xAAA6, 0x9997, - 0x89A9, 0xBA98, 0xEFCB, 0xDCFA, 0x456D, 0x765C, 0x230F, 0x103E, - 0x0373, 0x3042, 0x6511, 0x5620, 0xCFB7, 0xFC86, 0xA9D5, 0x9AE4, - 0x8ADA, 0xB9EB, 0xECB8, 0xDF89, 0x461E, 0x752F, 0x207C, 0x134D, - 0x06E6, 0x35D7, 0x6084, 0x53B5, 0xCA22, 0xF913, 0xAC40, 0x9F71, - 0x8F4F, 0xBC7E, 0xE92D, 0xDA1C, 0x438B, 0x70BA, 0x25E9, 0x16D8, - 0x0595, 0x36A4, 0x63F7, 0x50C6, 0xC951, 0xFA60, 0xAF33, 0x9C02, - 0x8C3C, 0xBF0D, 0xEA5E, 0xD96F, 0x40F8, 0x73C9, 0x269A, 0x15AB, - 0x0DCC, 0x3EFD, 0x6BAE, 0x589F, 0xC108, 0xF239, 0xA76A, 0x945B, - 0x8465, 0xB754, 0xE207, 0xD136, 0x48A1, 0x7B90, 0x2EC3, 0x1DF2, - 0x0EBF, 0x3D8E, 0x68DD, 0x5BEC, 0xC27B, 0xF14A, 0xA419, 0x9728, - 0x8716, 0xB427, 0xE174, 0xD245, 0x4BD2, 0x78E3, 0x2DB0, 0x1E81, - 0x0B2A, 0x381B, 0x6D48, 0x5E79, 0xC7EE, 0xF4DF, 0xA18C, 0x92BD, - 0x8283, 0xB1B2, 0xE4E1, 0xD7D0, 0x4E47, 0x7D76, 0x2825, 0x1B14, - 0x0859, 0x3B68, 0x6E3B, 0x5D0A, 0xC49D, 0xF7AC, 0xA2FF, 0x91CE, - 0x81F0, 0xB2C1, 0xE792, 0xD4A3, 0x4D34, 0x7E05, 0x2B56, 0x1867, - 0x1B98, 0x28A9, 0x7DFA, 0x4ECB, 0xD75C, 0xE46D, 0xB13E, 0x820F, - 0x9231, 0xA100, 0xF453, 0xC762, 0x5EF5, 0x6DC4, 0x3897, 0x0BA6, - 0x18EB, 0x2BDA, 0x7E89, 0x4DB8, 0xD42F, 0xE71E, 0xB24D, 0x817C, - 0x9142, 0xA273, 0xF720, 0xC411, 0x5D86, 0x6EB7, 0x3BE4, 0x08D5, - 0x1D7E, 0x2E4F, 0x7B1C, 0x482D, 0xD1BA, 0xE28B, 0xB7D8, 0x84E9, - 0x94D7, 0xA7E6, 0xF2B5, 0xC184, 0x5813, 0x6B22, 0x3E71, 0x0D40, - 0x1E0D, 0x2D3C, 0x786F, 0x4B5E, 0xD2C9, 0xE1F8, 0xB4AB, 0x879A, - 0x97A4, 0xA495, 0xF1C6, 0xC2F7, 0x5B60, 0x6851, 0x3D02, 0x0E33, - 0x1654, 0x2565, 0x7036, 0x4307, 0xDA90, 0xE9A1, 0xBCF2, 0x8FC3, - 0x9FFD, 0xACCC, 0xF99F, 0xCAAE, 0x5339, 0x6008, 0x355B, 0x066A, - 0x1527, 0x2616, 0x7345, 0x4074, 0xD9E3, 0xEAD2, 0xBF81, 0x8CB0, - 0x9C8E, 0xAFBF, 0xFAEC, 0xC9DD, 0x504A, 0x637B, 0x3628, 0x0519, - 0x10B2, 0x2383, 0x76D0, 0x45E1, 0xDC76, 0xEF47, 0xBA14, 0x8925, - 0x991B, 0xAA2A, 0xFF79, 0xCC48, 0x55DF, 0x66EE, 0x33BD, 0x008C, - 0x13C1, 0x20F0, 0x75A3, 0x4692, 0xDF05, 0xEC34, 0xB967, 0x8A56, - 0x9A68, 0xA959, 0xFC0A, 0xCF3B, 0x56AC, 0x659D, 0x30CE, 0x03FF -},{ - 0x0000, 0x3730, 0x6E60, 0x5950, 0xDCC0, 0xEBF0, 0xB2A0, 0x8590, - 0xA9A1, 0x9E91, 0xC7C1, 0xF0F1, 0x7561, 0x4251, 0x1B01, 0x2C31, - 0x4363, 0x7453, 0x2D03, 0x1A33, 0x9FA3, 0xA893, 0xF1C3, 0xC6F3, - 0xEAC2, 0xDDF2, 0x84A2, 0xB392, 0x3602, 0x0132, 0x5862, 0x6F52, - 0x86C6, 0xB1F6, 0xE8A6, 0xDF96, 0x5A06, 0x6D36, 0x3466, 0x0356, - 0x2F67, 0x1857, 0x4107, 0x7637, 0xF3A7, 0xC497, 0x9DC7, 0xAAF7, - 0xC5A5, 0xF295, 0xABC5, 0x9CF5, 0x1965, 0x2E55, 0x7705, 0x4035, - 0x6C04, 0x5B34, 0x0264, 0x3554, 0xB0C4, 0x87F4, 0xDEA4, 0xE994, - 0x1DAD, 0x2A9D, 0x73CD, 0x44FD, 0xC16D, 0xF65D, 0xAF0D, 0x983D, - 0xB40C, 0x833C, 0xDA6C, 0xED5C, 0x68CC, 0x5FFC, 0x06AC, 0x319C, - 0x5ECE, 0x69FE, 0x30AE, 0x079E, 0x820E, 0xB53E, 0xEC6E, 0xDB5E, - 0xF76F, 0xC05F, 0x990F, 0xAE3F, 0x2BAF, 0x1C9F, 0x45CF, 0x72FF, - 0x9B6B, 0xAC5B, 0xF50B, 0xC23B, 0x47AB, 0x709B, 0x29CB, 0x1EFB, - 0x32CA, 0x05FA, 0x5CAA, 0x6B9A, 0xEE0A, 0xD93A, 0x806A, 0xB75A, - 0xD808, 0xEF38, 0xB668, 0x8158, 0x04C8, 0x33F8, 0x6AA8, 0x5D98, - 0x71A9, 0x4699, 0x1FC9, 0x28F9, 0xAD69, 0x9A59, 0xC309, 0xF439, - 0x3B5A, 0x0C6A, 0x553A, 0x620A, 0xE79A, 0xD0AA, 0x89FA, 0xBECA, - 0x92FB, 0xA5CB, 0xFC9B, 0xCBAB, 0x4E3B, 0x790B, 0x205B, 0x176B, - 0x7839, 0x4F09, 0x1659, 0x2169, 0xA4F9, 0x93C9, 0xCA99, 0xFDA9, - 0xD198, 0xE6A8, 0xBFF8, 0x88C8, 0x0D58, 0x3A68, 0x6338, 0x5408, - 0xBD9C, 0x8AAC, 0xD3FC, 0xE4CC, 0x615C, 0x566C, 0x0F3C, 0x380C, - 0x143D, 0x230D, 0x7A5D, 0x4D6D, 0xC8FD, 0xFFCD, 0xA69D, 0x91AD, - 0xFEFF, 0xC9CF, 0x909F, 0xA7AF, 0x223F, 0x150F, 0x4C5F, 0x7B6F, - 0x575E, 0x606E, 0x393E, 0x0E0E, 0x8B9E, 0xBCAE, 0xE5FE, 0xD2CE, - 0x26F7, 0x11C7, 0x4897, 0x7FA7, 0xFA37, 0xCD07, 0x9457, 0xA367, - 0x8F56, 0xB866, 0xE136, 0xD606, 0x5396, 0x64A6, 0x3DF6, 0x0AC6, - 0x6594, 0x52A4, 0x0BF4, 0x3CC4, 0xB954, 0x8E64, 0xD734, 0xE004, - 0xCC35, 0xFB05, 0xA255, 0x9565, 0x10F5, 0x27C5, 0x7E95, 0x49A5, - 0xA031, 0x9701, 0xCE51, 0xF961, 0x7CF1, 0x4BC1, 0x1291, 0x25A1, - 0x0990, 0x3EA0, 0x67F0, 0x50C0, 0xD550, 0xE260, 0xBB30, 0x8C00, - 0xE352, 0xD462, 0x8D32, 0xBA02, 0x3F92, 0x08A2, 0x51F2, 0x66C2, - 0x4AF3, 0x7DC3, 0x2493, 0x13A3, 0x9633, 0xA103, 0xF853, 0xCF63 -},{ - 0x0000, 0x76B4, 0xED68, 0x9BDC, 0xCAF1, 0xBC45, 0x2799, 0x512D, - 0x85C3, 0xF377, 0x68AB, 0x1E1F, 0x4F32, 0x3986, 0xA25A, 0xD4EE, - 0x1BA7, 0x6D13, 0xF6CF, 0x807B, 0xD156, 0xA7E2, 0x3C3E, 0x4A8A, - 0x9E64, 0xE8D0, 0x730C, 0x05B8, 0x5495, 0x2221, 0xB9FD, 0xCF49, - 0x374E, 0x41FA, 0xDA26, 0xAC92, 0xFDBF, 0x8B0B, 0x10D7, 0x6663, - 0xB28D, 0xC439, 0x5FE5, 0x2951, 0x787C, 0x0EC8, 0x9514, 0xE3A0, - 0x2CE9, 0x5A5D, 0xC181, 0xB735, 0xE618, 0x90AC, 0x0B70, 0x7DC4, - 0xA92A, 0xDF9E, 0x4442, 0x32F6, 0x63DB, 0x156F, 0x8EB3, 0xF807, - 0x6E9C, 0x1828, 0x83F4, 0xF540, 0xA46D, 0xD2D9, 0x4905, 0x3FB1, - 0xEB5F, 0x9DEB, 0x0637, 0x7083, 0x21AE, 0x571A, 0xCCC6, 0xBA72, - 0x753B, 0x038F, 0x9853, 0xEEE7, 0xBFCA, 0xC97E, 0x52A2, 0x2416, - 0xF0F8, 0x864C, 0x1D90, 0x6B24, 0x3A09, 0x4CBD, 0xD761, 0xA1D5, - 0x59D2, 0x2F66, 0xB4BA, 0xC20E, 0x9323, 0xE597, 0x7E4B, 0x08FF, - 0xDC11, 0xAAA5, 0x3179, 0x47CD, 0x16E0, 0x6054, 0xFB88, 0x8D3C, - 0x4275, 0x34C1, 0xAF1D, 0xD9A9, 0x8884, 0xFE30, 0x65EC, 0x1358, - 0xC7B6, 0xB102, 0x2ADE, 0x5C6A, 0x0D47, 0x7BF3, 0xE02F, 0x969B, - 0xDD38, 0xAB8C, 0x3050, 0x46E4, 0x17C9, 0x617D, 0xFAA1, 0x8C15, - 0x58FB, 0x2E4F, 0xB593, 0xC327, 0x920A, 0xE4BE, 0x7F62, 0x09D6, - 0xC69F, 0xB02B, 0x2BF7, 0x5D43, 0x0C6E, 0x7ADA, 0xE106, 0x97B2, - 0x435C, 0x35E8, 0xAE34, 0xD880, 0x89AD, 0xFF19, 0x64C5, 0x1271, - 0xEA76, 0x9CC2, 0x071E, 0x71AA, 0x2087, 0x5633, 0xCDEF, 0xBB5B, - 0x6FB5, 0x1901, 0x82DD, 0xF469, 0xA544, 0xD3F0, 0x482C, 0x3E98, - 0xF1D1, 0x8765, 0x1CB9, 0x6A0D, 0x3B20, 0x4D94, 0xD648, 0xA0FC, - 0x7412, 0x02A6, 0x997A, 0xEFCE, 0xBEE3, 0xC857, 0x538B, 0x253F, - 0xB3A4, 0xC510, 0x5ECC, 0x2878, 0x7955, 0x0FE1, 0x943D, 0xE289, - 0x3667, 0x40D3, 0xDB0F, 0xADBB, 0xFC96, 0x8A22, 0x11FE, 0x674A, - 0xA803, 0xDEB7, 0x456B, 0x33DF, 0x62F2, 0x1446, 0x8F9A, 0xF92E, - 0x2DC0, 0x5B74, 0xC0A8, 0xB61C, 0xE731, 0x9185, 0x0A59, 0x7CED, - 0x84EA, 0xF25E, 0x6982, 0x1F36, 0x4E1B, 0x38AF, 0xA373, 0xD5C7, - 0x0129, 0x779D, 0xEC41, 0x9AF5, 0xCBD8, 0xBD6C, 0x26B0, 0x5004, - 0x9F4D, 0xE9F9, 0x7225, 0x0491, 0x55BC, 0x2308, 0xB8D4, 0xCE60, - 0x1A8E, 0x6C3A, 0xF7E6, 0x8152, 0xD07F, 0xA6CB, 0x3D17, 0x4BA3 -},{ - 0x0000, 0xAA51, 0x4483, 0xEED2, 0x8906, 0x2357, 0xCD85, 0x67D4, - 0x022D, 0xA87C, 0x46AE, 0xECFF, 0x8B2B, 0x217A, 0xCFA8, 0x65F9, - 0x045A, 0xAE0B, 0x40D9, 0xEA88, 0x8D5C, 0x270D, 0xC9DF, 0x638E, - 0x0677, 0xAC26, 0x42F4, 0xE8A5, 0x8F71, 0x2520, 0xCBF2, 0x61A3, - 0x08B4, 0xA2E5, 0x4C37, 0xE666, 0x81B2, 0x2BE3, 0xC531, 0x6F60, - 0x0A99, 0xA0C8, 0x4E1A, 0xE44B, 0x839F, 0x29CE, 0xC71C, 0x6D4D, - 0x0CEE, 0xA6BF, 0x486D, 0xE23C, 0x85E8, 0x2FB9, 0xC16B, 0x6B3A, - 0x0EC3, 0xA492, 0x4A40, 0xE011, 0x87C5, 0x2D94, 0xC346, 0x6917, - 0x1168, 0xBB39, 0x55EB, 0xFFBA, 0x986E, 0x323F, 0xDCED, 0x76BC, - 0x1345, 0xB914, 0x57C6, 0xFD97, 0x9A43, 0x3012, 0xDEC0, 0x7491, - 0x1532, 0xBF63, 0x51B1, 0xFBE0, 0x9C34, 0x3665, 0xD8B7, 0x72E6, - 0x171F, 0xBD4E, 0x539C, 0xF9CD, 0x9E19, 0x3448, 0xDA9A, 0x70CB, - 0x19DC, 0xB38D, 0x5D5F, 0xF70E, 0x90DA, 0x3A8B, 0xD459, 0x7E08, - 0x1BF1, 0xB1A0, 0x5F72, 0xF523, 0x92F7, 0x38A6, 0xD674, 0x7C25, - 0x1D86, 0xB7D7, 0x5905, 0xF354, 0x9480, 0x3ED1, 0xD003, 0x7A52, - 0x1FAB, 0xB5FA, 0x5B28, 0xF179, 0x96AD, 0x3CFC, 0xD22E, 0x787F, - 0x22D0, 0x8881, 0x6653, 0xCC02, 0xABD6, 0x0187, 0xEF55, 0x4504, - 0x20FD, 0x8AAC, 0x647E, 0xCE2F, 0xA9FB, 0x03AA, 0xED78, 0x4729, - 0x268A, 0x8CDB, 0x6209, 0xC858, 0xAF8C, 0x05DD, 0xEB0F, 0x415E, - 0x24A7, 0x8EF6, 0x6024, 0xCA75, 0xADA1, 0x07F0, 0xE922, 0x4373, - 0x2A64, 0x8035, 0x6EE7, 0xC4B6, 0xA362, 0x0933, 0xE7E1, 0x4DB0, - 0x2849, 0x8218, 0x6CCA, 0xC69B, 0xA14F, 0x0B1E, 0xE5CC, 0x4F9D, - 0x2E3E, 0x846F, 0x6ABD, 0xC0EC, 0xA738, 0x0D69, 0xE3BB, 0x49EA, - 0x2C13, 0x8642, 0x6890, 0xC2C1, 0xA515, 0x0F44, 0xE196, 0x4BC7, - 0x33B8, 0x99E9, 0x773B, 0xDD6A, 0xBABE, 0x10EF, 0xFE3D, 0x546C, - 0x3195, 0x9BC4, 0x7516, 0xDF47, 0xB893, 0x12C2, 0xFC10, 0x5641, - 0x37E2, 0x9DB3, 0x7361, 0xD930, 0xBEE4, 0x14B5, 0xFA67, 0x5036, - 0x35CF, 0x9F9E, 0x714C, 0xDB1D, 0xBCC9, 0x1698, 0xF84A, 0x521B, - 0x3B0C, 0x915D, 0x7F8F, 0xD5DE, 0xB20A, 0x185B, 0xF689, 0x5CD8, - 0x3921, 0x9370, 0x7DA2, 0xD7F3, 0xB027, 0x1A76, 0xF4A4, 0x5EF5, - 0x3F56, 0x9507, 0x7BD5, 0xD184, 0xB650, 0x1C01, 0xF2D3, 0x5882, - 0x3D7B, 0x972A, 0x79F8, 0xD3A9, 0xB47D, 0x1E2C, 0xF0FE, 0x5AAF -},{ - 0x0000, 0x45A0, 0x8B40, 0xCEE0, 0x06A1, 0x4301, 0x8DE1, 0xC841, - 0x0D42, 0x48E2, 0x8602, 0xC3A2, 0x0BE3, 0x4E43, 0x80A3, 0xC503, - 0x1A84, 0x5F24, 0x91C4, 0xD464, 0x1C25, 0x5985, 0x9765, 0xD2C5, - 0x17C6, 0x5266, 0x9C86, 0xD926, 0x1167, 0x54C7, 0x9A27, 0xDF87, - 0x3508, 0x70A8, 0xBE48, 0xFBE8, 0x33A9, 0x7609, 0xB8E9, 0xFD49, - 0x384A, 0x7DEA, 0xB30A, 0xF6AA, 0x3EEB, 0x7B4B, 0xB5AB, 0xF00B, - 0x2F8C, 0x6A2C, 0xA4CC, 0xE16C, 0x292D, 0x6C8D, 0xA26D, 0xE7CD, - 0x22CE, 0x676E, 0xA98E, 0xEC2E, 0x246F, 0x61CF, 0xAF2F, 0xEA8F, - 0x6A10, 0x2FB0, 0xE150, 0xA4F0, 0x6CB1, 0x2911, 0xE7F1, 0xA251, - 0x6752, 0x22F2, 0xEC12, 0xA9B2, 0x61F3, 0x2453, 0xEAB3, 0xAF13, - 0x7094, 0x3534, 0xFBD4, 0xBE74, 0x7635, 0x3395, 0xFD75, 0xB8D5, - 0x7DD6, 0x3876, 0xF696, 0xB336, 0x7B77, 0x3ED7, 0xF037, 0xB597, - 0x5F18, 0x1AB8, 0xD458, 0x91F8, 0x59B9, 0x1C19, 0xD2F9, 0x9759, - 0x525A, 0x17FA, 0xD91A, 0x9CBA, 0x54FB, 0x115B, 0xDFBB, 0x9A1B, - 0x459C, 0x003C, 0xCEDC, 0x8B7C, 0x433D, 0x069D, 0xC87D, 0x8DDD, - 0x48DE, 0x0D7E, 0xC39E, 0x863E, 0x4E7F, 0x0BDF, 0xC53F, 0x809F, - 0xD420, 0x9180, 0x5F60, 0x1AC0, 0xD281, 0x9721, 0x59C1, 0x1C61, - 0xD962, 0x9CC2, 0x5222, 0x1782, 0xDFC3, 0x9A63, 0x5483, 0x1123, - 0xCEA4, 0x8B04, 0x45E4, 0x0044, 0xC805, 0x8DA5, 0x4345, 0x06E5, - 0xC3E6, 0x8646, 0x48A6, 0x0D06, 0xC547, 0x80E7, 0x4E07, 0x0BA7, - 0xE128, 0xA488, 0x6A68, 0x2FC8, 0xE789, 0xA229, 0x6CC9, 0x2969, - 0xEC6A, 0xA9CA, 0x672A, 0x228A, 0xEACB, 0xAF6B, 0x618B, 0x242B, - 0xFBAC, 0xBE0C, 0x70EC, 0x354C, 0xFD0D, 0xB8AD, 0x764D, 0x33ED, - 0xF6EE, 0xB34E, 0x7DAE, 0x380E, 0xF04F, 0xB5EF, 0x7B0F, 0x3EAF, - 0xBE30, 0xFB90, 0x3570, 0x70D0, 0xB891, 0xFD31, 0x33D1, 0x7671, - 0xB372, 0xF6D2, 0x3832, 0x7D92, 0xB5D3, 0xF073, 0x3E93, 0x7B33, - 0xA4B4, 0xE114, 0x2FF4, 0x6A54, 0xA215, 0xE7B5, 0x2955, 0x6CF5, - 0xA9F6, 0xEC56, 0x22B6, 0x6716, 0xAF57, 0xEAF7, 0x2417, 0x61B7, - 0x8B38, 0xCE98, 0x0078, 0x45D8, 0x8D99, 0xC839, 0x06D9, 0x4379, - 0x867A, 0xC3DA, 0x0D3A, 0x489A, 0x80DB, 0xC57B, 0x0B9B, 0x4E3B, - 0x91BC, 0xD41C, 0x1AFC, 0x5F5C, 0x971D, 0xD2BD, 0x1C5D, 0x59FD, - 0x9CFE, 0xD95E, 0x17BE, 0x521E, 0x9A5F, 0xDFFF, 0x111F, 0x54BF -},{ - 0x0000, 0xB861, 0x60E3, 0xD882, 0xC1C6, 0x79A7, 0xA125, 0x1944, - 0x93AD, 0x2BCC, 0xF34E, 0x4B2F, 0x526B, 0xEA0A, 0x3288, 0x8AE9, - 0x377B, 0x8F1A, 0x5798, 0xEFF9, 0xF6BD, 0x4EDC, 0x965E, 0x2E3F, - 0xA4D6, 0x1CB7, 0xC435, 0x7C54, 0x6510, 0xDD71, 0x05F3, 0xBD92, - 0x6EF6, 0xD697, 0x0E15, 0xB674, 0xAF30, 0x1751, 0xCFD3, 0x77B2, - 0xFD5B, 0x453A, 0x9DB8, 0x25D9, 0x3C9D, 0x84FC, 0x5C7E, 0xE41F, - 0x598D, 0xE1EC, 0x396E, 0x810F, 0x984B, 0x202A, 0xF8A8, 0x40C9, - 0xCA20, 0x7241, 0xAAC3, 0x12A2, 0x0BE6, 0xB387, 0x6B05, 0xD364, - 0xDDEC, 0x658D, 0xBD0F, 0x056E, 0x1C2A, 0xA44B, 0x7CC9, 0xC4A8, - 0x4E41, 0xF620, 0x2EA2, 0x96C3, 0x8F87, 0x37E6, 0xEF64, 0x5705, - 0xEA97, 0x52F6, 0x8A74, 0x3215, 0x2B51, 0x9330, 0x4BB2, 0xF3D3, - 0x793A, 0xC15B, 0x19D9, 0xA1B8, 0xB8FC, 0x009D, 0xD81F, 0x607E, - 0xB31A, 0x0B7B, 0xD3F9, 0x6B98, 0x72DC, 0xCABD, 0x123F, 0xAA5E, - 0x20B7, 0x98D6, 0x4054, 0xF835, 0xE171, 0x5910, 0x8192, 0x39F3, - 0x8461, 0x3C00, 0xE482, 0x5CE3, 0x45A7, 0xFDC6, 0x2544, 0x9D25, - 0x17CC, 0xAFAD, 0x772F, 0xCF4E, 0xD60A, 0x6E6B, 0xB6E9, 0x0E88, - 0xABF9, 0x1398, 0xCB1A, 0x737B, 0x6A3F, 0xD25E, 0x0ADC, 0xB2BD, - 0x3854, 0x8035, 0x58B7, 0xE0D6, 0xF992, 0x41F3, 0x9971, 0x2110, - 0x9C82, 0x24E3, 0xFC61, 0x4400, 0x5D44, 0xE525, 0x3DA7, 0x85C6, - 0x0F2F, 0xB74E, 0x6FCC, 0xD7AD, 0xCEE9, 0x7688, 0xAE0A, 0x166B, - 0xC50F, 0x7D6E, 0xA5EC, 0x1D8D, 0x04C9, 0xBCA8, 0x642A, 0xDC4B, - 0x56A2, 0xEEC3, 0x3641, 0x8E20, 0x9764, 0x2F05, 0xF787, 0x4FE6, - 0xF274, 0x4A15, 0x9297, 0x2AF6, 0x33B2, 0x8BD3, 0x5351, 0xEB30, - 0x61D9, 0xD9B8, 0x013A, 0xB95B, 0xA01F, 0x187E, 0xC0FC, 0x789D, - 0x7615, 0xCE74, 0x16F6, 0xAE97, 0xB7D3, 0x0FB2, 0xD730, 0x6F51, - 0xE5B8, 0x5DD9, 0x855B, 0x3D3A, 0x247E, 0x9C1F, 0x449D, 0xFCFC, - 0x416E, 0xF90F, 0x218D, 0x99EC, 0x80A8, 0x38C9, 0xE04B, 0x582A, - 0xD2C3, 0x6AA2, 0xB220, 0x0A41, 0x1305, 0xAB64, 0x73E6, 0xCB87, - 0x18E3, 0xA082, 0x7800, 0xC061, 0xD925, 0x6144, 0xB9C6, 0x01A7, - 0x8B4E, 0x332F, 0xEBAD, 0x53CC, 0x4A88, 0xF2E9, 0x2A6B, 0x920A, - 0x2F98, 0x97F9, 0x4F7B, 0xF71A, 0xEE5E, 0x563F, 0x8EBD, 0x36DC, - 0xBC35, 0x0454, 0xDCD6, 0x64B7, 0x7DF3, 0xC592, 0x1D10, 0xA571 -},{ - 0x0000, 0x47D3, 0x8FA6, 0xC875, 0x0F6D, 0x48BE, 0x80CB, 0xC718, - 0x1EDA, 0x5909, 0x917C, 0xD6AF, 0x11B7, 0x5664, 0x9E11, 0xD9C2, - 0x3DB4, 0x7A67, 0xB212, 0xF5C1, 0x32D9, 0x750A, 0xBD7F, 0xFAAC, - 0x236E, 0x64BD, 0xACC8, 0xEB1B, 0x2C03, 0x6BD0, 0xA3A5, 0xE476, - 0x7B68, 0x3CBB, 0xF4CE, 0xB31D, 0x7405, 0x33D6, 0xFBA3, 0xBC70, - 0x65B2, 0x2261, 0xEA14, 0xADC7, 0x6ADF, 0x2D0C, 0xE579, 0xA2AA, - 0x46DC, 0x010F, 0xC97A, 0x8EA9, 0x49B1, 0x0E62, 0xC617, 0x81C4, - 0x5806, 0x1FD5, 0xD7A0, 0x9073, 0x576B, 0x10B8, 0xD8CD, 0x9F1E, - 0xF6D0, 0xB103, 0x7976, 0x3EA5, 0xF9BD, 0xBE6E, 0x761B, 0x31C8, - 0xE80A, 0xAFD9, 0x67AC, 0x207F, 0xE767, 0xA0B4, 0x68C1, 0x2F12, - 0xCB64, 0x8CB7, 0x44C2, 0x0311, 0xC409, 0x83DA, 0x4BAF, 0x0C7C, - 0xD5BE, 0x926D, 0x5A18, 0x1DCB, 0xDAD3, 0x9D00, 0x5575, 0x12A6, - 0x8DB8, 0xCA6B, 0x021E, 0x45CD, 0x82D5, 0xC506, 0x0D73, 0x4AA0, - 0x9362, 0xD4B1, 0x1CC4, 0x5B17, 0x9C0F, 0xDBDC, 0x13A9, 0x547A, - 0xB00C, 0xF7DF, 0x3FAA, 0x7879, 0xBF61, 0xF8B2, 0x30C7, 0x7714, - 0xAED6, 0xE905, 0x2170, 0x66A3, 0xA1BB, 0xE668, 0x2E1D, 0x69CE, - 0xFD81, 0xBA52, 0x7227, 0x35F4, 0xF2EC, 0xB53F, 0x7D4A, 0x3A99, - 0xE35B, 0xA488, 0x6CFD, 0x2B2E, 0xEC36, 0xABE5, 0x6390, 0x2443, - 0xC035, 0x87E6, 0x4F93, 0x0840, 0xCF58, 0x888B, 0x40FE, 0x072D, - 0xDEEF, 0x993C, 0x5149, 0x169A, 0xD182, 0x9651, 0x5E24, 0x19F7, - 0x86E9, 0xC13A, 0x094F, 0x4E9C, 0x8984, 0xCE57, 0x0622, 0x41F1, - 0x9833, 0xDFE0, 0x1795, 0x5046, 0x975E, 0xD08D, 0x18F8, 0x5F2B, - 0xBB5D, 0xFC8E, 0x34FB, 0x7328, 0xB430, 0xF3E3, 0x3B96, 0x7C45, - 0xA587, 0xE254, 0x2A21, 0x6DF2, 0xAAEA, 0xED39, 0x254C, 0x629F, - 0x0B51, 0x4C82, 0x84F7, 0xC324, 0x043C, 0x43EF, 0x8B9A, 0xCC49, - 0x158B, 0x5258, 0x9A2D, 0xDDFE, 0x1AE6, 0x5D35, 0x9540, 0xD293, - 0x36E5, 0x7136, 0xB943, 0xFE90, 0x3988, 0x7E5B, 0xB62E, 0xF1FD, - 0x283F, 0x6FEC, 0xA799, 0xE04A, 0x2752, 0x6081, 0xA8F4, 0xEF27, - 0x7039, 0x37EA, 0xFF9F, 0xB84C, 0x7F54, 0x3887, 0xF0F2, 0xB721, - 0x6EE3, 0x2930, 0xE145, 0xA696, 0x618E, 0x265D, 0xEE28, 0xA9FB, - 0x4D8D, 0x0A5E, 0xC22B, 0x85F8, 0x42E0, 0x0533, 0xCD46, 0x8A95, - 0x5357, 0x1484, 0xDCF1, 0x9B22, 0x5C3A, 0x1BE9, 0xD39C, 0x944F -}}; - -/* Non - Reflected */ -uint16_t util_crc16(uint16_t current, const char *GMQCC_RESTRICT k, size_t len) { - uint16_t h = current; - - /* don't load twice */ - const uint8_t *GMQCC_RESTRICT data = (const uint8_t *GMQCC_RESTRICT)k; - size_t n; - - /* deal with the first bytes as bytes until we reach an 8 byte boundary */ - while (len & 7) { - h = (uint16_t)(h << 8) ^ (*util_crc16_table)[(h >> 8) ^ *data++]; - --len; - } - - #define SELECT_BULK(X, MOD) util_crc16_table[(X)][data[7-(X)] ^ (MOD)] - #define SELECT_DATA(X) util_crc16_table[(X)][data[7-(X)]] - - for (n = len / 8; n; --n) { - h = SELECT_BULK(7, (h >> 8)) ^ - SELECT_BULK(6, (h & 0xFF)) ^ - SELECT_DATA(5) ^ - SELECT_DATA(4) ^ - SELECT_DATA(3) ^ - SELECT_DATA(2) ^ - SELECT_DATA(1) ^ - SELECT_DATA(0); - data += 8; - len -= 8; - } - - #undef SELECT_BULK - #undef SELECT_DATA - - /* deal with the rest with the byte method */ - for (n = len & 7; n; --n) - h = (uint16_t)(h << 8) ^ (*util_crc16_table)[(h >> 8) ^ *data++]; - - return h; -} - -/* - * modifier is the match to make and the transposition from it, while add is the upper-value that determines the - * transposition from uppercase to lower case. - */ -static size_t util_strtransform(const char *in, char *out, size_t outsz, const char *mod, int add) { - size_t sz = 1; - for (; *in && sz < outsz; ++in, ++out, ++sz) { - *out = (*in == mod[0]) - ? mod[1] - : (util_isalpha(*in) && ((add > 0) ? util_isupper(*in) : !util_isupper(*in))) - ? *in + add - : *in; - } - *out = 0; - return sz-1; -} - -size_t util_strtocmd(const char *in, char *out, size_t outsz) { - return util_strtransform(in, out, outsz, "-_", 'A'-'a'); -} -size_t util_strtononcmd(const char *in, char *out, size_t outsz) { - return util_strtransform(in, out, outsz, "_-", 'a'-'A'); -} -size_t util_optimizationtostr(const char *in, char *out, size_t outsz) { - return util_strtransform(in, out, outsz, "_ ", 'a'-'A'); -} - -static int util_vasprintf(char **dat, const char *fmt, va_list args) { - int ret; - int len; - char *tmp = NULL; - char buf[128]; - va_list cpy; - - va_copy(cpy, 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; - } - - tmp = (char*)mem_a(len + 1); - if ((ret = vsnprintf(tmp, len + 1, fmt, args)) != len) { - mem_d(tmp); - *dat = NULL; - return -1; - } - - *dat = tmp; - return len; -} - -int util_snprintf(char *str, size_t size, const char *fmt, ...) { - va_list arg; - int ret; - va_start(arg, fmt); - ret = vsnprintf(str, size, fmt, arg); - va_end(arg); - return ret; -} - -int util_asprintf(char **ret, const char *fmt, ...) { - va_list args; - int read; - va_start(args, fmt); - read = util_vasprintf(ret, fmt, args); - va_end(args); - return read; -} - -int util_sscanf(const char *str, const char *format, ...) { - va_list args; - int read; - va_start(args, format); - read = vsscanf(str, format, args); - va_end(args); - return read; -} - -char *util_strncpy(char *dest, const char *src, size_t n) { - return strncpy(dest, src, n); -} - -char *util_strncat(char *dest, const char *src, size_t n) { - return strncat(dest, src, n); -} - -char *util_strcat(char *dest, const char *src) { - return strcat(dest, src); -} - -const char *util_strerror(int err) { - return strerror(err); -} - -const struct tm *util_localtime(const time_t *timer) { - return localtime(timer); -} - -const char *util_ctime(const time_t *timer) { - return ctime(timer); -} - -int util_getline(char **lineptr, size_t *n, FILE *stream) { - int chr; - int ret; - char *pos; - - if (!lineptr || !n || !stream) - return -1; - if (!*lineptr) { - if (!(*lineptr = (char*)mem_a((*n=64)))) - return -1; - } - - chr = *n; - pos = *lineptr; - - for (;;) { - int c = getc(stream); - - if (chr < 2) { - *n += (*n > 16) ? *n : 64; - chr = *n + *lineptr - pos; - if (!(*lineptr = (char*)mem_r(*lineptr,*n))) - return -1; - pos = *n - chr + *lineptr; - } - - if (ferror(stream)) - return -1; - if (c == EOF) { - if (pos == *lineptr) - return -1; - else - break; - } - - *pos++ = c; - chr--; - if (c == '\n') - break; - } - *pos = '\0'; - return (ret = pos - *lineptr); -} - -#ifndef _WIN32 -#include -bool util_isatty(FILE *file) { - if (file == stdout) return !!isatty(STDOUT_FILENO); - if (file == stderr) return !!isatty(STDERR_FILENO); - return false; -} -#else -bool util_isatty(FILE *file) { - return false; -} -#endif - -/* - * A small noncryptographic PRNG based on: - * http://burtleburtle.net/bob/rand/smallprng.html - */ -static uint32_t util_rand_state[4] = { - 0xF1EA5EED, 0x00000000, - 0x00000000, 0x00000000 -}; - -#define util_rand_rot(X, Y) (((X)<<(Y))|((X)>>(32-(Y)))) - -uint32_t util_rand() { - uint32_t last; - - last = util_rand_state[0] - util_rand_rot(util_rand_state[1], 27); - util_rand_state[0] = util_rand_state[1] ^ util_rand_rot(util_rand_state[2], 17); - util_rand_state[1] = util_rand_state[2] + util_rand_state[3]; - util_rand_state[2] = util_rand_state[3] + last; - util_rand_state[3] = util_rand_state[0] + last; - - return util_rand_state[3]; -} - -#undef util_rand_rot - -void util_seed(uint32_t value) { - size_t i; - - util_rand_state[0] = 0xF1EA5EED; - util_rand_state[1] = value; - util_rand_state[2] = value; - util_rand_state[3] = value; - - for (i = 0; i < 20; ++i) - (void)util_rand(); -} - -size_t hash(const char *string) { - size_t hash = 0; - for(; *string; ++string) { - hash += *string; - hash += (hash << 10); - hash ^= (hash >> 6); - } - hash += hash << 3; - hash ^= hash >> 11; - hash += hash << 15; - return hash; -} - diff --git a/util.cpp b/util.cpp new file mode 100644 index 0000000..ceed449 --- /dev/null +++ b/util.cpp @@ -0,0 +1,732 @@ +#include +#include +#include "gmqcc.h" + +const char *util_instr_str[VINSTR_END] = { + "DONE", "MUL_F", "MUL_V", "MUL_FV", + "MUL_VF", "DIV_F", "ADD_F", "ADD_V", + "SUB_F", "SUB_V", "EQ_F", "EQ_V", + "EQ_S", "EQ_E", "EQ_FNC", "NE_F", + "NE_V", "NE_S", "NE_E", "NE_FNC", + "LE", "GE", "LT", "GT", + "LOAD_F", "LOAD_V", "LOAD_S", "LOAD_ENT", + "LOAD_FLD", "LOAD_FNC", "ADDRESS", "STORE_F", + "STORE_V", "STORE_S", "STORE_ENT", "STORE_FLD", + "STORE_FNC", "STOREP_F", "STOREP_V", "STOREP_S", + "STOREP_ENT", "STOREP_FLD", "STOREP_FNC", "RETURN", + "NOT_F", "NOT_V", "NOT_S", "NOT_ENT", + "NOT_FNC", "IF", "IFNOT", "CALL0", + "CALL1", "CALL2", "CALL3", "CALL4", + "CALL5", "CALL6", "CALL7", "CALL8", + "STATE", "GOTO", "AND", "OR", + "BITAND", "BITOR" +}; + +/* + * only required if big endian .. otherwise no need to swap + * data. + */ +#if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_BIG || PLATFORM_BYTE_ORDER == -1 + static GMQCC_INLINE void util_swap16(uint16_t *d, size_t l) { + while (l--) { + d[l] = (d[l] << 8) | (d[l] >> 8); + } + } + + static GMQCC_INLINE void util_swap32(uint32_t *d, size_t l) { + while (l--) { + uint32_t v; + v = ((d[l] << 8) & 0xFF00FF00) | ((d[l] >> 8) & 0x00FF00FF); + d[l] = (v << 16) | (v >> 16); + } + } + + /* Some strange system doesn't like constants that big, AND doesn't recognize an ULL suffix + * so let's go the safe way + */ + static GMQCC_INLINE void util_swap64(uint32_t *d, size_t l) { + while (l--) { + uint64_t v; + v = ((d[l] << 8) & 0xFF00FF00FF00FF00) | ((d[l] >> 8) & 0x00FF00FF00FF00FF); + v = ((v << 16) & 0xFFFF0000FFFF0000) | ((v >> 16) & 0x0000FFFF0000FFFF); + d[l] = (v << 32) | (v >> 32); + } + } +#endif + +void util_endianswap(void *_data, size_t count, unsigned int typesize) { +# if PLATFORM_BYTE_ORDER == -1 /* runtime check */ + if (*((char*)&typesize)) + return; +#else + +# if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_LITTLE + /* prevent unused warnings */ + (void) _data; + (void) count; + (void) typesize; + return; +# else + switch (typesize) { + case 1: return; + case 2: + util_swap16((uint16_t*)_data, count); + return; + case 4: + util_swap32((uint32_t*)_data, count); + return; + case 8: + util_swap64((uint32_t*)_data, count); + return; + + default: + con_err ("util_endianswap: I don't know how to swap a %u byte structure!\n", typesize); + exit(EXIT_FAILURE); /* please blow the fuck up! */ + } +# endif +#endif +} + +void util_swap_header(prog_header_t *code_header) { + util_endianswap(&code_header->version, 1, sizeof(code_header->version)); + util_endianswap(&code_header->crc16, 1, sizeof(code_header->crc16)); + util_endianswap(&code_header->statements.offset, 1, sizeof(code_header->statements.offset)); + util_endianswap(&code_header->statements.length, 1, sizeof(code_header->statements.length)); + util_endianswap(&code_header->defs.offset, 1, sizeof(code_header->defs.offset)); + util_endianswap(&code_header->defs.length, 1, sizeof(code_header->defs.length)); + util_endianswap(&code_header->fields.offset, 1, sizeof(code_header->fields.offset)); + util_endianswap(&code_header->fields.length, 1, sizeof(code_header->fields.length)); + util_endianswap(&code_header->functions.offset, 1, sizeof(code_header->functions.offset)); + util_endianswap(&code_header->functions.length, 1, sizeof(code_header->functions.length)); + util_endianswap(&code_header->strings.offset, 1, sizeof(code_header->strings.offset)); + util_endianswap(&code_header->strings.length, 1, sizeof(code_header->strings.length)); + util_endianswap(&code_header->globals.offset, 1, sizeof(code_header->globals.offset)); + util_endianswap(&code_header->globals.length, 1, sizeof(code_header->globals.length)); + util_endianswap(&code_header->entfield, 1, sizeof(code_header->entfield)); +} + +void util_swap_statements(prog_section_statement_t *statements) { + size_t i; + + for (i = 0; i < vec_size(statements); ++i) { + util_endianswap(&statements[i].opcode, 1, sizeof(statements[i].opcode)); + util_endianswap(&statements[i].o1, 1, sizeof(statements[i].o1)); + util_endianswap(&statements[i].o2, 1, sizeof(statements[i].o2)); + util_endianswap(&statements[i].o3, 1, sizeof(statements[i].o3)); + } +} + +void util_swap_defs_fields(prog_section_both_t *section) { + size_t i; + + for (i = 0; i < vec_size(section); ++i) { + util_endianswap(§ion[i].type, 1, sizeof(section[i].type)); + util_endianswap(§ion[i].offset, 1, sizeof(section[i].offset)); + util_endianswap(§ion[i].name, 1, sizeof(section[i].name)); + } +} + +void util_swap_functions(prog_section_function_t *functions) { + size_t i; + + for (i = 0; i < vec_size(functions); ++i) { + util_endianswap(&functions[i].entry, 1, sizeof(functions[i].entry)); + util_endianswap(&functions[i].firstlocal, 1, sizeof(functions[i].firstlocal)); + util_endianswap(&functions[i].locals, 1, sizeof(functions[i].locals)); + util_endianswap(&functions[i].profile, 1, sizeof(functions[i].profile)); + util_endianswap(&functions[i].name, 1, sizeof(functions[i].name)); + util_endianswap(&functions[i].file, 1, sizeof(functions[i].file)); + util_endianswap(&functions[i].nargs, 1, sizeof(functions[i].nargs)); + /* Don't swap argsize[] - it's just a byte array, which Quake uses only as such. */ + } +} + +void util_swap_globals(int32_t *globals) { + util_endianswap(globals, vec_size(globals), sizeof(int32_t)); +} + +/* +* Based On: +* Slicing-by-8 algorithms by Michael E. +* Kounavis and Frank L. Berry from Intel Corp. +* http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf +* +* This code was made to be slightly less confusing with macros, which +* I suppose is somewhat ironic. +* +* The code had to be changed for non reflected on the output register +* since that's the method Quake uses. +* +* The code also had to be changed for CRC16, which is slightly harder +* since the CRC32 method in the original Intel paper used a different +* bit order convention. +* +* Notes about the table: +* - It's exactly 4K in size +* - 64 elements fit in a cache line +* - can do 8 iterations unrolled 8 times for free +* - The first 256 elements of the table are standard CRC16 table +* +* Table can be generated with the following utility: +*/ +#if 0 +#include +#include +int main(void) { + for (unsigned i = 0; i < 0x100; ++i) { + uint16_t x = i << 8; + for (int j = 0; j < 8; ++j) + x = (x << 1) ^ ((x & 0x8000) ? 0x1021 : 0); + tab[0][i] = x; + } + for (unsigned i = 0; i < 0x100; ++i) { + uint16_t c = tab[0][i]; + for (unsigned j = 1; j < 8; ++j) { + c = tab[0][c >> 8] ^ (c << 8); + tab[j][i] = c; + } + } + printf("static const uint16_t util_crc16_table[8][256] = {"); + for (int i = 0; i < 8; ++i) { + printf("{\n"); + for (int j = 0; j < 0x100; ++j) { + printf((j & 7) ? " " : " "); + printf((j != 0x100-1) ? "0x%04X," : "0x%04X", tab[i][j]); + if ((j & 7) == 7) + printf("\n"); + } + printf((i != 7) ? "}," : "}"); + } + printf("};\n"); + return 0; +} +#endif +/* + * Non-Reflective version is present as well as a reference. + * + * TODO: + * combine the crc16 into u32s and mask off low high for byte order + * to make the arrays smaller. + */ + +static const uint16_t util_crc16_table[8][256] = {{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +},{ + 0x0000, 0x3331, 0x6662, 0x5553, 0xCCC4, 0xFFF5, 0xAAA6, 0x9997, + 0x89A9, 0xBA98, 0xEFCB, 0xDCFA, 0x456D, 0x765C, 0x230F, 0x103E, + 0x0373, 0x3042, 0x6511, 0x5620, 0xCFB7, 0xFC86, 0xA9D5, 0x9AE4, + 0x8ADA, 0xB9EB, 0xECB8, 0xDF89, 0x461E, 0x752F, 0x207C, 0x134D, + 0x06E6, 0x35D7, 0x6084, 0x53B5, 0xCA22, 0xF913, 0xAC40, 0x9F71, + 0x8F4F, 0xBC7E, 0xE92D, 0xDA1C, 0x438B, 0x70BA, 0x25E9, 0x16D8, + 0x0595, 0x36A4, 0x63F7, 0x50C6, 0xC951, 0xFA60, 0xAF33, 0x9C02, + 0x8C3C, 0xBF0D, 0xEA5E, 0xD96F, 0x40F8, 0x73C9, 0x269A, 0x15AB, + 0x0DCC, 0x3EFD, 0x6BAE, 0x589F, 0xC108, 0xF239, 0xA76A, 0x945B, + 0x8465, 0xB754, 0xE207, 0xD136, 0x48A1, 0x7B90, 0x2EC3, 0x1DF2, + 0x0EBF, 0x3D8E, 0x68DD, 0x5BEC, 0xC27B, 0xF14A, 0xA419, 0x9728, + 0x8716, 0xB427, 0xE174, 0xD245, 0x4BD2, 0x78E3, 0x2DB0, 0x1E81, + 0x0B2A, 0x381B, 0x6D48, 0x5E79, 0xC7EE, 0xF4DF, 0xA18C, 0x92BD, + 0x8283, 0xB1B2, 0xE4E1, 0xD7D0, 0x4E47, 0x7D76, 0x2825, 0x1B14, + 0x0859, 0x3B68, 0x6E3B, 0x5D0A, 0xC49D, 0xF7AC, 0xA2FF, 0x91CE, + 0x81F0, 0xB2C1, 0xE792, 0xD4A3, 0x4D34, 0x7E05, 0x2B56, 0x1867, + 0x1B98, 0x28A9, 0x7DFA, 0x4ECB, 0xD75C, 0xE46D, 0xB13E, 0x820F, + 0x9231, 0xA100, 0xF453, 0xC762, 0x5EF5, 0x6DC4, 0x3897, 0x0BA6, + 0x18EB, 0x2BDA, 0x7E89, 0x4DB8, 0xD42F, 0xE71E, 0xB24D, 0x817C, + 0x9142, 0xA273, 0xF720, 0xC411, 0x5D86, 0x6EB7, 0x3BE4, 0x08D5, + 0x1D7E, 0x2E4F, 0x7B1C, 0x482D, 0xD1BA, 0xE28B, 0xB7D8, 0x84E9, + 0x94D7, 0xA7E6, 0xF2B5, 0xC184, 0x5813, 0x6B22, 0x3E71, 0x0D40, + 0x1E0D, 0x2D3C, 0x786F, 0x4B5E, 0xD2C9, 0xE1F8, 0xB4AB, 0x879A, + 0x97A4, 0xA495, 0xF1C6, 0xC2F7, 0x5B60, 0x6851, 0x3D02, 0x0E33, + 0x1654, 0x2565, 0x7036, 0x4307, 0xDA90, 0xE9A1, 0xBCF2, 0x8FC3, + 0x9FFD, 0xACCC, 0xF99F, 0xCAAE, 0x5339, 0x6008, 0x355B, 0x066A, + 0x1527, 0x2616, 0x7345, 0x4074, 0xD9E3, 0xEAD2, 0xBF81, 0x8CB0, + 0x9C8E, 0xAFBF, 0xFAEC, 0xC9DD, 0x504A, 0x637B, 0x3628, 0x0519, + 0x10B2, 0x2383, 0x76D0, 0x45E1, 0xDC76, 0xEF47, 0xBA14, 0x8925, + 0x991B, 0xAA2A, 0xFF79, 0xCC48, 0x55DF, 0x66EE, 0x33BD, 0x008C, + 0x13C1, 0x20F0, 0x75A3, 0x4692, 0xDF05, 0xEC34, 0xB967, 0x8A56, + 0x9A68, 0xA959, 0xFC0A, 0xCF3B, 0x56AC, 0x659D, 0x30CE, 0x03FF +},{ + 0x0000, 0x3730, 0x6E60, 0x5950, 0xDCC0, 0xEBF0, 0xB2A0, 0x8590, + 0xA9A1, 0x9E91, 0xC7C1, 0xF0F1, 0x7561, 0x4251, 0x1B01, 0x2C31, + 0x4363, 0x7453, 0x2D03, 0x1A33, 0x9FA3, 0xA893, 0xF1C3, 0xC6F3, + 0xEAC2, 0xDDF2, 0x84A2, 0xB392, 0x3602, 0x0132, 0x5862, 0x6F52, + 0x86C6, 0xB1F6, 0xE8A6, 0xDF96, 0x5A06, 0x6D36, 0x3466, 0x0356, + 0x2F67, 0x1857, 0x4107, 0x7637, 0xF3A7, 0xC497, 0x9DC7, 0xAAF7, + 0xC5A5, 0xF295, 0xABC5, 0x9CF5, 0x1965, 0x2E55, 0x7705, 0x4035, + 0x6C04, 0x5B34, 0x0264, 0x3554, 0xB0C4, 0x87F4, 0xDEA4, 0xE994, + 0x1DAD, 0x2A9D, 0x73CD, 0x44FD, 0xC16D, 0xF65D, 0xAF0D, 0x983D, + 0xB40C, 0x833C, 0xDA6C, 0xED5C, 0x68CC, 0x5FFC, 0x06AC, 0x319C, + 0x5ECE, 0x69FE, 0x30AE, 0x079E, 0x820E, 0xB53E, 0xEC6E, 0xDB5E, + 0xF76F, 0xC05F, 0x990F, 0xAE3F, 0x2BAF, 0x1C9F, 0x45CF, 0x72FF, + 0x9B6B, 0xAC5B, 0xF50B, 0xC23B, 0x47AB, 0x709B, 0x29CB, 0x1EFB, + 0x32CA, 0x05FA, 0x5CAA, 0x6B9A, 0xEE0A, 0xD93A, 0x806A, 0xB75A, + 0xD808, 0xEF38, 0xB668, 0x8158, 0x04C8, 0x33F8, 0x6AA8, 0x5D98, + 0x71A9, 0x4699, 0x1FC9, 0x28F9, 0xAD69, 0x9A59, 0xC309, 0xF439, + 0x3B5A, 0x0C6A, 0x553A, 0x620A, 0xE79A, 0xD0AA, 0x89FA, 0xBECA, + 0x92FB, 0xA5CB, 0xFC9B, 0xCBAB, 0x4E3B, 0x790B, 0x205B, 0x176B, + 0x7839, 0x4F09, 0x1659, 0x2169, 0xA4F9, 0x93C9, 0xCA99, 0xFDA9, + 0xD198, 0xE6A8, 0xBFF8, 0x88C8, 0x0D58, 0x3A68, 0x6338, 0x5408, + 0xBD9C, 0x8AAC, 0xD3FC, 0xE4CC, 0x615C, 0x566C, 0x0F3C, 0x380C, + 0x143D, 0x230D, 0x7A5D, 0x4D6D, 0xC8FD, 0xFFCD, 0xA69D, 0x91AD, + 0xFEFF, 0xC9CF, 0x909F, 0xA7AF, 0x223F, 0x150F, 0x4C5F, 0x7B6F, + 0x575E, 0x606E, 0x393E, 0x0E0E, 0x8B9E, 0xBCAE, 0xE5FE, 0xD2CE, + 0x26F7, 0x11C7, 0x4897, 0x7FA7, 0xFA37, 0xCD07, 0x9457, 0xA367, + 0x8F56, 0xB866, 0xE136, 0xD606, 0x5396, 0x64A6, 0x3DF6, 0x0AC6, + 0x6594, 0x52A4, 0x0BF4, 0x3CC4, 0xB954, 0x8E64, 0xD734, 0xE004, + 0xCC35, 0xFB05, 0xA255, 0x9565, 0x10F5, 0x27C5, 0x7E95, 0x49A5, + 0xA031, 0x9701, 0xCE51, 0xF961, 0x7CF1, 0x4BC1, 0x1291, 0x25A1, + 0x0990, 0x3EA0, 0x67F0, 0x50C0, 0xD550, 0xE260, 0xBB30, 0x8C00, + 0xE352, 0xD462, 0x8D32, 0xBA02, 0x3F92, 0x08A2, 0x51F2, 0x66C2, + 0x4AF3, 0x7DC3, 0x2493, 0x13A3, 0x9633, 0xA103, 0xF853, 0xCF63 +},{ + 0x0000, 0x76B4, 0xED68, 0x9BDC, 0xCAF1, 0xBC45, 0x2799, 0x512D, + 0x85C3, 0xF377, 0x68AB, 0x1E1F, 0x4F32, 0x3986, 0xA25A, 0xD4EE, + 0x1BA7, 0x6D13, 0xF6CF, 0x807B, 0xD156, 0xA7E2, 0x3C3E, 0x4A8A, + 0x9E64, 0xE8D0, 0x730C, 0x05B8, 0x5495, 0x2221, 0xB9FD, 0xCF49, + 0x374E, 0x41FA, 0xDA26, 0xAC92, 0xFDBF, 0x8B0B, 0x10D7, 0x6663, + 0xB28D, 0xC439, 0x5FE5, 0x2951, 0x787C, 0x0EC8, 0x9514, 0xE3A0, + 0x2CE9, 0x5A5D, 0xC181, 0xB735, 0xE618, 0x90AC, 0x0B70, 0x7DC4, + 0xA92A, 0xDF9E, 0x4442, 0x32F6, 0x63DB, 0x156F, 0x8EB3, 0xF807, + 0x6E9C, 0x1828, 0x83F4, 0xF540, 0xA46D, 0xD2D9, 0x4905, 0x3FB1, + 0xEB5F, 0x9DEB, 0x0637, 0x7083, 0x21AE, 0x571A, 0xCCC6, 0xBA72, + 0x753B, 0x038F, 0x9853, 0xEEE7, 0xBFCA, 0xC97E, 0x52A2, 0x2416, + 0xF0F8, 0x864C, 0x1D90, 0x6B24, 0x3A09, 0x4CBD, 0xD761, 0xA1D5, + 0x59D2, 0x2F66, 0xB4BA, 0xC20E, 0x9323, 0xE597, 0x7E4B, 0x08FF, + 0xDC11, 0xAAA5, 0x3179, 0x47CD, 0x16E0, 0x6054, 0xFB88, 0x8D3C, + 0x4275, 0x34C1, 0xAF1D, 0xD9A9, 0x8884, 0xFE30, 0x65EC, 0x1358, + 0xC7B6, 0xB102, 0x2ADE, 0x5C6A, 0x0D47, 0x7BF3, 0xE02F, 0x969B, + 0xDD38, 0xAB8C, 0x3050, 0x46E4, 0x17C9, 0x617D, 0xFAA1, 0x8C15, + 0x58FB, 0x2E4F, 0xB593, 0xC327, 0x920A, 0xE4BE, 0x7F62, 0x09D6, + 0xC69F, 0xB02B, 0x2BF7, 0x5D43, 0x0C6E, 0x7ADA, 0xE106, 0x97B2, + 0x435C, 0x35E8, 0xAE34, 0xD880, 0x89AD, 0xFF19, 0x64C5, 0x1271, + 0xEA76, 0x9CC2, 0x071E, 0x71AA, 0x2087, 0x5633, 0xCDEF, 0xBB5B, + 0x6FB5, 0x1901, 0x82DD, 0xF469, 0xA544, 0xD3F0, 0x482C, 0x3E98, + 0xF1D1, 0x8765, 0x1CB9, 0x6A0D, 0x3B20, 0x4D94, 0xD648, 0xA0FC, + 0x7412, 0x02A6, 0x997A, 0xEFCE, 0xBEE3, 0xC857, 0x538B, 0x253F, + 0xB3A4, 0xC510, 0x5ECC, 0x2878, 0x7955, 0x0FE1, 0x943D, 0xE289, + 0x3667, 0x40D3, 0xDB0F, 0xADBB, 0xFC96, 0x8A22, 0x11FE, 0x674A, + 0xA803, 0xDEB7, 0x456B, 0x33DF, 0x62F2, 0x1446, 0x8F9A, 0xF92E, + 0x2DC0, 0x5B74, 0xC0A8, 0xB61C, 0xE731, 0x9185, 0x0A59, 0x7CED, + 0x84EA, 0xF25E, 0x6982, 0x1F36, 0x4E1B, 0x38AF, 0xA373, 0xD5C7, + 0x0129, 0x779D, 0xEC41, 0x9AF5, 0xCBD8, 0xBD6C, 0x26B0, 0x5004, + 0x9F4D, 0xE9F9, 0x7225, 0x0491, 0x55BC, 0x2308, 0xB8D4, 0xCE60, + 0x1A8E, 0x6C3A, 0xF7E6, 0x8152, 0xD07F, 0xA6CB, 0x3D17, 0x4BA3 +},{ + 0x0000, 0xAA51, 0x4483, 0xEED2, 0x8906, 0x2357, 0xCD85, 0x67D4, + 0x022D, 0xA87C, 0x46AE, 0xECFF, 0x8B2B, 0x217A, 0xCFA8, 0x65F9, + 0x045A, 0xAE0B, 0x40D9, 0xEA88, 0x8D5C, 0x270D, 0xC9DF, 0x638E, + 0x0677, 0xAC26, 0x42F4, 0xE8A5, 0x8F71, 0x2520, 0xCBF2, 0x61A3, + 0x08B4, 0xA2E5, 0x4C37, 0xE666, 0x81B2, 0x2BE3, 0xC531, 0x6F60, + 0x0A99, 0xA0C8, 0x4E1A, 0xE44B, 0x839F, 0x29CE, 0xC71C, 0x6D4D, + 0x0CEE, 0xA6BF, 0x486D, 0xE23C, 0x85E8, 0x2FB9, 0xC16B, 0x6B3A, + 0x0EC3, 0xA492, 0x4A40, 0xE011, 0x87C5, 0x2D94, 0xC346, 0x6917, + 0x1168, 0xBB39, 0x55EB, 0xFFBA, 0x986E, 0x323F, 0xDCED, 0x76BC, + 0x1345, 0xB914, 0x57C6, 0xFD97, 0x9A43, 0x3012, 0xDEC0, 0x7491, + 0x1532, 0xBF63, 0x51B1, 0xFBE0, 0x9C34, 0x3665, 0xD8B7, 0x72E6, + 0x171F, 0xBD4E, 0x539C, 0xF9CD, 0x9E19, 0x3448, 0xDA9A, 0x70CB, + 0x19DC, 0xB38D, 0x5D5F, 0xF70E, 0x90DA, 0x3A8B, 0xD459, 0x7E08, + 0x1BF1, 0xB1A0, 0x5F72, 0xF523, 0x92F7, 0x38A6, 0xD674, 0x7C25, + 0x1D86, 0xB7D7, 0x5905, 0xF354, 0x9480, 0x3ED1, 0xD003, 0x7A52, + 0x1FAB, 0xB5FA, 0x5B28, 0xF179, 0x96AD, 0x3CFC, 0xD22E, 0x787F, + 0x22D0, 0x8881, 0x6653, 0xCC02, 0xABD6, 0x0187, 0xEF55, 0x4504, + 0x20FD, 0x8AAC, 0x647E, 0xCE2F, 0xA9FB, 0x03AA, 0xED78, 0x4729, + 0x268A, 0x8CDB, 0x6209, 0xC858, 0xAF8C, 0x05DD, 0xEB0F, 0x415E, + 0x24A7, 0x8EF6, 0x6024, 0xCA75, 0xADA1, 0x07F0, 0xE922, 0x4373, + 0x2A64, 0x8035, 0x6EE7, 0xC4B6, 0xA362, 0x0933, 0xE7E1, 0x4DB0, + 0x2849, 0x8218, 0x6CCA, 0xC69B, 0xA14F, 0x0B1E, 0xE5CC, 0x4F9D, + 0x2E3E, 0x846F, 0x6ABD, 0xC0EC, 0xA738, 0x0D69, 0xE3BB, 0x49EA, + 0x2C13, 0x8642, 0x6890, 0xC2C1, 0xA515, 0x0F44, 0xE196, 0x4BC7, + 0x33B8, 0x99E9, 0x773B, 0xDD6A, 0xBABE, 0x10EF, 0xFE3D, 0x546C, + 0x3195, 0x9BC4, 0x7516, 0xDF47, 0xB893, 0x12C2, 0xFC10, 0x5641, + 0x37E2, 0x9DB3, 0x7361, 0xD930, 0xBEE4, 0x14B5, 0xFA67, 0x5036, + 0x35CF, 0x9F9E, 0x714C, 0xDB1D, 0xBCC9, 0x1698, 0xF84A, 0x521B, + 0x3B0C, 0x915D, 0x7F8F, 0xD5DE, 0xB20A, 0x185B, 0xF689, 0x5CD8, + 0x3921, 0x9370, 0x7DA2, 0xD7F3, 0xB027, 0x1A76, 0xF4A4, 0x5EF5, + 0x3F56, 0x9507, 0x7BD5, 0xD184, 0xB650, 0x1C01, 0xF2D3, 0x5882, + 0x3D7B, 0x972A, 0x79F8, 0xD3A9, 0xB47D, 0x1E2C, 0xF0FE, 0x5AAF +},{ + 0x0000, 0x45A0, 0x8B40, 0xCEE0, 0x06A1, 0x4301, 0x8DE1, 0xC841, + 0x0D42, 0x48E2, 0x8602, 0xC3A2, 0x0BE3, 0x4E43, 0x80A3, 0xC503, + 0x1A84, 0x5F24, 0x91C4, 0xD464, 0x1C25, 0x5985, 0x9765, 0xD2C5, + 0x17C6, 0x5266, 0x9C86, 0xD926, 0x1167, 0x54C7, 0x9A27, 0xDF87, + 0x3508, 0x70A8, 0xBE48, 0xFBE8, 0x33A9, 0x7609, 0xB8E9, 0xFD49, + 0x384A, 0x7DEA, 0xB30A, 0xF6AA, 0x3EEB, 0x7B4B, 0xB5AB, 0xF00B, + 0x2F8C, 0x6A2C, 0xA4CC, 0xE16C, 0x292D, 0x6C8D, 0xA26D, 0xE7CD, + 0x22CE, 0x676E, 0xA98E, 0xEC2E, 0x246F, 0x61CF, 0xAF2F, 0xEA8F, + 0x6A10, 0x2FB0, 0xE150, 0xA4F0, 0x6CB1, 0x2911, 0xE7F1, 0xA251, + 0x6752, 0x22F2, 0xEC12, 0xA9B2, 0x61F3, 0x2453, 0xEAB3, 0xAF13, + 0x7094, 0x3534, 0xFBD4, 0xBE74, 0x7635, 0x3395, 0xFD75, 0xB8D5, + 0x7DD6, 0x3876, 0xF696, 0xB336, 0x7B77, 0x3ED7, 0xF037, 0xB597, + 0x5F18, 0x1AB8, 0xD458, 0x91F8, 0x59B9, 0x1C19, 0xD2F9, 0x9759, + 0x525A, 0x17FA, 0xD91A, 0x9CBA, 0x54FB, 0x115B, 0xDFBB, 0x9A1B, + 0x459C, 0x003C, 0xCEDC, 0x8B7C, 0x433D, 0x069D, 0xC87D, 0x8DDD, + 0x48DE, 0x0D7E, 0xC39E, 0x863E, 0x4E7F, 0x0BDF, 0xC53F, 0x809F, + 0xD420, 0x9180, 0x5F60, 0x1AC0, 0xD281, 0x9721, 0x59C1, 0x1C61, + 0xD962, 0x9CC2, 0x5222, 0x1782, 0xDFC3, 0x9A63, 0x5483, 0x1123, + 0xCEA4, 0x8B04, 0x45E4, 0x0044, 0xC805, 0x8DA5, 0x4345, 0x06E5, + 0xC3E6, 0x8646, 0x48A6, 0x0D06, 0xC547, 0x80E7, 0x4E07, 0x0BA7, + 0xE128, 0xA488, 0x6A68, 0x2FC8, 0xE789, 0xA229, 0x6CC9, 0x2969, + 0xEC6A, 0xA9CA, 0x672A, 0x228A, 0xEACB, 0xAF6B, 0x618B, 0x242B, + 0xFBAC, 0xBE0C, 0x70EC, 0x354C, 0xFD0D, 0xB8AD, 0x764D, 0x33ED, + 0xF6EE, 0xB34E, 0x7DAE, 0x380E, 0xF04F, 0xB5EF, 0x7B0F, 0x3EAF, + 0xBE30, 0xFB90, 0x3570, 0x70D0, 0xB891, 0xFD31, 0x33D1, 0x7671, + 0xB372, 0xF6D2, 0x3832, 0x7D92, 0xB5D3, 0xF073, 0x3E93, 0x7B33, + 0xA4B4, 0xE114, 0x2FF4, 0x6A54, 0xA215, 0xE7B5, 0x2955, 0x6CF5, + 0xA9F6, 0xEC56, 0x22B6, 0x6716, 0xAF57, 0xEAF7, 0x2417, 0x61B7, + 0x8B38, 0xCE98, 0x0078, 0x45D8, 0x8D99, 0xC839, 0x06D9, 0x4379, + 0x867A, 0xC3DA, 0x0D3A, 0x489A, 0x80DB, 0xC57B, 0x0B9B, 0x4E3B, + 0x91BC, 0xD41C, 0x1AFC, 0x5F5C, 0x971D, 0xD2BD, 0x1C5D, 0x59FD, + 0x9CFE, 0xD95E, 0x17BE, 0x521E, 0x9A5F, 0xDFFF, 0x111F, 0x54BF +},{ + 0x0000, 0xB861, 0x60E3, 0xD882, 0xC1C6, 0x79A7, 0xA125, 0x1944, + 0x93AD, 0x2BCC, 0xF34E, 0x4B2F, 0x526B, 0xEA0A, 0x3288, 0x8AE9, + 0x377B, 0x8F1A, 0x5798, 0xEFF9, 0xF6BD, 0x4EDC, 0x965E, 0x2E3F, + 0xA4D6, 0x1CB7, 0xC435, 0x7C54, 0x6510, 0xDD71, 0x05F3, 0xBD92, + 0x6EF6, 0xD697, 0x0E15, 0xB674, 0xAF30, 0x1751, 0xCFD3, 0x77B2, + 0xFD5B, 0x453A, 0x9DB8, 0x25D9, 0x3C9D, 0x84FC, 0x5C7E, 0xE41F, + 0x598D, 0xE1EC, 0x396E, 0x810F, 0x984B, 0x202A, 0xF8A8, 0x40C9, + 0xCA20, 0x7241, 0xAAC3, 0x12A2, 0x0BE6, 0xB387, 0x6B05, 0xD364, + 0xDDEC, 0x658D, 0xBD0F, 0x056E, 0x1C2A, 0xA44B, 0x7CC9, 0xC4A8, + 0x4E41, 0xF620, 0x2EA2, 0x96C3, 0x8F87, 0x37E6, 0xEF64, 0x5705, + 0xEA97, 0x52F6, 0x8A74, 0x3215, 0x2B51, 0x9330, 0x4BB2, 0xF3D3, + 0x793A, 0xC15B, 0x19D9, 0xA1B8, 0xB8FC, 0x009D, 0xD81F, 0x607E, + 0xB31A, 0x0B7B, 0xD3F9, 0x6B98, 0x72DC, 0xCABD, 0x123F, 0xAA5E, + 0x20B7, 0x98D6, 0x4054, 0xF835, 0xE171, 0x5910, 0x8192, 0x39F3, + 0x8461, 0x3C00, 0xE482, 0x5CE3, 0x45A7, 0xFDC6, 0x2544, 0x9D25, + 0x17CC, 0xAFAD, 0x772F, 0xCF4E, 0xD60A, 0x6E6B, 0xB6E9, 0x0E88, + 0xABF9, 0x1398, 0xCB1A, 0x737B, 0x6A3F, 0xD25E, 0x0ADC, 0xB2BD, + 0x3854, 0x8035, 0x58B7, 0xE0D6, 0xF992, 0x41F3, 0x9971, 0x2110, + 0x9C82, 0x24E3, 0xFC61, 0x4400, 0x5D44, 0xE525, 0x3DA7, 0x85C6, + 0x0F2F, 0xB74E, 0x6FCC, 0xD7AD, 0xCEE9, 0x7688, 0xAE0A, 0x166B, + 0xC50F, 0x7D6E, 0xA5EC, 0x1D8D, 0x04C9, 0xBCA8, 0x642A, 0xDC4B, + 0x56A2, 0xEEC3, 0x3641, 0x8E20, 0x9764, 0x2F05, 0xF787, 0x4FE6, + 0xF274, 0x4A15, 0x9297, 0x2AF6, 0x33B2, 0x8BD3, 0x5351, 0xEB30, + 0x61D9, 0xD9B8, 0x013A, 0xB95B, 0xA01F, 0x187E, 0xC0FC, 0x789D, + 0x7615, 0xCE74, 0x16F6, 0xAE97, 0xB7D3, 0x0FB2, 0xD730, 0x6F51, + 0xE5B8, 0x5DD9, 0x855B, 0x3D3A, 0x247E, 0x9C1F, 0x449D, 0xFCFC, + 0x416E, 0xF90F, 0x218D, 0x99EC, 0x80A8, 0x38C9, 0xE04B, 0x582A, + 0xD2C3, 0x6AA2, 0xB220, 0x0A41, 0x1305, 0xAB64, 0x73E6, 0xCB87, + 0x18E3, 0xA082, 0x7800, 0xC061, 0xD925, 0x6144, 0xB9C6, 0x01A7, + 0x8B4E, 0x332F, 0xEBAD, 0x53CC, 0x4A88, 0xF2E9, 0x2A6B, 0x920A, + 0x2F98, 0x97F9, 0x4F7B, 0xF71A, 0xEE5E, 0x563F, 0x8EBD, 0x36DC, + 0xBC35, 0x0454, 0xDCD6, 0x64B7, 0x7DF3, 0xC592, 0x1D10, 0xA571 +},{ + 0x0000, 0x47D3, 0x8FA6, 0xC875, 0x0F6D, 0x48BE, 0x80CB, 0xC718, + 0x1EDA, 0x5909, 0x917C, 0xD6AF, 0x11B7, 0x5664, 0x9E11, 0xD9C2, + 0x3DB4, 0x7A67, 0xB212, 0xF5C1, 0x32D9, 0x750A, 0xBD7F, 0xFAAC, + 0x236E, 0x64BD, 0xACC8, 0xEB1B, 0x2C03, 0x6BD0, 0xA3A5, 0xE476, + 0x7B68, 0x3CBB, 0xF4CE, 0xB31D, 0x7405, 0x33D6, 0xFBA3, 0xBC70, + 0x65B2, 0x2261, 0xEA14, 0xADC7, 0x6ADF, 0x2D0C, 0xE579, 0xA2AA, + 0x46DC, 0x010F, 0xC97A, 0x8EA9, 0x49B1, 0x0E62, 0xC617, 0x81C4, + 0x5806, 0x1FD5, 0xD7A0, 0x9073, 0x576B, 0x10B8, 0xD8CD, 0x9F1E, + 0xF6D0, 0xB103, 0x7976, 0x3EA5, 0xF9BD, 0xBE6E, 0x761B, 0x31C8, + 0xE80A, 0xAFD9, 0x67AC, 0x207F, 0xE767, 0xA0B4, 0x68C1, 0x2F12, + 0xCB64, 0x8CB7, 0x44C2, 0x0311, 0xC409, 0x83DA, 0x4BAF, 0x0C7C, + 0xD5BE, 0x926D, 0x5A18, 0x1DCB, 0xDAD3, 0x9D00, 0x5575, 0x12A6, + 0x8DB8, 0xCA6B, 0x021E, 0x45CD, 0x82D5, 0xC506, 0x0D73, 0x4AA0, + 0x9362, 0xD4B1, 0x1CC4, 0x5B17, 0x9C0F, 0xDBDC, 0x13A9, 0x547A, + 0xB00C, 0xF7DF, 0x3FAA, 0x7879, 0xBF61, 0xF8B2, 0x30C7, 0x7714, + 0xAED6, 0xE905, 0x2170, 0x66A3, 0xA1BB, 0xE668, 0x2E1D, 0x69CE, + 0xFD81, 0xBA52, 0x7227, 0x35F4, 0xF2EC, 0xB53F, 0x7D4A, 0x3A99, + 0xE35B, 0xA488, 0x6CFD, 0x2B2E, 0xEC36, 0xABE5, 0x6390, 0x2443, + 0xC035, 0x87E6, 0x4F93, 0x0840, 0xCF58, 0x888B, 0x40FE, 0x072D, + 0xDEEF, 0x993C, 0x5149, 0x169A, 0xD182, 0x9651, 0x5E24, 0x19F7, + 0x86E9, 0xC13A, 0x094F, 0x4E9C, 0x8984, 0xCE57, 0x0622, 0x41F1, + 0x9833, 0xDFE0, 0x1795, 0x5046, 0x975E, 0xD08D, 0x18F8, 0x5F2B, + 0xBB5D, 0xFC8E, 0x34FB, 0x7328, 0xB430, 0xF3E3, 0x3B96, 0x7C45, + 0xA587, 0xE254, 0x2A21, 0x6DF2, 0xAAEA, 0xED39, 0x254C, 0x629F, + 0x0B51, 0x4C82, 0x84F7, 0xC324, 0x043C, 0x43EF, 0x8B9A, 0xCC49, + 0x158B, 0x5258, 0x9A2D, 0xDDFE, 0x1AE6, 0x5D35, 0x9540, 0xD293, + 0x36E5, 0x7136, 0xB943, 0xFE90, 0x3988, 0x7E5B, 0xB62E, 0xF1FD, + 0x283F, 0x6FEC, 0xA799, 0xE04A, 0x2752, 0x6081, 0xA8F4, 0xEF27, + 0x7039, 0x37EA, 0xFF9F, 0xB84C, 0x7F54, 0x3887, 0xF0F2, 0xB721, + 0x6EE3, 0x2930, 0xE145, 0xA696, 0x618E, 0x265D, 0xEE28, 0xA9FB, + 0x4D8D, 0x0A5E, 0xC22B, 0x85F8, 0x42E0, 0x0533, 0xCD46, 0x8A95, + 0x5357, 0x1484, 0xDCF1, 0x9B22, 0x5C3A, 0x1BE9, 0xD39C, 0x944F +}}; + +/* Non - Reflected */ +uint16_t util_crc16(uint16_t current, const char *GMQCC_RESTRICT k, size_t len) { + uint16_t h = current; + + /* don't load twice */ + const uint8_t *GMQCC_RESTRICT data = (const uint8_t *GMQCC_RESTRICT)k; + size_t n; + + /* deal with the first bytes as bytes until we reach an 8 byte boundary */ + while (len & 7) { + h = (uint16_t)(h << 8) ^ (*util_crc16_table)[(h >> 8) ^ *data++]; + --len; + } + + #define SELECT_BULK(X, MOD) util_crc16_table[(X)][data[7-(X)] ^ (MOD)] + #define SELECT_DATA(X) util_crc16_table[(X)][data[7-(X)]] + + for (n = len / 8; n; --n) { + h = SELECT_BULK(7, (h >> 8)) ^ + SELECT_BULK(6, (h & 0xFF)) ^ + SELECT_DATA(5) ^ + SELECT_DATA(4) ^ + SELECT_DATA(3) ^ + SELECT_DATA(2) ^ + SELECT_DATA(1) ^ + SELECT_DATA(0); + data += 8; + len -= 8; + } + + #undef SELECT_BULK + #undef SELECT_DATA + + /* deal with the rest with the byte method */ + for (n = len & 7; n; --n) + h = (uint16_t)(h << 8) ^ (*util_crc16_table)[(h >> 8) ^ *data++]; + + return h; +} + +/* + * modifier is the match to make and the transposition from it, while add is the upper-value that determines the + * transposition from uppercase to lower case. + */ +static size_t util_strtransform(const char *in, char *out, size_t outsz, const char *mod, int add) { + size_t sz = 1; + for (; *in && sz < outsz; ++in, ++out, ++sz) { + *out = (*in == mod[0]) + ? mod[1] + : (util_isalpha(*in) && ((add > 0) ? util_isupper(*in) : !util_isupper(*in))) + ? *in + add + : *in; + } + *out = 0; + return sz-1; +} + +size_t util_strtocmd(const char *in, char *out, size_t outsz) { + return util_strtransform(in, out, outsz, "-_", 'A'-'a'); +} +size_t util_strtononcmd(const char *in, char *out, size_t outsz) { + return util_strtransform(in, out, outsz, "_-", 'a'-'A'); +} +size_t util_optimizationtostr(const char *in, char *out, size_t outsz) { + return util_strtransform(in, out, outsz, "_ ", 'a'-'A'); +} + +static int util_vasprintf(char **dat, const char *fmt, va_list args) { + int ret; + int len; + char *tmp = NULL; + char buf[128]; + va_list cpy; + + va_copy(cpy, 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; + } + + tmp = (char*)mem_a(len + 1); + if ((ret = vsnprintf(tmp, len + 1, fmt, args)) != len) { + mem_d(tmp); + *dat = NULL; + return -1; + } + + *dat = tmp; + return len; +} + +int util_snprintf(char *str, size_t size, const char *fmt, ...) { + va_list arg; + int ret; + va_start(arg, fmt); + ret = vsnprintf(str, size, fmt, arg); + va_end(arg); + return ret; +} + +int util_asprintf(char **ret, const char *fmt, ...) { + va_list args; + int read; + va_start(args, fmt); + read = util_vasprintf(ret, fmt, args); + va_end(args); + return read; +} + +int util_sscanf(const char *str, const char *format, ...) { + va_list args; + int read; + va_start(args, format); + read = vsscanf(str, format, args); + va_end(args); + return read; +} + +char *util_strncpy(char *dest, const char *src, size_t n) { + return strncpy(dest, src, n); +} + +char *util_strncat(char *dest, const char *src, size_t n) { + return strncat(dest, src, n); +} + +char *util_strcat(char *dest, const char *src) { + return strcat(dest, src); +} + +const char *util_strerror(int err) { + return strerror(err); +} + +const struct tm *util_localtime(const time_t *timer) { + return localtime(timer); +} + +const char *util_ctime(const time_t *timer) { + return ctime(timer); +} + +int util_getline(char **lineptr, size_t *n, FILE *stream) { + int chr; + int ret; + char *pos; + + if (!lineptr || !n || !stream) + return -1; + if (!*lineptr) { + if (!(*lineptr = (char*)mem_a((*n=64)))) + return -1; + } + + chr = *n; + pos = *lineptr; + + for (;;) { + int c = getc(stream); + + if (chr < 2) { + *n += (*n > 16) ? *n : 64; + chr = *n + *lineptr - pos; + if (!(*lineptr = (char*)mem_r(*lineptr,*n))) + return -1; + pos = *n - chr + *lineptr; + } + + if (ferror(stream)) + return -1; + if (c == EOF) { + if (pos == *lineptr) + return -1; + else + break; + } + + *pos++ = c; + chr--; + if (c == '\n') + break; + } + *pos = '\0'; + return (ret = pos - *lineptr); +} + +#ifndef _WIN32 +#include +bool util_isatty(FILE *file) { + if (file == stdout) return !!isatty(STDOUT_FILENO); + if (file == stderr) return !!isatty(STDERR_FILENO); + return false; +} +#else +bool util_isatty(FILE *file) { + return false; +} +#endif + +/* + * A small noncryptographic PRNG based on: + * http://burtleburtle.net/bob/rand/smallprng.html + */ +static uint32_t util_rand_state[4] = { + 0xF1EA5EED, 0x00000000, + 0x00000000, 0x00000000 +}; + +#define util_rand_rot(X, Y) (((X)<<(Y))|((X)>>(32-(Y)))) + +uint32_t util_rand() { + uint32_t last; + + last = util_rand_state[0] - util_rand_rot(util_rand_state[1], 27); + util_rand_state[0] = util_rand_state[1] ^ util_rand_rot(util_rand_state[2], 17); + util_rand_state[1] = util_rand_state[2] + util_rand_state[3]; + util_rand_state[2] = util_rand_state[3] + last; + util_rand_state[3] = util_rand_state[0] + last; + + return util_rand_state[3]; +} + +#undef util_rand_rot + +void util_seed(uint32_t value) { + size_t i; + + util_rand_state[0] = 0xF1EA5EED; + util_rand_state[1] = value; + util_rand_state[2] = value; + util_rand_state[3] = value; + + for (i = 0; i < 20; ++i) + (void)util_rand(); +} + +size_t hash(const char *string) { + size_t hash = 0; + for(; *string; ++string) { + hash += *string; + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += hash << 3; + hash ^= hash >> 11; + hash += hash << 15; + return hash; +} +