blob: 3e8800507f63e3273bf38f36837d6ac9578e1972 [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 "sset.h"
#include "dominate.h"
#include "flowgraph.h"
#include "linearize.h"
#include "flow.h" // for convert_load_instruction()
// 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
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 bool insn_before(struct instruction *a, struct instruction *b)
{
struct basic_block *bb = a->bb;
struct instruction *insn;
assert(b->bb == bb);
FOR_EACH_PTR(bb->insns, insn) {
if (insn == a)
return true;
if (insn == b)
return false;
} END_FOR_EACH_PTR(insn);
assert(0);
}
static void kill_store(struct instruction *insn)
{
remove_use(&insn->src);
remove_use(&insn->target);
insn->bb = NULL;
}
static void rewrite_local_var(struct basic_block *bb, pseudo_t addr, int nbr_stores, int nbr_uses)
{
struct instruction *insn;
pseudo_t val = NULL;
if (!bb)
return;
FOR_EACH_PTR(bb->insns, insn) {
if (!insn->bb || insn->src != addr)
continue;
switch (insn->opcode) {
case OP_LOAD:
if (!val)
val = undef_pseudo();
convert_load_instruction(insn, val);
break;
case OP_STORE:
val = insn->target;
// can't use kill_instruction() unless
// we add a fake user to val
kill_store(insn);
break;
}
} END_FOR_EACH_PTR(insn);
}
static bool rewrite_single_store(struct instruction *store)
{
pseudo_t addr = store->src;
struct pseudo_user *pu;
FOR_EACH_PTR(addr->users, pu) {
struct instruction *insn = pu->insn;
if (insn->opcode != OP_LOAD)
continue;
// Let's try to replace the value of the load
// by the value from the store. This is only valid
// if the store dominate the load.
if (insn->bb == store->bb) {
// the load and the store are in the same BB
// we can convert if the load is after the store.
if (!insn_before(store, insn))
continue;
} else if (!domtree_dominates(store->bb, insn->bb)) {
// we can't convert this load
continue;
}
// OK, we can rewrite this load
// undefs ?
convert_load_instruction(insn, store->target);
} END_FOR_EACH_PTR(pu);
// is there some unconverted loads?
if (pseudo_user_list_size(addr->users) > 1)
return false;
kill_store(store);
return true;
}
static struct sset *processed;
// 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)
{
struct basic_block_list *alpha = NULL;
struct basic_block_list *idf = NULL;
struct basic_block *samebb = NULL;
struct instruction *store = 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
sset_reset(processed);
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++;
store = insn;
if (!sset_testset(processed, bb->nr))
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 (nbr_stores == 1) {
if (rewrite_single_store(store))
return;
}
// 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 pseudo_t lookup_var(struct basic_block *bb, struct symbol *var)
{
do {
pseudo_t val = phi_map_lookup(bb->phi_map, var);
if (val)
return val;
} while ((bb = bb->idom));
return undef_pseudo();
}
static struct instruction_list *phis_all;
static struct instruction_list *phis_used;
static void ssa_rename_insn(struct basic_block *bb, struct instruction *insn)
{
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->target);
kill_store(insn);
break;
case OP_LOAD:
addr = insn->src;
if (addr->type != PSEUDO_SYM)
break;
var = addr->sym;
if (!var || !var->torename)
break;
val = lookup_var(bb, var);
convert_load_instruction(insn, val);
break;
case OP_PHI:
var = insn->type;
if (!var || !var->torename)
break;
phi_map_update(&bb->phi_map, var, insn->target);
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);
pseudo_t val = lookup_var(par, var);
pseudo_t phi = alloc_phi(par, val, var);
phi->ident = var->ident;
add_instruction(&par->insns, term);
use_pseudo(insn, phi, add_pseudo(&insn->phi_list, 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);
}
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;
} END_FOR_EACH_PTR(bb);
processed = sset_init(first, last);
// try to promote memory accesses to pseudos
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);
}