blob: acbc477d5f709ecb145a94889f123be2b662ea40 [file] [log] [blame]
/*
* Storage - associate pseudos with "storage" that keeps them alive
* between basic blocks. The aim is to be able to turn as much of
* the global storage allocation problem as possible into a local
* per-basic-block one.
*
* Copyright (C) 2004 Linus Torvalds
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "symbol.h"
#include "expression.h"
#include "linearize.h"
#include "storage.h"
ALLOCATOR(storage, "storages");
ALLOCATOR(storage_hash, "storage hash");
#define MAX_STORAGE_HASH 64
static struct storage_hash_list *storage_hash_table[MAX_STORAGE_HASH];
static inline unsigned int storage_hash(struct basic_block *bb, pseudo_t pseudo, enum inout_enum inout)
{
unsigned hash = hashval(bb) + hashval(pseudo) + hashval(inout);
hash += hash / MAX_STORAGE_HASH;
return hash & (MAX_STORAGE_HASH-1);
}
static int hash_list_cmp(const void *_a, const void *_b)
{
const struct storage_hash *a = _a;
const struct storage_hash *b = _b;
if (a->pseudo != b->pseudo)
return a->pseudo < b->pseudo ? -1 : 1;
return 0;
}
static void sort_hash_list(struct storage_hash_list **listp)
{
sort_list((struct ptr_list **)listp, hash_list_cmp);
}
struct storage_hash_list *gather_storage(struct basic_block *bb, enum inout_enum inout)
{
int i;
struct storage_hash *entry, *prev;
struct storage_hash_list *list = NULL;
for (i = 0; i < MAX_STORAGE_HASH; i++) {
struct storage_hash *hash;
FOR_EACH_PTR(storage_hash_table[i], hash) {
if (hash->bb == bb && hash->inout == inout)
add_ptr_list(&list, hash);
} END_FOR_EACH_PTR(hash);
}
sort_hash_list(&list);
prev = NULL;
FOR_EACH_PTR(list, entry) {
if (prev && entry->pseudo == prev->pseudo) {
assert(entry == prev);
DELETE_CURRENT_PTR(entry);
}
prev = entry;
} END_FOR_EACH_PTR(entry);
PACK_PTR_LIST(&list);
return list;
}
static void name_storage(void)
{
int i;
int name = 0;
for (i = 0; i < MAX_STORAGE_HASH; i++) {
struct storage_hash *hash;
FOR_EACH_PTR(storage_hash_table[i], hash) {
struct storage *storage = hash->storage;
if (storage->name)
continue;
storage->name = ++name;
} END_FOR_EACH_PTR(hash);
}
}
struct storage *lookup_storage(struct basic_block *bb, pseudo_t pseudo, enum inout_enum inout)
{
struct storage_hash_list *list = storage_hash_table[storage_hash(bb,pseudo,inout)];
struct storage_hash *hash;
FOR_EACH_PTR(list, hash) {
if (hash->bb == bb && hash->pseudo == pseudo && hash->inout == inout)
return hash->storage;
} END_FOR_EACH_PTR(hash);
return NULL;
}
void add_storage(struct storage *storage, struct basic_block *bb, pseudo_t pseudo, enum inout_enum inout)
{
struct storage_hash_list **listp = storage_hash_table + storage_hash(bb,pseudo,inout);
struct storage_hash *hash = alloc_storage_hash(storage);
hash->bb = bb;
hash->pseudo = pseudo;
hash->inout = inout;
add_ptr_list(listp, hash);
}
static int storage_hash_cmp(const void *_a, const void *_b)
{
const struct storage_hash *a = _a;
const struct storage_hash *b = _b;
struct storage *aa = a->storage;
struct storage *bb = b->storage;
if (a->bb != b->bb)
return a->bb < b->bb ? -1 : 1;
if (a->inout != b->inout)
return a->inout < b->inout ? -1 : 1;
if (aa->type != bb->type)
return aa->type < bb->type ? -1 : 1;
if (aa->regno != bb->regno)
return aa->regno < bb->regno ? -1 : 1;
return 0;
}
static void vrfy_storage(struct storage_hash_list **listp)
{
struct storage_hash *entry, *last;
sort_list((struct ptr_list **)listp, storage_hash_cmp);
last = NULL;
FOR_EACH_PTR(*listp, entry) {
if (last) {
struct storage *a = last->storage;
struct storage *b = entry->storage;
if (a == b)
continue;
if (last->bb == entry->bb
&& last->inout == entry->inout
&& a->type != REG_UDEF
&& a->type == b->type
&& a->regno == b->regno) {
printf("\t BAD: same storage as %s in %p: %s (%s and %s)\n",
last->inout == STOR_IN ? "input" : "output",
last->bb,
show_storage(a),
show_pseudo(last->pseudo),
show_pseudo(entry->pseudo));
}
}
last = entry;
} END_FOR_EACH_PTR(entry);
}
void free_storage(void)
{
int i;
for (i = 0; i < MAX_STORAGE_HASH; i++) {
vrfy_storage(storage_hash_table + i);
free_ptr_list(storage_hash_table + i);
}
}
const char *show_storage(struct storage *s)
{
static char buffer[1024];
if (!s)
return "none";
switch (s->type) {
case REG_REG:
sprintf(buffer, "reg%d (%d)", s->regno, s->name);
break;
case REG_STACK:
sprintf(buffer, "%d(SP) (%d)", s->offset, s->name);
break;
case REG_ARG:
sprintf(buffer, "ARG%d (%d)", s->regno, s->name);
break;
default:
sprintf(buffer, "%d:%d (%d)", s->type, s->regno, s->name);
break;
}
return buffer;
}
/*
* Combine two storage allocations into one.
*
* We just randomly pick one over the other, and replace
* the other uses.
*/
static struct storage * combine_storage(struct storage *src, struct storage *dst)
{
struct storage **usep;
/* Remove uses of "src_storage", replace with "dst" */
FOR_EACH_PTR(src->users, usep) {
assert(*usep == src);
*usep = dst;
add_ptr_list(&dst->users, usep);
} END_FOR_EACH_PTR(usep);
/* Mark it unused */
src->type = REG_BAD;
src->users = NULL;
return dst;
}
static void set_up_bb_storage(struct basic_block *bb)
{
struct basic_block *child;
FOR_EACH_PTR(bb->children, child) {
pseudo_t pseudo;
FOR_EACH_PTR(child->needs, pseudo) {
struct storage *child_in, *parent_out;
parent_out = lookup_storage(bb, pseudo, STOR_OUT);
child_in = lookup_storage(child, pseudo, STOR_IN);
if (parent_out) {
if (!child_in) {
add_storage(parent_out, child, pseudo, STOR_IN);
continue;
}
if (parent_out == child_in)
continue;
combine_storage(parent_out, child_in);
continue;
}
if (child_in) {
add_storage(child_in, bb, pseudo, STOR_OUT);
continue;
}
parent_out = alloc_storage();
add_storage(parent_out, bb, pseudo, STOR_OUT);
add_storage(parent_out, child, pseudo, STOR_IN);
} END_FOR_EACH_PTR(pseudo);
} END_FOR_EACH_PTR(child);
}
static void set_up_argument_storage(struct entrypoint *ep, struct basic_block *bb)
{
pseudo_t arg;
FOR_EACH_PTR(bb->needs, arg) {
struct storage *storage = alloc_storage();
/* FIXME! Totally made-up argument passing conventions */
if (arg->type == PSEUDO_ARG) {
storage->type = REG_ARG;
storage->regno = arg->nr;
}
add_storage(storage, bb, arg, STOR_IN);
} END_FOR_EACH_PTR(arg);
}
/*
* One phi-source may feed multiple phi nodes. If so, combine
* the storage output for this bb into one entry to reduce
* storage pressure.
*/
static void combine_phi_storage(struct basic_block *bb)
{
struct instruction *insn;
FOR_EACH_PTR(bb->insns, insn) {
struct instruction *phi;
struct storage *last;
if (!insn->bb || insn->opcode != OP_PHISOURCE)
continue;
last = NULL;
FOR_EACH_PTR(insn->phi_users, phi) {
struct storage *storage = lookup_storage(bb, phi->target, STOR_OUT);
if (!storage) {
DELETE_CURRENT_PTR(phi);
continue;
}
if (last && storage != last)
storage = combine_storage(storage, last);
last = storage;
} END_FOR_EACH_PTR(phi);
PACK_PTR_LIST(&insn->phi_users);
} END_FOR_EACH_PTR(insn);
}
void set_up_storage(struct entrypoint *ep)
{
struct basic_block *bb;
/* First set up storage for the incoming arguments */
set_up_argument_storage(ep, ep->entry->bb);
/* Then do a list of all the inter-bb storage */
FOR_EACH_PTR(ep->bbs, bb) {
set_up_bb_storage(bb);
combine_phi_storage(bb);
} END_FOR_EACH_PTR(bb);
name_storage();
}