quakeforge/tools/qfcc/source/statements.c
Bill Currie 56410ddc58 Prevent merged if/goto losing its way.
When mering if/goto (ie, if skipping a goto), the rest of the dead code
remover is used to delete the goto. That part of the code unuses the goto's
label. The if was getting the goto's label without the lable's used count
being incremented (the usaged temporarily increases by one). I have no idea
why the problem showed up randomly, but this seems to fix it (it fixes /a/
bug, anyway).
2012-05-04 22:35:20 +09:00

1328 lines
31 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 "QF/va.h"
#include "diagnostic.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 "qc-parse.h"
static void
print_operand (operand_t *op)
{
switch (op->op_type) {
case op_symbol:
printf ("(%s) ", pr_type_name[op->type]);
printf ("%s", op->o.symbol->name);
break;
case op_value:
printf ("(%s) ", pr_type_name[op->type]);
switch (op->o.value->type) {
case ev_string:
printf ("\"%s\"", op->o.value->v.string_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], op);
break;
case op_pointer:
printf ("ptr %p", op->o.pointer);
break;
case op_alias:
printf ("alias %s ", pr_type_name[op->type]);
print_operand (op->o.alias);
break;
}
}
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 *free_sblocks;
static statement_t *free_statements;
static operand_t *free_operands;
static sblock_t *
new_sblock (void)
{
sblock_t *sblock;
ALLOC (256, sblock_t, sblocks, sblock);
sblock->tail = &sblock->statements;
return sblock;
}
static 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;
}
static statement_t *
new_statement (const char *opcode, expr_t *expr)
{
statement_t *statement;
ALLOC (256, statement_t, statements, statement);
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;
}
static void
free_operand (operand_t *op)
{
if (op->next) {
//FIXME this should be an error, but due to the way operands are use,
//it can happen.
debug (0, "free_operand: double free");
return;
}
op->next = free_operands;
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);
s->next = free_statements;
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);
}
sblock->next = free_sblocks;
free_sblocks = sblock;
}
static operand_t *
temp_operand (type_t *type)
{
operand_t *op = new_operand (op_temp);
op->type = low_level_type (type);
op->size = type_size (type);
return op;
}
static operand_t *
short_operand (short short_val)
{
operand_t *op = new_operand (op_value);
op->type = ev_short;
op->o.value = calloc (1, sizeof (ex_value_t));
op->o.value->type = ev_short;
op->o.value->v.short_val = short_val;
return op;
}
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;
}
}
static int
is_goto (statement_t *s)
{
return !strcmp (s->opcode, "<GOTO>");
}
static int
is_return (statement_t *s)
{
return !strncmp (s->opcode, "<RETURN", 7);
}
static int
is_conditional (statement_t *s)
{
return !strncmp (s->opcode, "<IF", 3);
}
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 ("<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 ("<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 (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);
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;
} 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 {
if (dst_expr->type == ex_uexpr
&& dst_expr->e.expr.op == '&') {
opcode = "=";
dst_expr = unary_expr ('.', dst_expr);
}
sblock = statement_subexpr (sblock, dst_expr, &dst);
ofs = 0;
}
if (op)
*op = src;
}
s = new_statement (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 (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 (binary_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.type == 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.type == 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 ("=", a);
s->opa = arg;
s->opb = p;
sblock_add_statement (sblock, s);
}
}
}
}
opcode = va ("<%sCALL%d>", pref, count);
s = new_statement (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)
{
if (e->type == ex_uexpr) {
sblock = statement_subexpr (sblock, e->e.expr.e1, op);
(*op)->type = ev_pointer;
}
return sblock;
}
static statement_t *
lea_statement (operand_t *pointer, operand_t *offset, expr_t *e)
{
statement_t *s = new_statement ("&", 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 ("&", 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) {
*op = new_operand (op_symbol);
(*op)->type = low_level_type (type);
(*op)->o.symbol = e->e.expr.e1->e.symbol;
} 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 ("<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 (".", deref);
s->opa = ptr;
s->opb = offs;
s->opc = *op;
sblock_add_statement (sblock, s);
}
} else if (e->type == ex_value && e->e.value.type == ev_pointer) {
*op = new_operand (op_pointer);
(*op)->type = low_level_type (e->e.value.v.pointer.type);
(*op)->o.pointer = &e->e.value.v.pointer;
}
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_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;
default:
opcode = convert_op (e->e.expr.op);
if (!opcode)
internal_error (e, "ice ice baby");
s = new_statement (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_alias (sblock_t *sblock, expr_t *e, operand_t **op)
{
*op = new_operand (op_alias);
(*op)->type = low_level_type (e->e.expr.type);
sblock = statement_subexpr (sblock, e->e.expr.e1, &(*op)->o.alias);
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 ("=", 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 (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)
{
*op = new_operand (op_symbol);
(*op)->type = low_level_type (e->e.symbol->type);
(*op)->o.symbol = e->e.symbol;
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_value (sblock_t *sblock, expr_t *e, operand_t **op)
{
*op = new_operand (op_value);
(*op)->type = e->e.value.type;
(*op)->o.value = &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
expr_block, // ex_block
expr_expr,
expr_uexpr,
expr_symbol,
expr_temp,
0, // ex_nil
expr_value,
};
if (!e) {
*op = 0;
return sblock;
}
if (e->type < 0 || e->type > ex_value)
internal_error (e, "bad expression type");
if (!sfuncs[e->type])
internal_error (e, "unexpected expression 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 ("<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);
}
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) {
*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 (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 (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,
statement_block,
statement_expr,
statement_uexpr,
statement_nonexec, // ex_symbol
statement_nonexec, // ex_temp
statement_nonexec, // ex_nil
statement_nonexec, // ex_value
};
for (/**/; e; e = e->next) {
if (e->type < 0 || e->type > ex_value)
internal_error (e, "bad expression type");
sblock = sfuncs[e->type] (sblock, e);
}
return sblock;
}
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 void
dump_flow (sblock_t *sblock, const char *stage)
{
char *fname;
fname = nva ("%s.%s.%s.dot", GETSTR (pr.source_file), current_func->name,
stage);
print_flow (sblock, fname);
free (fname);
}
static void
thread_jumps (sblock_t *blocks)
{
sblock_t *sblock;
if (!blocks)
return;
for (sblock = blocks; sblock; sblock = sblock->next) {
statement_t *s;
ex_label_t **label, *l;
s = (statement_t *) sblock->tail;
if (is_goto (s))
label = &s->opa->o.label;
else if (is_conditional (s))
label = &s->opb->o.label;
else
continue;
for (l = *label;
l->dest->statements && is_goto (l->dest->statements);
l = l->dest->statements->opa->o.label) {
}
if (l != *label) {
unuse_label (*label);
l->used++;
*label = l;
}
}
}
static void
join_blocks (sblock_t *sblock, sblock_t *dest)
{
statement_t **s;
statement_t *g;
expr_t *label;
for (s = &sblock->statements; (*s) != (statement_t *) sblock->tail;
s = &(*s)->next)
;
debug (0, "joining blocks %p %p", sblock, dest);
unuse_label ((*s)->opa->o.label);
free_statement (*s);
sblock->tail = s;
// append dest's statements to sblock
*sblock->tail = dest->statements;
sblock->tail = dest->tail;
// clear dest's statement list
dest->tail = &dest->statements;
dest->statements = 0;
// put a goto statement into dest incase the code in sblock flows into dest
// the goto will jump to dest's old
label = new_label_expr ();
label->e.label.dest = dest->next;
label->e.label.next = dest->next->labels;
label->e.label.used++;
dest->next->labels = &label->e.label;
g = new_statement ("<GOTO>", 0);
g->opa = new_operand (op_label);
g->opa->o.label = &label->e.label;
sblock_add_statement (dest, g);
// stich dest back into the block list immediately after sblock
dest->next = sblock->next;
sblock->next = dest;
}
static int
merge_blocks (sblock_t *blocks)
{
sblock_t *sblock;
int did_something = 0;
if (!blocks)
return 0;
for (sblock = blocks; sblock; sblock = sblock->next) {
statement_t *s;
sblock_t *dest;
sblock_t *sb;
s = (statement_t *) sblock->tail;
if (!is_goto (s))
continue;
dest = s->opa->o.label->dest;
// The destination block must not be the current block
if (dest == sblock) {
warning (0, "infinite loop detected");
continue;
}
// the destiniation block must have only one label and one user for
// that label (ie, no other branch statement jumps to the block).
// also, don't try to move a marker end-block
if (dest->labels->next || dest->labels->used > 1 || !dest->statements)
continue;
// the destination block must be otherwise unreachable (preceeded by
// an unconditional jump (goto or return))
if (dest == blocks)
continue;
for (sb = blocks; sb; sb = sb->next)
if (sb->next == dest)
break;
if (!sb) // dest is
internal_error (0, "dangling label");
s = (statement_t *) sb->tail;
if (!is_goto (s) && !is_return (s))
continue;
// desination block is reachable via only goto of the current block
if (!dest->next)
dest->next = new_sblock ();
sb->next = dest->next; // pull dest out of the chain
join_blocks (sblock, dest);
did_something = 1;
}
return did_something;
}
static void
remove_dead_blocks (sblock_t *blocks)
{
sblock_t *sblock;
int did_something;
int pass = 0;
if (!blocks)
return;
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;
}
s = (statement_t *) sblock->tail;
if (is_conditional (s) && 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 (!is_goto (s) && !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);
s = (statement_t *) sb->tail;
if (is_goto (s))
label = s->opa->o.label;
else if (is_conditional (s))
label = s->opb->o.label;
unuse_label (label);
did_something = 1;
sblock->next = sb->next;
free_sblock (sb);
}
}
} while (did_something);
}
static void
check_final_block (sblock_t *sblock)
{
statement_t *s;
symbol_t *return_symbol = 0;
operand_t *return_operand = 0;
const char *return_opcode = "<RETURN_V>";
if (!sblock)
return;
while (sblock->next)
sblock = sblock->next;
s = (statement_t *) sblock->tail;
if (is_goto (s))
return; // the end of function is the end of a loop
if (is_return (s))
return;
if (current_func->sym->type->t.func.type != &type_void)
warning (0, "control reaches end of non-void function");
if (options.traditional || options.code.progsversion == PROG_ID_VERSION) {
expr_t *e = new_ret_expr (current_func->sym->type->t.func.type);
return_symbol = e->e.expr.e1->e.symbol;//FIXME ick
return_opcode = "<RETURN>";
}
if (return_symbol) {
return_operand = new_operand (op_symbol);
return_operand->type = ev_void;
return_operand->o.symbol = return_symbol;
}
s = new_statement (return_opcode, 0);
s->opa = return_operand;
sblock_add_statement (sblock, s);
}
sblock_t *
make_statements (expr_t *e)
{
sblock_t *sblock = new_sblock ();
// print_expr (e);
statement_slist (sblock, e);
if (options.block_dot.initial)
dump_flow (sblock, "initial");
thread_jumps (sblock);
if (options.block_dot.thread)
dump_flow (sblock, "thread");
do {
remove_dead_blocks (sblock);
} while (merge_blocks (sblock));
if (options.block_dot.dead)
dump_flow (sblock, "dead");
check_final_block (sblock);
if (options.block_dot.final)
dump_flow (sblock, "final");
return sblock;
}