root/usr/src/tools/smatch/src/storage.c
/*
 * 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();
}