blob: de4216fb02c9257afc84bd48601c500b545707fa [file] [log] [blame]
// SPDX-License-Identifier: MIT
//
// SSA conversion
// Copyright (C) 2005 Luc Van Oostenryck
//
#include <assert.h>
#include "ssa.h"
#include "lib.h"
#include "dominate.h"
#include "flowgraph.h"
#include "linearize.h"
#include "simplify.h"
#include "flow.h"
// Is it possible and desirable for this to be promoted to a pseudo?
static inline bool is_promotable(struct symbol *type)
{
struct symbol *member;
int bf_seen;
int nbr;
if (type->type == SYM_NODE)
type = type->ctype.base_type;
switch (type->type) {
case SYM_ENUM:
case SYM_BITFIELD:
case SYM_PTR:
case SYM_RESTRICT: // OK, always integer types
return 1;
case SYM_STRUCT:
// we allow a single scalar field
// but a run of bitfields count for 1
// (and packed bifields are excluded).
if (type->packed)
return 0;
nbr = 0;
bf_seen = 0;
FOR_EACH_PTR(type->symbol_list, member) {
if (is_bitfield_type(member)) {
if (bf_seen)
continue;
bf_seen = 1;
} else {
bf_seen = 0;
}
if (!is_scalar_type(member))
return 0;
if (nbr++)
return 0;
} END_FOR_EACH_PTR(member);
if (bf_seen && (type->bit_size > long_ctype.bit_size))
return 0;
return 1;
case SYM_UNION:
// FIXME: should be like struct but has problem
// when used with/for type cohercion
// -----> OK if only same sized integral types
FOR_EACH_PTR(type->symbol_list, member) {
if (member->bit_size != type->bit_size)
return 0;
if (!is_integral_type(member))
return 0;
} END_FOR_EACH_PTR(member);
return 1;
default:
break;
}
if (type->ctype.base_type == &int_type)
return 1;
if (type->ctype.base_type == &fp_type)
return 1;
return 0;
}
static void kill_store(struct instruction *insn)
{
remove_use(&insn->src);
remove_use(&insn->target);
insn->bb = NULL;
}
static bool same_memop(struct instruction *a, struct instruction *b)
{
if (a->size != b->size || a->offset != b->offset)
return false;
if (is_integral_type(a->type) && is_float_type(b->type))
return false;
if (is_float_type(a->type) && is_integral_type(b->type))
return false;
return true;
}
static void rewrite_local_var(struct basic_block *bb, pseudo_t addr, int nbr_stores, int nbr_uses)
{
struct instruction *store = NULL;
struct instruction *insn;
bool remove = false;
if (!bb)
return;
FOR_EACH_PTR(bb->insns, insn) {
if (!insn->bb || insn->src != addr)
continue;
switch (insn->opcode) {
case OP_LOAD:
if (!store)
replace_with_pseudo(insn, undef_pseudo());
else if (same_memop(store, insn))
replace_with_pseudo(insn, store->target);
else
remove = false;
break;
case OP_STORE:
store = insn;
remove = true;
break;
}
} END_FOR_EACH_PTR(insn);
if (remove)
kill_store(store);
}
// we would like to know:
// is there one or more stores?
// are all loads & stores local/done in a single block?
static void ssa_convert_one_var(struct entrypoint *ep, struct symbol *var)
{
unsigned long generation = ++bb_generation;
struct basic_block_list *alpha = NULL;
struct basic_block_list *idf = NULL;
struct basic_block *samebb = NULL;
struct basic_block *bb;
struct pseudo_user *pu;
unsigned long mod = var->ctype.modifiers;
bool local = true;
int nbr_stores = 0;
int nbr_uses = 0;
pseudo_t addr;
/* Never used as a symbol? */
addr = var->pseudo;
if (!addr)
return;
/* We don't do coverage analysis of volatiles.. */
if (mod & MOD_VOLATILE)
return;
/* ..and symbols with external visibility need more care */
mod &= (MOD_NONLOCAL | MOD_STATIC | MOD_ADDRESSABLE);
if (mod)
goto external_visibility;
if (!is_promotable(var))
return;
// 1) insert in the worklist all BBs that may modify var
FOR_EACH_PTR(addr->users, pu) {
struct instruction *insn = pu->insn;
struct basic_block *bb = insn->bb;
switch (insn->opcode) {
case OP_STORE:
nbr_stores++;
if (bb->generation != generation) {
bb->generation = generation;
add_bb(&alpha, bb);
}
/* fall through */
case OP_LOAD:
if (local) {
if (!samebb)
samebb = bb;
else if (samebb != bb)
local = false;
}
nbr_uses++;
break;
case OP_SYMADDR:
mod |= MOD_ADDRESSABLE;
goto external_visibility;
default:
warning(var->pos, "symbol '%s' pseudo used in unexpected way",
show_ident(var->ident));
}
} END_FOR_EACH_PTR(pu);
// if all uses are local to a single block
// they can easily be rewritten and doesn't need phi-nodes
// FIXME: could be done for extended BB too
if (local) {
rewrite_local_var(samebb, addr, nbr_stores, nbr_uses);
return;
}
idf_compute(ep, &idf, alpha);
FOR_EACH_PTR(idf, bb) {
struct instruction *node = insert_phi_node(bb, var);
node->phi_var = var->pseudo;
} END_FOR_EACH_PTR(bb);
var->torename = 1;
external_visibility:
if (mod & (MOD_NONLOCAL | MOD_STATIC))
return;
kill_dead_stores(ep, addr, !mod);
}
static struct instruction *lookup_var(struct basic_block *bb, struct symbol *var)
{
do {
struct instruction *insn = phi_map_lookup(bb->phi_map, var);
if (insn)
return insn;
} while ((bb = bb->idom));
return NULL;
}
static struct instruction_list *phis_all;
static struct instruction_list *phis_used;
static struct instruction_list *stores;
static bool matching_load(struct instruction *def, struct instruction *insn)
{
if (insn->size != def->size)
return false;
switch (def->opcode) {
case OP_STORE:
case OP_LOAD:
if (insn->offset != def->offset)
return false;
case OP_PHI:
break;
default:
return false;
}
return true;
}
static void ssa_rename_insn(struct basic_block *bb, struct instruction *insn)
{
struct instruction *def;
struct symbol *var;
pseudo_t addr;
pseudo_t val;
switch (insn->opcode) {
case OP_STORE:
addr = insn->src;
if (addr->type != PSEUDO_SYM)
break;
var = addr->sym;
if (!var || !var->torename)
break;
phi_map_update(&bb->phi_map, var, insn);
add_instruction(&stores, insn);
break;
case OP_LOAD:
addr = insn->src;
if (addr->type != PSEUDO_SYM)
break;
var = addr->sym;
if (!var || !var->torename)
break;
def = lookup_var(bb, var);
if (!def) {
val = undef_pseudo();
} else if (!matching_load(def, insn)) {
var->torename = false;
break;
} else {
val = def->target;
}
replace_with_pseudo(insn, val);
break;
case OP_PHI:
var = insn->type;
if (!var || !var->torename)
break;
phi_map_update(&bb->phi_map, var, insn);
add_instruction(&phis_all, insn);
break;
}
}
static void ssa_rename_insns(struct entrypoint *ep)
{
struct basic_block *bb;
FOR_EACH_PTR(ep->bbs, bb) {
struct instruction *insn;
FOR_EACH_PTR(bb->insns, insn) {
if (!insn->bb)
continue;
ssa_rename_insn(bb, insn);
} END_FOR_EACH_PTR(insn);
} END_FOR_EACH_PTR(bb);
}
static void mark_phi_used(pseudo_t val)
{
struct instruction *node;
if (val->type != PSEUDO_REG)
return;
node = val->def;
if (node->opcode != OP_PHI)
return;
if (node->used)
return;
node->used = 1;
add_instruction(&phis_used, node);
}
static void ssa_rename_phi(struct instruction *insn)
{
struct basic_block *par;
struct symbol *var;
if (!insn->phi_var)
return;
var = insn->phi_var->sym;
if (!var->torename)
return;
FOR_EACH_PTR(insn->bb->parents, par) {
struct instruction *term = delete_last_instruction(&par->insns);
struct instruction *def = lookup_var(par, var);
pseudo_t val = def ? def->target : undef_pseudo();
pseudo_t phi = alloc_phi(par, val, var);
phi->ident = var->ident;
add_instruction(&par->insns, term);
link_phi(insn, phi);
mark_phi_used(val);
} END_FOR_EACH_PTR(par);
}
static void ssa_rename_phis(struct entrypoint *ep)
{
struct instruction *phi;
phis_used = NULL;
FOR_EACH_PTR(phis_all, phi) {
if (has_users(phi->target)) {
phi->used = 1;
add_instruction(&phis_used, phi);
}
} END_FOR_EACH_PTR(phi);
FOR_EACH_PTR(phis_used, phi) {
if (!phi->bb)
continue;
ssa_rename_phi(phi);
} END_FOR_EACH_PTR(phi);
}
static void remove_dead_stores(struct instruction_list *stores)
{
struct instruction *store;
FOR_EACH_PTR(stores, store) {
struct symbol *var = store->addr->sym;
if (var->torename)
kill_store(store);
} END_FOR_EACH_PTR(store);
}
void ssa_convert(struct entrypoint *ep)
{
struct basic_block *bb;
pseudo_t pseudo;
int first, last;
// calculate the number of BBs
first = ep->entry->bb->nr;
last = first;
FOR_EACH_PTR(ep->bbs, bb) {
int nr = bb->nr;
if (nr > last)
last = nr;
bb->phi_map = NULL;
} END_FOR_EACH_PTR(bb);
// try to promote memory accesses to pseudos
stores = NULL;
FOR_EACH_PTR(ep->accesses, pseudo) {
ssa_convert_one_var(ep, pseudo->sym);
} END_FOR_EACH_PTR(pseudo);
// rename the converted accesses
phis_all = phis_used = NULL;
ssa_rename_insns(ep);
ssa_rename_phis(ep);
// remove now dead stores
remove_dead_stores(stores);
}