/*
 * Copyright (C) 2002-2014, Parrot Foundation.
 */

/*

=head1 NAME

compilers/imcc/optimizer.c

=head1 DESCRIPTION

Optimization occurs in three stages:
  1) pre_optimizer -- runs before control flow graph (CFG) is built
  2) optimizer     -- runs after CFG is built, but before register allocation
  3) post_optimizer -- runs after register allocation

pre_optimizer
-------------

During pre-optimization we perform optimizations which don't require
full knowledge of the control flow graph and the life ranges of each
variable. This phase is handled by two functions: pre_optimize() and
cfg_optimize().

pre_optimize() runs before the construction of the CFG begins. It calls
strength_reduce() to perform simple strength reduction, and if_branch()
to rewrite certain if/branch/label constructs (for details, see
if_branch() below).

[pre_optimize() may also be called later, during the main optimization
 phase, but this is not guaranteed.]

cfg_optimize() runs during the construction of the CFG. It calls
branch_branch() to perform jump optimization (i.e. branches to
branch statements or jumps to jumps are converted into single
branches/jumps to the final destination), unused_label() to remove
unused labels and dead_code_remove() to remove unreachable code
(e.g. basic blocks which are never entered or instructions after
 and unconditional branch which are never branched to).

cfg_optimize may be called multiple times during the construction of the
CFG depending on whether or not it finds anything to optimize.

subst_constants ... rewrite e.g. add_i_ic_ic

optimizer
---------

runs with CFG and life info

used_once ... deletes assignments, when LHS is unused

constant_propagation

post_optimizer: currently pcc_optimize in pcc.c
---------------

runs after register allocation

e.g. eliminate new Px .PerlUndef because Px where different before

=head2 Functions

=over 4

=cut

*/

#include <string.h>
#include "imc.h"
#include "pbc.h"
#include "optimizer.h"
#include "pmc/pmc_callcontext.h"
#include "parrot/oplib/core_ops.h"

/* HEADERIZER HFILE: compilers/imcc/optimizer.h */

/* HEADERIZER BEGIN: static */
/* Don't modify between HEADERIZER BEGIN / HEADERIZER END.  Your changes will be lost. */

static int branch_branch(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
        __attribute__nonnull__(1)
        __attribute__nonnull__(2)
        FUNC_MODIFIES(*imcc)
        FUNC_MODIFIES(*unit);

static int branch_cond_loop(
    ARGMOD(imc_info_t *imcc),
    ARGMOD(IMC_Unit *unit))
        __attribute__nonnull__(1)
        __attribute__nonnull__(2)
        FUNC_MODIFIES(*imcc)
        FUNC_MODIFIES(*unit);

PARROT_WARN_UNUSED_RESULT
static int branch_cond_loop_swap(
    ARGMOD(imc_info_t *imcc),
    ARGMOD(IMC_Unit *unit),
    ARGMOD(Instruction *branch),
    ARGMOD(Instruction *start),
    ARGMOD(Instruction *cond))
        __attribute__nonnull__(1)
        __attribute__nonnull__(2)
        __attribute__nonnull__(3)
        __attribute__nonnull__(4)
        __attribute__nonnull__(5)
        FUNC_MODIFIES(*imcc)
        FUNC_MODIFIES(*unit)
        FUNC_MODIFIES(*branch)
        FUNC_MODIFIES(*start)
        FUNC_MODIFIES(*cond);

PARROT_WARN_UNUSED_RESULT
static int branch_reorg(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
        __attribute__nonnull__(1)
        __attribute__nonnull__(2)
        FUNC_MODIFIES(*imcc)
        FUNC_MODIFIES(*unit);

static int constant_propagation(
    ARGMOD(imc_info_t *imcc),
    ARGMOD(IMC_Unit *unit))
        __attribute__nonnull__(1)
        __attribute__nonnull__(2)
        FUNC_MODIFIES(*imcc)
        FUNC_MODIFIES(*unit);

static int dead_code_remove(
    ARGMOD(imc_info_t *imcc),
    ARGMOD(IMC_Unit *unit))
        __attribute__nonnull__(1)
        __attribute__nonnull__(2)
        FUNC_MODIFIES(*imcc)
        FUNC_MODIFIES(*unit);

PARROT_WARN_UNUSED_RESULT
static int eval_ins(
    ARGMOD(imc_info_t *imcc),
    ARGIN(const char *op),
    size_t ops,
    ARGIN(SymReg **r))
        __attribute__nonnull__(1)
        __attribute__nonnull__(2)
        __attribute__nonnull__(4)
        FUNC_MODIFIES(*imcc);

static int if_branch(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
        __attribute__nonnull__(1)
        __attribute__nonnull__(2)
        FUNC_MODIFIES(*imcc)
        FUNC_MODIFIES(*unit);

static int strength_reduce(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
        __attribute__nonnull__(1)
        __attribute__nonnull__(2)
        FUNC_MODIFIES(*imcc)
        FUNC_MODIFIES(*unit);

PARROT_WARN_UNUSED_RESULT
static int unused_label(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
        __attribute__nonnull__(1)
        __attribute__nonnull__(2)
        FUNC_MODIFIES(*imcc)
        FUNC_MODIFIES(*unit);

static int used_once(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
        __attribute__nonnull__(1)
        __attribute__nonnull__(2)
        FUNC_MODIFIES(*imcc)
        FUNC_MODIFIES(*unit);

#define ASSERT_ARGS_branch_branch __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
       PARROT_ASSERT_ARG(imcc) \
    , PARROT_ASSERT_ARG(unit))
#define ASSERT_ARGS_branch_cond_loop __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
       PARROT_ASSERT_ARG(imcc) \
    , PARROT_ASSERT_ARG(unit))
#define ASSERT_ARGS_branch_cond_loop_swap __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
       PARROT_ASSERT_ARG(imcc) \
    , PARROT_ASSERT_ARG(unit) \
    , PARROT_ASSERT_ARG(branch) \
    , PARROT_ASSERT_ARG(start) \
    , PARROT_ASSERT_ARG(cond))
#define ASSERT_ARGS_branch_reorg __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
       PARROT_ASSERT_ARG(imcc) \
    , PARROT_ASSERT_ARG(unit))
#define ASSERT_ARGS_constant_propagation __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
       PARROT_ASSERT_ARG(imcc) \
    , PARROT_ASSERT_ARG(unit))
#define ASSERT_ARGS_dead_code_remove __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
       PARROT_ASSERT_ARG(imcc) \
    , PARROT_ASSERT_ARG(unit))
#define ASSERT_ARGS_eval_ins __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
       PARROT_ASSERT_ARG(imcc) \
    , PARROT_ASSERT_ARG(op) \
    , PARROT_ASSERT_ARG(r))
#define ASSERT_ARGS_if_branch __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
       PARROT_ASSERT_ARG(imcc) \
    , PARROT_ASSERT_ARG(unit))
#define ASSERT_ARGS_strength_reduce __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
       PARROT_ASSERT_ARG(imcc) \
    , PARROT_ASSERT_ARG(unit))
#define ASSERT_ARGS_unused_label __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
       PARROT_ASSERT_ARG(imcc) \
    , PARROT_ASSERT_ARG(unit))
#define ASSERT_ARGS_used_once __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
       PARROT_ASSERT_ARG(imcc) \
    , PARROT_ASSERT_ARG(unit))
