mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-14 17:01:22 +00:00
df7c08a010
Only as scalars, I still need to think about what to do for vectors and quaternions due to param size issues. Also, doubles are not yet guaranteed to be correctly aligned.
1766 lines
41 KiB
C
1766 lines
41 KiB
C
/*
|
|
statements.c
|
|
|
|
Internal statements
|
|
|
|
Copyright (C) 2011 Bill Currie <bill@taniwha.org>
|
|
|
|
Author: Bill Currie <bill@taniwha.org>
|
|
Date: 2011/06/18
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "qfalloca.h"
|
|
|
|
#include "QF/alloc.h"
|
|
#include "QF/va.h"
|
|
|
|
#include "dags.h"
|
|
#include "diagnostic.h"
|
|
#include "dot.h"
|
|
#include "expr.h"
|
|
#include "function.h"
|
|
#include "options.h"
|
|
#include "qfcc.h"
|
|
#include "reloc.h"
|
|
#include "statements.h"
|
|
#include "strpool.h"
|
|
#include "symtab.h"
|
|
#include "type.h"
|
|
#include "value.h"
|
|
#include "qc-parse.h"
|
|
|
|
static const char *op_type_names[] = {
|
|
"op_def",
|
|
"op_value",
|
|
"op_label",
|
|
"op_temp",
|
|
};
|
|
|
|
const char *
|
|
optype_str (op_type_e type)
|
|
{
|
|
if (type > op_temp)
|
|
return "<invalid op_type>";
|
|
return op_type_names[type];
|
|
}
|
|
|
|
const char *
|
|
operand_string (operand_t *op)
|
|
{
|
|
if (!op)
|
|
return "";
|
|
switch (op->op_type) {
|
|
case op_def:
|
|
return op->o.def->name;
|
|
case op_value:
|
|
switch (op->o.value->lltype) {
|
|
case ev_string:
|
|
return va ("\"%s\"",
|
|
quote_string (op->o.value->v.string_val));
|
|
case ev_double:
|
|
return va ("%g", op->o.value->v.double_val);
|
|
case ev_float:
|
|
return va ("%g", op->o.value->v.float_val);
|
|
case ev_vector:
|
|
return va ("'%g %g %g'",
|
|
op->o.value->v.vector_val[0],
|
|
op->o.value->v.vector_val[1],
|
|
op->o.value->v.vector_val[2]);
|
|
case ev_quat:
|
|
return va ("'%g %g %g %g'",
|
|
op->o.value->v.quaternion_val[0],
|
|
op->o.value->v.quaternion_val[1],
|
|
op->o.value->v.quaternion_val[2],
|
|
op->o.value->v.quaternion_val[3]);
|
|
case ev_pointer:
|
|
return va ("ptr %d", op->o.value->v.pointer.val);
|
|
case ev_field:
|
|
return va ("field %d", op->o.value->v.pointer.val);
|
|
case ev_entity:
|
|
return va ("ent %d", op->o.value->v.integer_val);
|
|
case ev_func:
|
|
return va ("func %d", op->o.value->v.integer_val);
|
|
case ev_integer:
|
|
return va ("int %d", op->o.value->v.integer_val);
|
|
case ev_uinteger:
|
|
return va ("uint %u", op->o.value->v.uinteger_val);
|
|
case ev_short:
|
|
return va ("short %d", op->o.value->v.short_val);
|
|
case ev_void:
|
|
return "(void)";
|
|
case ev_invalid:
|
|
return "(invalid)";
|
|
case ev_type_count:
|
|
return "(type_count)";
|
|
}
|
|
break;
|
|
case op_label:
|
|
return op->o.label->name;
|
|
case op_temp:
|
|
if (op->o.tempop.alias)
|
|
return va ("<tmp %s %p:%d:%p:%d:%d>",
|
|
pr_type_name[op->type->type],
|
|
op, op->o.tempop.users,
|
|
op->o.tempop.alias,
|
|
op->o.tempop.offset,
|
|
op->o.tempop.alias->o.tempop.users);
|
|
return va ("<tmp %s %p:%d>", pr_type_name[op->o.tempop.type->type],
|
|
op, op->o.tempop.users);
|
|
case op_alias:
|
|
{
|
|
const char *alias = operand_string (op->o.alias);
|
|
char *buf = alloca (strlen (alias) + 1);
|
|
strcpy (buf, alias);
|
|
return va ("alias(%s,%s)", pr_type_name[op->type->type], buf);
|
|
}
|
|
}
|
|
return ("??");
|
|
}
|
|
|
|
void
|
|
print_operand (operand_t *op)
|
|
{
|
|
switch (op->op_type) {
|
|
case op_def:
|
|
printf ("(%s) ", pr_type_name[op->type->type]);
|
|
printf ("%s", op->o.def->name);
|
|
break;
|
|
case op_value:
|
|
printf ("(%s) ", pr_type_name[op->type->type]);
|
|
switch (op->o.value->lltype) {
|
|
case ev_string:
|
|
printf ("\"%s\"", op->o.value->v.string_val);
|
|
break;
|
|
case ev_double:
|
|
printf ("%g", op->o.value->v.double_val);
|
|
break;
|
|
case ev_float:
|
|
printf ("%g", op->o.value->v.float_val);
|
|
break;
|
|
case ev_vector:
|
|
printf ("'%g", op->o.value->v.vector_val[0]);
|
|
printf (" %g", op->o.value->v.vector_val[1]);
|
|
printf (" %g'", op->o.value->v.vector_val[2]);
|
|
break;
|
|
case ev_quat:
|
|
printf ("'%g", op->o.value->v.quaternion_val[0]);
|
|
printf (" %g", op->o.value->v.quaternion_val[1]);
|
|
printf (" %g", op->o.value->v.quaternion_val[2]);
|
|
printf (" %g'", op->o.value->v.quaternion_val[3]);
|
|
break;
|
|
case ev_pointer:
|
|
printf ("(%s)[%d]",
|
|
pr_type_name[op->o.value->v.pointer.type->type],
|
|
op->o.value->v.pointer.val);
|
|
break;
|
|
case ev_field:
|
|
printf ("%d", op->o.value->v.pointer.val);
|
|
break;
|
|
case ev_entity:
|
|
case ev_func:
|
|
case ev_integer:
|
|
printf ("%d", op->o.value->v.integer_val);
|
|
break;
|
|
case ev_uinteger:
|
|
printf ("%u", op->o.value->v.uinteger_val);
|
|
break;
|
|
case ev_short:
|
|
printf ("%d", op->o.value->v.short_val);
|
|
break;
|
|
case ev_void:
|
|
case ev_invalid:
|
|
case ev_type_count:
|
|
internal_error (0, "weird value type");
|
|
}
|
|
break;
|
|
case op_label:
|
|
printf ("block %p", op->o.label->dest);
|
|
break;
|
|
case op_temp:
|
|
printf ("tmp (%s) %p", pr_type_name[op->type->type], op);
|
|
if (op->o.tempop.def)
|
|
printf (" %s", op->o.tempop.def->name);
|
|
break;
|
|
case op_alias:
|
|
printf ("alias(%s,", pr_type_name[op->type->type]);
|
|
print_operand (op->o.alias);
|
|
printf (")");
|
|
}
|
|
}
|
|
|
|
void
|
|
print_statement (statement_t *s)
|
|
{
|
|
printf ("(%s, ", s->opcode);
|
|
if (s->opa)
|
|
print_operand (s->opa);
|
|
printf (", ");
|
|
if (s->opb)
|
|
print_operand (s->opb);
|
|
printf (", ");
|
|
if (s->opc)
|
|
print_operand (s->opc);
|
|
printf (")\n");
|
|
}
|
|
|
|
static sblock_t *sblocks_freelist;
|
|
static statement_t *statements_freelist;
|
|
static operand_t *operands_freelist;
|
|
|
|
sblock_t *
|
|
new_sblock (void)
|
|
{
|
|
sblock_t *sblock;
|
|
ALLOC (256, sblock_t, sblocks, sblock);
|
|
sblock->tail = &sblock->statements;
|
|
return sblock;
|
|
}
|
|
|
|
void
|
|
sblock_add_statement (sblock_t *sblock, statement_t *statement)
|
|
{
|
|
// this should normally be null, but might be inserting
|
|
statement->next = *sblock->tail;
|
|
*sblock->tail = statement;
|
|
sblock->tail = &statement->next;
|
|
}
|
|
|
|
statement_t *
|
|
new_statement (st_type_t type, const char *opcode, expr_t *expr)
|
|
{
|
|
statement_t *statement;
|
|
ALLOC (256, statement_t, statements, statement);
|
|
statement->type = type;
|
|
statement->opcode = save_string (opcode);
|
|
statement->expr = expr;
|
|
return statement;
|
|
}
|
|
|
|
static operand_t *
|
|
new_operand (op_type_e op)
|
|
{
|
|
operand_t *operand;
|
|
ALLOC (256, operand_t, operands, operand);
|
|
operand->op_type = op;
|
|
return operand;
|
|
}
|
|
|
|
void
|
|
free_operand (operand_t *op)
|
|
{
|
|
FREE (operands, op);
|
|
}
|
|
|
|
static void
|
|
free_statement (statement_t *s)
|
|
{
|
|
// if (s->opa)
|
|
// free_operand (s->opa);
|
|
// if (s->opb)
|
|
// free_operand (s->opb);
|
|
// if (s->opc)
|
|
// free_operand (s->opc);
|
|
FREE (statements, s);
|
|
}
|
|
|
|
static void
|
|
free_sblock (sblock_t *sblock)
|
|
{
|
|
while (sblock->statements) {
|
|
statement_t *s = sblock->statements;
|
|
sblock->statements = s->next;
|
|
free_statement (s);
|
|
}
|
|
FREE (sblocks, sblock);
|
|
}
|
|
|
|
operand_t *
|
|
def_operand (def_t *def, type_t *type)
|
|
{
|
|
operand_t *op;
|
|
|
|
if (!type)
|
|
type = def->type;
|
|
op = new_operand (op_def);
|
|
op->type = type;
|
|
op->size = type_size (type);
|
|
op->o.def = def;
|
|
return op;
|
|
}
|
|
|
|
operand_t *
|
|
value_operand (ex_value_t *value)
|
|
{
|
|
operand_t *op;
|
|
op = new_operand (op_value);
|
|
op->type = value->type;
|
|
op->o.value = value;
|
|
return op;
|
|
}
|
|
|
|
operand_t *
|
|
temp_operand (type_t *type)
|
|
{
|
|
operand_t *op = new_operand (op_temp);
|
|
|
|
op->o.tempop.type = type;
|
|
op->type = type;
|
|
op->size = type_size (type);
|
|
return op;
|
|
}
|
|
|
|
int
|
|
tempop_overlap (tempop_t *t1, tempop_t *t2)
|
|
{
|
|
int offs1 = t1->offset;
|
|
int offs2 = t2->offset;
|
|
int size1 = type_size (t1->type);
|
|
int size2 = type_size (t2->type);
|
|
|
|
if (offs1 <= offs2 && offs1 + size1 >= offs2 + size2)
|
|
return 2; // t1 fully overlaps t2
|
|
if (offs1 < offs2 + size2 && offs2 < offs1 + size1)
|
|
return 1; // t1 and t2 at least partially overlap
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
tempop_visit_all (tempop_t *tempop, int overlap,
|
|
int (*visit) (tempop_t *, void *), void *data)
|
|
{
|
|
tempop_t *start_tempop = tempop;
|
|
operand_t *top;
|
|
int ret;
|
|
|
|
if ((ret = visit (tempop, data)))
|
|
return ret;
|
|
if (tempop->alias) {
|
|
top = tempop->alias;
|
|
if (top->op_type != op_temp) {
|
|
internal_error (0, "temp alias of non-temp operand");
|
|
}
|
|
tempop = &top->o.tempop;
|
|
if ((ret = visit (tempop, data)))
|
|
return ret;
|
|
}
|
|
for (top = tempop->alias_ops; top; top = top->next) {
|
|
if (top->op_type != op_temp) {
|
|
internal_error (0, "temp alias of non-temp operand");
|
|
}
|
|
tempop = &top->o.tempop;
|
|
if (tempop == start_tempop)
|
|
continue;
|
|
if (overlap && tempop_overlap (tempop, start_tempop) < overlap)
|
|
continue;
|
|
if ((ret = visit (tempop, data)))
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
operand_t *
|
|
alias_operand (type_t *type, operand_t *op)
|
|
{
|
|
operand_t *aop;
|
|
|
|
if (type_size (type) != type_size (op->type)) {
|
|
internal_error (0, "\naliasing operand with type of diffent size"
|
|
" (%d, %d)", type_size (type), type_size (op->type));
|
|
}
|
|
aop = new_operand (op_alias);
|
|
aop->o.alias = op;
|
|
aop->type = type;
|
|
aop->size = type_size (type);
|
|
return aop;
|
|
}
|
|
|
|
static operand_t *
|
|
short_operand (short short_val)
|
|
{
|
|
ex_value_t *val = new_short_val (short_val);
|
|
return value_operand (val);
|
|
}
|
|
|
|
static const char *
|
|
convert_op (int op)
|
|
{
|
|
switch (op) {
|
|
case PAS: return ".=";
|
|
case OR: return "||";
|
|
case AND: return "&&";
|
|
case EQ: return "==";
|
|
case NE: return "!=";
|
|
case LE: return "<=";
|
|
case GE: return ">=";
|
|
case LT: return "<";
|
|
case GT: return ">";
|
|
case '=': return "=";
|
|
case '+': return "+";
|
|
case '-': return "-";
|
|
case '*': return "*";
|
|
case '/': return "/";
|
|
case '%': return "%";
|
|
case '&': return "&";
|
|
case '|': return "|";
|
|
case '^': return "^";
|
|
case '~': return "~";
|
|
case '!': return "!";
|
|
case SHL: return "<<";
|
|
case SHR: return ">>";
|
|
case '.': return ".";
|
|
case 'i': return "<IF>";
|
|
case 'n': return "<IFNOT>";
|
|
case IFBE: return "<IFBE>";
|
|
case IFB: return "<IFB>";
|
|
case IFAE: return "<IFAE>";
|
|
case IFA: return "<IFA>";
|
|
case 'm': return "<MOVE>";
|
|
case 'M': return "<MOVEP>";
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int
|
|
statement_is_cond (statement_t *s)
|
|
{
|
|
if (!s)
|
|
return 0;
|
|
return !strncmp (s->opcode, "<IF", 3);
|
|
}
|
|
|
|
int
|
|
statement_is_goto (statement_t *s)
|
|
{
|
|
if (!s)
|
|
return 0;
|
|
return !strcmp (s->opcode, "<GOTO>");
|
|
}
|
|
|
|
int
|
|
statement_is_jumpb (statement_t *s)
|
|
{
|
|
if (!s)
|
|
return 0;
|
|
return !strcmp (s->opcode, "<JUMPB>");
|
|
}
|
|
|
|
int
|
|
statement_is_call (statement_t *s)
|
|
{
|
|
if (!s)
|
|
return 0;
|
|
if (!strncmp (s->opcode, "<CALL", 5))
|
|
return 1;
|
|
if (!strncmp (s->opcode, "<RCALL", 6))
|
|
return 2;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
statement_is_return (statement_t *s)
|
|
{
|
|
if (!s)
|
|
return 0;
|
|
return !strncmp (s->opcode, "<RETURN", 7);
|
|
}
|
|
|
|
sblock_t *
|
|
statement_get_target (statement_t *s)
|
|
{
|
|
if (statement_is_cond (s))
|
|
return s->opb->o.label->dest;
|
|
if (statement_is_goto (s))
|
|
return s->opa->o.label->dest;
|
|
return 0;
|
|
}
|
|
|
|
sblock_t **
|
|
statement_get_targetlist (statement_t *s)
|
|
{
|
|
sblock_t **target_list;
|
|
int count = 0, i;
|
|
def_t *table = 0;
|
|
expr_t *e;
|
|
|
|
if (statement_is_cond (s)) {
|
|
count = 1;
|
|
} else if (statement_is_goto (s)) {
|
|
count = 1;
|
|
} else if (statement_is_jumpb (s)) {
|
|
table = s->opa->o.def;
|
|
count = table->type->t.array.size;
|
|
}
|
|
target_list = malloc ((count + 1) * sizeof (sblock_t *));
|
|
target_list[count] = 0;
|
|
if (statement_is_cond (s)) {
|
|
target_list[0] = statement_get_target (s);
|
|
} else if (statement_is_goto (s)) {
|
|
target_list[0] = statement_get_target (s);
|
|
} else if (statement_is_jumpb (s)) {
|
|
if (table->alias)
|
|
internal_error (0, "aliased jump table");
|
|
e = table->initializer->e.block.head; //FIXME check!!!
|
|
for (i = 0; i < count; e = e->next, i++)
|
|
target_list[i] = e->e.labelref.label->dest;
|
|
}
|
|
return target_list;
|
|
}
|
|
|
|
static void
|
|
invert_conditional (statement_t *s)
|
|
{
|
|
if (!strcmp (s->opcode, "<IF>"))
|
|
s->opcode = "<IFNOT>";
|
|
else if (!strcmp (s->opcode, "<IFNOT>"))
|
|
s->opcode = "<IF>";
|
|
else if (!strcmp (s->opcode, "<IFBE>"))
|
|
s->opcode = "<IFA>";
|
|
else if (!strcmp (s->opcode, "<IFB>"))
|
|
s->opcode = "<IFAE>";
|
|
else if (!strcmp (s->opcode, "<IFAE>"))
|
|
s->opcode = "<IFB>";
|
|
else if (!strcmp (s->opcode, "<IFA>"))
|
|
s->opcode = "<IFBE>";
|
|
}
|
|
|
|
typedef sblock_t *(*statement_f) (sblock_t *, expr_t *);
|
|
typedef sblock_t *(*expr_f) (sblock_t *, expr_t *, operand_t **);
|
|
|
|
static sblock_t *statement_subexpr (sblock_t *sblock, expr_t *e,
|
|
operand_t **op);
|
|
static sblock_t *statement_slist (sblock_t *sblock, expr_t *e);
|
|
|
|
static sblock_t *
|
|
statement_branch (sblock_t *sblock, expr_t *e)
|
|
{
|
|
statement_t *s = 0;
|
|
const char *opcode;
|
|
|
|
if (e->type == ex_uexpr && e->e.expr.op == 'g') {
|
|
s = new_statement (st_flow, "<GOTO>", e);
|
|
s->opa = new_operand (op_label);
|
|
s->opa->o.label = &e->e.expr.e1->e.label;
|
|
} else {
|
|
if (e->e.expr.op == 'g') {
|
|
s = new_statement (st_flow, "<JUMPB>", e);
|
|
sblock = statement_subexpr (sblock, e->e.expr.e1, &s->opa);
|
|
sblock = statement_subexpr (sblock, e->e.expr.e2, &s->opb);
|
|
} else {
|
|
opcode = convert_op (e->e.expr.op);
|
|
s = new_statement (st_flow, opcode, e);
|
|
sblock = statement_subexpr (sblock, e->e.expr.e1, &s->opa);
|
|
s->opb = new_operand (op_label);
|
|
s->opb->o.label = &e->e.expr.e2->e.label;
|
|
}
|
|
}
|
|
|
|
sblock_add_statement (sblock, s);
|
|
sblock->next = new_sblock ();
|
|
return sblock->next;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_assign (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
statement_t *s;
|
|
expr_t *src_expr = e->e.expr.e2;
|
|
expr_t *dst_expr = e->e.expr.e1;
|
|
operand_t *src = 0;
|
|
operand_t *dst = 0;
|
|
operand_t *ofs = 0;
|
|
const char *opcode = convert_op (e->e.expr.op);
|
|
st_type_t type;
|
|
|
|
if (e->e.expr.op == '=') {
|
|
sblock = statement_subexpr (sblock, dst_expr, &dst);
|
|
src = dst;
|
|
sblock = statement_subexpr (sblock, src_expr, &src);
|
|
ofs = 0;
|
|
if (op)
|
|
*op = dst;
|
|
if (src == dst)
|
|
return sblock;
|
|
type = st_assign;
|
|
} else {
|
|
//FIXME this sucks. find a better way to handle both pointer
|
|
//dereferences and pointer assignements
|
|
sblock = statement_subexpr (sblock, src_expr, &src);
|
|
if (dst_expr->type == ex_expr
|
|
&& extract_type (dst_expr->e.expr.e1) == ev_pointer
|
|
&& !is_constant (dst_expr->e.expr.e1)) {
|
|
sblock = statement_subexpr (sblock, dst_expr->e.expr.e1, &dst);
|
|
sblock = statement_subexpr (sblock, dst_expr->e.expr.e2, &ofs);
|
|
} else {
|
|
sblock = statement_subexpr (sblock, dst_expr, &dst);
|
|
ofs = 0;
|
|
}
|
|
if (op)
|
|
*op = src;
|
|
type = st_ptrassign;
|
|
}
|
|
s = new_statement (type, opcode, e);
|
|
s->opa = src;
|
|
s->opb = dst;
|
|
s->opc = ofs;
|
|
sblock_add_statement (sblock, s);
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_move (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
statement_t *s;
|
|
type_t *type = e->e.expr.type;
|
|
expr_t *dst_expr = e->e.expr.e1;
|
|
expr_t *src_expr = e->e.expr.e2;
|
|
expr_t *size_expr;
|
|
operand_t *dst = 0;
|
|
operand_t *src = 0;
|
|
operand_t *size = 0;
|
|
|
|
if (!op)
|
|
op = &dst;
|
|
size_expr = new_short_expr (type_size (type));
|
|
sblock = statement_subexpr (sblock, dst_expr, op);
|
|
dst = *op;
|
|
sblock = statement_subexpr (sblock, src_expr, &src);
|
|
sblock = statement_subexpr (sblock, size_expr, &size);
|
|
s = new_statement (st_move, convert_op (e->e.expr.op), e);
|
|
s->opa = src;
|
|
s->opb = size;
|
|
s->opc = dst;
|
|
sblock_add_statement (sblock, s);
|
|
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
vector_call (sblock_t *sblock, expr_t *earg, expr_t *param, int ind,
|
|
operand_t **op)
|
|
{
|
|
//FIXME this should be done in the expression tree
|
|
expr_t *a, *v, *n;
|
|
int i;
|
|
static const char *names[] = {"x", "y", "z"};
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
n = new_name_expr (names[i]);
|
|
v = new_float_expr (earg->e.value->v.vector_val[i]);
|
|
a = assign_expr (field_expr (param, n), v);
|
|
param = new_param_expr (get_type (earg), ind);
|
|
a->line = earg->line;
|
|
a->file = earg->file;
|
|
sblock = statement_slist (sblock, a);
|
|
}
|
|
sblock = statement_subexpr (sblock, param, op);
|
|
return sblock;
|
|
}
|
|
|
|
|
|
static sblock_t *
|
|
expr_call (sblock_t *sblock, expr_t *call, operand_t **op)
|
|
{
|
|
expr_t *func = call->e.expr.e1;
|
|
expr_t *args = call->e.expr.e2;
|
|
expr_t *a;
|
|
expr_t *param;
|
|
operand_t *arguments[2] = {0, 0};
|
|
int count = 0;
|
|
int ind;
|
|
const char *opcode;
|
|
const char *pref = "";
|
|
statement_t *s;
|
|
|
|
for (a = args; a; a = a->next)
|
|
count++;
|
|
ind = count;
|
|
for (a = args; a; a = a->next) {
|
|
ind--;
|
|
param = new_param_expr (get_type (a), ind);
|
|
if (count && options.code.progsversion != PROG_ID_VERSION && ind < 2) {
|
|
pref = "R";
|
|
sblock = statement_subexpr (sblock, param, &arguments[ind]);
|
|
if (options.code.vector_calls && a->type == ex_value
|
|
&& a->e.value->lltype == ev_vector)
|
|
sblock = vector_call (sblock, a, param, ind, &arguments[ind]);
|
|
else
|
|
sblock = statement_subexpr (sblock, a, &arguments[ind]);
|
|
continue;
|
|
}
|
|
if (is_struct (get_type (param))) {
|
|
//FIXME this should be done in the expression tree
|
|
expr_t *mov = assign_expr (param, a);
|
|
mov->line = a->line;
|
|
mov->file = a->file;
|
|
sblock = statement_slist (sblock, mov);
|
|
} else {
|
|
if (options.code.vector_calls && a->type == ex_value
|
|
&& a->e.value->lltype == ev_vector) {
|
|
sblock = vector_call (sblock, a, param, ind, 0);
|
|
} else {
|
|
operand_t *p = 0;
|
|
operand_t *arg;
|
|
sblock = statement_subexpr (sblock, param, &p);
|
|
arg = p;
|
|
sblock = statement_subexpr (sblock, a, &arg);
|
|
if (arg != p) {
|
|
s = new_statement (st_assign, "=", a);
|
|
s->opa = arg;
|
|
s->opb = p;
|
|
sblock_add_statement (sblock, s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
opcode = va ("<%sCALL%d>", pref, count);
|
|
s = new_statement (st_func, opcode, call);
|
|
sblock = statement_subexpr (sblock, func, &s->opa);
|
|
s->opb = arguments[0];
|
|
s->opc = arguments[1];
|
|
sblock_add_statement (sblock, s);
|
|
sblock->next = new_sblock ();
|
|
return sblock->next;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_address (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
statement_t *s;
|
|
s = new_statement (st_expr, "&", e);
|
|
sblock = statement_subexpr (sblock, e->e.expr.e1, &s->opa);
|
|
s->opc = temp_operand (e->e.expr.type);
|
|
sblock_add_statement (sblock, s);
|
|
*(op) = s->opc;
|
|
return sblock;
|
|
}
|
|
|
|
static statement_t *
|
|
lea_statement (operand_t *pointer, operand_t *offset, expr_t *e)
|
|
{
|
|
statement_t *s = new_statement (st_expr, "&", e);
|
|
s->opa = pointer;
|
|
s->opb = offset;
|
|
s->opc = temp_operand (&type_pointer);
|
|
return s;
|
|
}
|
|
|
|
static statement_t *
|
|
address_statement (operand_t *value, expr_t *e)
|
|
{
|
|
statement_t *s = new_statement (st_expr, "&", e);
|
|
s->opa = value;
|
|
s->opc = temp_operand (&type_pointer);
|
|
return s;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_deref (sblock_t *sblock, expr_t *deref, operand_t **op)
|
|
{
|
|
type_t *type = deref->e.expr.type;
|
|
expr_t *e;
|
|
|
|
e = deref->e.expr.e1;
|
|
if (e->type == ex_uexpr && e->e.expr.op == '&'
|
|
&& e->e.expr.e1->type == ex_symbol) {
|
|
if (e->e.expr.e1->e.symbol->sy_type != sy_var)
|
|
internal_error (e, "address of non-var");
|
|
*op = def_operand (e->e.expr.e1->e.symbol->s.def, type);
|
|
} else if (e->type == ex_expr && e->e.expr.op == '&') {
|
|
statement_t *s;
|
|
operand_t *ptr = 0;
|
|
operand_t *offs = 0;
|
|
sblock = statement_subexpr (sblock, e->e.expr.e1, &ptr);
|
|
sblock = statement_subexpr (sblock, e->e.expr.e2, &offs);
|
|
if (!*op)
|
|
*op = temp_operand (type);
|
|
if (low_level_type (type) == ev_void) {
|
|
operand_t *src_addr;
|
|
operand_t *dst_addr;
|
|
|
|
s = lea_statement (ptr, offs, e);
|
|
src_addr = s->opc;
|
|
sblock_add_statement (sblock, s);
|
|
|
|
//FIXME an address immediate would be nice.
|
|
s = address_statement (*op, e);
|
|
dst_addr = s->opc;
|
|
sblock_add_statement (sblock, s);
|
|
|
|
s = new_statement (st_move, "<MOVEP>", deref);
|
|
s->opa = src_addr;
|
|
s->opb = short_operand (type_size (type));
|
|
s->opc = dst_addr;
|
|
sblock_add_statement (sblock, s);
|
|
} else {
|
|
s = new_statement (st_expr, ".", deref);
|
|
s->opa = ptr;
|
|
s->opb = offs;
|
|
s->opc = *op;
|
|
sblock_add_statement (sblock, s);
|
|
}
|
|
} else if (e->type == ex_value && e->e.value->lltype == ev_pointer) {
|
|
ex_pointer_t *ptr = &e->e.value->v.pointer;
|
|
*op = def_operand (alias_def (ptr->def, ptr->type, ptr->val),
|
|
ptr->type);
|
|
} else {
|
|
statement_t *s;
|
|
operand_t *ptr = 0;
|
|
|
|
sblock = statement_subexpr (sblock, e, &ptr);
|
|
if (!*op)
|
|
*op = temp_operand (type);
|
|
s = new_statement (st_expr, ".", deref);
|
|
s->opa = ptr;
|
|
s->opb = short_operand (0);
|
|
s->opc = *op;
|
|
sblock_add_statement (sblock, s);
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_block (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
if (!e->e.block.result)
|
|
internal_error (e, "block sub-expression without result");
|
|
sblock = statement_slist (sblock, e->e.block.head);
|
|
sblock = statement_subexpr (sblock, e->e.block.result, op);
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_alias (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
operand_t *aop = 0;
|
|
operand_t *top;
|
|
type_t *type;
|
|
def_t *def;
|
|
int offset = 0;
|
|
|
|
if (e->type == ex_expr) {
|
|
offset = expr_integer (e->e.expr.e2);
|
|
}
|
|
type = e->e.expr.type;
|
|
sblock = statement_subexpr (sblock, e->e.expr.e1, &aop);
|
|
if (type_compatible (aop->type, type)) {
|
|
if (offset) {
|
|
internal_error (e, "offset alias of same size type");
|
|
}
|
|
*op = aop;
|
|
return sblock;
|
|
}
|
|
if (aop->op_type == op_temp) {
|
|
while (aop->o.tempop.alias) {
|
|
aop = aop->o.tempop.alias;
|
|
if (aop->op_type != op_temp)
|
|
internal_error (e, "temp alias of non-temp var");
|
|
if (aop->o.tempop.alias)
|
|
bug (e, "aliased temp alias");
|
|
}
|
|
for (top = aop->o.tempop.alias_ops; top; top = top->next) {
|
|
if (top->type == type && top->o.tempop.offset == offset) {
|
|
break;
|
|
}
|
|
}
|
|
if (!top) {
|
|
top = temp_operand (type);
|
|
top->o.tempop.alias = aop;
|
|
top->o.tempop.offset = offset;
|
|
top->next = aop->o.tempop.alias_ops;
|
|
aop->o.tempop.alias_ops = top;
|
|
}
|
|
*op = top;
|
|
} else if (aop->op_type == op_def) {
|
|
def = aop->o.def;
|
|
while (def->alias)
|
|
def = def->alias;
|
|
*op = def_operand (alias_def (def, type, offset), 0);
|
|
} else {
|
|
internal_error (e, "invalid alias target: %s: %s",
|
|
optype_str (aop->op_type), operand_string (aop));
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_expr (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
const char *opcode;
|
|
statement_t *s;
|
|
|
|
switch (e->e.expr.op) {
|
|
case 'c':
|
|
sblock = expr_call (sblock, e, op);
|
|
break;
|
|
case '=':
|
|
case PAS:
|
|
sblock = expr_assign (sblock, e, op);
|
|
break;
|
|
case 'm':
|
|
case 'M':
|
|
sblock = expr_move (sblock, e, op);
|
|
break;
|
|
case 'A':
|
|
sblock = expr_alias (sblock, e, op);
|
|
break;
|
|
default:
|
|
opcode = convert_op (e->e.expr.op);
|
|
if (!opcode)
|
|
internal_error (e, "ice ice baby");
|
|
s = new_statement (st_expr, opcode, e);
|
|
sblock = statement_subexpr (sblock, e->e.expr.e1, &s->opa);
|
|
sblock = statement_subexpr (sblock, e->e.expr.e2, &s->opb);
|
|
if (!*op)
|
|
*op = temp_operand (e->e.expr.type);
|
|
s->opc = *op;
|
|
sblock_add_statement (sblock, s);
|
|
break;
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_cast (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
type_t *src_type;
|
|
type_t *type = e->e.expr.type;
|
|
statement_t *s;
|
|
|
|
src_type = get_type (e->e.expr.e1);
|
|
if ((src_type->type == ev_integer && type->type == ev_float)
|
|
|| (src_type->type == ev_float && type->type == ev_integer)) {
|
|
operand_t *src = 0;
|
|
sblock = statement_subexpr (sblock, e->e.expr.e1, &src);
|
|
*op = temp_operand (e->e.expr.type);
|
|
s = new_statement (st_expr, "<CONV>", e);
|
|
s->opa = src;
|
|
s->opc = *op;
|
|
sblock_add_statement (sblock, s);
|
|
} else {
|
|
sblock = expr_alias (sblock, e, op);
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_negate (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
expr_t *neg;
|
|
expr_t *zero;
|
|
|
|
zero = new_nil_expr ();
|
|
zero->file = e->file;
|
|
zero->line = e->line;
|
|
convert_nil (zero, e->e.expr.type);
|
|
neg = binary_expr ('-', zero, e->e.expr.e1);
|
|
neg->file = e->file;
|
|
neg->line = e->line;
|
|
return statement_subexpr (sblock, neg, op);
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_uexpr (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
const char *opcode;
|
|
statement_t *s;
|
|
|
|
switch (e->e.expr.op) {
|
|
case '&':
|
|
sblock = expr_address (sblock, e, op);
|
|
break;
|
|
case '.':
|
|
sblock = expr_deref (sblock, e, op);
|
|
break;
|
|
case 'A':
|
|
sblock = expr_alias (sblock, e, op);
|
|
break;
|
|
case 'C':
|
|
sblock = expr_cast (sblock, e, op);
|
|
break;
|
|
case '-':
|
|
// progs has no neg instruction!?!
|
|
sblock = expr_negate (sblock, e, op);
|
|
break;
|
|
default:
|
|
opcode = convert_op (e->e.expr.op);
|
|
if (!opcode)
|
|
internal_error (e, "ice ice baby");
|
|
s = new_statement (st_expr, opcode, e);
|
|
sblock = statement_subexpr (sblock, e->e.expr.e1, &s->opa);
|
|
if (!*op)
|
|
*op = temp_operand (e->e.expr.type);
|
|
s->opc = *op;
|
|
sblock_add_statement (sblock, s);
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_symbol (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
symbol_t *sym = e->e.symbol;
|
|
|
|
if (sym->sy_type == sy_var) {
|
|
*op = def_operand (sym->s.def, sym->type);
|
|
} else if (sym->sy_type == sy_const) {
|
|
*op = value_operand (sym->s.value);
|
|
} else if (sym->sy_type == sy_func) {
|
|
*op = def_operand (sym->s.func->def, 0);
|
|
} else {
|
|
internal_error (e, "unexpected symbol type: %s for %s",
|
|
symtype_str (sym->sy_type), sym->name);
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_temp (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
if (!e->e.temp.op)
|
|
e->e.temp.op = temp_operand (e->e.temp.type);
|
|
*op = e->e.temp.op;
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_vector_e (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
expr_t *x, *y, *z, *w;
|
|
expr_t *s, *v;
|
|
expr_t *ax, *ay, *az, *aw;
|
|
expr_t *as, *av;
|
|
expr_t *tmp;
|
|
type_t *vec_type = get_type (e);
|
|
int file = pr.source_file;
|
|
int line = pr.source_line;
|
|
|
|
pr.source_file = e->file;
|
|
pr.source_line = e->line;
|
|
|
|
tmp = new_temp_def_expr (vec_type);
|
|
if (vec_type == &type_vector) {
|
|
// guaranteed to have three elements
|
|
x = e->e.vector.list;
|
|
y = x->next;
|
|
z = y->next;
|
|
ax = new_name_expr ("x");
|
|
ay = new_name_expr ("y");
|
|
az = new_name_expr ("z");
|
|
ax = assign_expr (field_expr (tmp, ax), x);
|
|
ay = assign_expr (field_expr (tmp, ay), y);
|
|
az = assign_expr (field_expr (tmp, az), z);
|
|
sblock = statement_slist (sblock, ax);
|
|
sblock = statement_slist (sblock, ay);
|
|
sblock = statement_slist (sblock, az);
|
|
} else {
|
|
// guaranteed to have two or four elements
|
|
if (e->e.vector.list->next->next) {
|
|
// four vals: x, y, z, w
|
|
x = e->e.vector.list;
|
|
y = x->next;
|
|
z = y->next;
|
|
w = z->next;
|
|
ax = new_name_expr ("x");
|
|
ay = new_name_expr ("y");
|
|
az = new_name_expr ("z");
|
|
aw = new_name_expr ("w");
|
|
ax = assign_expr (field_expr (tmp, ax), x);
|
|
ay = assign_expr (field_expr (tmp, ay), y);
|
|
az = assign_expr (field_expr (tmp, az), z);
|
|
aw = assign_expr (field_expr (tmp, aw), w);
|
|
sblock = statement_slist (sblock, ax);
|
|
sblock = statement_slist (sblock, ay);
|
|
sblock = statement_slist (sblock, az);
|
|
sblock = statement_slist (sblock, aw);
|
|
} else {
|
|
// v, s
|
|
v = e->e.vector.list;
|
|
s = v->next;
|
|
av = new_name_expr ("v");
|
|
as = new_name_expr ("s");
|
|
av = assign_expr (field_expr (tmp, av), v);
|
|
as = assign_expr (field_expr (tmp, as), s);
|
|
sblock = statement_slist (sblock, av);
|
|
sblock = statement_slist (sblock, as);
|
|
}
|
|
}
|
|
pr.source_file = file;
|
|
pr.source_line = line;
|
|
sblock = statement_subexpr (sblock, tmp, op);
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_value (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
*op = value_operand (e->e.value);
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_subexpr (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
static expr_f sfuncs[] = {
|
|
0, // ex_error
|
|
0, // ex_state
|
|
0, // ex_bool
|
|
0, // ex_label
|
|
0, // ex_labelref
|
|
expr_block, // ex_block
|
|
expr_expr,
|
|
expr_uexpr,
|
|
expr_symbol,
|
|
expr_temp,
|
|
expr_vector_e, // ex_vector
|
|
0, // ex_nil
|
|
expr_value,
|
|
};
|
|
if (!e) {
|
|
*op = 0;
|
|
return sblock;
|
|
}
|
|
|
|
if (e->type > ex_value)
|
|
internal_error (e, "bad expression type");
|
|
if (!sfuncs[e->type])
|
|
internal_error (e, "unexpected expression type: %s",
|
|
expr_names[e->type]);
|
|
|
|
sblock = sfuncs[e->type] (sblock, e, op);
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_ignore (sblock_t *sblock, expr_t *e)
|
|
{
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_state (sblock_t *sblock, expr_t *e)
|
|
{
|
|
statement_t *s;
|
|
|
|
s = new_statement (st_state, "<STATE>", e);
|
|
sblock = statement_subexpr (sblock, e->e.state.frame, &s->opa);
|
|
sblock = statement_subexpr (sblock, e->e.state.think, &s->opb);
|
|
sblock = statement_subexpr (sblock, e->e.state.step, &s->opc);
|
|
sblock_add_statement (sblock, s);
|
|
return sblock;
|
|
}
|
|
|
|
static void
|
|
build_bool_block (expr_t *block, expr_t *e)
|
|
{
|
|
switch (e->type) {
|
|
case ex_bool:
|
|
build_bool_block (block, e->e.bool.e);
|
|
return;
|
|
case ex_label:
|
|
e->next = 0;
|
|
append_expr (block, e);
|
|
return;
|
|
case ex_expr:
|
|
if (e->e.expr.op == OR || e->e.expr.op == AND) {
|
|
build_bool_block (block, e->e.expr.e1);
|
|
build_bool_block (block, e->e.expr.e2);
|
|
} else if (e->e.expr.op == 'i') {
|
|
e->next = 0;
|
|
append_expr (block, e);
|
|
} else if (e->e.expr.op == 'n') {
|
|
e->next = 0;
|
|
append_expr (block, e);
|
|
} else {
|
|
e->next = 0;
|
|
append_expr (block, e);
|
|
}
|
|
return;
|
|
case ex_uexpr:
|
|
if (e->e.expr.op == 'g') {
|
|
e->next = 0;
|
|
append_expr (block, e);
|
|
return;
|
|
}
|
|
break;
|
|
case ex_block:
|
|
if (!e->e.block.result) {
|
|
expr_t *t;
|
|
for (e = e->e.block.head; e; e = t) {
|
|
t = e->next;
|
|
build_bool_block (block, e);
|
|
}
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
internal_error (e, "bad boolean");
|
|
}
|
|
|
|
static int
|
|
is_goto_expr (expr_t *e)
|
|
{
|
|
return e && e->type == ex_uexpr && e->e.expr.op == 'g';
|
|
}
|
|
|
|
static int
|
|
is_if_expr (expr_t *e)
|
|
{
|
|
return e && e->type == ex_expr && e->e.expr.op == 'i';
|
|
}
|
|
|
|
static int
|
|
is_ifnot_expr (expr_t *e)
|
|
{
|
|
return e && e->type == ex_expr && e->e.expr.op == 'n';
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_bool (sblock_t *sblock, expr_t *e)
|
|
{
|
|
expr_t **s;
|
|
expr_t *l;
|
|
expr_t *block = new_block_expr ();
|
|
|
|
build_bool_block (block, e);
|
|
|
|
s = &block->e.block.head;
|
|
while (*s) {
|
|
if (is_if_expr (*s) && is_goto_expr ((*s)->next)) {
|
|
l = (*s)->e.expr.e2;
|
|
for (e = (*s)->next->next; e && e->type == ex_label; e = e->next) {
|
|
if (e == l) {
|
|
l->e.label.used--;
|
|
e = *s;
|
|
e->e.expr.op = 'n';
|
|
e->e.expr.e2 = e->next->e.expr.e1;
|
|
e->next = e->next->next;
|
|
break;
|
|
}
|
|
}
|
|
s = &(*s)->next;
|
|
} else if (is_ifnot_expr (*s) && is_goto_expr ((*s)->next)) {
|
|
l = (*s)->e.expr.e2;
|
|
for (e = (*s)->next->next; e && e->type == ex_label; e = e->next) {
|
|
if (e == l) {
|
|
l->e.label.used--;
|
|
e = *s;
|
|
e->e.expr.op = 'i';
|
|
e->e.expr.e2 = e->next->e.expr.e1;
|
|
e->next = e->next->next;
|
|
break;
|
|
}
|
|
}
|
|
s = &(*s)->next;
|
|
} else if (is_goto_expr (*s)) {
|
|
l = (*s)->e.expr.e1;
|
|
for (e = (*s)->next; e && e->type == ex_label; e = e->next) {
|
|
if (e == l) {
|
|
l->e.label.used--;
|
|
*s = (*s)->next;
|
|
l = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (l)
|
|
s = &(*s)->next;
|
|
} else {
|
|
s = &(*s)->next;
|
|
}
|
|
}
|
|
sblock = statement_slist (sblock, block->e.block.head);
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_label (sblock_t *sblock, expr_t *e)
|
|
{
|
|
if (sblock->statements) {
|
|
sblock->next = new_sblock ();
|
|
sblock = sblock->next;
|
|
}
|
|
if (e->e.label.used) {
|
|
e->e.label.dest = sblock;
|
|
e->e.label.next = sblock->labels;
|
|
sblock->labels = &e->e.label;
|
|
} else {
|
|
debug (e, "dropping unused label %s", e->e.label.name);
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_block (sblock_t *sblock, expr_t *e)
|
|
{
|
|
if (sblock->statements) {
|
|
sblock->next = new_sblock ();
|
|
sblock = sblock->next;
|
|
}
|
|
sblock = statement_slist (sblock, e->e.block.head);
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_expr (sblock_t *sblock, expr_t *e)
|
|
{
|
|
switch (e->e.expr.op) {
|
|
case 'c':
|
|
sblock = expr_call (sblock, e, 0);
|
|
break;
|
|
case 'g':
|
|
case 'i':
|
|
case 'n':
|
|
case IFBE:
|
|
case IFB:
|
|
case IFAE:
|
|
case IFA:
|
|
sblock = statement_branch (sblock, e);
|
|
break;
|
|
case '=':
|
|
case PAS:
|
|
sblock = expr_assign (sblock, e, 0);
|
|
break;
|
|
case 'm':
|
|
case 'M':
|
|
sblock = expr_move (sblock, e, 0);
|
|
break;
|
|
default:
|
|
if (e->e.expr.op < 256)
|
|
debug (e, "e %c", e->e.expr.op);
|
|
else
|
|
debug (e, "e %d", e->e.expr.op);
|
|
if (options.warnings.executable)
|
|
warning (e, "Non-executable statement;"
|
|
" executing programmer instead.");
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_uexpr (sblock_t *sblock, expr_t *e)
|
|
{
|
|
const char *opcode;
|
|
statement_t *s;
|
|
|
|
switch (e->e.expr.op) {
|
|
case 'r':
|
|
debug (e, "RETURN");
|
|
opcode = "<RETURN>";
|
|
if (!e->e.expr.e1) {
|
|
if (options.code.progsversion != PROG_ID_VERSION) {
|
|
opcode = "<RETURN_V>";
|
|
} else {
|
|
e->e.expr.e1 = new_float_expr (0);
|
|
}
|
|
}
|
|
s = new_statement (st_func, opcode, e);
|
|
if (e->e.expr.e1)
|
|
sblock = statement_subexpr (sblock, e->e.expr.e1, &s->opa);
|
|
sblock_add_statement (sblock, s);
|
|
sblock->next = new_sblock ();
|
|
sblock = sblock->next;
|
|
break;
|
|
case 'g':
|
|
sblock = statement_branch (sblock, e);
|
|
break;
|
|
default:
|
|
debug (e, "e ue %d", e->e.expr.op);
|
|
if (options.warnings.executable)
|
|
warning (e, "Non-executable statement;"
|
|
" executing programmer instead.");
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_nonexec (sblock_t *sblock, expr_t *e)
|
|
{
|
|
if (!e->rvalue && options.warnings.executable)
|
|
warning (e, "Non-executable statement; executing programmer instead.");
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_slist (sblock_t *sblock, expr_t *e)
|
|
{
|
|
static statement_f sfuncs[] = {
|
|
statement_ignore, // ex_error
|
|
statement_state,
|
|
statement_bool,
|
|
statement_label,
|
|
0, // ex_labelref
|
|
statement_block,
|
|
statement_expr,
|
|
statement_uexpr,
|
|
statement_nonexec, // ex_symbol
|
|
statement_nonexec, // ex_temp
|
|
statement_nonexec, // ex_vector
|
|
statement_nonexec, // ex_nil
|
|
statement_nonexec, // ex_value
|
|
};
|
|
|
|
for (/**/; e; e = e->next) {
|
|
if (e->type > ex_value)
|
|
internal_error (e, "bad expression type");
|
|
sblock = sfuncs[e->type] (sblock, e);
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static void
|
|
move_labels (sblock_t *dst, sblock_t *src)
|
|
{
|
|
ex_label_t *src_labels = src->labels;
|
|
|
|
if (!src_labels)
|
|
return;
|
|
src_labels->dest = dst;
|
|
while (src_labels->next) {
|
|
src_labels = src_labels->next;
|
|
src_labels->dest = dst;
|
|
}
|
|
src_labels->next = dst->labels;
|
|
dst->labels = src->labels;
|
|
src->labels = 0;
|
|
}
|
|
|
|
static void
|
|
move_code (sblock_t *dst, sblock_t *src)
|
|
{
|
|
if (!src->statements)
|
|
return;
|
|
*dst->tail = src->statements;
|
|
dst->tail = src->tail;
|
|
src->statements = 0;
|
|
src->tail = &src->statements;
|
|
}
|
|
|
|
static sblock_t *
|
|
merge_blocks (sblock_t *blocks)
|
|
{
|
|
sblock_t **sb;
|
|
sblock_t *sblock;
|
|
statement_t *s;
|
|
|
|
if (!blocks)
|
|
return blocks;
|
|
// merge any blocks that can be merged
|
|
for (sblock = blocks; sblock; sblock = sblock->next) {
|
|
if (sblock->statements && sblock->next) {
|
|
s = (statement_t *) sblock->tail;
|
|
// func and flow statements end blocks
|
|
if (s->type >= st_func)
|
|
continue;
|
|
// labels begin blocks
|
|
if (sblock->next->labels)
|
|
continue;
|
|
// blocks can be merged
|
|
move_code (sblock, sblock->next);
|
|
}
|
|
}
|
|
for (sb = &blocks; (*sb)->next;) {
|
|
if (!(*sb)->statements) {
|
|
// empty non-final block
|
|
// move labels from empty block to next block
|
|
if ((*sb)->labels)
|
|
move_labels ((*sb)->next, (*sb));
|
|
sblock = *sb;
|
|
*sb = (*sb)->next;
|
|
free_sblock (sblock);
|
|
continue;
|
|
}
|
|
sb = &(*sb)->next;
|
|
}
|
|
// so long as blocks doesn't become null, remove an empty final block
|
|
if (sb != &blocks) {
|
|
if (!(*sb)->statements && !(*sb)->labels) {
|
|
// empty final block with no labels
|
|
sblock = *sb;
|
|
*sb = (*sb)->next;
|
|
free_sblock (sblock);
|
|
}
|
|
}
|
|
return blocks;
|
|
}
|
|
|
|
static void
|
|
remove_label_from_dest (ex_label_t *label)
|
|
{
|
|
sblock_t *sblock;
|
|
ex_label_t **l;
|
|
|
|
if (!label)
|
|
return;
|
|
|
|
debug (0, "dropping deceased label %s", label->name);
|
|
sblock = label->dest;
|
|
label->dest = 0;
|
|
for (l = &sblock->labels; *l; l = &(*l)->next) {
|
|
if (*l == label) {
|
|
*l = label->next;
|
|
label->next = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
unuse_label (ex_label_t *label)
|
|
{
|
|
if (label && !--label->used)
|
|
remove_label_from_dest (label);
|
|
}
|
|
|
|
static int
|
|
thread_jumps (sblock_t *blocks)
|
|
{
|
|
sblock_t *sblock;
|
|
int did_something = 0;
|
|
|
|
if (!blocks)
|
|
return 0;
|
|
for (sblock = blocks; sblock; sblock = sblock->next) {
|
|
statement_t *s;
|
|
ex_label_t **label, *l;
|
|
|
|
if (!sblock->statements)
|
|
continue;
|
|
s = (statement_t *) sblock->tail;
|
|
if (statement_is_goto (s))
|
|
label = &s->opa->o.label;
|
|
else if (statement_is_cond (s))
|
|
label = &s->opb->o.label;
|
|
else
|
|
continue;
|
|
for (l = *label;
|
|
l->dest->statements && statement_is_goto (l->dest->statements);
|
|
l = l->dest->statements->opa->o.label) {
|
|
}
|
|
if (l != *label) {
|
|
unuse_label (*label);
|
|
l->used++;
|
|
*label = l;
|
|
did_something = 1;
|
|
}
|
|
if ((statement_is_goto (s) || statement_is_cond (s))
|
|
&& (*label)->dest == sblock->next) {
|
|
statement_t **p;
|
|
unuse_label (*label);
|
|
for (p = &sblock->statements; *p != s; p = &(*p)->next)
|
|
;
|
|
free_statement (s);
|
|
*p = 0;
|
|
sblock->tail = p;
|
|
did_something = 1;
|
|
}
|
|
}
|
|
return did_something;
|
|
}
|
|
|
|
static int
|
|
remove_dead_blocks (sblock_t *blocks)
|
|
{
|
|
sblock_t *sblock;
|
|
int did_something;
|
|
int did_anything = 0;
|
|
int pass = 0;
|
|
|
|
if (!blocks)
|
|
return 0;
|
|
|
|
do {
|
|
debug (0, "dead block pass %d", pass++);
|
|
did_something = 0;
|
|
blocks->reachable = 1;
|
|
for (sblock = blocks; sblock->next; sblock = sblock->next) {
|
|
sblock_t *sb = sblock->next;
|
|
statement_t *s;
|
|
|
|
if (sb->labels) {
|
|
sb->reachable = 1;
|
|
continue;
|
|
}
|
|
if (!sblock->statements) {
|
|
sb->reachable = 1;
|
|
continue;
|
|
}
|
|
s = (statement_t *) sblock->tail;
|
|
if (statement_is_cond (s)
|
|
&& sb->statements && statement_is_goto (sb->statements)
|
|
&& s->opb->o.label->dest == sb->next) {
|
|
debug (0, "merging if/goto %p %p", sblock, sb);
|
|
unuse_label (s->opb->o.label);
|
|
s->opb->o.label = sb->statements->opa->o.label;
|
|
s->opb->o.label->used++;
|
|
invert_conditional (s);
|
|
sb->reachable = 0;
|
|
for (sb = sb->next; sb; sb = sb->next)
|
|
sb->reachable = 1;
|
|
break;
|
|
} else if (!statement_is_goto (s) && !statement_is_return (s)) {
|
|
sb->reachable = 1;
|
|
continue;
|
|
}
|
|
sb->reachable = 0;
|
|
}
|
|
for (sblock = blocks; sblock; sblock = sblock->next) {
|
|
while (sblock->next && !sblock->next->reachable) {
|
|
sblock_t *sb = sblock->next;
|
|
statement_t *s;
|
|
ex_label_t *label = 0;
|
|
|
|
debug (0, "removing dead block %p", sb);
|
|
|
|
if (sb->statements) {
|
|
s = (statement_t *) sb->tail;
|
|
if (statement_is_goto (s))
|
|
label = s->opa->o.label;
|
|
else if (statement_is_cond (s))
|
|
label = s->opb->o.label;
|
|
}
|
|
unuse_label (label);
|
|
did_something = 1;
|
|
did_anything = 1;
|
|
|
|
sblock->next = sb->next;
|
|
free_sblock (sb);
|
|
}
|
|
}
|
|
} while (did_something);
|
|
return did_anything;
|
|
}
|
|
|
|
static void
|
|
check_final_block (sblock_t *sblock)
|
|
{
|
|
statement_t *s = 0;
|
|
symbol_t *return_symbol = 0;
|
|
def_t *return_def = 0;
|
|
operand_t *return_operand = 0;
|
|
const char *return_opcode = "<RETURN_V>";
|
|
|
|
if (!sblock)
|
|
return;
|
|
while (sblock->next)
|
|
sblock = sblock->next;
|
|
if (sblock->statements) {
|
|
s = (statement_t *) sblock->tail;
|
|
if (statement_is_goto (s))
|
|
return; // the end of function is the end of a loop
|
|
if (statement_is_return (s))
|
|
return;
|
|
}
|
|
if (current_func->sym->type->t.func.type != &type_void)
|
|
warning (0, "control reaches end of non-void function");
|
|
if (s && s->type >= st_func) {
|
|
// func and flow end blocks, so we need to add a new block to take the
|
|
// return
|
|
sblock->next = new_sblock ();
|
|
sblock = sblock->next;
|
|
}
|
|
if (options.traditional || options.code.progsversion == PROG_ID_VERSION) {
|
|
return_symbol = make_symbol (".return", &type_param, pr.symtab->space,
|
|
sc_extern);
|
|
return_def = return_symbol->s.def;
|
|
return_opcode = "<RETURN>";
|
|
}
|
|
if (return_symbol)
|
|
return_operand = def_operand (return_def, &type_void);
|
|
s = new_statement (st_func, return_opcode, 0);
|
|
s->opa = return_operand;
|
|
sblock_add_statement (sblock, s);
|
|
}
|
|
|
|
void
|
|
dump_dot_sblock (void *data, const char *fname)
|
|
{
|
|
print_sblock ((sblock_t *) data, fname);
|
|
}
|
|
|
|
sblock_t *
|
|
make_statements (expr_t *e)
|
|
{
|
|
sblock_t *sblock = new_sblock ();
|
|
int did_something;
|
|
int pass = 0;
|
|
|
|
if (options.block_dot.expr)
|
|
dump_dot ("expr", e, dump_dot_expr);
|
|
statement_slist (sblock, e);
|
|
if (options.block_dot.initial)
|
|
dump_dot ("initial", sblock, dump_dot_sblock);
|
|
do {
|
|
did_something = thread_jumps (sblock);
|
|
if (options.block_dot.thread)
|
|
dump_dot (va ("thread-%d", pass), sblock, dump_dot_sblock);
|
|
did_something |= remove_dead_blocks (sblock);
|
|
sblock = merge_blocks (sblock);
|
|
if (options.block_dot.dead)
|
|
dump_dot (va ("dead-%d", pass), sblock, dump_dot_sblock);
|
|
pass++;
|
|
} while (did_something);
|
|
check_final_block (sblock);
|
|
if (options.block_dot.final)
|
|
dump_dot ("final", sblock, dump_dot_sblock);
|
|
|
|
return sblock;
|
|
}
|
|
|
|
static void
|
|
count_temp (operand_t *op)
|
|
{
|
|
if (!op)
|
|
return;
|
|
if (op->op_type == op_temp) {
|
|
while (op->o.tempop.alias)
|
|
op = op->o.tempop.alias;
|
|
op->o.tempop.users++;
|
|
}
|
|
}
|
|
|
|
void
|
|
statements_count_temps (sblock_t *sblock)
|
|
{
|
|
statement_t *st;
|
|
|
|
while (sblock) {
|
|
for (st = sblock->statements; st; st = st->next) {
|
|
count_temp (st->opa);
|
|
count_temp (st->opb);
|
|
count_temp (st->opc);
|
|
}
|
|
sblock = sblock->next;
|
|
}
|
|
}
|