quakeforge/tools/qfcc/source/statements.c
Bill Currie 2c8bec27c7 Fix a pile of warnings for gcc 12
Most were pretty easy and fairly logical, but gib's regex was a bit of a
pain until I figured out the real problem was the conditional
assignments.

However, libs/gamecode/test/test-conv4 fails when optimizing due to gcc
using vcvttps2dq (which is nice, actually) for vector forms, but not the
single equivalent other times. I haven't decided what to do with the
test (I might abandon it as it does seem to be UD).
2022-07-31 17:13:26 +09:00

2642 lines
65 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 {
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_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,
};
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;
}
}