/* Don't modify between HEADERIZER BEGIN / HEADERIZER END.  Your changes will be lost. */
/* HEADERIZER END: static */

/*

=item C<int pre_optimize(imc_info_t *imcc, IMC_Unit *unit)>

Handles optimizations occurring before the construction of the CFG.

=cut

*/

int
pre_optimize(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
{
    ASSERT_ARGS(pre_optimize)
    int changed = 0;

    if (imcc->optimizer_level & OPT_PRE) {
        IMCC_info(imcc, 2, "pre_optimize\n");
        changed += strength_reduce(imcc, unit);
        if (!imcc->dont_optimize)
            changed += if_branch(imcc, unit);
    }
    return changed;
}

/*

=item C<int cfg_optimize(imc_info_t *imcc, IMC_Unit *unit)>

Handles optimizations occurring during the construction of the CFG.
Returns TRUE if any optimization was performed. Otherwise, returns
FALSE.

=cut

*/

PARROT_WARN_UNUSED_RESULT
int
cfg_optimize(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
{
    ASSERT_ARGS(cfg_optimize)
    if (imcc->dont_optimize)
        return 0;
    if (imcc->optimizer_level & OPT_PRE) {
        IMCC_info(imcc, 2, "cfg_optimize\n");
        if (branch_branch(imcc, unit))
            return 1;
        if (branch_cond_loop(imcc, unit))
            return 1;
        if (branch_reorg(imcc, unit))
            return 1;
        if (unused_label(imcc, unit))
            return 1;
        if (dead_code_remove(imcc, unit))
            return 1;
    }
    return 0;
}

/*

=item C<int optimize(imc_info_t *imcc, IMC_Unit *unit)>

Runs after the CFG is built and handles constant propagation.

used_once ... deletes assignments, when LHS is unused and the
op is purely functional, i.e. no side-effects.

=cut

*/

int
optimize(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
{
    ASSERT_ARGS(optimize)
    int any = 0;
    if (imcc->optimizer_level & OPT_CFG) {
        IMCC_info(imcc, 2, "optimize\n");
        any = constant_propagation(imcc, unit);
        if (used_once(imcc, unit))
            return 1;
    }
    return any;
}

/*

=item C<const char * get_neg_op(const char *op, int *n)>

Get negated form of operator. If no negated form is known, return NULL.

=cut

*/

PARROT_WARN_UNUSED_RESULT
PARROT_CAN_RETURN_NULL
const char *
get_neg_op(ARGIN(const char *op), ARGOUT(int *n))
{
    ASSERT_ARGS(get_neg_op)
    PARROT_OBSERVER static const struct br_pairs {
        PARROT_OBSERVER const char * const op;
        PARROT_OBSERVER const char * const nop;
        int n;
    } br_pairs[] = {
        { "if", "unless", 2 },
        { "eq", "ne", 3 },
        { "gt", "le", 3 },
        { "ge", "lt", 3 },
    };
    size_t i;
    for (i = 0; i < N_ELEMENTS(br_pairs); i++) {
        *n = br_pairs[i].n;
        if (STREQ(op, br_pairs[i].op))
            return br_pairs[i].nop;
        if (STREQ(op, br_pairs[i].nop))
            return br_pairs[i].op;
    }
    return NULL;
}

/*
*
 */
/*

=item C<static int if_branch(imc_info_t *imcc, IMC_Unit *unit)>

Convert if/branch/label constructs of the form:

  if cond L1
  branch L2
  L1

to the simpler negated form:

  unless cond L2

=cut

*/

static int
if_branch(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
{
    ASSERT_ARGS(if_branch)
    Instruction *ins, *last;
    int reg, changed = 0;

    last = unit->instructions;
    if (!last->next)
        return changed;
    IMCC_info(imcc, 2, "\tif_branch\n");
    for (ins = last->next; ins;) {
        if ((last->type & ITBRANCH) &&          /* if ...L1 */
                (ins->type & IF_goto) &&        /* branch L2*/
                STREQ(ins->opname, "branch") &&
                (reg = get_branch_regno(last)) >= 0) {
            SymReg * const br_dest = last->symregs[reg];
            if (ins->next &&
                    (ins->next->type & ITLABEL) &&    /* L1 */
                    ins->next->symregs[0] == br_dest) {
                const char * neg_op;
                SymReg * const go = get_branch_reg(ins);
                int args;

                IMCC_debug(imcc, DEBUG_OPT1, "if_branch %s ... %s\n",
                        last->opname, br_dest->name);
                /* find the negated op (e.g if->unless, ne->eq ... */
                if ((neg_op = get_neg_op(last->opname, &args)) != NULL) {
                    Instruction * tmp;
                    last->symregs[reg] = go;
                    tmp = INS(imcc, unit, neg_op, "",
                              last->symregs, args, 0, 0);
                    last->op = tmp->op;
                    last->opsize = tmp->opsize;
                    mem_sys_free(last->opname);
                    last->opname = mem_sys_strdup(tmp->opname);
                    free_ins(tmp);

                    /* delete branch */
                    unit->ostat.deleted_ins++;
                    ins = delete_ins(unit, ins);
                    unit->ostat.if_branch++;
                    changed = 1;
                }
            } /* label found */
        } /* branch detected */
        last = ins;
        ins = ins->next;
    }
    return changed;
}

/*

=item C<static int strength_reduce(imc_info_t *imcc, IMC_Unit *unit)>

strength_reduce ... rewrites e.g add Ix, Ix, y => add Ix, y

These are run after constant simplification, so it is
guaranteed that one operand is non constant if opsize == 4

=cut

*/

static int
strength_reduce(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
{
    ASSERT_ARGS(strength_reduce)
    Instruction *ins, *tmp;
    SymReg *r;
    int changes = 0;
    FLOATVAL f;
    op_lib_t *core_ops = PARROT_GET_CORE_OPLIB(imcc->interp);

    IMCC_info(imcc, 2, "\tstrength_reduce\n");
    for (ins = unit->instructions; ins; ins = ins->next) {
        /*
         * add Ix, Ix, Iy => add Ix, Iy
         * add Ix, Iy, Ix => add Ix, Iy
         * sub Ix, Ix, Iy => sub Ix, Iy
         * mul Ix, Ix, Iy => sub Ix, Iy
         * mul Ix, Iy, Ix => sub Ix, Iy
         * div Ix, Ix, Iy => sub Ix, Iy
         * fdiv Ix, Ix, Iy => sub Ix, Iy
         * add Nx, Nx, Ny => add Nx, Ny
         * add Nx, Ny, Nx => add Nx, Ny
         * sub Nx, Nx, Ny => sub Nx, Ny
         * mul Nx, Nx, Ny => sub Nx, Ny
         * mul Nx, Ny, Nx => sub Nx, Ny
         * div Nx, Nx, Ny => sub Nx, Ny
         * fdiv Nx, Nx, Ny => sub Nx, Ny
         */
        if (((ins->op == &core_ops->op_info_table[PARROT_OP_sub_i_i_i] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_sub_i_i_ic] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_sub_i_ic_i] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_div_i_i_i] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_div_i_i_ic] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_div_i_ic_i] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_fdiv_i_i_i] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_fdiv_i_i_ic] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_fdiv_i_ic_i] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_sub_n_n_n] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_sub_n_n_nc] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_sub_n_nc_n] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_div_n_n_n] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_div_n_n_nc] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_div_n_nc_n] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_fdiv_n_n_n] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_fdiv_n_n_nc] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_fdiv_n_nc_n]) &&
             ins->symregs[0] == ins->symregs[1])
          || ((ins->op == &core_ops->op_info_table[PARROT_OP_add_i_i_i] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_add_i_i_ic] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_add_i_ic_i] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_mul_i_i_i] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_mul_i_i_ic] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_mul_i_ic_i] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_add_n_n_n] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_add_n_n_nc] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_add_n_nc_n] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_mul_n_n_n] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_mul_n_n_nc] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_mul_n_nc_n]) &&
             (ins->symregs[0] == ins->symregs[1] ||
              ins->symregs[0] == ins->symregs[2])))
        {
            if (DEBUG_OPT1 & imcc->debug) {
                IMCC_debug(imcc, DEBUG_OPT1, "opt1 ");
                IMCC_debug_ins(imcc, DEBUG_OPT1, ins);
                IMCC_debug(imcc, DEBUG_OPT1, "  => ");
            }
            if (ins->symregs[0] == ins->symregs[1]) {
                ins->symregs[1] = ins->symregs[2];
            }
            tmp = INS(imcc, unit, ins->opname, "", ins->symregs, 2, 0, 0);
            tmp->type = ITPUREFUNC;
            IMCC_debug_ins(imcc, DEBUG_OPT1, tmp);
            subst_ins(unit, ins, tmp, 1);
            ins = tmp;
            changes = 1;
        }
        /*
         * add Ix, 0     => delete
         * sub Ix, 0     => delete
         * mul Ix, 1     => delete
         * div Ix, 1     => delete
         * fdiv Ix, 1    => delete
         * add Nx, 0     => delete
         * sub Nx, 0     => delete
         * mul Nx, 1     => delete
         * div Nx, 1     => delete
         * fdiv Nx, 1    => delete
         */
        if (((ins->op == &core_ops->op_info_table[PARROT_OP_add_i_ic] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_sub_i_ic]) &&
                      IMCC_int_from_reg(imcc, ins->symregs[1]) == 0)
          || ((ins->op == &core_ops->op_info_table[PARROT_OP_mul_i_ic] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_div_i_ic] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_fdiv_i_ic]) &&
                      IMCC_int_from_reg(imcc, ins->symregs[1]) == 1)
          || ((ins->op == &core_ops->op_info_table[PARROT_OP_add_n_nc] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_sub_n_nc]) &&
                (f = atof(ins->symregs[1]->name), FLOAT_IS_ZERO(f)))
          || ((ins->op  == &core_ops->op_info_table[PARROT_OP_mul_n_nc]  ||
                ins->op == &core_ops->op_info_table[PARROT_OP_div_n_nc] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_fdiv_n_nc]) &&
                      atof(ins->symregs[1]->name) == 1.0))
        {
            if (DEBUG_OPT1 & imcc->debug) {
                IMCC_debug(imcc, DEBUG_OPT1, "opt1 ");
                IMCC_debug_ins(imcc, DEBUG_OPT1, ins);
                IMCC_debug(imcc, DEBUG_OPT1, "  => ");
            }
            ins = delete_ins(unit, ins);
            if (ins)
                ins = ins->prev ? ins->prev : unit->instructions;
            else
                break;
            IMCC_debug(imcc, DEBUG_OPT1, "deleted\n");
            changes = 1;
            continue;
        }
        /*
         * add Ix, 1     => inc Ix
         * add Nx, 1     => inc Nx
         * sub Ix, 1     => dec Ix
         * sub Nx, 1     => dec Nx
         */
        if (((ins->op == &core_ops->op_info_table[PARROT_OP_add_i_ic] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_sub_i_ic]) &&
                      IMCC_int_from_reg(imcc, ins->symregs[1]) == 1)
          || ((ins->op == &core_ops->op_info_table[PARROT_OP_add_n_nc] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_sub_n_nc]) &&
                      atof(ins->symregs[1]->name) == 1.0))
        {
            if (DEBUG_OPT1 & imcc->debug) {
                IMCC_debug(imcc, DEBUG_OPT1, "opt1 ", ins);
                IMCC_debug_ins(imcc, DEBUG_OPT1, ins);
                IMCC_debug(imcc, DEBUG_OPT1, "  => ");
            }
            --ins->symregs[1]->use_count;
            if (ins->op == &core_ops->op_info_table[PARROT_OP_add_i_ic] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_add_n_nc])
                tmp = INS(imcc, unit, "inc", "", ins->symregs, 1, 0, 0);
            else
                tmp = INS(imcc, unit, "dec", "", ins->symregs, 1, 0, 0);
            tmp->type = ITPUREFUNC;
            subst_ins(unit, ins, tmp, 1);
            IMCC_debug_ins(imcc, DEBUG_OPT1, tmp);
            ins = tmp;
            changes = 1;
            continue;
        }
        /*
         * add Ix, Iy, 0 => set Ix, Iy
         * add Ix, 0, Iy => set Ix, Iy
         * sub Ix, Iy, 0 => set Ix, Iy
         * mul Ix, Iy, 1 => set Ix, Iy
         * mul Ix, 1, Iy => set Ix, Iy
         * div Ix, Iy, 1 => set Ix, Iy
         * fdiv Ix, Iy, 1 => set Ix, Iy
         * add Nx, Ny, 0 => set Nx, Ny
         * add Nx, 0, Ny => set Nx, Ny
         * sub Nx, Ny, 0 => set Nx, Ny
         * mul Nx, Ny, 1 => set Nx, Ny
         * mul Nx, 1, Ny => set Nx, Ny
         * div Nx, Ny, 1 => set Nx, Ny
         * fdiv Nx, Ny, 1 => set Nx, Ny
         */
        if (((ins->op == &core_ops->op_info_table[PARROT_OP_add_i_i_ic] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_sub_i_i_ic]) &&
                      IMCC_int_from_reg(imcc, ins->symregs[2]) == 0)
          || (ins->op == &core_ops->op_info_table[PARROT_OP_add_i_ic_i] &&
                      IMCC_int_from_reg(imcc, ins->symregs[1]) == 0)
          || ((ins->op == &core_ops->op_info_table[PARROT_OP_mul_i_i_ic] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_div_i_i_ic] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_fdiv_i_i_ic]) &&
                      IMCC_int_from_reg(imcc, ins->symregs[2]) == 1)
          || (ins->op == &core_ops->op_info_table[PARROT_OP_mul_i_ic_i] &&
                      IMCC_int_from_reg(imcc, ins->symregs[1]) == 1)
          || ((ins->op == &core_ops->op_info_table[PARROT_OP_add_n_n_nc] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_sub_n_n_nc]) &&
                (f = atof(ins->symregs[2]->name), FLOAT_IS_ZERO(f)))
          || (ins->op == &core_ops->op_info_table[PARROT_OP_add_n_nc_n] &&
             (f = atof(ins->symregs[1]->name), FLOAT_IS_ZERO(f)))
          || ((ins->op == &core_ops->op_info_table[PARROT_OP_mul_n_n_nc] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_div_n_n_nc] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_fdiv_n_n_nc]) &&
                      atof(ins->symregs[2]->name) == 1.0)
          || (ins->op == &core_ops->op_info_table[PARROT_OP_mul_n_nc_n] &&
                      atof(ins->symregs[1]->name) == 1.0))
        {
            if (DEBUG_OPT1 & imcc->debug) {
                IMCC_debug(imcc, DEBUG_OPT1, "opt1 ");
                IMCC_debug_ins(imcc, DEBUG_OPT1, ins);
                IMCC_debug(imcc, DEBUG_OPT1, "  => ");
            }
            if (ins->symregs[1]->type == VTCONST) {
                --ins->symregs[1]->use_count;
                ins->symregs[1] = ins->symregs[2];
            }
            else {
                --ins->symregs[2]->use_count;
            }
            tmp = INS(imcc, unit, "set", "", ins->symregs, 2, 0, 0);
            IMCC_debug_ins(imcc, DEBUG_OPT1, tmp);
            subst_ins(unit, ins, tmp, 1);
            ins = tmp;
            changes = 1;
            continue;
        }
        /*
         * mul Ix, Iy, 0 => set Ix, 0
         * mul Ix, 0, Iy => set Ix, 0
         * mul Ix, 0     => set Ix, 0
         */
        if ((ins->op == &core_ops->op_info_table[PARROT_OP_mul_i_i_ic] &&
                      IMCC_int_from_reg(imcc, ins->symregs[2]) == 0)
          || ((ins->op == &core_ops->op_info_table[PARROT_OP_mul_i_ic_i] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_mul_i_ic]) &&
                      IMCC_int_from_reg(imcc, ins->symregs[1]) == 0)
          || (ins->op == &core_ops->op_info_table[PARROT_OP_mul_n_n_nc] &&
             (f = atof(ins->symregs[2]->name), FLOAT_IS_ZERO(f)))
          || ((ins->op == &core_ops->op_info_table[PARROT_OP_mul_n_nc_n] ||
                ins->op == &core_ops->op_info_table[PARROT_OP_mul_n_nc]) &&
                (f = atof(ins->symregs[1]->name), FLOAT_IS_ZERO(f))))
        {
            if (DEBUG_OPT1 & imcc->debug) {
                IMCC_debug(imcc, DEBUG_OPT1, "opt1 ");
                IMCC_debug_ins(imcc, DEBUG_OPT1, ins);
                IMCC_debug(imcc, DEBUG_OPT1, "  => ");
            }
            r = mk_const(imcc, "0", ins->symregs[0]->set);
            --ins->symregs[1]->use_count;
            if (ins->opsize == 4)
                --ins->symregs[2]->use_count;
            ins->symregs[1] = r;
            tmp = INS(imcc, unit, "set", "", ins->symregs, 2, 0, 0);
            IMCC_debug_ins(imcc, DEBUG_OPT1, tmp);
            subst_ins(unit, ins, tmp, 1);
            ins = tmp;
            changes = 1;
        }
        /*
         * set Ix, 0     => null Ix
         * set Nx, 0     => null Nx
         */
        if ((ins->op == &core_ops->op_info_table[PARROT_OP_set_i_ic] &&
                      IMCC_int_from_reg(imcc, ins->symregs[1]) == 0)
          || (ins->op == &core_ops->op_info_table[PARROT_OP_set_n_nc] &&
             (f = atof(ins->symregs[1]->name), FLOAT_IS_ZERO(f)) &&
              ins->symregs[1]->name[0] != '-'))
        {
            if (DEBUG_OPT1 & imcc->debug) {
                IMCC_debug(imcc, DEBUG_OPT1, "opt1 ");
                IMCC_debug_ins(imcc, DEBUG_OPT1, ins);
                IMCC_debug(imcc, DEBUG_OPT1, "  => ");
            }
            --ins->symregs[1]->use_count;
            tmp = INS(imcc, unit, "null", "", ins->symregs, 1, 0, 0);
            tmp->type = ITPUREFUNC;
            subst_ins(unit, ins, tmp, 1);
            IMCC_debug_ins(imcc, DEBUG_OPT1, tmp);
            ins = tmp;
            changes = 1;
            continue;
        }
    }
    return changes;
}

