mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-14 17:01:22 +00:00
5747ddb77f
For now, anyway, as the generated code looks good. There might be problems with actual pointer expressions, but it allows entity.field to work as expected rather than generate an ICE.
2680 lines
66 KiB
C
2680 lines
66 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 <inttypes.h>
|
|
|
|
#include "qfalloca.h"
|
|
|
|
#include "QF/alloc.h"
|
|
#include "QF/mathlib.h"
|
|
#include "QF/va.h"
|
|
|
|
#include "tools/qfcc/include/class.h"
|
|
#include "tools/qfcc/include/dags.h"
|
|
#include "tools/qfcc/include/defspace.h"
|
|
#include "tools/qfcc/include/diagnostic.h"
|
|
#include "tools/qfcc/include/dot.h"
|
|
#include "tools/qfcc/include/expr.h"
|
|
#include "tools/qfcc/include/function.h"
|
|
#include "tools/qfcc/include/method.h"
|
|
#include "tools/qfcc/include/options.h"
|
|
#include "tools/qfcc/include/qfcc.h"
|
|
#include "tools/qfcc/include/reloc.h"
|
|
#include "tools/qfcc/include/statements.h"
|
|
#include "tools/qfcc/include/strpool.h"
|
|
#include "tools/qfcc/include/symtab.h"
|
|
#include "tools/qfcc/include/type.h"
|
|
#include "tools/qfcc/include/value.h"
|
|
|
|
#include "tools/qfcc/source/qc-parse.h"
|
|
|
|
const char * const op_type_names[] = {
|
|
"op_def",
|
|
"op_value",
|
|
"op_label",
|
|
"op_temp",
|
|
"op_alias",
|
|
"op_nil",
|
|
"op_pseudo",
|
|
};
|
|
|
|
const char * const st_type_names[] = {
|
|
"st_none",
|
|
"st_expr",
|
|
"st_assign",
|
|
"st_ptrassign",
|
|
"st_move",
|
|
"st_ptrmove",
|
|
"st_memset",
|
|
"st_ptrmemset",
|
|
"st_state",
|
|
"st_func",
|
|
"st_flow",
|
|
"st_address",
|
|
};
|
|
|
|
const char *
|
|
optype_str (op_type_e type)
|
|
{
|
|
if (type > op_temp)
|
|
return "<invalid op_type>";
|
|
return op_type_names[type];
|
|
}
|
|
|
|
static const char *
|
|
tempop_string (operand_t *tmpop)
|
|
{
|
|
tempop_t *tempop = &tmpop->tempop;
|
|
if (tempop->alias) {
|
|
return va (0, "<tmp %s %p:%d:%p:%d:%d>",
|
|
pr_type_name[tempop->type->type],
|
|
tmpop, tempop->users,
|
|
tempop->alias,
|
|
tempop->offset,
|
|
tempop->alias->tempop.users);
|
|
}
|
|
return va (0, "<tmp %s %p:%d>", pr_type_name[tempop->type->type],
|
|
tmpop, tempop->users);
|
|
}
|
|
|
|
const char *
|
|
operand_string (operand_t *op)
|
|
{
|
|
if (!op)
|
|
return "";
|
|
switch (op->op_type) {
|
|
case op_def:
|
|
return op->def->name;
|
|
case op_value:
|
|
return get_value_string (op->value);
|
|
case op_label:
|
|
return op->label->name;
|
|
case op_temp:
|
|
return tempop_string (op);
|
|
case op_alias:
|
|
{
|
|
const char *alias = operand_string (op->alias);
|
|
char *buf = alloca (strlen (alias) + 1);
|
|
strcpy (buf, alias);
|
|
return va (0, "alias(%s,%s)", pr_type_name[op->type->type],
|
|
buf);
|
|
}
|
|
case op_nil:
|
|
return "nil";
|
|
case op_pseudo:
|
|
return va (0, "pseudo: %s", op->pseudoop->name);
|
|
}
|
|
return ("??");
|
|
}
|
|
|
|
static void
|
|
_print_operand (operand_t *op)
|
|
{
|
|
switch (op->op_type) {
|
|
case op_def:
|
|
printf ("(%s) ", get_type_string (op->type));
|
|
printf ("%s", op->def->name);
|
|
break;
|
|
case op_value:
|
|
printf ("(%s) %s", pr_type_name[op->type->type],
|
|
get_value_string (op->value));
|
|
break;
|
|
case op_label:
|
|
printf ("block %p", op->label->dest);
|
|
break;
|
|
case op_temp:
|
|
printf ("tmp (%s) %p", get_type_string (op->type), op);
|
|
if (op->tempop.def)
|
|
printf (" %s:%04x", op->tempop.def->name,
|
|
op->tempop.def->offset);
|
|
break;
|
|
case op_alias:
|
|
printf ("alias(%s,", get_type_string (op->type));
|
|
_print_operand (op->alias);
|
|
printf (")");
|
|
break;
|
|
case op_nil:
|
|
printf ("nil");
|
|
break;
|
|
case op_pseudo:
|
|
printf ("pseudo: %s", op->pseudoop->name);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
print_operand (operand_t *op)
|
|
{
|
|
_print_operand (op);
|
|
puts ("");
|
|
}
|
|
|
|
static void
|
|
print_operand_chain (const char *name, operand_t *op)
|
|
{
|
|
if (op) {
|
|
printf (" %s:", name);
|
|
while (op) {
|
|
printf (" ");
|
|
_print_operand (op);
|
|
op = op->next;
|
|
}
|
|
printf ("\n");
|
|
}
|
|
}
|
|
|
|
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");
|
|
print_operand_chain ("use", s->use);
|
|
print_operand_chain ("def", s->def);
|
|
print_operand_chain ("kill", s->kill);
|
|
}
|
|
|
|
static pseudoop_t *pseudoops_freelist;
|
|
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;
|
|
statement->number = -1; // indicates flow analysis not done yet
|
|
return statement;
|
|
}
|
|
|
|
static void
|
|
statement_add_use (statement_t *s, operand_t *use)
|
|
{
|
|
if (use) {
|
|
use->next = s->use;
|
|
s->use = use;
|
|
}
|
|
}
|
|
|
|
static void
|
|
statement_add_def (statement_t *s, operand_t *def)
|
|
{
|
|
if (def) {
|
|
def->next = s->def;
|
|
s->def = def;
|
|
}
|
|
}
|
|
|
|
static void
|
|
statement_add_kill (statement_t *s, operand_t *kill)
|
|
{
|
|
if (kill) {
|
|
kill->next = s->kill;
|
|
s->kill = kill;
|
|
}
|
|
}
|
|
|
|
static pseudoop_t *
|
|
new_pseudoop (const char *name)
|
|
{
|
|
pseudoop_t *pseudoop;
|
|
ALLOC (256, pseudoop_t, pseudoops, pseudoop);
|
|
pseudoop->name = save_string (name);
|
|
return pseudoop;
|
|
|
|
}
|
|
|
|
static operand_t *
|
|
new_operand (op_type_e op, expr_t *expr, void *return_addr)
|
|
{
|
|
operand_t *operand;
|
|
ALLOC (256, operand_t, operands, operand);
|
|
operand->op_type = op;
|
|
operand->expr = expr;
|
|
operand->return_addr = return_addr;
|
|
return operand;
|
|
}
|
|
|
|
static operand_t *
|
|
copy_operand (operand_t *src)
|
|
{
|
|
if (!src) {
|
|
return 0;
|
|
}
|
|
operand_t *cpy = new_operand (src->op_type, src->expr, 0);
|
|
*cpy = *src;
|
|
cpy->return_addr = __builtin_return_address (0);
|
|
return cpy;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static operand_t *
|
|
pseudo_operand (pseudoop_t *pseudoop, expr_t *expr)
|
|
{
|
|
operand_t *op;
|
|
op = new_operand (op_pseudo, expr, __builtin_return_address (0));
|
|
op->pseudoop = pseudoop;
|
|
op->size = 1;
|
|
return op;
|
|
}
|
|
|
|
operand_t *
|
|
nil_operand (type_t *type, expr_t *expr)
|
|
{
|
|
operand_t *op;
|
|
op = new_operand (op_nil, expr, __builtin_return_address (0));
|
|
op->type = type;
|
|
op->size = type_size (type);
|
|
op->width = type_width (type);
|
|
return op;
|
|
}
|
|
|
|
operand_t *
|
|
def_operand (def_t *def, type_t *type, expr_t *expr)
|
|
{
|
|
operand_t *op;
|
|
|
|
if (!type)
|
|
type = def->type;
|
|
op = new_operand (op_def, expr, __builtin_return_address (0));
|
|
op->type = type;
|
|
op->size = type_size (type);
|
|
op->width = type_width (type);
|
|
op->def = def;
|
|
return op;
|
|
}
|
|
|
|
operand_t *
|
|
return_operand (type_t *type, expr_t *expr)
|
|
{
|
|
symbol_t *return_symbol;
|
|
return_symbol = make_symbol (".return", &type_param, pr.symtab->space,
|
|
sc_extern);
|
|
if (!return_symbol->table) {
|
|
symtab_addsymbol (pr.symtab, return_symbol);
|
|
}
|
|
def_t *return_def = return_symbol->s.def;
|
|
return def_operand (alias_def (return_def, type, 0), 0, expr);
|
|
}
|
|
|
|
operand_t *
|
|
value_operand (ex_value_t *value, expr_t *expr)
|
|
{
|
|
operand_t *op;
|
|
op = new_operand (op_value, expr, __builtin_return_address (0));
|
|
op->type = value->type;
|
|
op->size = type_size (value->type);
|
|
op->width = type_width (value->type);
|
|
op->value = value;
|
|
return op;
|
|
}
|
|
|
|
operand_t *
|
|
temp_operand (type_t *type, expr_t *expr)
|
|
{
|
|
operand_t *op = new_operand (op_temp, expr, __builtin_return_address (0));
|
|
|
|
op->tempop.type = type;
|
|
op->type = type;
|
|
op->size = type_size (type);
|
|
op->width = type_width (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 (t1->alias) {
|
|
offs1 += t1->alias->tempop.offset;
|
|
}
|
|
if (t2->alias) {
|
|
offs2 += t2->alias->tempop.offset;
|
|
}
|
|
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 (top->expr, "temp alias of non-temp operand");
|
|
}
|
|
tempop = &top->tempop;
|
|
if ((ret = visit (tempop, data)))
|
|
return ret;
|
|
} else {
|
|
overlap = 0;
|
|
}
|
|
for (top = tempop->alias_ops; top; top = top->next) {
|
|
if (top->op_type != op_temp) {
|
|
internal_error (top->expr, "temp alias of non-temp operand");
|
|
}
|
|
tempop = &top->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, expr_t *expr)
|
|
{
|
|
operand_t *aop;
|
|
|
|
if (type_size (type) != type_size (op->type)) {
|
|
internal_error (op->expr,
|
|
"aliasing operand with type of different size: %d, %d",
|
|
type_size (type), type_size (op->type));
|
|
}
|
|
aop = new_operand (op_alias, expr, __builtin_return_address (0));
|
|
aop->alias = op;
|
|
aop->type = type;
|
|
aop->size = type_size (type);
|
|
aop->width = type_width (type);
|
|
return aop;
|
|
}
|
|
|
|
operand_t *
|
|
label_operand (expr_t *label)
|
|
{
|
|
operand_t *lop;
|
|
|
|
if (label->type != ex_label) {
|
|
internal_error (label, "not a label expression");
|
|
}
|
|
lop = new_operand (op_label, label, __builtin_return_address (0));
|
|
lop->label = &label->e.label;
|
|
return lop;
|
|
}
|
|
|
|
static operand_t *
|
|
short_operand (short short_val, expr_t *expr)
|
|
{
|
|
ex_value_t *val = new_short_val (short_val);
|
|
return value_operand (val, expr);
|
|
}
|
|
|
|
static const char *
|
|
convert_op (int op)
|
|
{
|
|
switch (op) {
|
|
case OR: return "or";
|
|
case AND: return "and";
|
|
case EQ: return "eq";
|
|
case NE: return "ne";
|
|
case LE: return "le";
|
|
case GE: return "ge";
|
|
case LT: return "lt";
|
|
case GT: return "gt";
|
|
case '+': return "add";
|
|
case '-': return "sub";
|
|
case '*': return "mul";
|
|
case '/': return "div";
|
|
case '%': return "rem";
|
|
case MOD: return "mod";
|
|
case '&': return "bitand";
|
|
case '|': return "bitor";
|
|
case '^': return "bitxor";
|
|
case '~': return "bitnot";
|
|
case '!': return "not";
|
|
case SHL: return "shl";
|
|
case SHR: return "shr";
|
|
case '.': return "load";
|
|
case CROSS: return "cross";
|
|
case DOT: return "dot";
|
|
case SCALE: return "scale";
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int
|
|
statement_is_cond (statement_t *s)
|
|
{
|
|
if (!s)
|
|
return 0;
|
|
return !strncmp (s->opcode, "if", 2);
|
|
}
|
|
|
|
int
|
|
statement_is_goto (statement_t *s)
|
|
{
|
|
if (!s)
|
|
return 0;
|
|
return !strcmp (s->opcode, "jump") && !s->opb;
|
|
}
|
|
|
|
int
|
|
statement_is_jumpb (statement_t *s)
|
|
{
|
|
if (!s)
|
|
return 0;
|
|
return !strcmp (s->opcode, "jump") && s->opb;
|
|
}
|
|
|
|
int
|
|
statement_is_call (statement_t *s)
|
|
{
|
|
if (!s)
|
|
return 0;
|
|
if (!strncmp (s->opcode, "call", 4))
|
|
return 1;
|
|
if (!strncmp (s->opcode, "rcall", 5))
|
|
return 2;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
statement_is_return (statement_t *s)
|
|
{
|
|
if (!s)
|
|
return 0;
|
|
return !strncmp (s->opcode, "return", 6);
|
|
}
|
|
|
|
static ex_label_t **
|
|
statement_get_labelref (statement_t *s)
|
|
{
|
|
if (statement_is_cond (s)
|
|
|| statement_is_goto (s)
|
|
|| statement_is_jumpb (s)) {
|
|
return &s->opa->label;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
sblock_t *
|
|
statement_get_target (statement_t *s)
|
|
{
|
|
ex_label_t **label = statement_get_labelref (s);
|
|
return label ? (*label)->dest : 0;
|
|
}
|
|
|
|
sblock_t **
|
|
statement_get_targetlist (statement_t *s)
|
|
{
|
|
sblock_t **target_list;
|
|
int count = 0, i;
|
|
def_t *table = 0;
|
|
element_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->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 (s->opa->expr, "aliased jump table");
|
|
e = table->initializer->e.compound.head; //FIXME check!!!
|
|
for (i = 0; i < count; e = e->next, i++)
|
|
target_list[i] = e->expr->e.labelref.label->dest;
|
|
}
|
|
return target_list;
|
|
}
|
|
|
|
static void
|
|
invert_conditional (statement_t *s)
|
|
{
|
|
if (!strcmp (s->opcode, "ifnz"))
|
|
s->opcode = "ifz";
|
|
else if (!strcmp (s->opcode, "ifz"))
|
|
s->opcode = "ifnz";
|
|
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 *expr_symbol (sblock_t *sblock, expr_t *e, operand_t **op);
|
|
static sblock_t *expr_def (sblock_t *sblock, expr_t *e, operand_t **op);
|
|
|
|
static sblock_t *
|
|
expr_address (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
statement_t *s;
|
|
expr_t *lvalue = e->e.address.lvalue;
|
|
expr_t *offset = e->e.address.offset;
|
|
|
|
if (lvalue->type == ex_alias && offset && is_constant (offset)) {
|
|
lvalue = new_offset_alias_expr (lvalue->e.alias.type,
|
|
lvalue->e.alias.expr,
|
|
expr_int (offset));
|
|
offset = 0;
|
|
}
|
|
|
|
s = new_statement (st_address, "lea", e);
|
|
sblock = statement_subexpr (sblock, lvalue, &s->opa);
|
|
if (offset) {
|
|
sblock = statement_subexpr (sblock, offset, &s->opb);
|
|
}
|
|
s->opc = temp_operand (e->e.address.type, e);
|
|
sblock_add_statement (sblock, s);
|
|
*(op) = s->opc;
|
|
return sblock;
|
|
}
|
|
|
|
static operand_t *
|
|
operand_address (operand_t *reference, expr_t *e)
|
|
{
|
|
def_t *def;
|
|
type_t *type;
|
|
int offset = 0;
|
|
|
|
type = reference->type;
|
|
switch (reference->op_type) {
|
|
case op_def:
|
|
// assumes aliasing is only one level deep which should be the
|
|
// case
|
|
def = reference->def;
|
|
if (def->alias) {
|
|
offset = def->offset;
|
|
def = def->alias;
|
|
}
|
|
return value_operand (new_pointer_val (offset, type, def, 0), e);
|
|
case op_temp:
|
|
// assumes aliasing is only one level deep which should be the
|
|
// case
|
|
if (reference->tempop.alias) {
|
|
offset = reference->tempop.offset;
|
|
reference = reference->tempop.alias;
|
|
}
|
|
return value_operand (new_pointer_val (offset, type, 0,
|
|
reference), e);
|
|
case op_alias:
|
|
//op_alias comes only from alias_operand and that is called
|
|
// by dags, so not expected
|
|
case op_value:
|
|
case op_label:
|
|
case op_nil:
|
|
case op_pseudo:
|
|
break;
|
|
}
|
|
internal_error (e, "invalid operand type for operand address: %s",
|
|
op_type_names[reference->op_type]);
|
|
}
|
|
|
|
static __attribute__((pure)) int
|
|
is_indirect (expr_t *e)
|
|
{
|
|
if ((e->type == ex_expr || e->type == ex_uexpr)
|
|
&& e->e.expr.op == '.') {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static sblock_t *addressing_mode (sblock_t *sblock, expr_t *ref,
|
|
operand_t **base, operand_t **offset,
|
|
pr_ushort_t *mode, operand_t **target);
|
|
static statement_t *lea_statement (operand_t *pointer, operand_t *offset,
|
|
expr_t *e);
|
|
|
|
static sblock_t *
|
|
expr_assign_copy (sblock_t *sblock, expr_t *e, operand_t **op, operand_t *src)
|
|
{
|
|
statement_t *s;
|
|
expr_t *dst_expr = e->e.assign.dst;
|
|
expr_t *src_expr = e->e.assign.src;
|
|
type_t *dst_type = get_type (dst_expr);
|
|
type_t *src_type = get_type (src_expr);
|
|
unsigned count;
|
|
expr_t *count_expr;
|
|
operand_t *dst = 0;
|
|
operand_t *size = 0;
|
|
static const char *opcode_sets[][2] = {
|
|
{"move", "movep"},
|
|
{"memset", "memsetp"},
|
|
};
|
|
const unsigned max_count = 1 << 16;
|
|
const char **opcode_set = opcode_sets[0];
|
|
const char *opcode;
|
|
int need_ptr = 0;
|
|
st_type_t type = st_move;
|
|
operand_t *use = 0;
|
|
operand_t *def = 0;
|
|
operand_t *kill = 0;
|
|
|
|
if ((src && src->op_type == op_nil) || src_expr->type == ex_nil) {
|
|
// switch to memset because nil is type agnostic 0 and structures
|
|
// can be any size
|
|
src_expr = new_int_expr (0);
|
|
sblock = statement_subexpr (sblock, src_expr, &src);
|
|
opcode_set = opcode_sets[1];
|
|
if (op) {
|
|
*op = nil_operand (dst_type, src_expr);
|
|
}
|
|
type = st_memset;
|
|
if (is_indirect (dst_expr)) {
|
|
need_ptr = 1;
|
|
}
|
|
} else {
|
|
if (is_indirect (src_expr)) {
|
|
src_expr = expr_file_line (address_expr (src_expr, 0), e);
|
|
need_ptr = 1;
|
|
}
|
|
if (!src) {
|
|
// This is the very right-hand node of a non-nil assignment chain
|
|
// (there may be more chains somwhere within src_expr, but they
|
|
// are not part of this chain as they are separated by another
|
|
// expression).
|
|
sblock = statement_subexpr (sblock, src_expr, &src);
|
|
}
|
|
// send the source operand back up through the assignment chain
|
|
// before modifying src if its address is needed
|
|
if (op) {
|
|
*op = src;
|
|
}
|
|
if (is_indirect (dst_expr)) {
|
|
if (is_variable (src_expr)) {
|
|
// FIXME this probably needs to be more agressive
|
|
// shouldn't emit code...
|
|
sblock = statement_subexpr (sblock, src_expr, &use);
|
|
}
|
|
if (options.code.progsversion == PROG_VERSION) {
|
|
// FIXME it was probably a mistake extracting the operand
|
|
// type from the statement expression in dags. Also, can't
|
|
// use address_expr() because src_expr may be a function call
|
|
// and unary_expr doesn't like that
|
|
src_expr = expr_file_line (
|
|
new_address_expr (get_type (src_expr), src_expr, 0),
|
|
src_expr);
|
|
s = lea_statement (src, 0, src_expr);
|
|
sblock_add_statement (sblock, s);
|
|
src = s->opc;
|
|
} else {
|
|
src = operand_address (src, src_expr);
|
|
}
|
|
need_ptr = 1;
|
|
}
|
|
}
|
|
if (need_ptr) {
|
|
// dst_expr and/or src_expr are dereferenced pointers, so need to
|
|
// un-dereference dst_expr to get the pointer and switch to movep
|
|
// or memsetp instructions.
|
|
if (is_variable (dst_expr)) {
|
|
// FIXME this probably needs to be more agressive
|
|
// shouldn't emit code...
|
|
sblock = statement_subexpr (sblock, dst_expr, &def);
|
|
sblock = statement_subexpr (sblock, dst_expr, &kill);
|
|
}
|
|
dst_expr = expr_file_line (address_expr (dst_expr, 0), e);
|
|
need_ptr = 1;
|
|
}
|
|
sblock = statement_subexpr (sblock, dst_expr, &dst);
|
|
|
|
if (type_size (dst_type) != type_size (src_type)) {
|
|
bug (e, "dst and src sizes differ in expr_assign_copy: %d %d",
|
|
type_size (dst_type), type_size (src_type));
|
|
}
|
|
count = min (type_size (dst_type), type_size (src_type));
|
|
if (count < (1 << 16)) {
|
|
count_expr = expr_file_line (new_short_expr (count), e);
|
|
} else {
|
|
count_expr = expr_file_line (new_int_expr (count), e);
|
|
}
|
|
sblock = statement_subexpr (sblock, count_expr, &size);
|
|
|
|
if (count < max_count && !need_ptr) {
|
|
opcode = opcode_set[0];
|
|
} else {
|
|
opcode = opcode_set[1];
|
|
type++; // from st_move/st_memset to st_ptrmove/st_ptrmemset
|
|
}
|
|
|
|
s = new_statement (type, opcode, e);
|
|
s->opa = src;
|
|
s->opb = size;
|
|
s->opc = dst;
|
|
s->use = use;
|
|
s->def = def;
|
|
s->kill = kill;
|
|
sblock_add_statement (sblock, s);
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_assign (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
statement_t *s;
|
|
expr_t *src_expr = e->e.assign.src;
|
|
expr_t *dst_expr = e->e.assign.dst;
|
|
type_t *dst_type = get_type (dst_expr);
|
|
operand_t *src = 0;
|
|
operand_t *dst = 0;
|
|
operand_t *ofs = 0;
|
|
operand_t *target = 0;
|
|
pr_ushort_t mode = 0; // assign
|
|
const char *opcode = "assign";
|
|
st_type_t type = st_assign;
|
|
|
|
if (src_expr->type == ex_assign) {
|
|
sblock = statement_subexpr (sblock, src_expr, &src);
|
|
if (is_structural (dst_type)) {
|
|
return expr_assign_copy (sblock, e, op, src);
|
|
}
|
|
if (is_indirect (dst_expr)) {
|
|
goto dereference_dst;
|
|
} else {
|
|
sblock = statement_subexpr (sblock, dst_expr, &dst);
|
|
}
|
|
} else {
|
|
if (is_structural (dst_type)) {
|
|
return expr_assign_copy (sblock, e, op, src);
|
|
}
|
|
|
|
if (is_indirect (dst_expr)) {
|
|
// If both dst_expr and src_expr are indirect, then a staging temp
|
|
// is needed, but emitting src_expr first generates that temp
|
|
// because src is null. If src_expr is not indirect and is a simple
|
|
// variable reference, then just the ref will be generated and thus
|
|
// will be assigned to the dereferenced destination. If src_expr
|
|
// is not simple, then a temp will be generated, so all good.
|
|
sblock = statement_subexpr (sblock, src_expr, &src);
|
|
goto dereference_dst;
|
|
} else {
|
|
// dst_expr is direct and known to be an l-value, so emitting
|
|
// its expression will simply generate a reference to that l-value
|
|
// which will be used as the default location to store src_expr's
|
|
// result
|
|
sblock = statement_subexpr (sblock, dst_expr, &dst);
|
|
src = dst;
|
|
sblock = statement_subexpr (sblock, src_expr, &src);
|
|
}
|
|
}
|
|
|
|
if (0) {
|
|
dereference_dst:
|
|
// dst_expr is a dereferenced pointer, so need to get its addressing
|
|
// parameters (base and offset) and switch to store instructions.
|
|
sblock = addressing_mode (sblock, dst_expr, &dst, &ofs, &mode, &target);
|
|
if (mode != 0) {
|
|
opcode = "store";
|
|
type = st_ptrassign;
|
|
} else {
|
|
ofs = 0;
|
|
}
|
|
}
|
|
if (op) {
|
|
*op = src;
|
|
}
|
|
if (src == dst) {
|
|
return sblock;
|
|
}
|
|
|
|
if (options.code.progsversion < PROG_VERSION
|
|
&& is_entity (dst->type) && ofs && is_field (ofs->type)) {
|
|
// need to get a pointer type, entity.field expressions do not provide
|
|
// one directly. FIXME it was probably a mistake extracting the operand
|
|
// type from the statement expression in dags
|
|
dst_expr = expr_file_line (address_expr (dst_expr, 0), dst_expr);
|
|
s = new_statement (st_address, "lea", dst_expr);
|
|
s->opa = dst;
|
|
s->opb = ofs;
|
|
s->opc = temp_operand (&type_ptr, dst_expr);
|
|
sblock_add_statement (sblock, s);
|
|
dst = s->opc;
|
|
ofs = 0;
|
|
}
|
|
s = new_statement (type, opcode, e);
|
|
s->opa = dst;
|
|
s->opb = ofs;
|
|
s->opc = src;
|
|
statement_add_def (s, copy_operand (target));
|
|
statement_add_kill (s, target);
|
|
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_v6p (sblock_t *sblock, expr_t *call, operand_t **op)
|
|
{
|
|
expr_t *func = call->e.branch.target;
|
|
expr_t *args = call->e.branch.args;
|
|
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;
|
|
|
|
// function arguments are in reverse order
|
|
for (a = args; a; a = a->next) {
|
|
if (a->type == ex_args) {
|
|
// v6p uses callN and pr_argc
|
|
continue;
|
|
}
|
|
count++;
|
|
}
|
|
ind = count;
|
|
for (a = args; a; a = a->next) {
|
|
if (a->type == ex_args) {
|
|
// v6p uses callN and pr_argc
|
|
continue;
|
|
}
|
|
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))) {
|
|
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, "assign", a);
|
|
s->opa = p;
|
|
s->opc = arg;
|
|
sblock_add_statement (sblock, s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
opcode = va (0, "%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];
|
|
if (op) {
|
|
*op = return_operand (call->e.branch.ret_type, call);
|
|
}
|
|
sblock_add_statement (sblock, s);
|
|
sblock->next = new_sblock ();
|
|
return sblock->next;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_call (sblock_t *sblock, expr_t *call, operand_t **op)
|
|
{
|
|
if (options.code.progsversion < PROG_VERSION) {
|
|
return expr_call_v6p (sblock, call, op);
|
|
}
|
|
if (!current_func->arguments) {
|
|
current_func->arguments = defspace_new (ds_virtual);
|
|
}
|
|
defspace_t *arg_space = current_func->arguments;
|
|
expr_t *func = call->e.branch.target;
|
|
expr_t **args = 0;
|
|
expr_t *args_va_list = 0; // .args (...) parameter
|
|
expr_t *args_params = 0; // first arg in ...
|
|
operand_t *use = 0;
|
|
operand_t *kill = 0;
|
|
int num_params = 0;
|
|
|
|
defspace_reset (arg_space);
|
|
|
|
int num_args = 0;
|
|
for (expr_t *a = call->e.branch.args; a; a = a->next) {
|
|
num_args++;
|
|
}
|
|
if (num_args) {
|
|
int i = num_args;
|
|
args = alloca (num_args * sizeof (expr_t *));
|
|
for (expr_t *a = call->e.branch.args; a; a = a->next) {
|
|
args[--i] = a;
|
|
}
|
|
}
|
|
int arg_num = 0;
|
|
for (int i = 0; i < num_args; i++) {
|
|
expr_t *a = args[i];
|
|
const char *arg_name = va (0, ".arg%d", arg_num++);
|
|
def_t *def = new_def (arg_name, 0, current_func->arguments,
|
|
sc_local);
|
|
type_t *arg_type = get_type (a);
|
|
int size = type_size (arg_type);
|
|
int alignment = arg_type->alignment;
|
|
if (alignment < 4) {
|
|
alignment = 4;
|
|
}
|
|
def->offset = defspace_alloc_aligned_highwater (arg_space, size,
|
|
alignment);
|
|
def->type = arg_type;
|
|
def->reg = current_func->temp_reg;
|
|
expr_t *def_expr = expr_file_line (new_def_expr (def), call);
|
|
if (a->type == ex_args) {
|
|
args_va_list = def_expr;
|
|
} else {
|
|
if (args_va_list && !args_params) {
|
|
args_params = def_expr;
|
|
}
|
|
if (args_va_list) {
|
|
num_params++;
|
|
}
|
|
expr_t *assign = assign_expr (def_expr, a);
|
|
expr_file_line (assign, call);
|
|
sblock = statement_slist (sblock, assign);
|
|
}
|
|
|
|
// The call both uses and kills the arguments: use is obvious, but kill
|
|
// is because the callee has direct access to them and might modify
|
|
// them
|
|
// need two ops for the one def because there's two lists
|
|
operand_t *u = def_operand (def, arg_type, call);
|
|
operand_t *k = def_operand (def, arg_type, call);
|
|
u->next = use;
|
|
use = u;
|
|
k->next = kill;
|
|
kill = k;
|
|
}
|
|
if (args_va_list) {
|
|
expr_t *assign;
|
|
expr_t *count;
|
|
expr_t *list;
|
|
expr_t *args_count = field_expr (args_va_list,
|
|
new_name_expr ("count"));
|
|
expr_t *args_list = field_expr (args_va_list,
|
|
new_name_expr ("list"));
|
|
expr_file_line (args_count, call);
|
|
expr_file_line (args_list, call);
|
|
|
|
count = new_short_expr (num_params);
|
|
assign = assign_expr (args_count, count);
|
|
expr_file_line (assign, call);
|
|
sblock = statement_slist (sblock, assign);
|
|
|
|
if (args_params) {
|
|
list = address_expr (args_params, &type_param);
|
|
} else {
|
|
list = new_nil_expr ();
|
|
}
|
|
expr_file_line (list, call);
|
|
assign = assign_expr (args_list, list);
|
|
expr_file_line (assign, call);
|
|
sblock = statement_slist (sblock, assign);
|
|
}
|
|
statement_t *s = new_statement (st_func, "call", call);
|
|
sblock = statement_subexpr (sblock, func, &s->opa);
|
|
if (!op) {
|
|
s->opc = short_operand (0, call);
|
|
} else {
|
|
if (!*op) {
|
|
*op = temp_operand (call->e.branch.ret_type, call);
|
|
}
|
|
s->opc = *op;
|
|
}
|
|
s->use = use;
|
|
s->kill = kill;
|
|
sblock_add_statement (sblock, s);
|
|
sblock->next = new_sblock ();
|
|
return sblock->next;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_branch (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
if (e->e.branch.type != pr_branch_call) {
|
|
internal_error (e, "unexpected branch type in expression: %d",
|
|
e->e.branch.type);
|
|
}
|
|
return expr_call (sblock, e, op);
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_branch (sblock_t *sblock, expr_t *e)
|
|
{
|
|
static const char *opcodes[] = {
|
|
"ifz",
|
|
"ifb",
|
|
"ifa",
|
|
0, // special handling
|
|
"ifnz",
|
|
"ifae",
|
|
"ifbe",
|
|
0, // not used here
|
|
};
|
|
statement_t *s = 0;
|
|
const char *opcode;
|
|
|
|
if (e->e.branch.type == pr_branch_call) {
|
|
return expr_call (sblock, e, 0);
|
|
}
|
|
if (e->e.branch.type == pr_branch_jump) {
|
|
if (e->e.branch.index) {
|
|
s = new_statement (st_flow, "jump", e);
|
|
sblock = statement_subexpr (sblock, e->e.branch.target, &s->opa);
|
|
sblock = statement_subexpr (sblock, e->e.branch.index, &s->opb);
|
|
} else {
|
|
s = new_statement (st_flow, "jump", e);
|
|
s->opa = label_operand (e->e.branch.target);
|
|
}
|
|
} else {
|
|
opcode = opcodes [e->e.branch.type];
|
|
s = new_statement (st_flow, opcode, e);
|
|
sblock = statement_subexpr (sblock, e->e.branch.test, &s->opc);
|
|
s->opa = label_operand (e->e.branch.target);
|
|
}
|
|
|
|
sblock_add_statement (sblock, s);
|
|
sblock->next = new_sblock ();
|
|
return sblock->next;
|
|
}
|
|
|
|
static operand_t *
|
|
find_def_operand (expr_t *ref)
|
|
{
|
|
operand_t *op = 0;
|
|
if (ref->type == ex_alias) {
|
|
return find_def_operand (ref->e.alias.expr);
|
|
} else if (ref->type == ex_address) {
|
|
return find_def_operand (ref->e.address.lvalue);
|
|
} else if (ref->type == ex_symbol) {
|
|
expr_symbol (0, ref, &op);
|
|
} else if (ref->type == ex_def) {
|
|
expr_def (0, ref, &op);
|
|
} else if (ref->type == ex_expr) {
|
|
debug (ref, "unexpected expr type: %s", expr_names[ref->type]);
|
|
} else {
|
|
internal_error (ref, "unexpected expr type: %s",
|
|
expr_names[ref->type]);
|
|
}
|
|
return op;
|
|
}
|
|
|
|
static sblock_t *
|
|
ptr_addressing_mode (sblock_t *sblock, expr_t *ref,
|
|
operand_t **base, operand_t **offset, pr_ushort_t *mode,
|
|
operand_t **target)
|
|
{
|
|
type_t *type = get_type (ref);
|
|
if (!is_ptr (type)) {
|
|
internal_error (ref, "expected pointer in ref");
|
|
}
|
|
if (ref->type == ex_address
|
|
&& (!ref->e.address.offset || is_constant (ref->e.address.offset))
|
|
&& ref->e.address.lvalue->type == ex_alias
|
|
&& (!ref->e.address.lvalue->e.alias.offset
|
|
|| is_constant (ref->e.address.lvalue->e.alias.offset))) {
|
|
expr_t *lvalue = ref->e.address.lvalue;
|
|
expr_t *offs = ref->e.address.offset;
|
|
expr_t *alias;
|
|
if (lvalue->e.alias.offset) {
|
|
if (offs) {
|
|
offs = binary_expr ('+', offs, lvalue->e.alias.offset);
|
|
} else {
|
|
offs = lvalue->e.alias.offset;
|
|
}
|
|
}
|
|
type = type->t.fldptr.type;
|
|
if (offs) {
|
|
expr_t *lv = lvalue->e.alias.expr;
|
|
type_t *lvtype = get_type (lv);
|
|
int o = expr_int (offs);
|
|
if (o < 0 || o + type_size (type) > type_size (lvtype)) {
|
|
// not a valid offset for the type, which technically should
|
|
// be an error, but there may be legitimate reasons for doing
|
|
// such pointer shenanigans
|
|
goto just_a_pointer;
|
|
}
|
|
alias = new_offset_alias_expr (type, lv, expr_int (offs));
|
|
} else {
|
|
alias = new_alias_expr (type, lvalue->e.alias.expr);
|
|
}
|
|
expr_file_line (alias, ref);
|
|
return addressing_mode (sblock, alias, base, offset, mode, target);
|
|
} else if (ref->type != ex_alias || ref->e.alias.offset) {
|
|
// probably just a pointer
|
|
just_a_pointer:
|
|
sblock = statement_subexpr (sblock, ref, base);
|
|
*offset = short_operand (0, ref);
|
|
*mode = 2; // mode C: ptr + constant index
|
|
} else if (is_ptr (get_type (ref->e.alias.expr))) {
|
|
// cast of one pointer type to another
|
|
return ptr_addressing_mode (sblock, ref->e.alias.expr, base, offset,
|
|
mode, target);
|
|
} else {
|
|
// alias with no offset
|
|
if (!is_integral (get_type (ref->e.alias.expr))) {
|
|
internal_error (ref, "expected integer expr in ref");
|
|
}
|
|
expr_t *intptr = ref->e.alias.expr;
|
|
if (intptr->type != ex_expr
|
|
|| (intptr->e.expr.op != '+'
|
|
&& intptr->e.expr.op != '-')) {
|
|
// treat ref as simple pointer
|
|
sblock = statement_subexpr (sblock, ref, base);
|
|
*offset = short_operand (0, ref);
|
|
*mode = 2; // mode C: ptr + constant index
|
|
} else {
|
|
expr_t *ptr = intptr->e.expr.e1;
|
|
expr_t *offs = intptr->e.expr.e2;
|
|
int const_offs;
|
|
if (target) {
|
|
if (ptr->type == ex_alias
|
|
&& is_ptr (get_type (ptr->e.alias.expr))) {
|
|
*target = find_def_operand (ptr->e.alias.expr);
|
|
}
|
|
}
|
|
// move the +/- to the offset
|
|
offs = unary_expr (intptr->e.expr.op, offs);
|
|
// make the base a pointer again
|
|
ptr = new_alias_expr (ref->e.alias.type, ptr);
|
|
sblock = statement_subexpr (sblock, ptr, base);
|
|
if (is_constant (offs)
|
|
&& (const_offs = expr_int (offs)) < 32768
|
|
&& const_offs >= -32768) {
|
|
*mode = 2;
|
|
*offset = short_operand (const_offs, ref);
|
|
} else {
|
|
*mode = 3;
|
|
sblock = statement_subexpr (sblock, offs, offset);
|
|
}
|
|
}
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
addressing_mode (sblock_t *sblock, expr_t *ref,
|
|
operand_t **base, operand_t **offset, pr_ushort_t *mode,
|
|
operand_t **target)
|
|
{
|
|
if (is_indirect (ref)) {
|
|
// ref is known to be either ex_expr or ex_uexpr, with '.' for
|
|
// the operator
|
|
if (ref->type == ex_expr) {
|
|
expr_t *ent_expr = ref->e.expr.e1;
|
|
expr_t *fld_expr = ref->e.expr.e2;
|
|
if (!is_entity (get_type (ent_expr))
|
|
|| !is_field (get_type (fld_expr))) {
|
|
print_expr (ref);
|
|
internal_error (ref, "expected entity.field");
|
|
}
|
|
sblock = statement_subexpr (sblock, ent_expr, base);
|
|
sblock = statement_subexpr (sblock, fld_expr, offset);
|
|
*mode = 1;//entity.field
|
|
} else if (ref->type == ex_uexpr) {
|
|
sblock = ptr_addressing_mode (sblock, ref->e.expr.e1, base, offset,
|
|
mode, target);
|
|
} else {
|
|
internal_error (ref, "unexpected expression type for indirect: %s",
|
|
expr_names[ref->type]);
|
|
}
|
|
} else {
|
|
sblock = statement_subexpr (sblock, ref, base);
|
|
*offset = short_operand (0, ref);
|
|
*mode = 0;
|
|
*target = 0;
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_return (sblock_t *sblock, expr_t *e)
|
|
{
|
|
const char *opcode;
|
|
statement_t *s;
|
|
|
|
debug (e, "RETURN");
|
|
opcode = "return";
|
|
if (!e->e.retrn.ret_val) {
|
|
if (options.code.progsversion == PROG_ID_VERSION) {
|
|
e->e.retrn.ret_val = new_float_expr (0);
|
|
}
|
|
}
|
|
s = new_statement (st_func, opcode, e);
|
|
if (options.code.progsversion < PROG_VERSION) {
|
|
if (e->e.retrn.ret_val) {
|
|
expr_t *ret_val = e->e.retrn.ret_val;
|
|
type_t *ret_type = get_type (ret_val);
|
|
|
|
// at_return is used for passing the result of a void_return
|
|
// function through void. v6 progs always use .return for the
|
|
// return value, so don't need to do anything special: just call
|
|
// the function and do a normal void return
|
|
if (!e->e.retrn.at_return) {
|
|
s->opa = return_operand (ret_type, e);
|
|
}
|
|
sblock = statement_subexpr (sblock, ret_val, &s->opa);
|
|
}
|
|
} else {
|
|
if (!e->e.retrn.at_return && e->e.retrn.ret_val) {
|
|
expr_t *ret_val = e->e.retrn.ret_val;
|
|
type_t *ret_type = get_type (ret_val);
|
|
operand_t *target = 0;
|
|
pr_ushort_t ret_crtl = type_size (ret_type) - 1;
|
|
pr_ushort_t mode = 0;
|
|
sblock = addressing_mode (sblock, ret_val, &s->opa, &s->opb, &mode,
|
|
&target);
|
|
ret_crtl |= mode << 5;
|
|
s->opc = short_operand (ret_crtl, e);
|
|
statement_add_use (s, target);
|
|
} else {
|
|
if (e->e.retrn.at_return) {
|
|
expr_t *call = e->e.retrn.ret_val;
|
|
if (!call || !is_function_call (call)) {
|
|
internal_error (e, "@return with no call");
|
|
}
|
|
// FIXME hard-coded reg, and assumes 3 is free
|
|
#define REG 3
|
|
expr_t *with = new_with_expr (11, REG, new_short_expr (0));
|
|
def_t *ret_ptr = new_def (0, 0, 0, sc_local);
|
|
operand_t *ret_op = def_operand (ret_ptr, &type_void, e);
|
|
ret_ptr->reg = REG;
|
|
expr_file_line (with, e);
|
|
sblock = statement_slist (sblock, with);
|
|
sblock = statement_subexpr (sblock, call, &ret_op);
|
|
}
|
|
s->opa = short_operand (0, e);
|
|
s->opb = short_operand (0, e);
|
|
s->opc = short_operand (-1, e); // void return
|
|
}
|
|
}
|
|
sblock_add_statement (sblock, s);
|
|
sblock->next = new_sblock ();
|
|
sblock = sblock->next;
|
|
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_adjstk (sblock_t *sblock, expr_t *e)
|
|
{
|
|
statement_t *s = new_statement (st_func, "adjstk", e);
|
|
s->opa = short_operand (e->e.adjstk.mode, e);
|
|
s->opb = short_operand (e->e.adjstk.offset, e);
|
|
|
|
sblock_add_statement (sblock, s);
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_with (sblock_t *sblock, expr_t *e)
|
|
{
|
|
statement_t *s = new_statement (st_func, "with", e);
|
|
s->opa = short_operand (e->e.with.mode, e);
|
|
s->opc = short_operand (e->e.with.reg, e);
|
|
sblock = statement_subexpr (sblock, e->e.with.with, &s->opb);
|
|
sblock_add_statement (sblock, s);
|
|
return sblock;
|
|
}
|
|
|
|
static statement_t *
|
|
lea_statement (operand_t *pointer, operand_t *offset, expr_t *e)
|
|
{
|
|
statement_t *s = new_statement (st_address, "lea", e);
|
|
s->opa = pointer;
|
|
s->opb = offset;
|
|
s->opc = temp_operand (&type_ptr, e);
|
|
return s;
|
|
}
|
|
|
|
static statement_t *
|
|
movep_statement (operand_t *dst, operand_t *src, type_t *type, expr_t *e)
|
|
{
|
|
operand_t *dst_addr = operand_address (dst, e);
|
|
statement_t *s = new_statement (st_ptrmove, "movep", e);
|
|
s->opa = src;
|
|
//FIXME large types
|
|
s->opb = short_operand (type_size (type), e);
|
|
s->opc = dst_addr;
|
|
return s;
|
|
}
|
|
|
|
static statement_t *
|
|
load_statement (operand_t *ptr, operand_t *offs, operand_t *op, expr_t *e)
|
|
{
|
|
statement_t *s = new_statement (st_expr, "load", e);
|
|
s->opa = ptr;
|
|
s->opb = offs;
|
|
s->opc = op;
|
|
return s;
|
|
}
|
|
|
|
static statement_t *
|
|
assign_statement (operand_t *dst, operand_t *src, expr_t *e)
|
|
{
|
|
statement_t *s = new_statement (st_assign, "assign", e);
|
|
s->opa = dst;
|
|
s->opc = src;
|
|
return s;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_deref (sblock_t *sblock, expr_t *deref, operand_t **op)
|
|
{
|
|
type_t *load_type = deref->e.expr.type;
|
|
expr_t *ptr_expr = deref->e.expr.e1;
|
|
operand_t *base = 0;
|
|
operand_t *offset = 0;
|
|
operand_t *target = 0;
|
|
pr_ushort_t mode;
|
|
statement_t *s;
|
|
|
|
sblock = addressing_mode (sblock, deref, &base, &offset, &mode, &target);
|
|
|
|
if (!*op) {
|
|
*op = temp_operand (load_type, deref);
|
|
}
|
|
|
|
switch (mode) {
|
|
case 0://direct def access
|
|
// FIXME should check that offset is 0, but currently,
|
|
// addressing_mode always sets offset to 0 for mode 0
|
|
s = assign_statement (*op, base, deref);
|
|
sblock_add_statement (sblock, s);
|
|
statement_add_use (s, target);
|
|
return sblock;
|
|
case 1://entity.field
|
|
case 2://const indexed pointer
|
|
case 3://var indexed pointer
|
|
break;
|
|
default:
|
|
internal_error (deref, "unexpected addressing mode: %d", mode);
|
|
}
|
|
|
|
if (low_level_type (load_type) == ev_void) {
|
|
s = lea_statement (base, offset, ptr_expr);
|
|
sblock_add_statement (sblock, s);
|
|
|
|
s = movep_statement (*op, s->opc, load_type, deref);
|
|
sblock_add_statement (sblock, s);
|
|
} else {
|
|
s = load_statement (base, offset, *op, deref);
|
|
sblock_add_statement (sblock, s);
|
|
}
|
|
statement_add_use (s, target);
|
|
|
|
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->e.alias.offset) {
|
|
offset = expr_int (e->e.alias.offset);
|
|
}
|
|
type = e->e.alias.type;
|
|
sblock = statement_subexpr (sblock, e->e.alias.expr, &aop);
|
|
if (type_compatible (aop->type, type)) {
|
|
if (offset) {
|
|
//For types to be compatible, they must be the same size, thus this
|
|
//seemingly mismatched error
|
|
internal_error (e, "offset alias of same size type");
|
|
}
|
|
*op = aop;
|
|
return sblock;
|
|
}
|
|
if (aop->op_type == op_temp) {
|
|
while (aop->tempop.alias) {
|
|
aop = aop->tempop.alias;
|
|
if (aop->op_type != op_temp)
|
|
internal_error (e, "temp alias of non-temp var");
|
|
if (aop->tempop.alias)
|
|
bug (e, "aliased temp alias");
|
|
}
|
|
for (top = aop->tempop.alias_ops; top; top = top->next) {
|
|
if (top->type == type && top->tempop.offset == offset) {
|
|
break;
|
|
}
|
|
}
|
|
if (!top) {
|
|
top = temp_operand (type, e);
|
|
top->tempop.alias = aop;
|
|
top->tempop.offset = offset;
|
|
top->next = aop->tempop.alias_ops;
|
|
aop->tempop.alias_ops = top;
|
|
}
|
|
*op = top;
|
|
} else if (aop->op_type == op_def) {
|
|
def = aop->def;
|
|
while (def->alias)
|
|
def = def->alias;
|
|
*op = def_operand (alias_def (def, type, offset), 0, e);
|
|
} else if (aop->op_type == op_value) {
|
|
*op = value_operand (aop->value, e);
|
|
(*op)->type = type;
|
|
} 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;
|
|
|
|
opcode = convert_op (e->e.expr.op);
|
|
if (!opcode)
|
|
internal_error (e, "ice ice baby");
|
|
if (strcmp (opcode, "ne") == 0 && is_string (get_type (e->e.expr.e1))) {
|
|
opcode = "cmp";
|
|
}
|
|
if (strcmp (opcode, "dot") == 0) {
|
|
if (type_width (get_type (e->e.expr.e1)) == 2) {
|
|
opcode = "cdot";
|
|
}
|
|
if (type_width (get_type (e->e.expr.e1)) == 3) {
|
|
opcode = "vdot";
|
|
}
|
|
if (type_width (get_type (e->e.expr.e1)) == 4) {
|
|
opcode = "qdot";
|
|
}
|
|
}
|
|
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, e);
|
|
s->opc = *op;
|
|
sblock_add_statement (sblock, s);
|
|
|
|
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 (is_scalar (src_type) && is_scalar (type)) {
|
|
operand_t *src = 0;
|
|
sblock = statement_subexpr (sblock, e->e.expr.e1, &src);
|
|
*op = temp_operand (e->e.expr.type, e);
|
|
s = new_statement (st_expr, "conv", e);
|
|
s->opa = src;
|
|
if (options.code.progsversion == PROG_VERSION) {
|
|
int from = type_cast_map[src_type->type];
|
|
int to = type_cast_map[type->type];
|
|
int width = type_width (src_type) - 1;
|
|
int conv = TYPE_CAST_CODE (from, to, width);
|
|
s->opb = short_operand (conv, e);
|
|
}
|
|
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_deref (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, e);
|
|
s->opc = *op;
|
|
sblock_add_statement (sblock, s);
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_horizontal (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
const char *opcode = "hops";
|
|
statement_t *s;
|
|
int hop;
|
|
type_t *res_type = e->e.hop.type;
|
|
type_t *vec_type = get_type (e->e.hop.vec);
|
|
|
|
switch (e->e.hop.op) {
|
|
case '&':
|
|
hop = 0;
|
|
break;
|
|
case '|':
|
|
hop = 1;
|
|
break;
|
|
case '^':
|
|
hop = 2;
|
|
break;
|
|
case '+':
|
|
if (is_integral (vec_type)) {
|
|
hop = 3;
|
|
} else {
|
|
hop = 7;
|
|
}
|
|
break;
|
|
case NAND:
|
|
hop = 4;
|
|
break;
|
|
case NOR:
|
|
hop = 5;
|
|
break;
|
|
case XNOR:
|
|
hop = 6;
|
|
break;
|
|
default:
|
|
internal_error (e, "invalid horizontal op");
|
|
}
|
|
hop |= (type_width (vec_type) - 1) << 3;
|
|
hop |= (pr_type_size[vec_type->type] - 1) << 5;
|
|
|
|
s = new_statement (st_expr, opcode, e);
|
|
sblock = statement_subexpr (sblock, e->e.hop.vec, &s->opa);
|
|
s->opb = short_operand (hop, e);
|
|
if (!*op) {
|
|
*op = temp_operand (res_type, e);
|
|
}
|
|
s->opc = *op;
|
|
sblock_add_statement (sblock, s);
|
|
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_swizzle (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
const char *opcode = "swizzle";
|
|
statement_t *s;
|
|
int swiz = 0;
|
|
type_t *res_type = e->e.swizzle.type;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
swiz |= e->e.swizzle.source[i] & 3;
|
|
}
|
|
swiz |= (e->e.swizzle.neg & 0xf) << 8;
|
|
swiz |= (e->e.swizzle.zero & 0xf) << 12;
|
|
|
|
s = new_statement (st_expr, opcode, e);
|
|
sblock = statement_subexpr (sblock, e->e.swizzle.src, &s->opa);
|
|
s->opb = short_operand (swiz, e);
|
|
if (!*op) {
|
|
*op = temp_operand (res_type, e);
|
|
}
|
|
s->opc = *op;
|
|
sblock_add_statement (sblock, s);
|
|
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_extend (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
type_t *src_type = get_type (e->e.extend.src);
|
|
type_t *res_type = e->e.extend.type;
|
|
int src_width = type_width (src_type);
|
|
int res_width = type_width (res_type);
|
|
type_t *src_base = base_type (src_type);
|
|
type_t *res_base = base_type (res_type);
|
|
static int mode[4][4] = {
|
|
{-1, 0, 1, 2},
|
|
{-1,-1, 3, 4},
|
|
{-1,-1,-1, 5},
|
|
{-1,-1,-1,-1},
|
|
};
|
|
int ext = mode[src_width - 1][res_width - 1];
|
|
ext |= (e->e.extend.extend & 3) << 3;
|
|
ext |= (pr_type_size[res_base->type] - 1) << 5;
|
|
if (ext < 0 || res_base != src_base) {
|
|
internal_error (e, "invalid type combination for extend %d %d %d",
|
|
ext, src_width, res_width);
|
|
}
|
|
|
|
statement_t *s = new_statement (st_expr, "extend", e);
|
|
sblock = statement_subexpr (sblock, e->e.extend.src, &s->opa);
|
|
s->opb = short_operand (ext, e);
|
|
if (!*op) {
|
|
*op = temp_operand (res_type, e);
|
|
}
|
|
s->opc = *op;
|
|
sblock_add_statement (sblock, s);
|
|
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_def (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
*op = def_operand (e->e.def, e->e.def->type, e);
|
|
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, e);
|
|
} else if (sym->sy_type == sy_const) {
|
|
*op = value_operand (sym->s.value, e);
|
|
} else if (sym->sy_type == sy_func) {
|
|
if (!sym->s.func) {
|
|
make_function (sym, 0, pr.symtab->space, sc_extern);
|
|
}
|
|
*op = def_operand (sym->s.func->def, 0, e);
|
|
} 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, e);
|
|
*op = e->e.temp.op;
|
|
return sblock;
|
|
}
|
|
|
|
static int
|
|
statement_copy_elements (sblock_t **sblock, expr_t *dst, expr_t *src, int base)
|
|
{
|
|
int index = 0;
|
|
for (expr_t *e = src->e.vector.list; e; e = e->next) {
|
|
if (e->type == ex_vector) {
|
|
index += statement_copy_elements (sblock, dst, e, index + base);
|
|
} else {
|
|
int size = type_size (base_type (get_type (dst)));
|
|
type_t *src_type = get_type (e);
|
|
expr_t *dst_ele = new_offset_alias_expr (src_type, dst,
|
|
size * (index + base));
|
|
index += type_width (src_type);
|
|
*sblock = statement_slist (*sblock, assign_expr (dst_ele, e));
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_vector_e (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
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);
|
|
statement_copy_elements (&sblock, tmp, e, 0);
|
|
|
|
pr.source_file = file;
|
|
pr.source_line = line;
|
|
sblock = statement_subexpr (sblock, tmp, op);
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_nil (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
type_t *nil = e->e.nil;
|
|
expr_t *size_expr;
|
|
size_t nil_size;
|
|
operand_t *zero;
|
|
operand_t *size;
|
|
statement_t *s;
|
|
|
|
if (!is_struct (nil) && !is_array (nil)) {
|
|
*op = value_operand (new_nil_val (nil), e);
|
|
return sblock;
|
|
}
|
|
if (!*op) {
|
|
*op = temp_operand (nil, e);
|
|
}
|
|
nil_size = type_size (nil);
|
|
if (nil_size < 0x10000) {
|
|
size_expr = new_short_expr (nil_size);
|
|
} else {
|
|
size_expr = new_int_expr (nil_size);
|
|
}
|
|
sblock = statement_subexpr (sblock, new_int_expr(0), &zero);
|
|
sblock = statement_subexpr (sblock, size_expr, &size);
|
|
|
|
s = new_statement (st_memset, "memset", e);
|
|
s->opa = zero;
|
|
s->opb = size;
|
|
s->opc = *op;
|
|
sblock_add_statement (sblock, s);
|
|
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_value (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
*op = value_operand (e->e.value, e);
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
expr_selector (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
return statement_subexpr (sblock, e->e.selector.sel_ref, op);
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_subexpr (sblock_t *sblock, expr_t *e, operand_t **op)
|
|
{
|
|
static expr_f sfuncs[ex_count] = {
|
|
[ex_block] = expr_block,
|
|
[ex_expr] = expr_expr,
|
|
[ex_uexpr] = expr_uexpr,
|
|
[ex_horizontal] = expr_horizontal,
|
|
[ex_swizzle] = expr_swizzle,
|
|
[ex_def] = expr_def,
|
|
[ex_symbol] = expr_symbol,
|
|
[ex_temp] = expr_temp,
|
|
[ex_vector] = expr_vector_e,
|
|
[ex_nil] = expr_nil,
|
|
[ex_value] = expr_value,
|
|
[ex_selector] = expr_selector,
|
|
[ex_alias] = expr_alias,
|
|
[ex_address] = expr_address,
|
|
[ex_assign] = expr_assign,
|
|
[ex_branch] = expr_branch,
|
|
[ex_extend] = expr_extend,
|
|
};
|
|
if (!e) {
|
|
*op = 0;
|
|
return sblock;
|
|
}
|
|
|
|
if (e->type >= ex_count)
|
|
internal_error (e, "bad sub-expression type: %d", e->type);
|
|
if (!sfuncs[e->type])
|
|
internal_error (e, "unexpected sub-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_assign:
|
|
e->next = 0;
|
|
append_expr (block, e);
|
|
return;
|
|
case ex_branch:
|
|
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 {
|
|
e->next = 0;
|
|
append_expr (block, e);
|
|
}
|
|
return;
|
|
case ex_uexpr:
|
|
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_branch && e->e.branch.type == pr_branch_jump
|
|
&& !e->e.branch.index;
|
|
}
|
|
|
|
static int
|
|
is_if_expr (expr_t *e)
|
|
{
|
|
return e && e->type == ex_branch && e->e.branch.type == pr_branch_ne;
|
|
}
|
|
|
|
static int
|
|
is_ifnot_expr (expr_t *e)
|
|
{
|
|
return e && e->type == ex_branch && e->e.branch.type == pr_branch_eq;
|
|
}
|
|
|
|
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.branch.target;
|
|
for (e = (*s)->next->next; e && e->type == ex_label; e = e->next) {
|
|
if (e == l) {
|
|
l->e.label.used--;
|
|
e = *s;
|
|
e->e.branch.type = pr_branch_eq;
|
|
e->e.branch.target = e->next->e.branch.target;
|
|
e->next = e->next->next;
|
|
break;
|
|
}
|
|
}
|
|
s = &(*s)->next;
|
|
} else if (is_ifnot_expr (*s) && is_goto_expr ((*s)->next)) {
|
|
l = (*s)->e.branch.target;
|
|
for (e = (*s)->next->next; e && e->type == ex_label; e = e->next) {
|
|
if (e == l) {
|
|
l->e.label.used--;
|
|
e = *s;
|
|
e->e.branch.type = pr_branch_ne;
|
|
e->e.branch.target = e->next->e.branch.target;
|
|
e->next = e->next->next;
|
|
break;
|
|
}
|
|
}
|
|
s = &(*s)->next;
|
|
} else if (is_goto_expr (*s)) {
|
|
l = (*s)->e.branch.target;
|
|
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 {
|
|
if (e->e.label.symbol) {
|
|
warning (e, "unused label %s", e->e.label.symbol->name);
|
|
} 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);
|
|
if (e->e.block.is_call) {
|
|
// for a fuction call, the call expresion is in only the result, not
|
|
// the actual block
|
|
sblock = statement_slist (sblock, e->e.block.result);
|
|
}
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_expr (sblock_t *sblock, expr_t *e)
|
|
{
|
|
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)
|
|
{
|
|
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_memset (sblock_t *sblock, expr_t *e)
|
|
{
|
|
expr_t *dst = e->e.memset.dst;
|
|
expr_t *val = e->e.memset.val;
|
|
expr_t *count = e->e.memset.count;
|
|
const char *opcode = "memset";
|
|
statement_t *s;
|
|
|
|
if (is_constant (count)) {
|
|
if (is_int (get_type (count))
|
|
&& (unsigned) expr_int (count) < 0x10000) {
|
|
count = new_short_expr (expr_int (count));
|
|
}
|
|
if (is_uint (get_type (count)) && expr_int (count) < 0x10000) {
|
|
count = new_short_expr (expr_uint (count));
|
|
}
|
|
}
|
|
s = new_statement (st_move, opcode, e);
|
|
sblock = statement_subexpr (sblock, dst, &s->opc);
|
|
sblock = statement_subexpr (sblock, count, &s->opb);
|
|
sblock = statement_subexpr (sblock, val, &s->opa);
|
|
sblock_add_statement (sblock, s);
|
|
return sblock;
|
|
}
|
|
|
|
static sblock_t *
|
|
statement_assign (sblock_t *sblock, expr_t *e)
|
|
{
|
|
return expr_assign (sblock, e, 0);
|
|
}
|
|
|
|
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[ex_count] = {
|
|
[ex_error] = statement_ignore,
|
|
[ex_state] = statement_state,
|
|
[ex_bool] = statement_bool,
|
|
[ex_label] = statement_label,
|
|
[ex_block] = statement_block,
|
|
[ex_expr] = statement_expr,
|
|
[ex_uexpr] = statement_uexpr,
|
|
[ex_def] = statement_nonexec,
|
|
[ex_symbol] = statement_nonexec,
|
|
[ex_temp] = statement_nonexec,
|
|
[ex_vector] = statement_nonexec,
|
|
[ex_nil] = statement_nonexec,
|
|
[ex_value] = statement_nonexec,
|
|
[ex_memset] = statement_memset,
|
|
[ex_assign] = statement_assign,
|
|
[ex_branch] = statement_branch,
|
|
[ex_return] = statement_return,
|
|
[ex_adjstk] = statement_adjstk,
|
|
[ex_with] = statement_with,
|
|
};
|
|
|
|
for (/**/; e; e = e->next) {
|
|
if (e->type >= ex_count || !sfuncs[e->type])
|
|
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 || !label->dest)
|
|
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->label;
|
|
if (!(*label)->dest && (*label)->symbol) {
|
|
error (s->opa->expr, "undefined label `%s'",
|
|
(*label)->symbol->name);
|
|
(*label)->symbol = 0;
|
|
}
|
|
} else if (statement_is_cond (s)) {
|
|
label = &s->opa->label;
|
|
} else {
|
|
continue;
|
|
}
|
|
for (l = *label;
|
|
l->dest && l->dest->statements
|
|
&& statement_is_goto (l->dest->statements);
|
|
l = *statement_get_labelref (l->dest->statements)) {
|
|
// empty loop
|
|
}
|
|
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)
|
|
&& statement_get_target (s) == sb->next) {
|
|
debug (0, "merging if/goto %p %p", sblock, sb);
|
|
ex_label_t **labelref = statement_get_labelref(s);
|
|
unuse_label (*labelref);
|
|
*labelref = *statement_get_labelref (sb->statements);
|
|
(*labelref)->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;
|
|
ex_label_t **labelref = statement_get_labelref (s);
|
|
label = labelref ? *labelref : 0;
|
|
}
|
|
unuse_label (label);
|
|
did_something = 1;
|
|
did_anything = 1;
|
|
|
|
sblock->next = sb->next;
|
|
free_sblock (sb);
|
|
}
|
|
}
|
|
} while (did_something);
|
|
return did_anything;
|
|
}
|
|
|
|
static void
|
|
super_dealloc_warning (expr_t *expr, pseudoop_t *op)
|
|
{
|
|
warning (expr,
|
|
"control may reach end of derived -dealloc without invoking %s",
|
|
op->name);
|
|
}
|
|
|
|
static void
|
|
search_for_super_dealloc (sblock_t *sblock)
|
|
{
|
|
operand_t *op;
|
|
pseudoop_t *super_dealloc = new_pseudoop ("[super dealloc]");
|
|
int super_dealloc_found = 0;
|
|
super_dealloc->next = current_func->pseudo_ops;
|
|
current_func->pseudo_ops = super_dealloc;
|
|
super_dealloc->uninitialized = super_dealloc_warning;
|
|
while (sblock) {
|
|
for (statement_t *st = sblock->statements; st; st = st->next) {
|
|
if (statement_is_return (st)) {
|
|
op = pseudo_operand (super_dealloc, st->expr);
|
|
op->next = st->use;
|
|
st->use = op;
|
|
continue;
|
|
}
|
|
if (!statement_is_call (st)) {
|
|
continue;
|
|
}
|
|
// effectively checks target
|
|
if (st->opa->op_type != op_def
|
|
|| strcmp (st->opa->def->name, "obj_msgSend_super") != 0) {
|
|
continue;
|
|
}
|
|
// function arguments are in reverse order, and the selector
|
|
// is the second argument (or second last in the list)
|
|
expr_t *arg;
|
|
for (arg = st->expr->e.branch.args;
|
|
arg && arg->next && arg->next->next; arg = arg->next) {
|
|
}
|
|
if (arg && arg->next && is_selector (arg)) {
|
|
selector_t *sel = get_selector (st->expr->e.branch.args);
|
|
if (sel && strcmp (sel->name, "dealloc") == 0) {
|
|
op = pseudo_operand (super_dealloc, st->expr);
|
|
op->next = st->use;
|
|
st->def = op;
|
|
super_dealloc_found++;
|
|
}
|
|
}
|
|
}
|
|
sblock = sblock->next;
|
|
}
|
|
// warn only when NOT optimizing because flow analysis will catch the
|
|
// missed invokation
|
|
if (!super_dealloc_found && !options.code.optimize) {
|
|
warning (0, "Derived class -dealloc does not call [super dealloc]");
|
|
}
|
|
}
|
|
|
|
static void
|
|
check_final_block (sblock_t *sblock)
|
|
{
|
|
statement_t *s = 0;
|
|
|
|
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 (!is_void(current_func->sym->type->t.func.type))
|
|
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;
|
|
}
|
|
s = new_statement (st_func, "return", 0);
|
|
if (options.code.progsversion == PROG_VERSION) {
|
|
s->opa = short_operand (0, 0);
|
|
s->opb = short_operand (0, 0);
|
|
s->opc = short_operand (-1, 0);
|
|
} else {
|
|
if (options.traditional
|
|
|| options.code.progsversion == PROG_ID_VERSION) {
|
|
s->opa = return_operand (&type_void, 0);
|
|
}
|
|
}
|
|
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;
|
|
class_t *class;
|
|
|
|
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 (0, "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 (0, "dead-%d", pass), sblock, dump_dot_sblock);
|
|
pass++;
|
|
} while (did_something);
|
|
check_final_block (sblock);
|
|
if (current_class && (class = extract_class (current_class))) {
|
|
// If the class is a root class, then it is not possible for there
|
|
// to be a call to [super dealloc] so do not check. However, any
|
|
// derived class implementeing -dealloc must call [super dealloc]
|
|
// (or some other deallocator (FIXME: later))
|
|
// FIXME better check for dealloc?
|
|
if (class->super_class && !strcmp (current_func->name, "-dealloc")) {
|
|
search_for_super_dealloc (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->tempop.alias)
|
|
op = op->tempop.alias;
|
|
op->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;
|
|
}
|
|
}
|