mirror of
https://github.com/DarkPlacesEngine/gmqcc.git
synced 2024-11-23 20:33:05 +00:00
Merging master and adapting _MEM_* on new mem-vector functions
This commit is contained in:
commit
f8b3081c44
7 changed files with 734 additions and 182 deletions
270
ast.c
270
ast.c
|
@ -57,12 +57,18 @@ static void ast_expression_init(ast_expression *self,
|
|||
self->expression.codegen = codegen;
|
||||
self->expression.vtype = TYPE_VOID;
|
||||
self->expression.next = NULL;
|
||||
MEM_VECTOR_INIT(&self->expression, params);
|
||||
}
|
||||
|
||||
static void ast_expression_delete(ast_expression *self)
|
||||
{
|
||||
size_t i;
|
||||
if (self->expression.next)
|
||||
ast_delete(self->expression.next);
|
||||
for (i = 0; i < self->expression.params_count; ++i) {
|
||||
ast_delete(self->expression.params[i]);
|
||||
}
|
||||
MEM_VECTOR_CLEAR(&self->expression, params);
|
||||
}
|
||||
|
||||
static void ast_expression_delete_full(ast_expression *self)
|
||||
|
@ -71,9 +77,26 @@ static void ast_expression_delete_full(ast_expression *self)
|
|||
mem_d(self);
|
||||
}
|
||||
|
||||
MEM_VEC_FUNCTIONS(ast_expression_common, ast_value*, params)
|
||||
|
||||
static ast_expression* ast_type_copy(lex_ctx ctx, const ast_expression *ex);
|
||||
static ast_value* ast_value_copy(const ast_value *self)
|
||||
{
|
||||
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);
|
||||
if (!cp->expression.next) {
|
||||
ast_value_delete(cp);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return cp;
|
||||
}
|
||||
|
||||
static ast_expression* ast_type_copy(lex_ctx ctx, const ast_expression *ex)
|
||||
{
|
||||
const ast_expression_common *cpex;
|
||||
size_t i;
|
||||
const ast_expression_common *fromex;
|
||||
ast_expression_common *selfex;
|
||||
|
||||
if (!ex)
|
||||
|
@ -82,23 +105,32 @@ static ast_expression* ast_type_copy(lex_ctx ctx, const ast_expression *ex)
|
|||
{
|
||||
ast_instantiate(ast_expression, ctx, ast_expression_delete_full);
|
||||
|
||||
cpex = &ex->expression;
|
||||
fromex = &ex->expression;
|
||||
selfex = &self->expression;
|
||||
|
||||
selfex->vtype = cpex->vtype;
|
||||
if (cpex->next)
|
||||
/* This may never be codegen()d */
|
||||
selfex->codegen = NULL;
|
||||
|
||||
selfex->vtype = fromex->vtype;
|
||||
if (fromex->next)
|
||||
{
|
||||
selfex->next = ast_type_copy(ctx, cpex->next);
|
||||
selfex->next = ast_type_copy(ctx, fromex->next);
|
||||
if (!selfex->next) {
|
||||
mem_d(self);
|
||||
ast_expression_delete_full(self);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
selfex->next = NULL;
|
||||
|
||||
/* This may never be codegen()d */
|
||||
selfex->codegen = NULL;
|
||||
for (i = 0; i < fromex->params_count; ++i) {
|
||||
ast_value *v = ast_value_copy(fromex->params[i]);
|
||||
if (!v || !ast_expression_common_params_add(selfex, v)) {
|
||||
ast_expression_delete_full(self);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +145,6 @@ ast_value* ast_value_new(lex_ctx ctx, const char *name, int t)
|
|||
self->name = name ? util_strdup(name) : NULL;
|
||||
self->expression.vtype = t;
|
||||
self->expression.next = NULL;
|
||||
MEM_VECTOR_INIT(self, params);
|
||||
self->isconst = false;
|
||||
memset(&self->constval, 0, sizeof(self->constval));
|
||||
|
||||
|
@ -121,16 +152,11 @@ ast_value* ast_value_new(lex_ctx ctx, const char *name, int t)
|
|||
|
||||
return self;
|
||||
}
|
||||
MEM_VEC_FUNCTIONS(ast_value, ast_value*, params)
|
||||
|
||||
void ast_value_delete(ast_value* self)
|
||||
{
|
||||
size_t i;
|
||||
if (self->name)
|
||||
mem_d((void*)self->name);
|
||||
for (i = 0; i < self->params_count; ++i)
|
||||
ast_value_delete(self->params[i]); /* delete, the ast_function is expected to die first */
|
||||
MEM_VECTOR_CLEAR(self, params);
|
||||
if (self->isconst) {
|
||||
switch (self->expression.vtype)
|
||||
{
|
||||
|
@ -152,6 +178,11 @@ void ast_value_delete(ast_value* self)
|
|||
mem_d(self);
|
||||
}
|
||||
|
||||
bool GMQCC_WARN ast_value_params_add(ast_value *self, ast_value *p)
|
||||
{
|
||||
return ast_expression_common_params_add(&self->expression, p);
|
||||
}
|
||||
|
||||
bool ast_value_set_name(ast_value *self, const char *name)
|
||||
{
|
||||
if (self->name)
|
||||
|
@ -181,6 +212,42 @@ void ast_binary_delete(ast_binary *self)
|
|||
mem_d(self);
|
||||
}
|
||||
|
||||
ast_unary* ast_unary_new(lex_ctx 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;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void ast_unary_delete(ast_unary *self)
|
||||
{
|
||||
ast_unref(self->operand);
|
||||
ast_expression_delete((ast_expression*)self);
|
||||
mem_d(self);
|
||||
}
|
||||
|
||||
ast_return* ast_return_new(lex_ctx 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;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void ast_return_delete(ast_return *self)
|
||||
{
|
||||
ast_unref(self->operand);
|
||||
ast_expression_delete((ast_expression*)self);
|
||||
mem_d(self);
|
||||
}
|
||||
|
||||
ast_entfield* ast_entfield_new(lex_ctx ctx, ast_expression *entity, ast_expression *field)
|
||||
{
|
||||
const ast_expression *outtype;
|
||||
|
@ -308,6 +375,34 @@ void ast_loop_delete(ast_loop *self)
|
|||
mem_d(self);
|
||||
}
|
||||
|
||||
ast_call* ast_call_new(lex_ctx ctx,
|
||||
ast_expression *funcexpr)
|
||||
{
|
||||
ast_instantiate(ast_call, ctx, ast_call_delete);
|
||||
ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_call_codegen);
|
||||
|
||||
MEM_VECTOR_INIT(self, params);
|
||||
|
||||
self->func = funcexpr;
|
||||
|
||||
return self;
|
||||
}
|
||||
MEM_VEC_FUNCTIONS(ast_call, ast_expression*, params)
|
||||
|
||||
void ast_call_delete(ast_call *self)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < self->params_count; ++i)
|
||||
ast_unref(self->params[i]);
|
||||
MEM_VECTOR_CLEAR(self, params);
|
||||
|
||||
if (self->func)
|
||||
ast_unref(self->func);
|
||||
|
||||
ast_expression_delete((ast_expression*)self);
|
||||
mem_d(self);
|
||||
}
|
||||
|
||||
ast_store* ast_store_new(lex_ctx ctx, int op,
|
||||
ast_value *dest, ast_expression *source)
|
||||
{
|
||||
|
@ -356,6 +451,19 @@ void ast_block_delete(ast_block *self)
|
|||
mem_d(self);
|
||||
}
|
||||
|
||||
bool ast_block_set_type(ast_block *self, ast_expression *from)
|
||||
{
|
||||
if (self->expression.next)
|
||||
ast_delete(self->expression.next);
|
||||
self->expression.vtype = from->expression.vtype;
|
||||
if (from->expression.next) {
|
||||
self->expression.next = ast_type_copy(self->expression.node.context, from->expression.next);
|
||||
if (!self->expression.next)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ast_function* ast_function_new(lex_ctx ctx, const char *name, ast_value *vtype)
|
||||
{
|
||||
ast_instantiate(ast_function, ctx, ast_function_delete);
|
||||
|
@ -373,6 +481,7 @@ ast_function* ast_function_new(lex_ctx ctx, const char *name, ast_value *vtype)
|
|||
MEM_VECTOR_INIT(self, blocks);
|
||||
|
||||
self->labelcount = 0;
|
||||
self->builtin = 0;
|
||||
|
||||
self->ir_func = NULL;
|
||||
self->curblock = NULL;
|
||||
|
@ -457,8 +566,10 @@ bool ast_value_codegen(ast_value *self, ast_function *func, bool lvalue, ir_valu
|
|||
* and the ast-user should take care of ast_global_codegen to be used
|
||||
* on all the globals.
|
||||
*/
|
||||
if (!self->ir_v)
|
||||
if (!self->ir_v) {
|
||||
printf("ast_value used before generated (%s)\n", self->name);
|
||||
return false;
|
||||
}
|
||||
*out = self->ir_v;
|
||||
return true;
|
||||
}
|
||||
|
@ -468,11 +579,12 @@ bool ast_global_codegen(ast_value *self, ir_builder *ir)
|
|||
ir_value *v = NULL;
|
||||
if (self->isconst && self->expression.vtype == TYPE_FUNCTION)
|
||||
{
|
||||
ir_function *func = ir_builder_create_function(ir, self->name);
|
||||
ir_function *func = ir_builder_create_function(ir, self->name, self->expression.next->expression.vtype);
|
||||
if (!func)
|
||||
return false;
|
||||
|
||||
self->constval.vfunc->ir_func = func;
|
||||
self->ir_v = func->value;
|
||||
/* The function is filled later on ast_function_codegen... */
|
||||
return true;
|
||||
}
|
||||
|
@ -497,10 +609,11 @@ bool ast_global_codegen(ast_value *self, ir_builder *ir)
|
|||
goto error;
|
||||
break;
|
||||
case TYPE_FUNCTION:
|
||||
printf("global of type function not properly generated\n");
|
||||
goto error;
|
||||
/* Cannot generate an IR value for a function,
|
||||
* need a pointer pointing to a function rather.
|
||||
*/
|
||||
goto error;
|
||||
default:
|
||||
printf("TODO: global constant type %i\n", self->expression.vtype);
|
||||
break;
|
||||
|
@ -516,7 +629,7 @@ error: /* clean up */
|
|||
return false;
|
||||
}
|
||||
|
||||
bool ast_local_codegen(ast_value *self, ir_function *func)
|
||||
bool ast_local_codegen(ast_value *self, ir_function *func, bool param)
|
||||
{
|
||||
ir_value *v = NULL;
|
||||
if (self->isconst && self->expression.vtype == TYPE_FUNCTION)
|
||||
|
@ -527,7 +640,7 @@ bool ast_local_codegen(ast_value *self, ir_function *func)
|
|||
return false;
|
||||
}
|
||||
|
||||
v = ir_function_create_local(func, self->name, self->expression.vtype);
|
||||
v = ir_function_create_local(func, self->name, self->expression.vtype, param);
|
||||
if (!v)
|
||||
return false;
|
||||
|
||||
|
@ -568,6 +681,7 @@ bool ast_function_codegen(ast_function *self, ir_builder *ir)
|
|||
{
|
||||
ir_function *irf;
|
||||
ir_value *dummy;
|
||||
ast_expression_common *ec;
|
||||
size_t i;
|
||||
|
||||
irf = self->ir_func;
|
||||
|
@ -576,6 +690,23 @@ bool ast_function_codegen(ast_function *self, ir_builder *ir)
|
|||
return false;
|
||||
}
|
||||
|
||||
/* fill the parameter list */
|
||||
ec = &self->vtype->expression;
|
||||
for (i = 0; i < ec->params_count; ++i)
|
||||
{
|
||||
if (!ir_function_params_add(irf, ec->params[i]->expression.vtype))
|
||||
return false;
|
||||
if (!self->builtin) {
|
||||
if (!ast_local_codegen(ec->params[i], self->ir_func, true))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (self->builtin) {
|
||||
irf->builtin = self->builtin;
|
||||
return true;
|
||||
}
|
||||
|
||||
self->curblock = ir_function_create_block(irf, "entry");
|
||||
if (!self->curblock)
|
||||
return false;
|
||||
|
@ -591,7 +722,9 @@ bool ast_function_codegen(ast_function *self, ir_builder *ir)
|
|||
{
|
||||
if (!self->vtype->expression.next ||
|
||||
self->vtype->expression.next->expression.vtype == TYPE_VOID)
|
||||
{
|
||||
return ir_block_create_return(self->curblock, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* error("missing return"); */
|
||||
|
@ -628,7 +761,7 @@ bool ast_block_codegen(ast_block *self, ast_function *func, bool lvalue, ir_valu
|
|||
/* generate locals */
|
||||
for (i = 0; i < self->locals_count; ++i)
|
||||
{
|
||||
if (!ast_local_codegen(self->locals[i], func->ir_func))
|
||||
if (!ast_local_codegen(self->locals[i], func->ir_func, false))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -700,6 +833,50 @@ bool ast_binary_codegen(ast_binary *self, ast_function *func, bool lvalue, ir_va
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ast_unary_codegen(ast_unary *self, ast_function *func, bool lvalue, ir_value **out)
|
||||
{
|
||||
ast_expression_codegen *cgen;
|
||||
ir_value *operand;
|
||||
|
||||
/* In the context of a unary operation, we can disregard
|
||||
* the lvalue flag.
|
||||
*/
|
||||
(void)lvalue;
|
||||
|
||||
cgen = self->operand->expression.codegen;
|
||||
/* lvalue! */
|
||||
if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand))
|
||||
return false;
|
||||
|
||||
*out = ir_block_create_unary(func->curblock, ast_function_label(func, "unary"),
|
||||
self->op, operand);
|
||||
if (!*out)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ast_return_codegen(ast_return *self, ast_function *func, bool lvalue, ir_value **out)
|
||||
{
|
||||
ast_expression_codegen *cgen;
|
||||
ir_value *operand;
|
||||
|
||||
/* In the context of a return operation, we can disregard
|
||||
* the lvalue flag.
|
||||
*/
|
||||
(void)lvalue;
|
||||
|
||||
cgen = self->operand->expression.codegen;
|
||||
/* lvalue! */
|
||||
if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand))
|
||||
return false;
|
||||
|
||||
if (!ir_block_create_return(func->curblock, operand))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ast_entfield_codegen(ast_entfield *self, ast_function *func, bool lvalue, ir_value **out)
|
||||
{
|
||||
ast_expression_codegen *cgen;
|
||||
|
@ -1148,3 +1325,56 @@ bool ast_loop_codegen(ast_loop *self, ast_function *func, bool lvalue, ir_value
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ast_call_codegen(ast_call *self, ast_function *func, bool lvalue, ir_value **out)
|
||||
{
|
||||
ast_expression_codegen *cgen;
|
||||
ir_value_vector params;
|
||||
ir_instr *callinstr;
|
||||
size_t i;
|
||||
|
||||
ir_value *funval = NULL;
|
||||
|
||||
/* return values are never rvalues */
|
||||
(void)lvalue;
|
||||
|
||||
cgen = self->func->expression.codegen;
|
||||
if (!(*cgen)((ast_expression*)(self->func), func, false, &funval))
|
||||
return false;
|
||||
if (!funval)
|
||||
return false;
|
||||
|
||||
MEM_VECTOR_INIT(¶ms, v);
|
||||
|
||||
/* parameters */
|
||||
for (i = 0; i < self->params_count; ++i)
|
||||
{
|
||||
ir_value *param;
|
||||
ast_expression *expr = self->params[i];
|
||||
|
||||
cgen = expr->expression.codegen;
|
||||
if (!(*cgen)(expr, func, false, ¶m))
|
||||
goto error;
|
||||
if (!param)
|
||||
goto error;
|
||||
if (!ir_value_vector_v_add(¶ms, param))
|
||||
goto error;
|
||||
}
|
||||
|
||||
callinstr = ir_block_create_call(func->curblock, ast_function_label(func, "call"), funval);
|
||||
if (!callinstr)
|
||||
goto error;
|
||||
|
||||
for (i = 0; i < params.v_count; ++i) {
|
||||
if (!ir_call_param(callinstr, params.v[i]))
|
||||
goto error;
|
||||
}
|
||||
|
||||
*out = ir_call_value(callinstr);
|
||||
|
||||
MEM_VECTOR_CLEAR(¶ms, v);
|
||||
return true;
|
||||
error:
|
||||
MEM_VECTOR_CLEAR(¶ms, v);
|
||||
return false;
|
||||
}
|
||||
|
|
76
ast.h
76
ast.h
|
@ -40,6 +40,9 @@ typedef struct ast_entfield_s ast_entfield;
|
|||
typedef struct ast_ifthen_s ast_ifthen;
|
||||
typedef struct ast_ternary_s ast_ternary;
|
||||
typedef struct ast_loop_s ast_loop;
|
||||
typedef struct ast_call_s ast_call;
|
||||
typedef struct ast_unary_s ast_unary;
|
||||
typedef struct ast_return_s ast_return;
|
||||
|
||||
/* Node interface with common components
|
||||
*/
|
||||
|
@ -78,7 +81,9 @@ typedef struct
|
|||
ast_expression_codegen *codegen;
|
||||
int vtype;
|
||||
ast_expression *next;
|
||||
MEM_VECTOR_MAKE(ast_value*, params);
|
||||
} ast_expression_common;
|
||||
MEM_VECTOR_PROTO(ast_expression_common, ast_value*, params);
|
||||
|
||||
/* Value
|
||||
*
|
||||
|
@ -109,12 +114,8 @@ struct ast_value_s
|
|||
} constval;
|
||||
|
||||
ir_value *ir_v;
|
||||
|
||||
/* if vtype is qc_function, params contain parameters, and
|
||||
* 'next' the return type.
|
||||
*/
|
||||
MEM_VECTOR_MAKE(ast_value*, params);
|
||||
};
|
||||
|
||||
ast_value* ast_value_new(lex_ctx ctx, const char *name, int qctype);
|
||||
/* This will NOT delete an underlying ast_function */
|
||||
void ast_value_delete(ast_value*);
|
||||
|
@ -122,9 +123,11 @@ void ast_value_delete(ast_value*);
|
|||
bool ast_value_set_name(ast_value*, const char *name);
|
||||
|
||||
bool ast_value_codegen(ast_value*, ast_function*, bool lvalue, ir_value**);
|
||||
bool ast_local_codegen(ast_value *self, ir_function *func);
|
||||
bool ast_local_codegen(ast_value *self, ir_function *func, bool isparam);
|
||||
bool ast_global_codegen(ast_value *self, ir_builder *ir);
|
||||
|
||||
bool GMQCC_WARN ast_value_params_add(ast_value*, ast_value*);
|
||||
|
||||
/* Binary
|
||||
*
|
||||
* A value-returning binary expression.
|
||||
|
@ -145,6 +148,41 @@ void ast_binary_delete(ast_binary*);
|
|||
|
||||
bool ast_binary_codegen(ast_binary*, ast_function*, bool lvalue, ir_value**);
|
||||
|
||||
/* Unary
|
||||
*
|
||||
* Regular unary expressions: not,neg
|
||||
*/
|
||||
struct ast_unary_s
|
||||
{
|
||||
ast_expression_common expression;
|
||||
|
||||
int op;
|
||||
ast_expression *operand;
|
||||
};
|
||||
ast_unary* ast_unary_new(lex_ctx ctx,
|
||||
int op,
|
||||
ast_expression *expr);
|
||||
void ast_unary_delete(ast_unary*);
|
||||
|
||||
bool ast_unary_codegen(ast_unary*, ast_function*, bool lvalue, ir_value**);
|
||||
|
||||
/* Return
|
||||
*
|
||||
* Make sure 'return' only happens at the end of a block, otherwise the IR
|
||||
* will refuse to create further instructions.
|
||||
* This should be honored by the parser.
|
||||
*/
|
||||
struct ast_return_s
|
||||
{
|
||||
ast_expression_common expression;
|
||||
ast_expression *operand;
|
||||
};
|
||||
ast_return* ast_return_new(lex_ctx ctx,
|
||||
ast_expression *expr);
|
||||
void ast_return_delete(ast_return*);
|
||||
|
||||
bool ast_return_codegen(ast_return*, ast_function*, bool lvalue, ir_value**);
|
||||
|
||||
/* Entity-field
|
||||
*
|
||||
* This must do 2 things:
|
||||
|
@ -284,6 +322,29 @@ void ast_loop_delete(ast_loop*);
|
|||
|
||||
bool ast_loop_codegen(ast_loop*, ast_function*, bool lvalue, ir_value**);
|
||||
|
||||
/* CALL node
|
||||
*
|
||||
* Contains an ast_expression as target, rather than an ast_function/value.
|
||||
* Since it's how QC works, every ast_function has an ast_value
|
||||
* associated anyway - in other words, the VM contains function
|
||||
* pointers for every function anyway. Thus, this node will call
|
||||
* expression.
|
||||
* Additionally it contains a list of ast_expressions as parameters.
|
||||
* Since calls can return values, an ast_call is also an ast_expression.
|
||||
*/
|
||||
struct ast_call_s
|
||||
{
|
||||
ast_expression_common expression;
|
||||
ast_expression *func;
|
||||
MEM_VECTOR_MAKE(ast_expression*, params);
|
||||
};
|
||||
ast_call* ast_call_new(lex_ctx ctx,
|
||||
ast_expression *funcexpr);
|
||||
void ast_call_delete(ast_call*);
|
||||
bool ast_call_codegen(ast_call*, ast_function*, bool lvalue, ir_value**);
|
||||
|
||||
MEM_VECTOR_PROTO(ast_call, ast_expression*, params);
|
||||
|
||||
/* Blocks
|
||||
*
|
||||
*/
|
||||
|
@ -296,6 +357,7 @@ struct ast_block_s
|
|||
};
|
||||
ast_block* ast_block_new(lex_ctx ctx);
|
||||
void ast_block_delete(ast_block*);
|
||||
bool ast_block_set_type(ast_block*, ast_expression *from);
|
||||
|
||||
MEM_VECTOR_PROTO(ast_block, ast_value*, locals);
|
||||
MEM_VECTOR_PROTO(ast_block, ast_expression*, exprs);
|
||||
|
@ -319,6 +381,8 @@ struct ast_function_s
|
|||
ast_value *vtype;
|
||||
const char *name;
|
||||
|
||||
int builtin;
|
||||
|
||||
ir_function *ir_func;
|
||||
ir_block *curblock;
|
||||
ir_block *breakblock;
|
||||
|
|
35
gmqcc.h
35
gmqcc.h
|
@ -77,7 +77,7 @@
|
|||
* This is a hack to silent clang regarding empty
|
||||
* body if statements.
|
||||
*/
|
||||
#define GMQCC_SUPRESS_EMPTY_BODY do { } while (0)
|
||||
#define GMQCC_SUPPRESS_EMPTY_BODY do { } while (0)
|
||||
|
||||
/*
|
||||
* Inline is not supported in < C90, however some compilers
|
||||
|
@ -371,6 +371,13 @@ enum {
|
|||
};
|
||||
|
||||
extern size_t type_sizeof[TYPE_COUNT];
|
||||
extern uint16_t type_store_instr[TYPE_COUNT];
|
||||
/* could use type_store_instr + INSTR_STOREP_F - INSTR_STORE_F
|
||||
* but this breaks when TYPE_INTEGER is added, since with the enhanced
|
||||
* instruction set, the old ones are left untouched, thus the _I instructions
|
||||
* are at a seperate place.
|
||||
*/
|
||||
extern uint16_t type_storep_instr[TYPE_COUNT];
|
||||
|
||||
typedef struct {
|
||||
uint32_t offset; /* Offset in file of where data begins */
|
||||
|
@ -605,7 +612,7 @@ static const struct {
|
|||
{ "EQ_V" , 0, 4 },
|
||||
{ "EQ_S" , 0, 4 },
|
||||
{ "EQ_E" , 0, 4 },
|
||||
{ "ES_FNC" , 0, 6 },
|
||||
{ "EQ_FNC" , 0, 6 },
|
||||
{ "NE_F" , 0, 4 },
|
||||
{ "NE_V" , 0, 4 },
|
||||
{ "NE_S" , 0, 4 },
|
||||
|
@ -694,7 +701,7 @@ extern int opts_compiler;
|
|||
size_t name##_count; \
|
||||
size_t name##_alloc
|
||||
|
||||
#define _MEM_VEC_FUN_ADD(Tself, Twhat, mem) \
|
||||
#define MEM_VEC_FUN_ADD(Tself, Twhat, mem) \
|
||||
bool GMQCC_WARN Tself##_##mem##_add(Tself *self, Twhat f) \
|
||||
{ \
|
||||
Twhat *reall; \
|
||||
|
@ -716,7 +723,7 @@ bool GMQCC_WARN Tself##_##mem##_add(Tself *self, Twhat f) \
|
|||
return true; \
|
||||
}
|
||||
|
||||
#define _MEM_VEC_FUN_REMOVE(Tself, Twhat, mem) \
|
||||
#define MEM_VEC_FUN_REMOVE(Tself, Twhat, mem) \
|
||||
bool GMQCC_WARN Tself##_##mem##_remove(Tself *self, size_t idx) \
|
||||
{ \
|
||||
size_t i; \
|
||||
|
@ -741,7 +748,7 @@ bool GMQCC_WARN Tself##_##mem##_remove(Tself *self, size_t idx) \
|
|||
return true; \
|
||||
}
|
||||
|
||||
#define _MEM_VEC_FUN_FIND(Tself, Twhat, mem) \
|
||||
#define MEM_VEC_FUN_FIND(Tself, Twhat, mem) \
|
||||
bool GMQCC_WARN Tself##_##mem##_find(Tself *self, Twhat obj, size_t *idx) \
|
||||
{ \
|
||||
size_t i; \
|
||||
|
@ -756,7 +763,7 @@ bool GMQCC_WARN Tself##_##mem##_find(Tself *self, Twhat obj, size_t *idx) \
|
|||
return false; \
|
||||
}
|
||||
|
||||
#define _MEM_VEC_FUN_APPEND(Tself, Twhat, mem) \
|
||||
#define MEM_VEC_FUN_APPEND(Tself, Twhat, mem) \
|
||||
bool GMQCC_WARN Tself##_##mem##_append(Tself *s, Twhat *p, size_t c) \
|
||||
{ \
|
||||
Twhat *reall; \
|
||||
|
@ -782,7 +789,7 @@ bool GMQCC_WARN Tself##_##mem##_append(Tself *s, Twhat *p, size_t c) \
|
|||
return true; \
|
||||
}
|
||||
|
||||
#define _MEM_VEC_FUN_RESIZE(Tself, Twhat, mem) \
|
||||
#define MEM_VEC_FUN_RESIZE(Tself, Twhat, mem) \
|
||||
bool GMQCC_WARN Tself##_##mem##_resize(Tself *s, size_t c) \
|
||||
{ \
|
||||
Twhat *reall; \
|
||||
|
@ -806,7 +813,7 @@ bool GMQCC_WARN Tself##_##mem##_resize(Tself *s, size_t c) \
|
|||
return true; \
|
||||
}
|
||||
|
||||
#define _MEM_VEC_FUN_CLEAR(Tself, mem) \
|
||||
#define MEM_VEC_FUN_CLEAR(Tself, mem) \
|
||||
void Tself##_##mem##_clear(Tself *self) \
|
||||
{ \
|
||||
if (!self->mem) \
|
||||
|
@ -832,18 +839,20 @@ void Tself##_##mem##_clear(Tself *self) \
|
|||
}
|
||||
|
||||
#define MEM_VEC_FUNCTIONS(Tself, Twhat, mem) \
|
||||
_MEM_VEC_FUN_REMOVE(Tself, Twhat, mem) \
|
||||
_MEM_VEC_FUN_ADD(Tself, Twhat, mem)
|
||||
MEM_VEC_FUN_REMOVE(Tself, Twhat, mem) \
|
||||
MEM_VEC_FUN_ADD(Tself, Twhat, mem)
|
||||
|
||||
#define MEM_VEC_FUNCTIONS_ALL(Tself, Twhat, mem) \
|
||||
MEM_VEC_FUNCTIONS(Tself, Twhat, mem) \
|
||||
_MEM_VEC_FUN_CLEAR(Tself, mem) \
|
||||
_MEM_VEC_FUN_FIND(Tself, Twhat, mem)
|
||||
MEM_VEC_FUN_CLEAR(Tself, mem) \
|
||||
MEM_VEC_FUN_FIND(Tself, Twhat, mem)
|
||||
|
||||
enum store_types {
|
||||
store_global,
|
||||
store_local, /* local, assignable for now, should get promoted later */
|
||||
store_value /* unassignable */
|
||||
store_param, /* parameters, they are locals with a fixed position */
|
||||
store_value, /* unassignable */
|
||||
store_return /* unassignable, at OFS_RETURN */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
|
|
393
ir.c
393
ir.c
|
@ -44,6 +44,38 @@ size_t type_sizeof[TYPE_COUNT] = {
|
|||
3, /* TYPE_VARIANT */
|
||||
};
|
||||
|
||||
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_ENT, /* integer type */
|
||||
#endif
|
||||
INSTR_STORE_V, /* variant, should never be accessed */
|
||||
};
|
||||
|
||||
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 */
|
||||
#endif
|
||||
INSTR_STOREP_V, /* variant, should never be accessed */
|
||||
};
|
||||
|
||||
MEM_VEC_FUNCTIONS(ir_value_vector, ir_value*, v)
|
||||
|
||||
/***********************************************************************
|
||||
*IR Builder
|
||||
*/
|
||||
|
@ -108,20 +140,32 @@ ir_function* ir_builder_get_function(ir_builder *self, const char *name)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
ir_function* ir_builder_create_function(ir_builder *self, const char *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);
|
||||
fn = ir_function_new(self, outtype);
|
||||
if (!ir_function_set_name(fn, name) ||
|
||||
!ir_builder_functions_add(self, fn) )
|
||||
{
|
||||
ir_function_delete(fn);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn->value = ir_builder_create_global(self, fn->name, TYPE_FUNCTION);
|
||||
if (!fn->value) {
|
||||
ir_function_delete(fn);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn->value->isconst = true;
|
||||
fn->value->outtype = outtype;
|
||||
fn->value->constval.vfunc = fn;
|
||||
fn->value->context = fn->context;
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
|
@ -159,7 +203,7 @@ void ir_function_enumerate(ir_function*);
|
|||
bool ir_function_calculate_liferanges(ir_function*);
|
||||
bool ir_function_allocate_locals(ir_function*);
|
||||
|
||||
ir_function* ir_function_new(ir_builder* owner)
|
||||
ir_function* ir_function_new(ir_builder* owner, int outtype)
|
||||
{
|
||||
ir_function *self;
|
||||
self = (ir_function*)mem_a(sizeof(*self));
|
||||
|
@ -175,7 +219,9 @@ ir_function* ir_function_new(ir_builder* owner)
|
|||
self->owner = owner;
|
||||
self->context.file = "<@no context>";
|
||||
self->context.line = 0;
|
||||
self->retype = TYPE_VOID;
|
||||
self->outtype = outtype;
|
||||
self->value = NULL;
|
||||
self->builtin = 0;
|
||||
MEM_VECTOR_INIT(self, params);
|
||||
MEM_VECTOR_INIT(self, blocks);
|
||||
MEM_VECTOR_INIT(self, values);
|
||||
|
@ -187,6 +233,7 @@ ir_function* ir_function_new(ir_builder* owner)
|
|||
MEM_VEC_FUNCTIONS(ir_function, ir_value*, values)
|
||||
MEM_VEC_FUNCTIONS(ir_function, ir_block*, blocks)
|
||||
MEM_VEC_FUNCTIONS(ir_function, ir_value*, locals)
|
||||
MEM_VEC_FUNCTIONS(ir_function, int, params)
|
||||
|
||||
bool ir_function_set_name(ir_function *self, const char *name)
|
||||
{
|
||||
|
@ -215,6 +262,8 @@ void ir_function_delete(ir_function *self)
|
|||
ir_value_delete(self->locals[i]);
|
||||
MEM_VECTOR_CLEAR(self, locals);
|
||||
|
||||
/* self->value is deleted by the builder */
|
||||
|
||||
mem_d(self);
|
||||
}
|
||||
|
||||
|
@ -236,6 +285,9 @@ ir_block* ir_function_create_block(ir_function *self, const char *label)
|
|||
|
||||
bool ir_function_finalize(ir_function *self)
|
||||
{
|
||||
if (self->builtin)
|
||||
return true;
|
||||
|
||||
if (!ir_function_naive_phi(self))
|
||||
return false;
|
||||
|
||||
|
@ -259,14 +311,21 @@ ir_value* ir_function_get_local(ir_function *self, const char *name)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
ir_value* ir_function_create_local(ir_function *self, const char *name, int vtype)
|
||||
ir_value* ir_function_create_local(ir_function *self, const char *name, int vtype, bool param)
|
||||
{
|
||||
ir_value *ve = ir_function_get_local(self, name);
|
||||
if (ve) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ve = ir_value_var(name, store_local, vtype);
|
||||
if (param &&
|
||||
self->locals_count &&
|
||||
self->locals[self->locals_count-1]->store != store_param) {
|
||||
printf("cannot add parameters after adding locals\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ve = ir_value_var(name, (param ? store_param : store_local), vtype);
|
||||
if (!ir_function_locals_add(self, ve)) {
|
||||
ir_value_delete(ve);
|
||||
return NULL;
|
||||
|
@ -356,11 +415,13 @@ ir_instr* ir_instr_new(ir_block* owner, int op)
|
|||
self->bops[0] = NULL;
|
||||
self->bops[1] = NULL;
|
||||
MEM_VECTOR_INIT(self, phi);
|
||||
MEM_VECTOR_INIT(self, params);
|
||||
|
||||
self->eid = 0;
|
||||
return self;
|
||||
}
|
||||
MEM_VEC_FUNCTIONS(ir_instr, ir_phi_entry_t, phi)
|
||||
MEM_VEC_FUNCTIONS(ir_instr, ir_value*, params)
|
||||
|
||||
void ir_instr_delete(ir_instr *self)
|
||||
{
|
||||
|
@ -374,14 +435,22 @@ void ir_instr_delete(ir_instr *self)
|
|||
for (i = 0; i < self->phi_count; ++i) {
|
||||
size_t idx;
|
||||
if (ir_value_writes_find(self->phi[i].value, self, &idx))
|
||||
if (ir_value_writes_remove(self->phi[i].value, idx)) GMQCC_SUPRESS_EMPTY_BODY;
|
||||
if (ir_value_writes_remove(self->phi[i].value, idx)) GMQCC_SUPPRESS_EMPTY_BODY;
|
||||
if (ir_value_reads_find(self->phi[i].value, self, &idx))
|
||||
if (ir_value_reads_remove (self->phi[i].value, idx)) GMQCC_SUPRESS_EMPTY_BODY;
|
||||
if (ir_value_reads_remove (self->phi[i].value, idx)) GMQCC_SUPPRESS_EMPTY_BODY;
|
||||
}
|
||||
MEM_VECTOR_CLEAR(self, phi);
|
||||
if (ir_instr_op(self, 0, NULL, false)) GMQCC_SUPRESS_EMPTY_BODY;
|
||||
if (ir_instr_op(self, 1, NULL, false)) GMQCC_SUPRESS_EMPTY_BODY;
|
||||
if (ir_instr_op(self, 2, NULL, false)) GMQCC_SUPRESS_EMPTY_BODY;
|
||||
for (i = 0; i < self->params_count; ++i) {
|
||||
size_t idx;
|
||||
if (ir_value_writes_find(self->params[i], self, &idx))
|
||||
if (ir_value_writes_remove(self->params[i], idx)) GMQCC_SUPPRESS_EMPTY_BODY;
|
||||
if (ir_value_reads_find(self->params[i], self, &idx))
|
||||
if (ir_value_reads_remove (self->params[i], idx)) GMQCC_SUPPRESS_EMPTY_BODY;
|
||||
}
|
||||
MEM_VECTOR_CLEAR(self, params);
|
||||
if (ir_instr_op(self, 0, NULL, false)) GMQCC_SUPPRESS_EMPTY_BODY;
|
||||
if (ir_instr_op(self, 1, NULL, false)) GMQCC_SUPPRESS_EMPTY_BODY;
|
||||
if (ir_instr_op(self, 2, NULL, false)) GMQCC_SUPPRESS_EMPTY_BODY;
|
||||
mem_d(self);
|
||||
}
|
||||
|
||||
|
@ -423,6 +492,7 @@ ir_value* ir_value_var(const char *name, int storetype, int vtype)
|
|||
self = (ir_value*)mem_a(sizeof(*self));
|
||||
self->vtype = vtype;
|
||||
self->fieldtype = TYPE_VOID;
|
||||
self->outtype = TYPE_VOID;
|
||||
self->store = storetype;
|
||||
MEM_VECTOR_INIT(self, reads);
|
||||
MEM_VECTOR_INIT(self, writes);
|
||||
|
@ -486,6 +556,15 @@ bool ir_value_set_float(ir_value *self, float f)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ir_value_set_func(ir_value *self, int f)
|
||||
{
|
||||
if (self->vtype != TYPE_FUNCTION)
|
||||
return false;
|
||||
self->constval.vint = f;
|
||||
self->isconst = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ir_value_set_vector(ir_value *self, vector v)
|
||||
{
|
||||
if (self->vtype != TYPE_VECTOR)
|
||||
|
@ -773,46 +852,14 @@ bool ir_block_create_store(ir_block *self, ir_value *target, ir_value *what)
|
|||
else
|
||||
vtype = target->vtype;
|
||||
|
||||
switch (vtype) {
|
||||
case TYPE_FLOAT:
|
||||
#if 0
|
||||
if (what->vtype == TYPE_INTEGER)
|
||||
op = INSTR_CONV_ITOF;
|
||||
else
|
||||
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 = INSTR_STORE_F;
|
||||
break;
|
||||
case TYPE_VECTOR:
|
||||
op = INSTR_STORE_V;
|
||||
break;
|
||||
case TYPE_ENTITY:
|
||||
op = INSTR_STORE_ENT;
|
||||
break;
|
||||
case TYPE_STRING:
|
||||
op = INSTR_STORE_S;
|
||||
break;
|
||||
case TYPE_FIELD:
|
||||
op = INSTR_STORE_FLD;
|
||||
break;
|
||||
#if 0
|
||||
case TYPE_INTEGER:
|
||||
if (what->vtype == TYPE_INTEGER)
|
||||
op = INSTR_CONV_FTOI;
|
||||
else
|
||||
op = INSTR_STORE_I;
|
||||
break;
|
||||
#endif
|
||||
case TYPE_POINTER:
|
||||
#if 0
|
||||
op = INSTR_STORE_I;
|
||||
#else
|
||||
op = INSTR_STORE_ENT;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
/* Unknown type */
|
||||
return false;
|
||||
}
|
||||
op = type_store_instr[vtype];
|
||||
|
||||
return ir_block_create_store_op(self, op, target, what);
|
||||
}
|
||||
|
||||
|
@ -829,38 +876,8 @@ bool ir_block_create_storep(ir_block *self, ir_value *target, ir_value *what)
|
|||
*/
|
||||
vtype = what->vtype;
|
||||
|
||||
switch (vtype) {
|
||||
case TYPE_FLOAT:
|
||||
op = INSTR_STOREP_F;
|
||||
break;
|
||||
case TYPE_VECTOR:
|
||||
op = INSTR_STOREP_V;
|
||||
break;
|
||||
case TYPE_ENTITY:
|
||||
op = INSTR_STOREP_ENT;
|
||||
break;
|
||||
case TYPE_STRING:
|
||||
op = INSTR_STOREP_S;
|
||||
break;
|
||||
case TYPE_FIELD:
|
||||
op = INSTR_STOREP_FLD;
|
||||
break;
|
||||
#if 0
|
||||
case TYPE_INTEGER:
|
||||
op = INSTR_STOREP_I;
|
||||
break;
|
||||
#endif
|
||||
case TYPE_POINTER:
|
||||
#if 0
|
||||
op = INSTR_STOREP_I;
|
||||
#else
|
||||
op = INSTR_STOREP_ENT;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
/* Unknown type */
|
||||
return false;
|
||||
}
|
||||
op = type_storep_instr[vtype];
|
||||
|
||||
return ir_block_create_store_op(self, op, target, what);
|
||||
}
|
||||
|
||||
|
@ -1017,6 +1034,47 @@ bool ir_phi_add(ir_instr* self, ir_block *b, ir_value *v)
|
|||
return ir_instr_phi_add(self, pe);
|
||||
}
|
||||
|
||||
/* call related code */
|
||||
ir_instr* ir_block_create_call(ir_block *self, const char *label, ir_value *func)
|
||||
{
|
||||
ir_value *out;
|
||||
ir_instr *in;
|
||||
in = ir_instr_new(self, INSTR_CALL0);
|
||||
if (!in)
|
||||
return NULL;
|
||||
out = ir_value_out(self->owner, label, store_return, 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_block_instr_add(self, in))
|
||||
{
|
||||
ir_instr_delete(in);
|
||||
ir_value_delete(out);
|
||||
return NULL;
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
ir_value* ir_call_value(ir_instr *self)
|
||||
{
|
||||
return self->_ops[0];
|
||||
}
|
||||
|
||||
bool ir_call_param(ir_instr* self, ir_value *v)
|
||||
{
|
||||
if (!ir_instr_params_add(self, v))
|
||||
return false;
|
||||
if (!ir_value_reads_add(v, self)) {
|
||||
if (!ir_instr_params_remove(self, self->params_count-1))
|
||||
GMQCC_SUPPRESS_EMPTY_BODY;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* binary op related code */
|
||||
|
||||
ir_value* ir_block_create_binop(ir_block *self,
|
||||
|
@ -1111,6 +1169,39 @@ ir_value* ir_block_create_binop(ir_block *self,
|
|||
return ir_block_create_general_instr(self, label, opcode, left, right, ot);
|
||||
}
|
||||
|
||||
ir_value* ir_block_create_unary(ir_block *self,
|
||||
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:
|
||||
#if 0
|
||||
case INSTR_NOT_I:
|
||||
#endif
|
||||
ot = TYPE_FLOAT;
|
||||
break;
|
||||
/* QC doesn't have other unary operations. We expect extensions to fill
|
||||
* the above list, otherwise we assume out-type = in-type, eg for an
|
||||
* unary minus
|
||||
*/
|
||||
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, label, opcode, operand, NULL, ot);
|
||||
}
|
||||
|
||||
ir_value* ir_block_create_general_instr(ir_block *self, const char *label,
|
||||
int op, ir_value *a, ir_value *b, int outype)
|
||||
{
|
||||
|
@ -1406,7 +1497,7 @@ static bool ir_block_naive_phi(ir_block *self)
|
|||
if (v->writes[w]->_ops[0] == v)
|
||||
v->writes[w]->_ops[0] = instr->_ops[0];
|
||||
|
||||
if (old->store != store_value && old->store != store_local)
|
||||
if (old->store != store_value && old->store != store_local && old->store != store_param)
|
||||
{
|
||||
/* If it originally wrote to a global we need to store the value
|
||||
* there as welli
|
||||
|
@ -1565,6 +1656,9 @@ bool ir_function_allocate_locals(ir_function *self)
|
|||
|
||||
function_allocator alloc;
|
||||
|
||||
if (!self->locals_count)
|
||||
return true;
|
||||
|
||||
MEM_VECTOR_INIT(&alloc, locals);
|
||||
MEM_VECTOR_INIT(&alloc, sizes);
|
||||
MEM_VECTOR_INIT(&alloc, positions);
|
||||
|
@ -1611,6 +1705,10 @@ bool ir_function_allocate_locals(ir_function *self)
|
|||
if (!function_allocator_positions_add(&alloc, 0))
|
||||
goto error;
|
||||
|
||||
if (alloc.sizes_count)
|
||||
pos = alloc.positions[0] + alloc.sizes[0];
|
||||
else
|
||||
pos = 0;
|
||||
for (i = 1; i < alloc.sizes_count; ++i)
|
||||
{
|
||||
pos = alloc.positions[i-1] + alloc.sizes[i-1];
|
||||
|
@ -1779,8 +1877,11 @@ static bool ir_block_life_propagate(ir_block *self, ir_block *prev, bool *change
|
|||
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_local &&
|
||||
value->store != store_param)
|
||||
continue;
|
||||
|
||||
/* read operands */
|
||||
|
@ -2109,8 +2210,56 @@ tailcall:
|
|||
}
|
||||
|
||||
if (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8) {
|
||||
printf("TODO: call instruction\n");
|
||||
return false;
|
||||
/* Trivial call translation:
|
||||
* copy all params to OFS_PARM*
|
||||
* if the output's storetype is not store_return,
|
||||
* add append a STORE instruction!
|
||||
*
|
||||
* NOTES on how to do it better without much trouble:
|
||||
* -) The liferanges!
|
||||
* Simply check the liferange of all parameters for
|
||||
* other CALLs. For each param with no CALL in its
|
||||
* liferange, we can store it in an OFS_PARM at
|
||||
* generation already. This would even include later
|
||||
* reuse.... probably... :)
|
||||
*/
|
||||
size_t p;
|
||||
ir_value *retvalue;
|
||||
|
||||
for (p = 0; p < instr->params_count; ++p)
|
||||
{
|
||||
ir_value *param = instr->params[p];
|
||||
|
||||
stmt.opcode = INSTR_STORE_F;
|
||||
stmt.o3.u1 = 0;
|
||||
|
||||
stmt.opcode = type_store_instr[param->vtype];
|
||||
stmt.o1.u1 = param->code.globaladdr;
|
||||
stmt.o2.u1 = OFS_PARM0 + 3 * p;
|
||||
if (code_statements_add(stmt) < 0)
|
||||
return false;
|
||||
}
|
||||
stmt.opcode = INSTR_CALL0 + instr->params_count;
|
||||
if (stmt.opcode > INSTR_CALL8)
|
||||
stmt.opcode = INSTR_CALL8;
|
||||
stmt.o1.u1 = instr->_ops[1]->code.globaladdr;
|
||||
stmt.o2.u1 = 0;
|
||||
stmt.o3.u1 = 0;
|
||||
if (code_statements_add(stmt) < 0)
|
||||
return false;
|
||||
|
||||
retvalue = instr->_ops[0];
|
||||
if (retvalue && retvalue->store != store_return && retvalue->life_count)
|
||||
{
|
||||
/* not to be kept in OFS_RETURN */
|
||||
stmt.opcode = type_store_instr[retvalue->vtype];
|
||||
stmt.o1.u1 = OFS_RETURN;
|
||||
stmt.o2.u1 = retvalue->code.globaladdr;
|
||||
stmt.o3.u1 = 0;
|
||||
if (code_statements_add(stmt) < 0)
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (instr->opcode == INSTR_STATE) {
|
||||
|
@ -2138,10 +2287,8 @@ tailcall:
|
|||
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_NOT_F &&
|
||||
stmt.opcode <= INSTR_NOT_FNC))
|
||||
else if (stmt.opcode >= INSTR_STORE_F &&
|
||||
stmt.opcode <= INSTR_STORE_FNC)
|
||||
{
|
||||
/* 2-operand instructions with A -> B */
|
||||
stmt.o2.u1 = stmt.o3.u1;
|
||||
|
@ -2157,6 +2304,7 @@ tailcall:
|
|||
static bool gen_function_code(ir_function *self)
|
||||
{
|
||||
ir_block *block;
|
||||
prog_section_statement stmt;
|
||||
|
||||
/* Starting from entry point, we generate blocks "as they come"
|
||||
* for now. Dead blocks will not be translated obviously.
|
||||
|
@ -2174,6 +2322,14 @@ static bool gen_function_code(ir_function *self)
|
|||
printf("failed to generate blocks for '%s'\n", self->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* otherwise code_write crashes since it debug-prints functions until AINSTR_END */
|
||||
stmt.opcode = AINSTR_END;
|
||||
stmt.o1.u1 = 0;
|
||||
stmt.o2.u1 = 0;
|
||||
stmt.o3.u1 = 0;
|
||||
if (code_statements_add(stmt) < 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2185,8 +2341,7 @@ static bool gen_global_function(ir_builder *ir, ir_value *global)
|
|||
size_t i;
|
||||
size_t local_var_end;
|
||||
|
||||
if (!global->isconst ||
|
||||
!global->constval.vfunc)
|
||||
if (!global->isconst || (!global->constval.vfunc))
|
||||
{
|
||||
printf("Invalid state of function-global: not constant: %s\n", global->name);
|
||||
return false;
|
||||
|
@ -2202,10 +2357,8 @@ static bool gen_global_function(ir_builder *ir, ir_value *global)
|
|||
for (i = 0;i < 8; ++i) {
|
||||
if (i >= fun.nargs)
|
||||
fun.argsize[i] = 0;
|
||||
else if (irfun->params[i] == TYPE_VECTOR)
|
||||
fun.argsize[i] = 3;
|
||||
else
|
||||
fun.argsize[i] = 1;
|
||||
fun.argsize[i] = type_sizeof[irfun->params[i]];
|
||||
}
|
||||
|
||||
fun.firstlocal = code_globals_elements;
|
||||
|
@ -2234,10 +2387,14 @@ static bool gen_global_function(ir_builder *ir, ir_value *global)
|
|||
code_globals_add(0);
|
||||
}
|
||||
|
||||
fun.entry = code_statements_elements;
|
||||
if (!gen_function_code(irfun)) {
|
||||
printf("Failed to generate code for function %s\n", irfun->name);
|
||||
return false;
|
||||
if (irfun->builtin)
|
||||
fun.entry = irfun->builtin;
|
||||
else {
|
||||
fun.entry = code_statements_elements;
|
||||
if (!gen_function_code(irfun)) {
|
||||
printf("Failed to generate code for function %s\n", irfun->name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return (code_functions_add(fun) >= 0);
|
||||
|
@ -2245,6 +2402,7 @@ static bool gen_global_function(ir_builder *ir, ir_value *global)
|
|||
|
||||
static bool ir_builder_gen_global(ir_builder *self, ir_value *global)
|
||||
{
|
||||
size_t i;
|
||||
int32_t *iptr;
|
||||
prog_section_def def;
|
||||
|
||||
|
@ -2289,31 +2447,43 @@ static bool ir_builder_gen_global(ir_builder *self, ir_value *global)
|
|||
}
|
||||
case TYPE_VECTOR:
|
||||
{
|
||||
size_t d;
|
||||
if (code_defs_add(def) < 0)
|
||||
return false;
|
||||
|
||||
if (global->isconst) {
|
||||
iptr = (int32_t*)&global->constval.vvec;
|
||||
global->code.globaladdr = code_globals_add(iptr[0]);
|
||||
if (code_globals_add(iptr[1]) < 0 || code_globals_add(iptr[2]) < 0)
|
||||
if (global->code.globaladdr < 0)
|
||||
return false;
|
||||
for (d = 1; d < type_sizeof[global->vtype]; ++d)
|
||||
{
|
||||
if (code_globals_add(iptr[d]) < 0)
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
global->code.globaladdr = code_globals_add(0);
|
||||
if (code_globals_add(0) < 0 || code_globals_add(0) < 0)
|
||||
if (global->code.globaladdr < 0)
|
||||
return false;
|
||||
for (d = 1; d < type_sizeof[global->vtype]; ++d)
|
||||
{
|
||||
if (code_globals_add(0) < 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return global->code.globaladdr >= 0;
|
||||
}
|
||||
case TYPE_FUNCTION:
|
||||
if (code_defs_add(def) < 0)
|
||||
return false;
|
||||
global->code.globaladdr = code_globals_elements;
|
||||
code_globals_add(code_functions_elements);
|
||||
return gen_global_function(self, global);
|
||||
case TYPE_VARIANT:
|
||||
/* assume biggest type */
|
||||
global->code.globaladdr = code_globals_add(0);
|
||||
code_globals_add(0);
|
||||
code_globals_add(0);
|
||||
for (i = 1; i < type_sizeof[TYPE_VARIANT]; ++i)
|
||||
code_globals_add(0);
|
||||
return true;
|
||||
default:
|
||||
/* refuse to create 'void' type or any other fancy business. */
|
||||
|
@ -2328,21 +2498,6 @@ bool ir_builder_generate(ir_builder *self, const char *filename)
|
|||
|
||||
code_init();
|
||||
|
||||
/* FIXME: generate TYPE_FUNCTION globals and link them
|
||||
* to their ir_function.
|
||||
*/
|
||||
|
||||
for (i = 0; i < self->functions_count; ++i)
|
||||
{
|
||||
ir_value *funval;
|
||||
ir_function *fun = self->functions[i];
|
||||
|
||||
funval = ir_builder_create_global(self, fun->name, TYPE_FUNCTION);
|
||||
funval->isconst = true;
|
||||
funval->constval.vfunc = fun;
|
||||
funval->context = fun->context;
|
||||
}
|
||||
|
||||
for (i = 0; i < self->globals_count; ++i)
|
||||
{
|
||||
if (!ir_builder_gen_global(self, self->globals[i])) {
|
||||
|
@ -2398,6 +2553,10 @@ 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);
|
||||
strncat(ind, "\t", IND_BUFSZ);
|
||||
if (f->locals_count)
|
||||
|
|
30
ir.h
30
ir.h
|
@ -40,6 +40,8 @@ typedef struct ir_value_s {
|
|||
lex_ctx context;
|
||||
/* even the IR knows the subtype of a field */
|
||||
int fieldtype;
|
||||
/* and the output type of a function */
|
||||
int outtype;
|
||||
|
||||
MEM_VECTOR_MAKE(struct ir_instr_s*, reads);
|
||||
MEM_VECTOR_MAKE(struct ir_instr_s*, writes);
|
||||
|
@ -79,6 +81,7 @@ MEM_VECTOR_PROTO_ALL(ir_value, struct ir_instr_s*, reads);
|
|||
MEM_VECTOR_PROTO_ALL(ir_value, struct ir_instr_s*, writes);
|
||||
|
||||
bool GMQCC_WARN ir_value_set_float(ir_value*, float f);
|
||||
bool GMQCC_WARN ir_value_set_func(ir_value*, int f);
|
||||
#if 0
|
||||
bool GMQCC_WARN ir_value_set_int(ir_value*, int i);
|
||||
#endif
|
||||
|
@ -100,6 +103,13 @@ bool ir_values_overlap(const ir_value*, const ir_value*);
|
|||
void ir_value_dump(ir_value*, int (*oprintf)(const char*,...));
|
||||
void ir_value_dump_life(ir_value *self, int (*oprintf)(const char*,...));
|
||||
|
||||
/* A vector of IR values */
|
||||
typedef struct {
|
||||
MEM_VECTOR_MAKE(ir_value*, v);
|
||||
} ir_value_vector;
|
||||
MEM_VECTOR_PROTO(ir_value_vector, ir_value*, v);
|
||||
|
||||
/* PHI data */
|
||||
typedef struct ir_phi_entry_s
|
||||
{
|
||||
ir_value *value;
|
||||
|
@ -115,6 +125,7 @@ typedef struct ir_instr_s
|
|||
struct ir_block_s* (bops[2]);
|
||||
|
||||
MEM_VECTOR_MAKE(ir_phi_entry_t, phi);
|
||||
MEM_VECTOR_MAKE(ir_value*, params);
|
||||
|
||||
/* For the temp-allocation */
|
||||
size_t eid;
|
||||
|
@ -128,6 +139,8 @@ void ir_instr_delete(ir_instr*);
|
|||
MEM_VECTOR_PROTO(ir_value, ir_phi_entry_t, phi);
|
||||
bool GMQCC_WARN ir_instr_op(ir_instr*, int op, ir_value *value, bool writing);
|
||||
|
||||
MEM_VECTOR_PROTO(ir_value, ir_value*, params);
|
||||
|
||||
void ir_instr_dump(ir_instr* in, char *ind, int (*oprintf)(const char*,...));
|
||||
|
||||
/* block */
|
||||
|
@ -164,6 +177,8 @@ MEM_VECTOR_PROTO_ALL(ir_block, ir_block*, entries);
|
|||
|
||||
ir_value* ir_block_create_binop(ir_block*, const char *label, int op,
|
||||
ir_value *left, ir_value *right);
|
||||
ir_value* ir_block_create_unary(ir_block*, const char *label, int op,
|
||||
ir_value *operand);
|
||||
bool GMQCC_WARN ir_block_create_store_op(ir_block*, int op, ir_value *target, ir_value *what);
|
||||
bool GMQCC_WARN ir_block_create_store(ir_block*, ir_value *target, ir_value *what);
|
||||
bool GMQCC_WARN ir_block_create_storep(ir_block*, ir_value *target, ir_value *what);
|
||||
|
@ -186,6 +201,9 @@ ir_value* ir_block_create_div(ir_block*, const char *label, ir_value *l, ir_valu
|
|||
ir_instr* ir_block_create_phi(ir_block*, const char *label, int vtype);
|
||||
ir_value* ir_phi_value(ir_instr*);
|
||||
bool GMQCC_WARN ir_phi_add(ir_instr*, ir_block *b, ir_value *v);
|
||||
ir_instr* ir_block_create_call(ir_block*, const char *label, ir_value *func);
|
||||
ir_value* ir_call_value(ir_instr*);
|
||||
bool GMQCC_WARN ir_call_param(ir_instr*, ir_value*);
|
||||
|
||||
bool GMQCC_WARN ir_block_create_return(ir_block*, ir_value *opt_value);
|
||||
|
||||
|
@ -209,10 +227,14 @@ void ir_block_dump(ir_block*, char *ind, int (*oprintf)(const char*,...));
|
|||
typedef struct ir_function_s
|
||||
{
|
||||
char *name;
|
||||
int retype;
|
||||
int outtype;
|
||||
MEM_VECTOR_MAKE(int, params);
|
||||
MEM_VECTOR_MAKE(ir_block*, blocks);
|
||||
|
||||
int builtin;
|
||||
|
||||
ir_value *value;
|
||||
|
||||
/* values generated from operations
|
||||
* which might get optimized away, so anything
|
||||
* in there needs to be deleted in the dtor.
|
||||
|
@ -235,7 +257,7 @@ typedef struct ir_function_s
|
|||
struct ir_builder_s *owner;
|
||||
} ir_function;
|
||||
|
||||
ir_function* ir_function_new(struct ir_builder_s *owner);
|
||||
ir_function* ir_function_new(struct ir_builder_s *owner, int returntype);
|
||||
void ir_function_delete(ir_function*);
|
||||
|
||||
bool GMQCC_WARN ir_function_collect_value(ir_function*, ir_value *value);
|
||||
|
@ -245,7 +267,7 @@ MEM_VECTOR_PROTO(ir_function, int, params);
|
|||
MEM_VECTOR_PROTO(ir_function, ir_block*, blocks);
|
||||
|
||||
ir_value* ir_function_get_local(ir_function *self, const char *name);
|
||||
ir_value* ir_function_create_local(ir_function *self, const char *name, int vtype);
|
||||
ir_value* ir_function_create_local(ir_function *self, const char *name, int vtype, bool param);
|
||||
|
||||
bool GMQCC_WARN ir_function_finalize(ir_function*);
|
||||
/*
|
||||
|
@ -275,7 +297,7 @@ MEM_VECTOR_PROTO(ir_builder, ir_function*, functions);
|
|||
MEM_VECTOR_PROTO(ir_builder, ir_value*, globals);
|
||||
|
||||
ir_function* ir_builder_get_function(ir_builder*, const char *fun);
|
||||
ir_function* ir_builder_create_function(ir_builder*, const char *name);
|
||||
ir_function* ir_builder_create_function(ir_builder*, const char *name, int outtype);
|
||||
|
||||
ir_value* ir_builder_get_global(ir_builder*, const char *fun);
|
||||
ir_value* ir_builder_create_global(ir_builder*, const char *name, int vtype);
|
||||
|
|
|
@ -28,6 +28,13 @@ do { \
|
|||
MKGLOBAL(name); \
|
||||
} while(0)
|
||||
|
||||
#define MKCONSTSTRING(name, value) \
|
||||
do { \
|
||||
name->isconst = true; \
|
||||
name->constval.vstring = util_strdup(value); \
|
||||
MKGLOBAL(name); \
|
||||
} while(0)
|
||||
|
||||
#define STATE(a) \
|
||||
do { \
|
||||
ast_expression *exp = (ast_expression*)(a); \
|
||||
|
@ -40,6 +47,17 @@ do { \
|
|||
#define BIN(op, a, b) \
|
||||
(ast_expression*)ast_binary_new(ctx, INSTR_##op, (ast_expression*)(a), (ast_expression*)(b))
|
||||
|
||||
#define CALL(what) \
|
||||
do { \
|
||||
ast_call *call = ast_call_new(ctx, (ast_expression*)what); \
|
||||
|
||||
#define CALLPARAM(x) \
|
||||
assert(ast_call_params_add(call, (ast_expression*)x));
|
||||
|
||||
#define ENDCALL() \
|
||||
STATE(call); \
|
||||
} while(0)
|
||||
|
||||
#define WHILE(cond) \
|
||||
do { \
|
||||
ast_expression *wh_cond = (ast_expression*)(cond); \
|
||||
|
@ -56,18 +74,51 @@ do { \
|
|||
STATE(loop); \
|
||||
} while(0)
|
||||
|
||||
#define FUNCTION(name) \
|
||||
do { \
|
||||
ast_function *func_##name; \
|
||||
ast_block *my_funcblock; \
|
||||
DEFVAR(var_##name); \
|
||||
VARnamed(TYPE_FUNCTION, var_##name, name); \
|
||||
MKGLOBAL(var_##name); \
|
||||
func_##name = ast_function_new(ctx, #name, var_##name); \
|
||||
assert(functions_add(func_##name) >= 0); \
|
||||
my_funcblock = ast_block_new(ctx); \
|
||||
assert(my_funcblock); \
|
||||
assert(ast_function_blocks_add(func_##name, my_funcblock)); \
|
||||
#define BUILTIN(name, outtype, number) \
|
||||
do { \
|
||||
ast_function *func_##name; \
|
||||
ast_value *thisfuncval; \
|
||||
ast_function *thisfunc; \
|
||||
DEFVAR(return_##name); \
|
||||
VARnamed(TYPE_FUNCTION, name, name); \
|
||||
VARnamed(outtype, return_##name, "#returntype"); \
|
||||
name->expression.next = (ast_expression*)return_##name; \
|
||||
MKGLOBAL(name); \
|
||||
func_##name = ast_function_new(ctx, #name, name); \
|
||||
thisfunc = func_##name; \
|
||||
(void)thisfunc; \
|
||||
thisfuncval = name; \
|
||||
(void)thisfuncval; \
|
||||
assert(functions_add(func_##name) >= 0); \
|
||||
func_##name->builtin = number;
|
||||
|
||||
#define ENDBUILTIN() } while(0)
|
||||
|
||||
#define PARAM(ptype, name) \
|
||||
do { \
|
||||
DEFVAR(parm); \
|
||||
VARnamed(ptype, parm, name); \
|
||||
assert(ast_value_params_add(thisfuncval, parm)); \
|
||||
} while(0)
|
||||
|
||||
#define FUNCTION(name, outtype) \
|
||||
do { \
|
||||
ast_function *thisfunc; \
|
||||
ast_function *func_##name; \
|
||||
ast_block *my_funcblock; \
|
||||
DEFVAR(var_##name); \
|
||||
DEFVAR(return_##name); \
|
||||
VARnamed(TYPE_FUNCTION, var_##name, name); \
|
||||
VARnamed(outtype, return_##name, "#returntype"); \
|
||||
var_##name->expression.next = (ast_expression*)return_##name; \
|
||||
MKGLOBAL(var_##name); \
|
||||
func_##name = ast_function_new(ctx, #name, var_##name); \
|
||||
thisfunc = func_##name; \
|
||||
(void)thisfunc; \
|
||||
assert(functions_add(func_##name) >= 0); \
|
||||
my_funcblock = ast_block_new(ctx); \
|
||||
assert(my_funcblock); \
|
||||
assert(ast_function_blocks_add(func_##name, my_funcblock)); \
|
||||
curblock = my_funcblock;
|
||||
|
||||
#define MKLOCAL(var) \
|
||||
|
|
|
@ -27,28 +27,45 @@ int main()
|
|||
DEFVAR(f0);
|
||||
DEFVAR(f1);
|
||||
DEFVAR(f5);
|
||||
DEFVAR(sHello);
|
||||
DEFVAR(print);
|
||||
|
||||
/* opts_debug = true; */
|
||||
|
||||
BUILTIN(print, TYPE_VOID, -1);
|
||||
PARAM(TYPE_STRING, text);
|
||||
ENDBUILTIN();
|
||||
|
||||
TESTINIT();
|
||||
VAR(TYPE_FLOAT, f0);
|
||||
VAR(TYPE_FLOAT, f1);
|
||||
VAR(TYPE_FLOAT, f5);
|
||||
VAR(TYPE_STRING, sHello);
|
||||
MKCONSTFLOAT(f0, 0.0);
|
||||
MKCONSTFLOAT(f1, 1.0);
|
||||
MKCONSTFLOAT(f5, 5.0);
|
||||
MKCONSTSTRING(sHello, "Hello, World\n");
|
||||
|
||||
FUNCTION(main);
|
||||
FUNCTION(foo, TYPE_VOID);
|
||||
ENDFUNCTION(foo);
|
||||
|
||||
VAR(TYPE_FLOAT, vi);
|
||||
VAR(TYPE_FLOAT, vx);
|
||||
FUNCTION(main, TYPE_VOID);
|
||||
|
||||
MKLOCAL(vi);
|
||||
MKLOCAL(vx);
|
||||
VAR(TYPE_FLOAT, vi);
|
||||
VAR(TYPE_FLOAT, vx);
|
||||
|
||||
STATE(ASSIGN(STORE_F, vi, f0));
|
||||
WHILE(BIN(LT, vi, f5));
|
||||
STATE(ASSIGN(STORE_F, vx, BIN(MUL_F, vi, f5)));
|
||||
STATE(ASSIGN(STORE_F, vi, BIN(ADD_F, vi, f1)));
|
||||
ENDWHILE();
|
||||
MKLOCAL(vi);
|
||||
MKLOCAL(vx);
|
||||
|
||||
STATE(ASSIGN(STORE_F, vi, f0));
|
||||
WHILE(BIN(LT, vi, f5));
|
||||
STATE(ASSIGN(STORE_F, vx, BIN(MUL_F, vi, f5)));
|
||||
STATE(ASSIGN(STORE_F, vi, BIN(ADD_F, vi, f1)));
|
||||
ENDWHILE();
|
||||
|
||||
CALL(print)
|
||||
CALLPARAM(sHello)
|
||||
ENDCALL();
|
||||
|
||||
ENDFUNCTION(main);
|
||||
|
||||
|
|
Loading…
Reference in a new issue