-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
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)
+++ /dev/null
-#include <stdlib.h>
-#include <string.h>
-
-#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 <stdio.h>
-bool ast_state_codegen(ast_state *self, ast_function *func, bool lvalue, ir_value **out)
-{
- ast_expression_codegen *cgen;
-
- ir_value *frameval, *thinkval;
-
- if (lvalue) {
- compile_error(ast_ctx(self), "not an l-value (state operation)");
- return false;
- }
- if (self->expression.outr) {
- compile_error(ast_ctx(self), "internal error: ast_state cannot be reused!");
- return false;
- }
- *out = NULL;
-
- cgen = self->framenum->codegen;
- if (!(*cgen)((ast_expression*)(self->framenum), func, false, &frameval))
- return false;
- if (!frameval)
- return false;
-
- cgen = self->nextthink->codegen;
- if (!(*cgen)((ast_expression*)(self->nextthink), func, false, &thinkval))
- return false;
- if (!frameval)
- return false;
-
- if (!ir_block_create_state_op(func->curblock, ast_ctx(self), frameval, thinkval)) {
- compile_error(ast_ctx(self), "failed to create STATE instruction");
- return false;
- }
-
- self->expression.outr = (ir_value*)1;
- return true;
-}
-
-bool ast_call_codegen(ast_call *self, ast_function *func, bool lvalue, ir_value **out)
-{
- ast_expression_codegen *cgen;
- 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;
-}
--- /dev/null
+#include <stdlib.h>
+#include <string.h>
+
+#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 <stdio.h>
+bool ast_state_codegen(ast_state *self, ast_function *func, bool lvalue, ir_value **out)
+{
+ ast_expression_codegen *cgen;
+
+ ir_value *frameval, *thinkval;
+
+ if (lvalue) {
+ compile_error(ast_ctx(self), "not an l-value (state operation)");
+ return false;
+ }
+ if (self->expression.outr) {
+ compile_error(ast_ctx(self), "internal error: ast_state cannot be reused!");
+ return false;
+ }
+ *out = NULL;
+
+ cgen = self->framenum->codegen;
+ if (!(*cgen)((ast_expression*)(self->framenum), func, false, &frameval))
+ return false;
+ if (!frameval)
+ return false;
+
+ cgen = self->nextthink->codegen;
+ if (!(*cgen)((ast_expression*)(self->nextthink), func, false, &thinkval))
+ return false;
+ if (!frameval)
+ return false;
+
+ if (!ir_block_create_state_op(func->curblock, ast_ctx(self), frameval, thinkval)) {
+ compile_error(ast_ctx(self), "failed to create STATE instruction");
+ return false;
+ }
+
+ self->expression.outr = (ir_value*)1;
+ return true;
+}
+
+bool ast_call_codegen(ast_call *self, ast_function *func, bool lvalue, ir_value **out)
+{
+ ast_expression_codegen *cgen;
+ 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;
+}
+++ /dev/null
-#include <string.h>
-#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);
-}
--- /dev/null
+#include <string.h>
+#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);
+}
+++ /dev/null
-#include <stdio.h>
-#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;
-}
--- /dev/null
+#include <stdio.h>
+#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;
+}
+++ /dev/null
-#ifndef QCVM_LOOP
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#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 "<<<invalid string>>>";
-
- 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("<null>,")) == -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("<illegal instruction %d>\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 <math.h>
-
-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 <V> pass a vector parameter to main()\n"
- " -float <f> pass a float parameter to main()\n"
- " -string <s> 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 */
--- /dev/null
+#ifndef QCVM_LOOP
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#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 "<<<invalid string>>>";
+
+ 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("<null>,")) == -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("<illegal instruction %d>\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 <math.h>
+
+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 <V> pass a vector parameter to main()\n"
+ " -float <f> pass a float parameter to main()\n"
+ " -string <s> 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 */
+++ /dev/null
-#include <string.h>
-#include <math.h>
-
-#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 <fenv.h> 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);
-}
--- /dev/null
+#include <string.h>
+#include <math.h>
+
+#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 <fenv.h> 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);
+}
+++ /dev/null
-#include <string.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-
-#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<string, ppmacro*> */
- 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("\"<failed to determine timestamp>\"");
-
- 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
- * <numbers> => True if the number is not 0
- * !<factor> => True if the factor yields false
- * !!<factor> => ERROR on 2 or more unary nots
- * <macro> => becomes the macro's FIRST token regardless of parameters
- * <e> && <e> => True if both expressions are true
- * <e> || <e> => True if either expression is true
- * <string> => False
- * <ident> => False (remember for macros the <macro> 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);
-}
--- /dev/null
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#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<string, ppmacro*> */
+ 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("\"<failed to determine timestamp>\"");
+
+ 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
+ * <numbers> => True if the number is not 0
+ * !<factor> => True if the factor yields false
+ * !!<factor> => ERROR on 2 or more unary nots
+ * <macro> => becomes the macro's FIRST token regardless of parameters
+ * <e> && <e> => True if both expressions are true
+ * <e> || <e> => True if either expression is true
+ * <string> => False
+ * <ident> => False (remember for macros the <macro> 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);
+}
+++ /dev/null
-#include <string.h>
-#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);
-
- /* <callisnan> = isnan(x); */
- vec_push(callisnan->params, (ast_expression*)x);
-
- /* <callisinf> = isinf(x); */
- vec_push(callisinf->params, (ast_expression*)x);
-
- /* return (!<callisnan> || <callisinf>); */
- 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 <callisfinite> */
- 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);
-
- /* <callsqrt> = 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]
- )
- );
-
- /* <calllog> = log(x + <callsqrt>); */
- vec_push(calllog->params,
- (ast_expression*)ast_binary_new(
- intrin_ctx(intrin),
- INSTR_ADD_F,
- (ast_expression*)x,
- (ast_expression*)callsqrt
- )
- );
-
- /* return <calllog>; */
- 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);
-
- /* <callsqrt> = 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]
- )
- );
-
- /* <calllog> = log(x + <callsqrt>); */
- vec_push(calllog->params,
- (ast_expression*)ast_binary_new(
- intrin_ctx(intrin),
- INSTR_ADD_F,
- (ast_expression*)x,
- (ast_expression*)callsqrt
- )
- );
-
- /* return <calllog>; */
- 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);
-
- /* <callog> = 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 * <calllog>; */
- 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 <callpow> */
- 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);
-
- /* <callexp> = exp(x); */
- vec_push(callexp->params, (ast_expression*)x);
-
- /* return <callexp> - 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
- )
- );
-
- /* <callpow1> = 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 / <callpow1>;
- */
- 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
- )
- );
-
- /* <callpow2> = 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 */
- )
- );
-
- /*
- * <expgt1> = {
- * result = <callpow2>;
- * 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) {
- * <expgt1>
- * }
- */
- 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
- )
- );
-
- /*
- * <callsqrt1> = 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 */
- )
- )
- );
-
- /*
- * <midltexp> = {
- * 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
- )
- );
-
- /*
- * <midltexpelse> = {
- * 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
- )
- )
- );
-
- /*
- * <callsqrt2> = sqrt(square)
- */
- vec_push(callsqrt2->params, (ast_expression*)square);
-
- /*
- * <whileblock> = {
- * square = <callsqrt2>;
- * if (mid < exp)
- * <midltexp>;
- * else
- * <midltexpelse>;
- *
- * 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 */
- )
- )
- );
-
- /*
- * <callabs> = 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 (<callfabs> > epsilon)
- * <whileblock>
- */
- 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 * <call> */
- 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]
- )
- );
- }
- }
-
- /*
- * <plt1> = {
- * power = 1.0f / power;
- * sign *= -1.0f;
- * }
- * <blt1> = {
- * 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]
- )
- );
- }
-
- /*
- * <plt1orblt1> = {
- * if (power <= 0.0 || base <= 0.0f)
- * return __builtin_nan();
- * if (power < 1.0f)
- * <plt1>
- * if (base < 1.0f)
- * <blt1>
- * }
- */
- 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);
-
- /* <callpow> = pow(2, b) */
- vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]);
- vec_push(callpow->params, (ast_expression*)b);
-
- /* <callfloor> = floor(a [instr] <callpow>) */
- vec_push(
- callfloor->params,
- (ast_expression*)ast_binary_new(
- intrin_ctx(intrin),
- instr,
- (ast_expression*)a,
- (ast_expression*)callpow
- )
- );
-
- /* return <callfloor> */
- 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);
-}
--- /dev/null
+#include <string.h>
+#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);
+
+ /* <callisnan> = isnan(x); */
+ vec_push(callisnan->params, (ast_expression*)x);
+
+ /* <callisinf> = isinf(x); */
+ vec_push(callisinf->params, (ast_expression*)x);
+
+ /* return (!<callisnan> || <callisinf>); */
+ 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 <callisfinite> */
+ 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);
+
+ /* <callsqrt> = 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]
+ )
+ );
+
+ /* <calllog> = log(x + <callsqrt>); */
+ vec_push(calllog->params,
+ (ast_expression*)ast_binary_new(
+ intrin_ctx(intrin),
+ INSTR_ADD_F,
+ (ast_expression*)x,
+ (ast_expression*)callsqrt
+ )
+ );
+
+ /* return <calllog>; */
+ 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);
+
+ /* <callsqrt> = 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]
+ )
+ );
+
+ /* <calllog> = log(x + <callsqrt>); */
+ vec_push(calllog->params,
+ (ast_expression*)ast_binary_new(
+ intrin_ctx(intrin),
+ INSTR_ADD_F,
+ (ast_expression*)x,
+ (ast_expression*)callsqrt
+ )
+ );
+
+ /* return <calllog>; */
+ 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);
+
+ /* <callog> = 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 * <calllog>; */
+ 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 <callpow> */
+ 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);
+
+ /* <callexp> = exp(x); */
+ vec_push(callexp->params, (ast_expression*)x);
+
+ /* return <callexp> - 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
+ )
+ );
+
+ /* <callpow1> = 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 / <callpow1>;
+ */
+ 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
+ )
+ );
+
+ /* <callpow2> = 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 */
+ )
+ );
+
+ /*
+ * <expgt1> = {
+ * result = <callpow2>;
+ * 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) {
+ * <expgt1>
+ * }
+ */
+ 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
+ )
+ );
+
+ /*
+ * <callsqrt1> = 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 */
+ )
+ )
+ );
+
+ /*
+ * <midltexp> = {
+ * 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
+ )
+ );
+
+ /*
+ * <midltexpelse> = {
+ * 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
+ )
+ )
+ );
+
+ /*
+ * <callsqrt2> = sqrt(square)
+ */
+ vec_push(callsqrt2->params, (ast_expression*)square);
+
+ /*
+ * <whileblock> = {
+ * square = <callsqrt2>;
+ * if (mid < exp)
+ * <midltexp>;
+ * else
+ * <midltexpelse>;
+ *
+ * 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 */
+ )
+ )
+ );
+
+ /*
+ * <callabs> = 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 (<callfabs> > epsilon)
+ * <whileblock>
+ */
+ 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 * <call> */
+ 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]
+ )
+ );
+ }
+ }
+
+ /*
+ * <plt1> = {
+ * power = 1.0f / power;
+ * sign *= -1.0f;
+ * }
+ * <blt1> = {
+ * 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]
+ )
+ );
+ }
+
+ /*
+ * <plt1orblt1> = {
+ * if (power <= 0.0 || base <= 0.0f)
+ * return __builtin_nan();
+ * if (power < 1.0f)
+ * <plt1>
+ * if (base < 1.0f)
+ * <blt1>
+ * }
+ */
+ 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);
+
+ /* <callpow> = pow(2, b) */
+ vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]);
+ vec_push(callpow->params, (ast_expression*)b);
+
+ /* <callfloor> = floor(a [instr] <callpow>) */
+ vec_push(
+ callfloor->params,
+ (ast_expression*)ast_binary_new(
+ intrin_ctx(intrin),
+ instr,
+ (ast_expression*)a,
+ (ast_expression*)callpow
+ )
+ );
+
+ /* return <callfloor> */
+ 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);
+}
+++ /dev/null
-#include <stdlib.h>
-#include <string.h>
-
-#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",
- "<no-expression>"
-};
-
-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<<o))
- {
- size_t idx;
- bool in_living = vec_ir_value_find(self->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<<o))
- {
- 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]);
- }
- }
- }
- /* 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 "<INVALID>";
- 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 "<UNK>";
- }
-}
-
-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);
- }
-}
--- /dev/null
+#include <stdlib.h>
+#include <string.h>
+
+#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",
+ "<no-expression>"
+};
+
+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<<o))
+ {
+ size_t idx;
+ bool in_living = vec_ir_value_find(self->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<<o))
+ {
+ 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]);
+ }
+ }
+ }
+ /* 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 "<INVALID>";
+ 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 "<UNK>";
+ }
+}
+
+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);
+ }
+}
+++ /dev/null
-#include <string.h>
-#include <stdlib.h>
-
-#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 : "<string-source>");
- 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);
-}
--- /dev/null
+#include <string.h>
+#include <stdlib.h>
+
+#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 : "<string-source>");
+ 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);
+}
+++ /dev/null
-#include <stdlib.h>
-#include <string.h>
-
-#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<flag> enable a flag\n"
- " -fno-<flag> disable a flag\n"
- " -fhelp list possible flags\n");
- con_out(" -W<warning> enable a warning\n"
- " -Wno-<warning> disable a warning\n"
- " -Wall enable all warnings\n");
- con_out(" -Werror treat warnings as errors\n"
- " -Werror-<warning> treat a warning as error\n"
- " -Wno-error-<warning> opposite of the above\n");
- con_out(" -Whelp list possible warnings\n");
- con_out(" -O<number> optimization level\n"
- " -O<name> enable specific optimization\n"
- " -Ono-<name> disable specific optimization\n"
- " -Ohelp list optimizations\n");
- con_out(" -force-crc=num force a specific checksum into the header\n");
- con_out(" -state-fps=num emulate OP_STATE with the specified FPS\n");
- con_out(" -coverage add coverage support\n");
- return -1;
-}
-
-/* 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;
-}
--- /dev/null
+#include <stdlib.h>
+#include <string.h>
+
+#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<flag> enable a flag\n"
+ " -fno-<flag> disable a flag\n"
+ " -fhelp list possible flags\n");
+ con_out(" -W<warning> enable a warning\n"
+ " -Wno-<warning> disable a warning\n"
+ " -Wall enable all warnings\n");
+ con_out(" -Werror treat warnings as errors\n"
+ " -Werror-<warning> treat a warning as error\n"
+ " -Wno-error-<warning> opposite of the above\n");
+ con_out(" -Whelp list possible warnings\n");
+ con_out(" -O<number> optimization level\n"
+ " -O<name> enable specific optimization\n"
+ " -Ono-<name> disable specific optimization\n"
+ " -Ohelp list optimizations\n");
+ con_out(" -force-crc=num force a specific checksum into the header\n");
+ con_out(" -state-fps=num emulate OP_STATE with the specified FPS\n");
+ con_out(" -coverage add coverage support\n");
+ return -1;
+}
+
+/* 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;
+}
+++ /dev/null
-#include <string.h>
-#include <stdlib.h>
-
-#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);
-}
--- /dev/null
+#include <string.h>
+#include <stdlib.h>
+
+#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);
+}
+++ /dev/null
-#include <string.h>
-#include <math.h>
-
-#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), "<void>", 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), "<void>", 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()>", 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[]>", 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, "<type(from_def)>");
- } else
- var = ast_value_new(ctx, "<type>", 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:f>", 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:f>", 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 '<something>' */
- 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 "<INVALID>";
- }
-}
-
-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 '<something>' */
- 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 = <inexact>; should propagate the inexact flag
- */
- if (var->cvq == CV_CONST && var->expression.vtype == TYPE_FLOAT) {
- if (cval && cval->hasvalue && cval->cvq == CV_CONST)
- var->inexact = cval->inexact;
- }
- }
-
-another:
- 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 = "<internal>";
- 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, "<vector.x>", TYPE_NOEXPR);
- parser->const_vec[1] = ast_value_new(empty_ctx, "<vector.y>", TYPE_NOEXPR);
- parser->const_vec[2] = ast_value_new(empty_ctx, "<vector.z>", 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;
-}
--- /dev/null
+#include <string.h>
+#include <math.h>
+
+#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), "<void>", 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), "<void>", 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()>", 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[]>", 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, "<type(from_def)>");
+ } else
+ var = ast_value_new(ctx, "<type>", 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:f>", 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:f>", 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 '<something>' */
+ 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 "<INVALID>";
+ }
+}
+
+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 '<something>' */
+ 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 = <inexact>; should propagate the inexact flag
+ */
+ if (var->cvq == CV_CONST && var->expression.vtype == TYPE_FLOAT) {
+ if (cval && cval->hasvalue && cval->cvq == CV_CONST)
+ var->inexact = cval->inexact;
+ }
+ }
+
+another:
+ 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 = "<internal>";
+ 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, "<vector.x>", TYPE_NOEXPR);
+ parser->const_vec[1] = ast_value_new(empty_ctx, "<vector.y>", TYPE_NOEXPR);
+ parser->const_vec[2] = ast_value_new(empty_ctx, "<vector.z>", 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;
+}
+++ /dev/null
-#include <string.h>
-#include <stdlib.h>
-
-#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);
-}
--- /dev/null
+#include <string.h>
+#include <stdlib.h>
+
+#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);
+}
+++ /dev/null
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <sys/stat.h>
-
-#include <dirent.h>
-#include <unistd.h>
-
-#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 <math.h>
-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)) ? "<<nothing else to compare>>" : 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;
-}
--- /dev/null
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <unistd.h>
+
+#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 <math.h>
+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)) ? "<<nothing else to compare>>" : 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;
+}
+++ /dev/null
-#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;
-}
--- /dev/null
+#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;
+}
+++ /dev/null
-#include <stdlib.h>
-#include <string.h>
-#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 <stdio.h>
-#include <stdint.h>
-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 <unistd.h>
-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;
-}
-
--- /dev/null
+#include <stdlib.h>
+#include <string.h>
+#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 <stdio.h>
+#include <stdint.h>
+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 <unistd.h>
+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;
+}
+