/*

=item C<static int constant_propagation(imc_info_t *imcc, IMC_Unit *unit)>

Does conservative constant propagation.

This code will not propagate constants past labels, invokecc, yield or saves,
even though sometimes it may be safe.

=cut

*/

static int
constant_propagation(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
{
    ASSERT_ARGS(constant_propagation)
    Instruction *ins;
    SymReg *c, *o;
    int any = 0;

    o = c = NULL; /* silence compiler uninit warning, but XXX better to handle flow well */

    IMCC_info(imcc, 2, "\tconstant_propagation\n");
    for (ins = unit->instructions; ins; ins = ins->next) {
        int found = 0;
        if ((STREQ(ins->opname, "set") || (0 == memcmp(ins->opname, "set_", 4))) &&
            ins->opsize == 3 &&             /* no keyed set */
            ins->symregs[1]->type == VTCONST && /* const rhs */
            ins->symregs[0]->set != 'P' &&  /* no PMC consts */
            /* skip type coercions, only same type GH #1043 */
            ins->symregs[0]->set == ins->symregs[1]->set)
        {
            found = 1;
            c = ins->symregs[1];
            o = ins->symregs[0];
        }
        else if (STREQ(ins->opname, "null") && ins->symregs[0]->set == 'I') {
            found = 1;
            c = mk_const(imcc, "0", 'I');
            o = ins->symregs[0];
        } /* this would be good because 'set I0, 0' is reduced to 'null I0'
               before it gets to us */

        if (found) {
            Instruction *ins2;

            /* Propagate the constant into all subsequent ops in the same basic block.
               But skip on the first write into this constant.
               Also skip on invokecc or yield ops, as they can contain pop_eh and
               undo the constant value. GH #1044 */
            IMCC_debug(imcc, DEBUG_OPT2, "propagating constant %s from ", c->name);
            IMCC_debug_ins(imcc, DEBUG_OPT2, ins);
            for (ins2 = ins->next; ins2; ins2 = ins2->next) {
                int i;
                if (ins2->bbindex != ins->bbindex)
                    /* restrict to within a basic block */
                    goto next_constant;
                if (imcc->verbose) {
                    IMCC_debug(imcc, DEBUG_OPT2, "\tchecking op ");
                    IMCC_debug_ins(imcc, DEBUG_OPT2, ins2);
                }
                if (ins2->type & ITPCCSUB || ins->type & ITPCCYIELD)
                    goto next_constant;
                /* was opsize - 2, changed to n_r - 1 */
                for (i = ins2->symreg_count - 1; i >= 0; i--) {
                    if (STREQ(o->name, ins2->symregs[i]->name)) {
                        if (imcc->verbose) {
                            IMCC_debug(imcc, DEBUG_OPT2,
                                       " checking %s in register %i of ",
                                       o->name, i);
                            IMCC_debug_ins(imcc, DEBUG_OPT2, ins2);
                        }
                        if (instruction_writes(ins2, ins2->symregs[i]))
                            goto next_constant;
                        else if (instruction_reads(ins2, ins2->symregs[i])) {
                            Instruction *tmp;
                            SymReg *old;
                            op_info_t *oldop = ins2->op;

                            IMCC_debug(imcc, DEBUG_OPT2,
                                       "\tinto register %i of ", i);
                            IMCC_debug_ins(imcc, DEBUG_OPT2, ins2);
                            old = ins2->symregs[i];
                            ins2->symregs[i] = c;
                            /* first we try subst_constants for e.g. if 10 < 5 goto next*/
                            tmp = IMCC_subst_constants(imcc,
                                unit, ins2->opname, ins2->symregs, ins2->opsize,
                                &found);
                            if (found) {
                                const Instruction * const prev = ins2->prev;
                                if (prev) {
                                    any = 1;
                                    if (tmp) { /* see syn/clash_1.pir or syn/const_31.pir */
                                        subst_ins(unit, ins2, tmp, 1);
                                        IMCC_debug(imcc, DEBUG_OPT2, " reduced to ");
                                        IMCC_debug_ins(imcc, DEBUG_OPT2, tmp);
                                    }
                                    else { /* see syn/macro_10.pir */
                                        IMCC_debug(imcc, DEBUG_OPT2, " deleted ");
                                        IMCC_debug_ins(imcc, DEBUG_OPT2, ins2);
                                        --old->use_count;
                                        unit->ostat.deleted_ins++;
                                        ins2 = delete_ins(unit, ins2);
                                    }
                                    ins2 = prev->next;
                                }
                            }
                            else {
                                char fullname[128];
                                check_op(imcc, &ins2->op, fullname, ins2->opname,
                                    ins2->symregs, ins2->symreg_count, ins2->keys);
                                if (!ins2->op) {
                                    ins2->op = oldop;
                                    ins2->symregs[i] = old;
                                    IMCC_debug(imcc, DEBUG_OPT2,
                                            "\t- no %s ", fullname);
                                    IMCC_debug_ins(imcc, DEBUG_OPT2, ins2);
                                }
                                else {
                                    --old->use_count;
                                    any = 1;
                                    IMCC_debug(imcc, DEBUG_OPT2, " -> ");
                                    IMCC_debug_ins(imcc, DEBUG_OPT2, ins2);
                                }
                            }
                        }
                    }

                }/* for (i ... )*/
            }/* for (ins2 ... )*/
        } /* if */
  next_constant:;

    }/*for (ins ... )*/
    return any;
}

/*

=item C<Instruction * IMCC_subst_constants_umix(imc_info_t *imcc, IMC_Unit
*unit, const char *name, SymReg **r, int n)>

rewrite e.g. add_n_ic => add_n_nc

=cut

*/

PARROT_WARN_UNUSED_RESULT
PARROT_CAN_RETURN_NULL
Instruction *
IMCC_subst_constants_umix(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit),
        ARGIN(const char *name), ARGMOD(SymReg **r), int n)
{
    ASSERT_ARGS(IMCC_subst_constants_umix)
    Instruction *tmp;
    PARROT_OBSERVER const char * const ops[] = {
        "abs", "add", "div", "mul", "sub", "fdiv"
    };
    size_t i;
    char b[128];

    tmp = NULL;
    for (i = 0; i < N_ELEMENTS(ops); i++) {
        if (n == 3 &&
                r[0]->set == 'N' &&
                r[1]->type == VTCONST &&
                r[1]->set == 'I' &&
                STREQ(name, ops[i])) {
            IMCC_debug(imcc, DEBUG_OPT1, "opt1 %s_nc_ic => ", name);
            strcpy(b, r[1]->name);
            r[1] = mk_const(imcc, b, 'N');
            tmp = INS(imcc, unit, name, "", r, 2, 0, 0);
            IMCC_debug_ins(imcc, DEBUG_OPT1, tmp);
        }
    }
    return tmp;
}

