/* statements.c Internal statements Copyright (C) 2011 Bill Currie Author: Bill Currie 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 #endif #ifdef HAVE_STRINGS_H # include #endif #include #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 "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 < 0 || type > op_temp) return ""; 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->type) { case ev_string: return va ("\"%s\"", quote_string (op->o.value->v.string_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: while (op->o.tempop.alias) op = op->o.tempop.alias; return va ("", op, op->o.tempop.users); } return ("??"); } static void print_operand (operand_t *op) { switch (op->op_type) { case op_def: printf ("(%s) ", pr_type_name[op->type]); printf ("%s", op->o.def->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); if (op->o.tempop.def) printf (" %s", op->o.tempop.def->name); 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; 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; } static void __attribute__((unused)) //FIXME 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 * temp_operand (type_t *type) { operand_t *op = new_operand (op_temp); op->o.tempop.type = type; 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 ""; case 'n': return ""; case IFBE: return ""; case IFB: return ""; case IFAE: return ""; case IFA: return ""; case 'm': return ""; case 'M': return ""; default: return 0; } } int statement_is_cond (statement_t *s) { if (!s) return 0; return !strncmp (s->opcode, "opcode, ""); } int statement_is_jumpb (statement_t *s) { if (!s) return 0; return !strcmp (s->opcode, ""); } int statement_is_call (statement_t *s) { if (!s) return 0; if (!strncmp (s->opcode, "opcode, "opcode, "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, "")) s->opcode = ""; else if (!strcmp (s->opcode, "")) s->opcode = ""; else if (!strcmp (s->opcode, "")) s->opcode = ""; else if (!strcmp (s->opcode, "")) s->opcode = ""; else if (!strcmp (s->opcode, "")) s->opcode = ""; else if (!strcmp (s->opcode, "")) s->opcode = ""; } 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, "", 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, "", 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 { 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; 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 (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 (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) { 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 (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 = new_operand (op_def); (*op)->type = low_level_type (type); (*op)->o.def = e->e.expr.e1->e.symbol->s.def; } 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, "", 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->type == ev_pointer) { ex_pointer_t *ptr = &e->e.value->v.pointer; *op = new_operand (op_def); (*op)->type = low_level_type (ptr->type); (*op)->o.def = alias_def (ptr->def, ptr->type, ptr->val); } 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_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 (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_alias (sblock_t *sblock, expr_t *e, operand_t **op) { operand_t *aop = 0; operand_t *top; etype_t type; def_t *def; sblock = statement_subexpr (sblock, e->e.expr.e1, &aop); 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"); } type = low_level_type (e->e.expr.type); if (aop->type == type) { top = aop; } else { for (top = aop->o.tempop.alias_ops; top; top = top->next) if (top->type == type) break; } if (!top) { top = new_operand (op_temp); top->type = type; top->o.tempop.alias = aop; } *op = top; } else if (aop->op_type == op_def) { def = aop->o.def; while (def->alias) def = def->alias; *op = new_operand (op_def); (*op)->type = low_level_type (e->e.expr.type); (*op)->o.def = alias_def (def, ev_types[(*op)->type], 0); } else if (aop->op_type == op_value && aop->o.value->type == ev_pointer) { ex_pointer_t *ptr = &aop->o.value->v.pointer; *op = new_operand (op_def); (*op)->type = low_level_type (ptr->type); (*op)->o.def = alias_def (ptr->def, ptr->type, ptr->val); } else { internal_error (e, "invalid alias target: %s: %s", optype_str (aop->op_type), operand_string (aop)); } 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, "", 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 = new_operand (op_def); (*op)->type = low_level_type (sym->type); (*op)->o.def = sym->s.def; } else if (sym->sy_type == sy_const) { *op = new_operand (op_value); (*op)->type = sym->s.value->type; (*op)->o.value = sym->s.value; } else if (sym->sy_type == sy_func) { *op = new_operand (op_def); (*op)->type = ev_func; (*op)->o.def = sym->s.func->def; } else { internal_error (e, "unexpected symbol type: %s", symtype_str(sym->sy_type)); } 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 0, // ex_labelref 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 (st_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) { 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 = ""; if (!e->e.expr.e1) { if (options.code.progsversion != PROG_ID_VERSION) { opcode = ""; } 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 (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_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 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; 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; } if (statement_is_goto (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; } } } 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 (st_flow, "", 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; if (!sblock->statements) continue; s = (statement_t *) sblock->tail; if (!statement_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"); if (!sb->statements) continue; s = (statement_t *) sb->tail; if (!statement_is_goto (s) && !statement_is_return (s)) continue; // desination block is reachable only via 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; } 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; 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; def_t *return_def = 0; operand_t *return_operand = 0; const char *return_opcode = ""; 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 (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 = ""; } if (return_symbol) { return_operand = new_operand (op_def); return_operand->type = ev_void; return_operand->o.def = return_def; } 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 (); 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); thread_jumps (sblock); if (options.block_dot.thread) dump_dot ("thread", sblock, dump_dot_sblock); do { remove_dead_blocks (sblock); } while (merge_blocks (sblock)); if (options.block_dot.dead) dump_dot ("dead", sblock, dump_dot_sblock); 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; } }