/*

=item C<static int eval_ins(imc_info_t *imcc, const char *op, size_t ops, SymReg
**r)>

Run one parrot instruction, registers are filled with the
according constants. If an exception occurs, return -1, aborting
this optimization: this lets the runtime handle any exceptions.

=cut

*/

PARROT_WARN_UNUSED_RESULT
static int
eval_ins(ARGMOD(imc_info_t *imcc), ARGIN(const char *op), size_t ops,
        ARGIN(SymReg **r))
{
    ASSERT_ARGS(eval_ins)
    opcode_t eval[4], *pc;
    int i;
    op_info_t *op_info = (op_info_t *)Parrot_hash_get(imcc->interp, imcc->interp->op_hash, op);
    if (!op_info || !STREQ(op_info->full_name, op))
        IMCC_fatal(imcc, 1, "eval_ins: op '%s' not found\n", op);
    /* now fill registers */
    eval[0] = 0;
    for (i = 0; i < op_info->op_count - 1; i++) {
        switch (op_info->types[i]) {
          case PARROT_ARG_IC:
            PARROT_ASSERT(i && ops == (unsigned int)i);
            /* set branch offset to zero */
            eval[i + 1] = 0;
            break;
          case PARROT_ARG_I:
          case PARROT_ARG_N:
          case PARROT_ARG_S:
            eval[i + 1] = i;        /* regs used are I0, I1, I2 */
            if (ops <= 2 || i) { /* fill source regs */
                SymReg * const r_ = r[i]->type & VT_CONSTP ? r[i]->reg : r[i];
                switch (r[i]->set) {
                  case 'I':
                    REG_INT(imcc->interp, i) = IMCC_int_from_reg(imcc, r_);
                    break;
                  case 'N':
                    {
                        STRING * const s = Parrot_str_new(imcc->interp, r_->name, 0);
                        REG_NUM(imcc->interp, i) = Parrot_str_to_num(imcc->interp, s);
                    }
                    break;
                  case 'S':
                    REG_STR(imcc->interp, i) = IMCC_string_from_reg(imcc, r_);
                    break;
                  default:
                    break;
                }
            }
            break;
          default:
            IMCC_fatal(imcc, 1, "eval_ins"
                    "invalid arg #%d for op '%s' not found\n",
                    i, op);
        }
    }

    /* eval the opcode */
    Parrot_runloop_new_jump_point(imcc->interp);
    if (setjmp(imcc->interp->current_runloop->resume))
        return -1;

    pc = (OP_INFO_OPFUNC(op_info)) (eval, imcc->interp);
    Parrot_runloop_free_jump_point(imcc->interp);
    /* the returned pc is either incremented by op_count or is eval,
     * as the branch offset is 0 - return true if it branched
     */
    PARROT_ASSERT(pc == eval + op_info->op_count || pc == eval);
    return pc == eval;
}

/*

=item C<Instruction * IMCC_subst_constants(imc_info_t *imcc, IMC_Unit *unit,
const char *name, SymReg **r, int n, int *ok)>

rewrite e.g. add_n_nc_nc => set_n_nc
             abs_i_ic    => set_i_ic
             eq_ic_ic_ic => branch_ic / delete
             if_ic_ic    => branch_ic / delete

=cut

*/

PARROT_WARN_UNUSED_RESULT
PARROT_CAN_RETURN_NULL
Instruction *
IMCC_subst_constants(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit),
        ARGIN(const char *name), ARGMOD(SymReg **r), int n, ARGOUT(int *ok))
{
    ASSERT_ARGS(IMCC_subst_constants)
    Instruction *tmp = NULL;
    PARROT_OBSERVER const char * const ops[] = {
        "add", "sub", "mul", "div", "fdiv", "pow",
        "cmod", "mod", "atan",
        "shr", "shl", "lsr",
        "gcd", "lcm",
        "band", "bor", "bxor",
        "bands", "bors", "bxors",
        "and", "or", "xor",
        "iseq", "isne", "islt", "isle", "isgt", "isge", "cmp", "concat"
    };
    PARROT_OBSERVER const char * const ops2[] = {
        "abs", "neg", "not", "fact", "sqrt", "ceil", "floor",
        "acos", "acot", "asec", "asin", "atan",
        "cos", "cosh", "coth",
        "exp", "ln", "log10", "log2", "sec",
        "sech", "sin", "sinh", "tan", "tanh", "fact"
    };
    PARROT_OBSERVER const char * const ops3[] = {
        "eq", "ne", "gt", "ge", "lt", "le"
    };
    PARROT_OBSERVER const char * const ops4[] = {
        "if", "unless"
    };

    size_t i;
    const char *fmt;
    char op[20];
    const char *debug_fmt = NULL;
    int found = 0, branched;
    *ok = 0;

    /* construct a FLOATVAL_FMT with needed precision.
      TT #308  XXX Should use Configure.pl to figure these out,
      but it's not clear just what is needed.
      The value of '16' for NUMVAL_SIZE == 8 was one larger than the
      default FLOATVAL_FMT of '15' determined by Configure.pl.  The
      reason for this difference, if there is one, should be documented.
      The values of.18Lg and .31Lg are guesses.
    */
#if NUMVAL_SIZE == 8
    fmt = "%0.16g";
#elif NUMVAL_SIZE == 12
    fmt = "%0.18Lg";
#elif NUMVAL_SIZE == 16
    fmt = "%0.31Lg";
#else
    fmt = FLOATVAL_FMT;
    /* Since it's not clear why this is needed, it's not clear what to
       do if it's an unknown case.
    */
    IMCC_fatal(imcc, 0,
       "IMCC_subst_constants:  unexpected NUMVAL_SIZE = %d\n",
       NUMVAL_SIZE);
#endif

    for (i = 0; i < N_ELEMENTS(ops); i++) {
        if (n == 4 &&
                r[1]->type & (VTCONST|VT_CONSTP) &&
                r[2]->type & (VTCONST|VT_CONSTP) &&
                STREQ(name, ops[i])) {
            found = 4;
            /* create instruction e.g. add_i_ic_ic => add_i_i_i */
            snprintf(op, sizeof (op), "%s_%c_%c_%c", name, tolower((unsigned char)r[0]->set),
                    tolower((unsigned char)r[1]->set), tolower((unsigned char)r[2]->set));
            debug_fmt = "opt %s_x_xc_xc => ";
            break;
        }
    }
    for (i = 0; !found && i < N_ELEMENTS(ops2); i++) {
        /* abs_i_ic ... */
        if (n == 3) {
            PARROT_ASSERT(r[1]);
            if (r[1]->type & (VTCONST|VT_CONSTP) &&
                    STREQ(name, ops2[i])) {
                found = 3;
                snprintf(op, sizeof (op), "%s_%c_%c", name, tolower((unsigned char)r[0]->set),
                        tolower((unsigned char)r[1]->set));
                debug_fmt = "opt %s_x_xc => ";
                break;
            }
        }
    }
    for (i = 0; !found && i < N_ELEMENTS(ops3); i++) {
        /* eq_xc_xc_labelc ... */
        if (n == 4 &&
                r[0]->type & (VTCONST|VT_CONSTP) &&
                r[1]->type & (VTCONST|VT_CONSTP)  &&
                STREQ(name, ops3[i])) {
            found = 2;
            snprintf(op, sizeof (op), "%s_%c_%c_ic", name, tolower((unsigned char)r[0]->set),
                    tolower((unsigned char)r[1]->set));
            debug_fmt = "opt %s_xc_xc_ic => ";
            break;
        }
    }
    for (i = 0; !found && i < N_ELEMENTS(ops4); i++) {
        /* if_xc_labelc, unless */
        if (n == 3 &&
                r[0]->type & (VTCONST|VT_CONSTP) &&
                STREQ(name, ops4[i])) {
            found = 1;
            snprintf(op, sizeof (op), "%s_%c_ic", name, tolower((unsigned char)r[0]->set));
            debug_fmt = "opt %s_xc_ic => ";
            break;
        }
    }

    if (!found) {
        return NULL;
    }

    /* XXX We can get to this point with debug_fmt = NULL */
    IMCC_debug(imcc, DEBUG_OPT1, debug_fmt, name);
    /* we construct a parrot instruction
     * here and let parrot do the calculation in a
     * separate context and make a constant
     * from the result.
     */
    branched = eval_ins(imcc, op, found, r);
    if (branched == -1) {
         /* Don't set ok
          * (See http://rt.perl.org/rt3/Ticket/Display.html?id=43048 for info) */
        return NULL;
    }
    /*
     * for math ops result is in I0/N0
     * if it was a branch with constant args, the result is
     * the return value
     */
    if (found <= 2) {
        /* create a branch or delete instruction */
        if (branched) {
            r[0] = r[found];
            tmp = INS(imcc, unit, "branch", "", r, 1, 0, 0);
            if (tmp)
                *ok = 1;
        }
        else {
            IMCC_debug(imcc, DEBUG_OPT1, "deleted\n");
            *ok = 1;
        }
    }
    else {
        /* create set x, constant */
        char b[128];
        switch (r[0]->set) {
          case 'I':
            snprintf(b, sizeof (b), INTVAL_FMT, REG_INT(imcc->interp, 0));
            r[1] = mk_const(imcc, b, r[0]->set);
            break;
          case 'N':
            snprintf(b, sizeof (b), fmt, REG_NUM(imcc->interp, 0));
            r[1] = mk_const(imcc, b, r[0]->set);
            break;
          case 'S':
          {
            char * const cstr = Parrot_str_to_cstring(imcc->interp, REG_STR(imcc->interp, 0));
            const STR_VTABLE* encoding = REG_STR(imcc->interp, 0)->encoding;
            if (encoding == Parrot_ascii_encoding_ptr) {
                r[1] = mk_const(imcc, cstr, r[0]->set);
            }
            else {
                snprintf(b, sizeof (b), "%s:\"%s\"", encoding->name, cstr);
                r[1] = mk_const(imcc, b, 'U');
            }
            Parrot_str_free_cstring(cstr);
          }
          default:
            break;
        }
        tmp = INS(imcc, unit, "set", "", r, 2, 0, 0);
        if (tmp)
            *ok = 1;
    }
    if (tmp)
        IMCC_debug_ins(imcc, DEBUG_OPT1, tmp);
    return tmp;
}


/* optimizations with CFG built */

/*

=item C<static int branch_branch(imc_info_t *imcc, IMC_Unit *unit)>

if I0 goto L1  => if IO goto L2
...
L1:
branch L2

Returns TRUE if any optimizations were performed. Otherwise, returns
FALSE.

=cut

*/

static int
branch_branch(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
{
    ASSERT_ARGS(branch_branch)
    Instruction *ins;
    int changed = 0;

    IMCC_info(imcc, 2, "\tbranch_branch\n");
    /* reset statistic globals */
    for (ins = unit->instructions; ins; ins = ins->next) {
        if (get_branch_regno(ins) >= 0) {
            SymReg * const r = get_sym(imcc, get_branch_reg(ins)->name);

            if (r && (r->type & VTADDRESS) && r->first_ins) {
                Instruction * const next = r->first_ins->next;
                /* if (!next ||
                       STREQ(next->symregs[0]->name, get_branch_reg(ins)->name))
                     break; */
                if (next &&
                        (next->type & IF_goto) &&
                        STREQ(next->opname, "branch") &&
                        !STREQ(next->symregs[0]->name, get_branch_reg(ins)->name)) {
                    const int regno = get_branch_regno(ins);
                    IMCC_debug(imcc, DEBUG_OPT1,
                            "found branch to branch '%s' ",
                            r->first_ins->symregs[0]->name);
                    IMCC_debug_ins(imcc, DEBUG_OPT1, next);
                    unit->ostat.branch_branch++;
                    if (regno < 0)
                        Parrot_ex_throw_from_c_noargs(imcc->interp,
                            EXCEPTION_INTERNAL_PANIC,
                            "Register number determination failed in branch_branch()");

                    ins->symregs[regno] = next->symregs[0];
                    changed = 1;
                }
            }
        }
    }
    return changed;
}

/*

=item C<static int branch_reorg(imc_info_t *imcc, IMC_Unit *unit)>

branch L2  => ...
L1:           branch L4
...           L1:
branch L3     ...
L2:           branch L3
...           L5:
branch L4
L5:

Returns TRUE if any optimizations were performed. Otherwise, returns
FALSE.

=cut

*/

PARROT_WARN_UNUSED_RESULT
static int
branch_reorg(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
{
    ASSERT_ARGS(branch_reorg)
    unsigned int i;
    int changed = 0;

    IMCC_info(imcc, 2, "\tbranch_reorg\n");
    for (i = 0; i < unit->n_basic_blocks; i++) {
        Instruction *ins = unit->bb_list[i]->end;

        /* if basic block ends with unconditional jump */
        if ((ins->type & IF_goto) && STREQ(ins->opname, "branch")) {
            SymReg * const r = get_sym(imcc, ins->symregs[0]->name);

            if (r && (r->type & VTADDRESS) && r->first_ins) {
                Edge               *edge;
                Instruction * const start = r->first_ins;
                int                 found = 0;

                for (edge = unit->bb_list[start->bbindex]->pred_list;
                     edge;
                     edge = edge->pred_next) {

                    if (edge->from->index == start->bbindex - 1) {
                        found = 1;
                        break;
                    }
                }

                /* if target block is not reached by falling into it
                 * from another block */
                if (!found) {
                    Instruction *end;
                    /* move target block and its positional successors
                     * to follow block with unconditional jump
                     * (this could actually be in another block) */
                    for (end = start; end->next; end = end->next) {
                        if ((end->type & IF_goto) &&
                                STREQ(end->opname, "branch")) {
                            break;
                        }
                    }

                    /* this was screwing up multi-block loops... */
                    if (end != ins && ins->next != NULL) {
                        ins->next->prev = end;
                        start->prev->next = end->next;
                        if (end->next)
                            end->next->prev = start->prev;

                        end->next   = ins->next;
                        ins->next   = start;
                        start->prev = ins;

                        IMCC_debug(imcc, DEBUG_OPT1,
                                "found branch to reorganize '%s' ",
                                r->first_ins->symregs[0]->name);
                        IMCC_debug_ins(imcc, DEBUG_OPT1, ins);

                        /* unconditional jump can be eliminated */
                        unit->ostat.deleted_ins++;
                        ins = delete_ins(unit, ins);
                        return 1;
                    }
                }
            }
        }
    }

    return changed;
}

/*

=item C<static int branch_cond_loop_swap(imc_info_t *imcc, IMC_Unit *unit,
Instruction *branch, Instruction *start, Instruction *cond)>

Converts conditional loops to post-test

Returns TRUE if any optimizations were performed. Otherwise, returns
FALSE.

See L<https://github.com/parrot/parrot/issues/1037> for a problem with nci

=cut

*/

PARROT_WARN_UNUSED_RESULT
static int
branch_cond_loop_swap(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit), ARGMOD(Instruction *branch),
        ARGMOD(Instruction *start), ARGMOD(Instruction *cond))
{
    ASSERT_ARGS(branch_cond_loop_swap)
    int changed = 0;
    int args;
    const char * const neg_op = get_neg_op(cond->opname, &args);
    if (neg_op) {
        const size_t size  = strlen(branch->symregs[0]->name) + 10; /* + '_post999' */
        char * const label = (char *)mem_sys_allocate(size);
        int count;
        int found = 0;

        for (count = 1; count != 999; ++count) {
            snprintf(label, size, "%s_post%d", branch->symregs[0]->name, count);
            if (get_sym(imcc, label) == NULL) {
                found = 1;
                break;
            }
        }

        if (found) {
            Instruction *tmp;
            SymReg *regs[3], *r;
            int reg_index;

            /* cond_op has 2 or 3 args */
            PARROT_ASSERT(args <= 3);

            r = mk_local_label(imcc, label);
            tmp = INS_LABEL(imcc, unit, r, 0);
            insert_ins(unit, cond, tmp);

            for (start = start->next; start != cond; start = start->next) {
                if (!(start->type & ITLABEL)) {
                    tmp = INS(imcc, unit, start->opname, "",
                        start->symregs, start->symreg_count, start->keys, 0);
                    prepend_ins(unit, branch, tmp);
                }
            }

            for (count = 0; count != args; ++count) {
                regs[count] = cond->symregs[count];
            }

            reg_index = get_branch_regno(cond);
            if (reg_index < 0)
                Parrot_ex_throw_from_c_noargs(imcc->interp,
                    EXCEPTION_INTERNAL_PANIC,
                    "Negative branch register address detected");

            regs[reg_index] = mk_label_address(imcc, label);
            tmp = INS(imcc, unit, (const char*)neg_op, "", regs, args, 0, 0);

            IMCC_debug(imcc, DEBUG_OPT1, /* XXX GH #1037 */
                "loop %s -> %s converted to post-test, added label %s\n",
                branch->symregs[0]->name, get_branch_reg(cond)->name, label);

            subst_ins(unit, branch, tmp, 1);
            unit->ostat.branch_cond_loop++;
            changed = 1;
        }

        mem_sys_free(label);
    }

    return changed;
}

/*

=item C<static int branch_cond_loop(imc_info_t *imcc, IMC_Unit *unit)>

start:           => start:
if cond goto end    if cond goto end
...
branch start        start_post1:
end:                ...
                    unless cond goto start_post562
                    end:

The basic premise is "branch (A) to conditional (B), where B goes to
just after A."

Returns TRUE if any optimizations were performed. Otherwise, returns
FALSE.

=cut

*/

static int
branch_cond_loop(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
{
    ASSERT_ARGS(branch_cond_loop)
    Instruction *ins, *cond, *start, *prev;
    int changed = 0, found;

    IMCC_info(imcc, 2, "\tbranch_cond_loop\n");
    /* reset statistic globals */

    for (ins = unit->instructions; ins; ins = ins->next) {
        if ((ins->type & IF_goto) && STREQ(ins->opname, "branch")) {
            /* get `end` label */
            Instruction * const end = ins->next;
            /* get `start` label */
            SymReg * const r = get_sym(imcc, ins->symregs[0]->name);

            if (end && (end->type & ITLABEL) &&
                r && (r->type & VTADDRESS) && r->first_ins) {

                start = r->first_ins;
                found = 0;

                for (cond = start->next; cond; cond = cond->next) {
                    /* no good if it's an unconditional branch*/
                    if (cond->type & IF_goto && STREQ(cond->opname, "branch")) {
                        break;
                    }
                    else if ((cond->type & ITPCCRET) || (cond->type & ITPCCSUB)
                            || (cond->type & ITCALL)) {
                        break;
                        /* just until we can copy set_args et al */
                    }
                    else if ((cond->type & ITBRANCH) && (get_branch_regno(cond) >= 0)) {
                        found = 1;
                        break;
                    }
                    else if (STREQ(cond->opname, "get_global")) {
                        /* XXX GH 1037: avoid get_global in a loop which was initialized by nci */
                        IMCC_debug(imcc, DEBUG_OPT1, "skip get_global for branch_cond_loop_swap ");
                        IMCC_debug_ins(imcc, DEBUG_OPT1, cond);
                        break;
                    }
                }

                if (found) {
                    const char * const lbl = get_branch_reg(cond)->name;
                    const SymReg * const r1 = get_sym(imcc, lbl);
                    if (r1 && (r1->type & VTADDRESS) && r1->first_ins == end) {
                        /* the current ins is replaced - remember prev
                         * and set ins again after the changes
                         */
                        prev = ins->prev;
                        if (!prev)
                            continue;
                        changed |= branch_cond_loop_swap(imcc,
                                unit, ins, start, cond);
                        ins = prev->next;
                    }
                }
            }
        }
    }
    return changed;
}

/*

=item C<static int unused_label(imc_info_t *imcc, IMC_Unit *unit)>

Removes unused labels.

Returns TRUE if any optimizations were performed. Otherwise, returns
FALSE.

=cut

*/

PARROT_WARN_UNUSED_RESULT
static int
unused_label(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
{
    ASSERT_ARGS(unused_label)
    unsigned int i;
    int used;
    int changed = 0;

    IMCC_info(imcc, 2, "\tunused_label\n");
    for (i = 1; i < unit->n_basic_blocks; i++) {
        Instruction *ins = unit->bb_list[i]->start;
        if ((ins->type & ITLABEL) && *ins->symregs[0]->name != '_') {
            const SymReg * const lab = ins->symregs[0];
            used = 0;

            if (!lab->first_ins)
                continue;
#if 1
            else if (lab->last_ins)
                used = 1;
#else
            else {
                int j;
                for (j=0; unit->bb_list[j]; j++) {
                    /* a branch can be the first ins in a block
                     * (if prev ins was a label)
                     * or the last ins in a block */
                    Instruction *ins2;
                    SymReg      *addr;

                    ins2 = unit->bb_list[j]->start;
                    if ((ins2->type & ITBRANCH) &&
                            (addr = get_branch_reg(ins2)) != 0) {
                        if (addr == lab && addr->type == VTADDRESS) {
                            used = 1;
                            break;
                        }
                    }
                    ins2 = unit->bb_list[j]->end;
                    if ((ins2->type & ITBRANCH) &&
                            (addr = get_branch_reg(ins2)) != 0) {
                        if (addr == lab && addr->type == VTADDRESS) {
                            used = 1;
                            break;
                        }
                    }
                }
            }
#endif
            if (!used) {
                unit->ostat.deleted_labels++;
                IMCC_debug(imcc, DEBUG_OPT1,
                           "block %d label %s deleted\n", i, lab->name);
                unit->ostat.deleted_ins++;
                ins = delete_ins(unit, ins);
                changed = 1;
            }

        }
    }
    return changed;
}

/*

=item C<static int dead_code_remove(imc_info_t *imcc, IMC_Unit *unit)>

dead code elimination
... unreachable blocks
... unreachable instructions

=cut

*/

static int
dead_code_remove(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
{
    ASSERT_ARGS(dead_code_remove)
    unsigned int i;
    int changed = 0;
    Instruction *ins, *last;

    /* this could be a separate level, now it's done with -O1 */
    if (!(imcc->optimizer_level & OPT_PRE))
        return 0;
    IMCC_info(imcc, 2, "\tdead_code_remove\n");

    /* Unreachable blocks */

    for (i = 1; i < unit->n_basic_blocks; i++) {
        Basic_block * const bb = unit->bb_list[i];

        if ((bb->start->type & ITLABEL) && *bb->start->symregs[0]->name == '_')
            continue;
        /* this block isn't entered from anywhere */
        if (!bb->pred_list) {
            const unsigned int bbi = bb->index;
            IMCC_debug(imcc, DEBUG_OPT1,
                       "found dead block %d\n", bb->index);

            for (ins = bb->start; ins && ins->bbindex == bbi;) {
                IMCC_debug(imcc, DEBUG_OPT1,
                        "\tins deleted (dead block) ");
                IMCC_debug_ins(imcc, DEBUG_OPT1, ins);
                ins = delete_ins(unit, ins);
                unit->ostat.deleted_ins++;
                changed++;
            }
        }
    }


    /* Unreachable instructions */

    for (last = unit->instructions, ins = last->next;
         last && ins;
         ins = ins->next) {

        if ((last->type & IF_goto) && !(ins->type & ITLABEL) &&
            STREQ(last->opname, "branch")) {
            IMCC_debug(imcc, DEBUG_OPT1,
                    "unreachable ins deleted (after branch) ");
            IMCC_debug_ins(imcc, DEBUG_OPT1, ins);
            ins = delete_ins(unit, ins);
            unit->ostat.deleted_ins++;
            changed++;
        }

        /*
         *   branch L1     => --
         * L1: ...            L1:
         */
        if (ins && last && (last->type & IF_goto) && (ins->type & ITLABEL) &&
                STREQ(last->opname, "branch") &&
                STREQ(last->symregs[0]->name, ins->symregs[0]->name)) {
            IMCC_debug(imcc, DEBUG_OPT1, "dead branch deleted ");
            IMCC_debug_ins(imcc, DEBUG_OPT1, ins);
            ins = delete_ins(unit, last);
            unit->ostat.deleted_ins++;
            changed++;
        }
        last = ins;
        if (!ins)
            break;
    }
    return changed;
}

/* optimizations with CFG & life info built */
/*

=item C<static int used_once(imc_info_t *imcc, IMC_Unit *unit)>

used_once ... deletes assignments, when LHS is unused.
Only for pure functional ops. Keep ops with sideeffects even if the LHS is never used.

=cut

*/

static int
used_once(ARGMOD(imc_info_t *imcc), ARGMOD(IMC_Unit *unit))
{
    ASSERT_ARGS(used_once)
    Instruction *ins;
    int opt = 0;

    for (ins = unit->instructions; ins; ins = ins->next) {
        SymReg * const r = ins->symregs[0];
        if (r) {
            /* GH 1036: keep side-effects: P0 = pop P1 vs pop P1 */
            if (ins->type & ITPUREFUNC
            && (r->use_count == 1 && r->lhs_use_count == 1)) {
                IMCC_debug(imcc, DEBUG_OPT2, "used once deleted ");
                IMCC_debug_ins(imcc, DEBUG_OPT2, ins);

                ins = delete_ins(unit, ins);

                /* find previous instruction or first instruction of this CU
                 * ... but only the latter if it wasn't deleted */
                ins = ins->prev
                    ? ins->prev
                    : opt ? unit->instructions : NULL;

                unit->ostat.deleted_ins++;
                unit->ostat.used_once++;
                opt++;

                if (!ins) /* if there's no next GH 1042 */
                    break;
            }
        }
    }
    return opt;
}

/*

=back

=cut

*/

/*
 * Local variables:
 *   c-file-style: "parrot"
 * End:
 * vim: expandtab shiftwidth=4 cinoptions='\:2=2' :
 */
