Subversion Repositories tendra.SVN

Rev

Rev 5 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (c) 2002-2005 The TenDRA Project <http://www.tendra.org/>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of The TenDRA Project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific, prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
 * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id$
 */
/*
                 Crown Copyright (c) 1996

    This TenDRA(r) Computer Program is subject to Copyright
    owned by the United Kingdom Secretary of State for Defence
    acting through the Defence Evaluation and Research Agency
    (DERA).  It is made available to Recipients with a
    royalty-free licence for its use, reproduction, transfer
    to other parties and amendment for any purpose not excluding
    product development provided that any such use et cetera
    shall be deemed to be acceptance of the following conditions:-

        (1) Its Recipients shall ensure that this Notice is
        reproduced upon any copies or amended versions of it;

        (2) Any amended version of it shall be clearly marked to
        show both the nature of and the organisation responsible
        for the relevant amendment or amendments;

        (3) Its onward transfer from a recipient to another
        party shall be deemed to be that party's acceptance of
        these conditions;

        (4) DERA gives no warranty or assurance as to its
        quality or suitability for any purpose and DERA accepts
        no liability whatsoever in relation to any use to which
        it may be put.
*/
/*
                            VERSION INFORMATION
                            ===================

--------------------------------------------------------------------------
$Header: /u/g/release/CVSROOT/Source/src/installers/680x0/common/ops_int.c,v 1.1.1.1 1998/01/17 15:55:49 release Exp $
--------------------------------------------------------------------------
$Log: ops_int.c,v $
 * Revision 1.1.1.1  1998/01/17  15:55:49  release
 * First version to be checked into rolling release.
 *
Revision 1.5  1997/11/13 08:27:15  ma
All avs test passed (except add_to_ptr).

Revision 1.4  1997/11/10 15:38:07  ma
.

Revision 1.3  1997/11/09 14:12:27  ma
Fixed max_min & splitted add_const into add_const and sub_const to make
error handling work.

Revision 1.2  1997/10/29 10:22:26  ma
Replaced use_alloca with has_alloca.

Revision 1.1.1.1  1997/10/13 12:42:56  ma
First version.

Revision 1.6  1997/10/13 08:49:47  ma
Made all pl_tests for general proc & exception handling pass.

Revision 1.5  1997/09/25 06:45:24  ma
All general_proc tests passed

Revision 1.4  1997/06/24 10:56:06  ma
Added changes for "Plumhall Patch"

Revision 1.3  1997/06/18 10:09:42  ma
Checking in before merging with Input Baseline changes.

Revision 1.2  1997/04/20 11:30:34  ma
Introduced gcproc.c & general_proc.[ch].
Added cases for apply_general_proc next to apply_proc in all files.

Revision 1.1.1.1  1997/03/14 07:50:16  ma
Imported from DRA

 * Revision 1.1.1.1  1996/09/20  10:56:57  john
 *
 * Revision 1.3  1996/07/30  16:31:23  john
 * Removed offset conversion
 *
 * Revision 1.2  1996/07/05  14:24:16  john
 * Changes for spec 3.1
 *
 * Revision 1.1.1.1  1996/03/26  15:45:15  john
 *
 * Revision 1.7  94/11/16  10:37:25  10:37:25  ra (Robert Andrews)
 * Added support for integer absolute.
 *
 * Revision 1.6  94/11/08  11:23:45  11:23:45  ra (Robert Andrews)
 * The operations addq and subq on A-registers do not set the condition
 * flags.
 *
 * Revision 1.5  94/06/29  14:23:27  14:23:27  ra (Robert Andrews)
 * Added maximum and minimum operations.
 *
 * Revision 1.4  94/02/21  16:01:29  16:01:29  ra (Robert Andrews)
 * Made a couple of integer literals into longs.
 *
 * Revision 1.3  93/04/19  13:35:28  13:35:28  ra (Robert Andrews)
 * Change_varieties in division routines were the wrong way round.
 *
 * Revision 1.2  93/03/03  14:49:05  14:49:05  ra (Robert Andrews)
 * Started adding support for error treatments.
 *
 * Revision 1.1  93/02/22  17:16:20  17:16:20  ra (Robert Andrews)
 * Initial revision
 *
--------------------------------------------------------------------------
*/


#include "config.h"
#include "common_types.h"
#include "assembler.h"
#include "basicread.h"
#include "check.h"
#include "exp.h"
#include "expmacs.h"
#include "externs.h"
#include "install_fns.h"
#include "shapemacs.h"
#include "tags.h"
#include "mach.h"
#include "mach_ins.h"
#include "where.h"
#include "mach_op.h"
#include "instr.h"
#include "codex.h"
#include "instrs.h"
#include "coder.h"
#include "tests.h"
#include "operations.h"
#include "evaluate.h"
#include "utility.h"
#include "translate.h"
#include "ops_shared.h"
extern void add_const(shape, long, where);

/*
    DO AN ADD BY A LOAD EFFECTIVE ADDRESS

    The m_lea instruction is used to add the constant offset to the value
    a and put the result into dest.  The flag psh is true to indicate
    that the result should be pushed onto the stack.
*/

void load_ea
(shape sha, long offset, where a, where dest, bool psh)
{
    if (have_overflow()) {
        move(sha, a, D0);
        add_const(sha, offset, D0);
        move(sha, D0, dest);
        have_cond = 0;
    } else {
        exp ra = simple_exp(dummy_tag);
        son(ra) = a.wh_exp;
        no(ra) = 8 * offset;
        if (psh) {
            ins1(m_pea, L32, zw(ra), 0);
            stack_size -= 32;
        } else {
            ins2(m_lea, L32, L32, zw(ra), dest, 1);
        }
        retcell(ra);
        have_cond = 0;
    }
    return;
}


/*
    INCREASE BY A CONSTANT

    The value dest is increased or decreased by the constant n.
*/

void addsub_const
(shape sha, long n, where dest, bool use_sub)
{
    int instr;
    bool negate = 0, use_quick = 0;

    long sz = shape_size(sha);
    if (n == 0) return;

    /* If destination is a value we just have to test for overflow */

    if (whereis(dest) == Value) {
       long v = nw(dest);
       if (is_signed(sha)) {
          if (use_sub)
             n = -n;
          if (v>0 && n>0) {
             if (v > range_max(sha) - n)
                test_overflow(UNCONDITIONAL);
          }
          else if (v<0 && n<0) {
             if (v < range_min(sha) - n)
                test_overflow(UNCONDITIONAL);
          }
       }
       else { /* unsigned addition */
          if (use_sub) {
             /* will v - n underflow ? */
             if ((unsigned)v < (unsigned)range_min(sha) - (unsigned)n)
                test_overflow(UNCONDITIONAL);
          }
          else {
             if ((unsigned)v > (unsigned)range_max(sha) - (unsigned)n)
                test_overflow(UNCONDITIONAL);
          }
       }
       return;
    }


    /* Destination is not just a value */

    /* If we don't have to test for overflow, we can chose wheter to add/sub */
    /* Changeing add and sub might allow us to use quick add or sub */
    if (! have_overflow()) {
       /* But -(INT_MIN) can't be represented in signed shape */
       if (n != INT_MIN) {
          if (n < 0)
             negate = 1;
          if ((n < 8) && (n > -8))
             use_quick = 1;
       }
    }
    else {
       if ((unsigned long)n < 8)
          use_quick = 1;
    }

    /* Special handling for address regs. */
    if (whereis(dest) == Areg) {
       if (use_quick) {
          have_cond = 0;
       }
       else {
          if (use_sub)
             n = -n;
          load_ea(sha, n, dest, dest, 0);
          return;
       }
    }

    /* Find appropriate ADD/SUB */
    if (negate) {
       n = -n;
       use_sub = ! use_sub;
    }

    if (use_sub) {
       if (use_quick)
          instr = ins(sz, ml_subq);
       else
          instr = ins(sz, ml_sub);
    }
    else {
       if (use_quick)
          instr = ins(sz, ml_addq);
       else
          instr = ins(sz, ml_add);
    }

    ins2n(instr, n, sz, dest, 1);
    set_cond(dest, sz);
    test_overflow(ON_SHAPE(sha));
}

void add_const
(shape sha, long n, where dest)
{
   addsub_const(sha, n, dest, 0);
}

void sub_const
(shape sha, long n, where dest)
{
   addsub_const(sha, n, dest, 1);
}

/*
    AUXILIARY ADD ROUTINE

    The value inc (of shape sha and size sz) is added to dest.
*/

static void add_aux
(shape sha, long sz, where inc, where dest)
{
    int instr;
    long whi = whereis(inc);
    long whd = whereis(dest);
    if (whd == Freg) {
        move(sha, dest, D0);
        add_aux(sha, sz, inc, D0);
        move(sha, D0, dest);
        return;
    }
    if (whi == Value) {
        long v = nw(inc);
        if (is_offset(inc.wh_exp))v /= 8;
        add_const(sha, v, dest);
        return;
    }
    if (whi == Freg) {
        move(sha, inc, D0);
        add_aux(sha, sz, D0, dest);
        return;
    }

    if (have_overflow() && whd == Areg) {
        /* Skip to end */
    } else if (whi == Dreg || whd == Dreg || whd == Areg) {
        instr = ins(sz, ml_add);
        ins2(instr, sz, sz, inc, dest, 1);
        if (whd == Areg) {
            have_cond = 0;
        } else {
            set_cond(dest, sz);
        }
        test_overflow(ON_SHAPE(sha));
        return;
    }
    move(sha, inc, D0);
    add_aux(sha, sz, D0, dest);
    return;
}


/*
    ADD CONSTANT ROUTINE

    The constant c is added to the value inc, and the result is stored
    in dest.
*/

static void addsub_const_3_args
(shape sha, long sz, long c, where inc, where dest, bool use_sub)
{
    if (c == 0) {
        move(sha, inc, dest);
        return;
    }
    switch (whereis(dest)) {
        case Dreg: {
            move(sha, inc, dest);
            addsub_const(sha, c, dest, use_sub);
            return;
        }
        case Areg: {
            if (whereis(inc) == Areg) {
                load_ea(sha, c, inc, dest, 0);
                return;
            }
            move(sha, inc, dest);
            addsub_const(sha, c, dest, use_sub);
            return;
        }
        default : {
            long whi = whereis(inc);
            if (whi == Dreg && last_use(inc)) {
                addsub_const(sha, c, inc, use_sub);
                move(sha, inc, dest);
                set_cond(dest, sz);
                return;
            }
            if (whi == Areg && (  name(dest.wh_exp) == apply_tag
                                 || name(dest.wh_exp) == apply_general_tag
                                 || name(dest.wh_exp) == tail_call_tag)) {
                load_ea(sha, c, inc, dest, 1);
                return;
            }
            addsub_const_3_args(sha, sz, c, inc, D0, use_sub);
            move(sha, D0, dest);
            set_cond(dest, sz);
            return;
        }
    }
}


/*
    MAIN ADD ROUTINE

    The values a1 and a2 of shape sha are added and the result stored in
    dest.
*/

void add
(shape sha, where a1, where a2, where dest)
{
    long sz = shape_size(sha);
    long rt = shtype(sha);
    long wh1, wh2, whd;

    if (rt == Freg) {
        fl_binop(fplus_tag, sha, a1, a2, dest);
        return;
    }

    if (eq_where(a1, dest)) {
        add_aux(sha, sz, a2, dest);
        return;
    }

    if (eq_where(a2, dest)) {
        add_aux(sha, sz, a1, dest);
        return;
    }

    wh1 = whereis(a1);
    wh2 = whereis(a2);
    whd = whereis(dest);

    if (wh1 == Value) {
        long v1 = nw(a1);
        if (is_offset(a1.wh_exp))v1 /= 8;
        if (wh2 == Value && !have_overflow()) {
            long v2 = nw(a2);
            if (is_offset(a2.wh_exp))v2 /= 8;
            move(sha, mnw(v1 + v2), dest);
            return;
        }
        addsub_const_3_args(sha, sz, v1, a2, dest, 0);
        return;
    }

    if (wh2 == Value) {
        long v2 = nw(a2);
        if (is_offset(a2.wh_exp))v2 /= 8;
        addsub_const_3_args(sha, sz, v2, a1, dest, 0);
        return;
    }

    if (whd == Dreg) {
        if (!interfere(a2, dest)) {
            move(sha, a1, dest);
            add_aux(sha, sz, a2, dest);
            return;
        }
        if (!interfere(a1, dest)) {
            move(sha, a2, dest);
            add_aux(sha, sz, a1, dest);
            return;
        }
    }

    if (wh1 == Dreg && last_use(a1)) {
        add_aux(sha, sz, a2, a1);
        move(sha, a1, dest);
        set_cond(dest, sz);
        return;
    }

    if (wh2 == Dreg && last_use(a2)) {
        add_aux(sha, sz, a1, a2);
        move(sha, a2, dest);
        set_cond(dest, sz);
        return;
    }

    if (wh1 == Dreg) {
        move(sha, a2, D0);
        add_aux(sha, sz, a1, D0);
    } else {
        move(sha, a1, D0);
        add_aux(sha, sz, a2, D0);
    }
    move(sha, D0, dest);
    set_cond(dest, sz);
    return;
}


/*
    AUXILIARY SUBTRACT ROUTINE

    The value a is subtracted from dest.
*/

static void sub_aux
(shape sha, long sz, where a, where dest)
{
    long wha = whereis(a);
    long whd = whereis(dest);
    if (whd == Freg) {
        move(sha, dest, D0);
        sub_aux(sha, sz, a, D0);
        move(sha, D0, dest);
        return;
    }

    if (wha == Value) {
        long v = nw(a);
        if (is_offset(a.wh_exp))v /= 8;
        sub_const(sha, v, dest);
        return;
    }

    if (wha != Freg) {
        if (have_overflow() && whd == Areg) {
            /* Skip to end */
        } else if (whd == Dreg || whd == Areg || wha == Dreg) {
            int instr = ins(sz, ml_sub);
            ins2(instr, sz, sz, a, dest, 1);
            if (whd == Areg) {
                have_cond = 0;
            } else {
                set_cond(dest, sz);
            }
            test_overflow(ON_SHAPE(sha));
            return;
        }
    }
    move(sha, a, D0);
    sub_aux(sha, sz, D0, dest);
    set_cond(dest, sz);
    return;
}


/*
    MAIN SUBTRACT ROUTINE

    The value a2 of shape sha is subtracted from a1 and the result is
    stored in dest.
*/

void sub
(shape sha, where a1, where a2, where dest)
{
    long sz = shape_size(sha);
    long wh1, wh2, whd;

    if (eq_where(a1, a2)) {
        move(sha, zero, dest);
        return;
    }

    if (eq_where(a2, dest) && !eq_where(dest,zero)) {
        sub_aux(sha, sz, a1, dest);
        return;
    }

    wh1 = whereis(a1);
    wh2 = whereis(a2);
    whd = whereis(dest);

    if (wh1 == Value) {
        long v1 = nw(a1);
        if (is_offset(a1.wh_exp))v1 /= 8;
        if (wh2 == Value) {
            long v2 = nw(a2);
            if (is_offset(a2.wh_exp))v2 /= 8;

            if (is_signed(sha)) {
               if (v2>0 && v1<0) {
                  if (-v1 > range_max(sha) - v2)
                  test_overflow(UNCONDITIONAL);
               }
               else if (v2<0 && v1>0) {
                  if (v2 < range_min(sha) + v1)
                  test_overflow(UNCONDITIONAL);
               }
            }
            else {
               if ((unsigned)v1> (unsigned)v2)
               test_overflow(UNCONDITIONAL);
            }

            move(sha, mnw(v2 - v1), dest);
            return;
        }
        addsub_const_3_args(sha, sz, v1, a2, dest, 1);
        return;
    }

    if (wh2 == Value && nw(a2) == 0) {
        negate(sha, a1, dest);
        return;
    }

    if ((whd == Dreg || whd == Areg) && !interfere(a1, dest)) {
        move(sha, a2, dest);
        sub_aux(sha, sz, a1, dest);
        return;
    }

    move(sha, a2, D0);
    sub_aux(sha, sz, a1, D0);
    move(sha, D0, dest);
    set_cond(dest, sz);
    return;
}


/*
    NEGATE ROUTINE

    The value a of shape sha is negated and the result is stored in dest.
*/

void negate
(shape sha, where a, where dest)
{
    int instr;
    long sz = shape_size(sha);
    long rt = shtype(sha);
    long wha = whereis(a);
    long whd = whereis(dest);

    if (rt == Freg) {
        negate_float(sha, a, dest);
        return;
    }

    if (wha == Value) {
        long c = nw(a);
        bool overflow = 0;

        if (is_offset(a.wh_exp))c /= 8;

        if (is_signed(sha)) {
           if (c < - range_max(sha))
           overflow = 1;
        }
        else {
           if (c != 0) {
              make_comment("negation of unsigned shape");
              overflow = 1;
           }
        }

        /* If there is overflow and we have an error treatment, do it */
        if (overflow && have_overflow()) {
           test_overflow(UNCONDITIONAL);
        }
        /* No, so move the value in place */
        else {
           move(sha, mnw(-c), dest);
        }

        return;
    }

    if (eq_where(a, dest) && whd != Areg) {
        instr = ins(sz, ml_neg);
        ins1(instr, sz, dest, 1);
        set_cond(dest, sz);
        test_overflow(ON_SHAPE(sha));

        return;
    }

    if (whd == Dreg) {
        move(sha, a, dest);
        negate(sha, dest, dest);
        return;
    }

    if (wha == Dreg && last_use(a)) {
        negate(sha, a, a);
        move(sha, a, dest);
        return;
    }

    move(sha, a, D0);
    negate(sha, D0, D0);
    move(sha, D0, dest);
    return;
}


/*
    AUXILIARY MULTIPLY ROUTINE

    The value dest of shape sha is multiplied by a.
*/

static void mult_aux
(shape sha, where a, where dest)
{
    bool sg = is_signed(sha);
    long sz = shape_size(sha);
    int instr = (sg ? m_mulsl : m_mulul);
    shape lsha = (sg ? slongsh : ulongsh);

    if (whereis(a) == Freg) {
        move(sha, a, D0);
        mult_aux(sha, D0, dest);
        return;
    }

    if (sz == 8 || (have_overflow() && (sz == 16))) {
        change_var_sh(lsha, sha, dest, dest);
        change_var_sh(lsha, sha, a, D0);
        ins2(instr, L32, L32, dest, D0, 1);
        test_overflow(ON_OVERFLOW);
        change_var_sh(sha, lsha, D0, dest);
        set_cond(dest, sz);
        return;
    }

    if (sz == 16)instr = (sg ? m_mulsw : m_muluw);

    if (whereis(dest) == Dreg) {
        if (whereis(a) == Areg) {
            if (eq_where(dest, D0)) {
                move(sha, a, D1);
                regsinproc |= regmsk(REG_D1);
                ins2(instr, sz, sz, D1, dest, 1);
            } else {
                move(sha, a, D0);
                ins2(instr, sz, sz, D0, dest, 1);
            }
        } else {
            ins2(instr, sz, sz, a, dest, 1);
        }
        test_overflow(ON_OVERFLOW);
        set_cond(dest, sz);
        return;
    }

    move(sha, dest, D0);
    if (whereis(a) == Areg) {
        move(sha, a, D1);
        regsinproc |= regmsk(REG_D1);
        ins2(instr, sz, sz, D1, D0, 1);
    } else {
        ins2(instr, sz, sz, a, D0, 1);
    }
    test_overflow(ON_OVERFLOW);
    move(sha, D0, dest);
    set_cond(dest, sz);
    return;
}


/*
    MULTIPLY USING LOAD EFFECTIVE ADDRESS

    The m_lea instruction is used to multiply a by the constant sf + 1
    where sf is 1, 2, 4 or 8.  If d is true then a further add instruction
    is used to multiply further by 2.  The result is stored in dest.
    This routine only applies to values of size 32.
*/

static void mult_clever
(where a, where dest, long sf, bool d)
{
    int r;
    where ar;
    mach_op *op1, *op2;
    if (whereis(dest) == Areg) {
        ar = dest;
        r = reg(dest.wh_regs);
    } else {
        r = next_tmp_reg();
        regsinproc |= regmsk(r);
        ar = register(r);
    }
    move(slongsh, a, ar);
    op1 = make_reg_index(r, r, 0, sf);
    op2 = make_register(r);
    make_instr(m_lea, op1, op2, regmsk(r));
    have_cond = 0;
    if (d) {
        op1 = make_register(r);
        op2 = make_register(r);
        make_instr(m_addl, op1, op2, regmsk(r));
    }
    tmp_reg_status = 1;
    move(slongsh, ar, dest);
    return;
}


/*
    MULTIPLY A REGISTER BY A POWER OF 2

    The register r is multiplied by 2 to the power of p.  The flag
    D1_used is passed on to shift_aux if necessary.
*/

static void mult_power2
(long p, where r, bool D1_used)
{
    switch (p) {
        case 0: return;
        case 1: ins2(m_addl, L32, L32, r, r, 1); return;
        default : {
            shift_aux(slongsh, mnw(p), r, r, 0, D1_used);
            return;
        }
    }
}


/*
    MULTIPLICATION UTILITY ROUTINE

    This routine is used by mult_const.  The values r1 and r2 represent
    registers.  If P denotes 2 to the power of p and Q denotes 2 to the
    power of q then :

        (a)  If first_time is true, then q will be zero and r2 will hold
             the same value as r1.  r1 is multiplied by P - 1.

        (b)  Otherwise, r1 is set equal to ( P * Q * r1 + ( P - 1 ) * r2 ).

    The flag D1_used is passed onto mult_power2 if necessary.
*/

static void mult_utility
(long p, long q, where r1, where r2, bool D1_used, bool first_time)
{
    if (first_time) {
        switch (p) {

            case 0 : return ;            /* Doesn't occur */
            case 1 : return ;           /* Multiply by one */

            case 2: {
                /* Multiply by 3 */
                ins2(m_addl, L32, L32, r1, r1, 1);
                ins2(m_addl, L32, L32, r2, r1, 1);
                return;
            }

            default : {
                mult_power2(p, r1, D1_used);
                ins2(m_subl, L32, L32, r2, r1, 1);
                return;
            }
        }
    } else {
        switch (p) {

            case 0: {
                /* P = 1 => r1 = ( Q * r1 ) */
                mult_power2(q, r1, D1_used);
                return;
            }

            case 1: {
                /* P = 2 => r1 = ( 2 * Q * r1 + r2 ) */
                mult_power2(q + 1, r1, D1_used);
                ins2(m_addl, L32, L32, r2, r1, 1);
                return;
            }

            case 2: {
                /* P = 4 => r1 = ( 4 * Q * r1 + 3 * r2 ) */
                mult_power2(q + 1, r1, D1_used);
                ins2(m_addl, L32, L32, r2, r1, 1);
                ins2(m_addl, L32, L32, r1, r1, 1);
                ins2(m_addl, L32, L32, r2, r1, 1);
                return;
            }

            default : {
                mult_power2(q, r1, D1_used);
                ins2(m_addl, L32, L32, r2, r1, 1);
                mult_power2(p, r1, D1_used);
                ins2(m_subl, L32, L32, r2, r1, 1);
                return;
            }
        }
    }
}


/*
    MULTIPLY BY A CONSTANT

    The value a1 of shape sha is multiplied by the constant value a2
    and the result is stored in dest.  All constant multiplications
    are done by means of shifts, adds and subtracts.  Certain small
    cases and powers of 2 are dealt with separately.  The main algorithm
    is to split the constant into sections of the form 00...0011...11.
*/

static void mult_const
(shape sha, where a1, where a2, where dest)
{
    long n = nw(a2), m, p, q, n0;
    where reg1, reg2;
    bool D1_used, dont_move = 0;
    bool started = 0, first_time = 1;

    long sz = shape_size(sha);

    long wh1 = whereis(a1);
    long whd = whereis(dest);

    if (is_offset(a2.wh_exp))n /= 8;
    switch (n) {

        case 0: {
            /* Multiply by zero = Load zero */
            move(sha, zero, dest);
            return;
        }

        case 1: {
            /* Multiply by one = Move */
            move(sha, a1, dest);
            return;
        }

        case -1: {
            /* Multiply by minus one = Negate */
            negate(sha, a1, dest);
            return;
        }

        case 2: {
            /* Multiply by two = Add */
            add(sha, a1, a1, dest);
            return;
        }

        case 5: {
            if (sz == 32) {
                mult_clever(a1, dest, L4, 0);
                return;
            }
            break;
        }

        case 9: {
            if (sz == 32) {
                mult_clever(a1, dest, L8, 0);
                return;
            }
            break;
        }

        case 10: {
            if (sz == 32) {
                mult_clever(a1, dest, L4, 1);
                return;
            }
            break;
        }

        case 18: {
            if (sz == 32) {
                mult_clever(a1, dest, L8, 1);
                return;
            }
            break;
        }
    }

    /* Find two registers */
    if (whd == Dreg && !eq_where(dest, D0)) {
        reg1 = dest;
        reg2 = D0;
        D1_used = 0;
    } else {
        reg1 = D0;
        reg2 = D1;
        D1_used = 1;
    }
    if (wh1 == Dreg && !eq_where(a1, reg1)) {
        reg2 = a1;
        D1_used = 0;
        dont_move = 1;
    }

    /* Deal with multiplications of less than 32 bits */
    if (sz < 32) {
        shape lsha = (is_signed(sha)? slongsh : ulongsh);
        change_var_sh(lsha, sha, a1, reg1);
        mult_const(lsha, reg1, a2, reg1);
        change_var_sh(sha, lsha, reg1, dest);
        return;
    }

    /* Now prepare to multiply by |n| */
    n0 = n;
    if (n < 0)n = -n;

    if (is_pow2(n)) {
        /* Powers of two are easy */
        p = log2(n);
        if (wh1 == Dreg && last_use(a1)) {
            reg1 = a1;
            D1_used = 0;
        } else {
            move(sha, a1, reg1);
        }
        mult_power2(p, reg1, D1_used);
    } else {
        /* The thing we are multiplying goes in reg1 */
        move(sha, a1, reg1);
        /* Copy reg1 into reg2 if necessary */
        if (!dont_move)move(slongsh, reg1, reg2);
        if (D1_used)regsinproc |= regmsk(REG_D1);
        /* p will count consecutive ones and q consecutive zeros */
        p = 0;
        q = 0;
        /* Scan through the 31 bits of n (the sign bit is zero), MSB first */
        for (m = pow2(30); m; m >>= 1) {
            if (m & n) {
                /* Set bit - record this */
                started = 1;
                p++;
            } else {
                /* Reset bit - record this */
                if (p) {
                    /* We have read q 0's, then p 1's, before this 0 */
                    mult_utility(p, q, reg1, reg2, 1, first_time);
                    first_time = 0;
                    /* Restart counts */
                    p = 0;
                    q = 0;
                }
                /* Record reset bit, ignoring initial zeros */
                if (started)q++;
            }
        }
        /* Deal with last batch of digits */
        if (p || q)mult_utility(p, q, reg1, reg2, 1, first_time);
    }
    /* Now put the result into dest - take care of sign of n now */
    if (n0 < 0) {
        negate(slongsh, reg1, dest);
    } else {
        move(slongsh, reg1, dest);
    }
    set_cond(dest, L32);
    return;
}


/*
    MAIN MULTIPLICATION ROUTINE

    The values a1 and a2 of shape sha are multiplied and the result is
    stored in dest.
*/

void mult
(shape sha, where a1, where a2, where dest)
{
    where w;
    long wh1 = whereis(a1);
    long wh2 = whereis(a2);
    long whd = whereis(dest);

    if (!have_overflow()) {
        /* Constant multiplication */
        if (wh1 == Value) {
            if (wh2 == Value) {
                long v1 = nw(a1);
                long v2 = nw(a2);
                if (is_offset(a1.wh_exp))v1 /= 8;
                if (is_offset(a2.wh_exp))v2 /= 8;
                move(sha, mnw(v1 * v2), dest);
                return;
            }
            mult_const(sha, a2, a1, dest);
            return;
        }

        if (wh2 == Value) {
            mult_const(sha, a1, a2, dest);
            return;
        }
    }

    if (eq_where(a1, a2)) {
        if (whd == Dreg) {
            move(sha, a1, dest);
            mult_aux(sha, dest, dest);
            return;
        } else {
            move(sha, a1, D0);
            mult_aux(sha, D0, D0);
            move(sha, D0, dest);
            return;
        }
    }

    if (eq_where(a1, dest)) {
        mult_aux(sha, a2, dest);
        return;
    }

    if (eq_where(a2, dest)) {
        mult_aux(sha, a1, dest);
        return;
    }

    if (whd == Dreg) {
        if (!interfere(a2, dest)) {
            move(sha, a1, dest);
            mult_aux(sha, a2, dest);
            return;
        }
        if (!interfere(a1, dest)) {
            move(sha, a2, dest);
            mult_aux(sha, a1, dest);
            return;
        }
    }

    if (shape_size(sha) == 8 ||
        ((shape_size(sha) ==16) && (have_overflow()))) {
        w = D1;
        regsinproc |= regmsk(REG_D1);
    } else {
        w = D0;
    }
    if (whereis(a2) == Areg) {
        move(sha, a2, w);
        mult_aux(sha, a1, w);
        move(sha, w, dest);
    } else {
        move(sha, a1, w);
        mult_aux(sha, a2, w);
        move(sha, w, dest);
    }
    return;
}


/*
    DIVISION BY A POWER OF 2

    The value top of shape sha is divided by the constant v which is a
    power of 2.  The result is stored in dest.
*/

static void div_power2
(shape sha, long v, where top, where dest)
{
    long n = log2(v);
    if (is_signed(sha)) {
        bool sw;
        where w;
        int instr;
        long sz = shape_size(sha);
        long lab = next_lab();
        exp jt = simple_exp(0);
        ptno(jt) = lab;

        if (whereis(dest) == Dreg) {
            w = dest;
        } else if (whereis(top) == Dreg && last_use(top)) {
            w = top;
        } else {
            w = D0;
        }
        move(sha, top, w);
        sw = cmp(sha, w, zero, tst_ge);
        branch(tst_ge, jt, 1, sw, 0);
        add(sha, w, mnw(v - 1), w);
        make_label(lab);
        instr = ins(sz, ml_asr);
        while (n) {
            long m = (n > 8 ? 7 : n);
            ins2n(instr, m, sz, w, 1);
            n -= m;
        }
        move(sha, w, dest);
        set_cond(dest, sz);
    } else {
        shift_aux(sha, mnw(n), top, dest, 1, 0);
    }
    return;
}


/*
    REMAINDER MODULO A POWER OF 2

    The remainder of the value top of shape sha when divided by the
    constant v (which is a power of 2) is stored in dest.
*/

static void rem_power2
(shape sha, long v, where top, where dest)
{
    if (is_signed(sha)) {
        bool sw;
        where w;
        long lab = next_lab();
        long end = next_lab();
        exp jt = simple_exp(0);
        exp je = simple_exp(0);
        ptno(jt) = lab;
        ptno(je) = end;

        if (whereis(dest) == Dreg) {
            w = dest;
        } else if (whereis(top) == Dreg && last_use(top)) {
            w = top;
        } else {
            w = D0;
        }
        move(sha, top, w);
        sw = cmp(sha, w, zero, tst_ge);
        branch(tst_ge, jt, 1, sw, 0);
        negate(sha, w, w);
        and(sha, mnw(v - 1), w, w);
        negate(sha, w, w);
        make_jump(m_bra, end);
        make_label(lab);
        and(sha, mnw(v - 1), w, w);
        make_label(end);
        move(sha, w, dest);
        set_cond(dest, shape_size(sha));
    } else {
        and(sha, mnw(v - 1), top, dest);
    }
    return;
}


/*
    REMAINDER MODULO A POWER OF 2 MINUS 1

    The remainder of the value top of shape sha when divided by the
    constant v (which is a power of 2 minus 1) is stored in dest.  The
    algorithm used is a modification of "casting out the nines".
*/

static bool rem_power2_1
(shape sha, long v, where top, where dest)
{
    bool sw;
    where d0, d1;
    long loop, end, tst;
    exp jloop, jend, jtst;
    bool s = is_signed(sha);

    if (s && (eq_where(top, D0) || eq_where(top, D1))) return(0);

    if (whereis(dest) == Dreg) {
        d1 = dest;
    } else {
        d1 = D1;
        regsinproc |= regmsk(REG_D1);
    }

    if (eq_where(d1, D0)) {
        d0 = D1;
        regsinproc |= regmsk(REG_D1);
    } else {
        d0 = D0;
    }

    loop = next_lab();
    jloop = simple_exp(0);
    ptno(jloop) = loop;
    end = next_lab();
    jend = simple_exp(0);
    ptno(jend) = end;
    tst = next_lab();
    jtst = simple_exp(0);
    ptno(jtst) = tst;

    move(sha, top, d1);
    if (s) {
        sw = cmp(sha, d1, zero, tst_ge);
        branch(tst_ge, jloop, s, sw, 0);
        negate(sha, d1, d1);
    }
    make_label(loop);
    move(sha, mnw(v), d0);
    sw = cmp(ulongsh, d1, d0, tst_le);
    branch(tst_le, jend, s, sw, 0);
    and(ulongsh, d1, d0, d0);
    rshift(ulongsh, mnw(log2(v + 1)), d1, d1);
    add(ulongsh, d0, d1, d1);
    make_jump(m_bra, loop);
    make_label(end);
    branch(tst_neq, jtst, s, sw, 0);
    move(sha, zero, d1);
    make_label(tst);
    if (s) {
        long ntst = next_lab();
        exp jntst = simple_exp(0);
        ptno(jntst) = ntst;
        sw = cmp(sha, top, zero, tst_ge);
        branch(tst_ge, jntst, 1, sw, 0);
        negate(sha, d1, d1);
        make_label(ntst);
    }
    have_cond = 0;
    move(sha, d1, dest);
    return(1);
}


/*
    MARKERS FOR DIVISION AND REMAINDER

    Division, remainder and combined division-remainder operations are
    all handled by a single routine.  The following values are used to
    indicate to the routine the operation type.
*/

#define  DIV            0
#define  REM            1
#define  BOTH           2


/*
    MAIN DIVISION AND REMAINDER ROUTINE

    The value top of shape sha is divided by bottom and, depending on
    the value of type, the quotient is stored in quot and the remainder
    in rem.  Which of the two possible division types used is determined
    by form : for example, if form is 1 then :

                    5 = ( -2 ) * ( -3 ) - 1

    whereas if form is 2 :

                    5 = ( -1 ) * ( -3 ) + 2

    The second form is the standard one.
*/

static void euclid
(shape sha, where bottom, where top, where quot, where rem, int type, int form)
{
    long v;
    bool b_const = 0;
    bool save_d1 = 0;
    bool d1_pending = 0;
    where qreg, rreg, breg;
    long sz = shape_size(sha);
    bool sg = is_signed(sha);
    shape lsha = (sg ? slongsh : ulongsh);

    /* The two forms are the same for unsigned division */
    if (!sg) form = 2;

    /* Deal with division by constants */
    if (name(bottom.wh_exp) == val_tag) {
        b_const = 1;
        v = nw(bottom);
        if (is_offset(bottom.wh_exp))v /= 8;
        switch (v) {

            case 0: {
                warning("Division by zero");
                if (have_overflow()) {
                   test_overflow(UNCONDITIONAL);
                }
                else {
                   if (type != REM)move(sha, zero, quot);
                   if (type != DIV)move(sha, zero, rem);
                }
                return;
            }

            case 1: {
                if (type != REM)move(sha, top, quot);
                if (type != DIV)move(sha, zero, rem);
                return;
            }

            case -1: {
                if (is_signed(sha)) { /* is it really negative */
                    if (type != REM || have_overflow())negate(sha, top, quot);
                    if (type != DIV)move(sha, zero, rem);
                    return;
                }
                /* fall through */
            }

            default : {
                if (form != 1) {
                    if ((!is_signed(sha) || v > 0) && is_pow2(v) && sz == 32) {
                        if (type == DIV) {
                            div_power2(sha, v, top, quot);
                            return;
                        }
                        if (type == REM) {
                            rem_power2(sha, v, top, rem);
                            return;
                        }
                    }
                    if (v > 7 && is_pow2(v + 1) && sz == 32) {
                        if (type == REM &&
                             rem_power2_1(sha, v, top, rem)) {
                            return;
                        }
                    }
                }
                break;
            }
        }
    }

    /* Check on pointless divisions */
    if (eq_where(top, bottom)) {
        if (type != REM)move(sha, mnw(1), quot);
        if (type != DIV)move(sha, zero, rem);
        return;
    }

    /* Now find two registers */
    if (type == BOTH && interfere(quot, rem)) {
        qreg = D0;
        rreg = D1;
        regsinproc |= regmsk(REG_D1);
        if (D1_is_special)save_d1 = 1;
    } else {
        if (type != REM && whereis(quot) == Dreg &&
             !interfere(quot, bottom)) {
            qreg = quot;
        } else {
            qreg = D0;
        }
        if (type != DIV && whereis(rem) == Dreg) {
            if (eq_where(rem, D0)) {
                qreg = D1;
                rreg = D0;
                regsinproc |= regmsk(REG_D1);
                if (D1_is_special)save_d1 = 1;
            } else {
                rreg = rem;
            }
        } else {
            if (eq_where(qreg, D0)) {
                rreg = D1;
                if (type == DIV) {
                    d1_pending = 1;
                } else {
                    regsinproc |= regmsk(REG_D1);
                    if (D1_is_special)save_d1 = 1;
                }
            } else {
                rreg = D0;
            }
        }
    }

    /* Save D1 if necessary */
    if (save_d1)push(slongsh, L32, D1);
#if 0
    /* Keep the denominator in form 1 */
    if (form == 1 && !b_const)push(slongsh, L32, bottom);
#endif
    /* Get the arguments into the correct positions */
    if (sz != 32) {
       bool d0_pushed = 0;

       make_comment("change variety top -> qreg");
       change_var_sh(lsha, sha, top, qreg);

       if (eq_where(qreg, D0)) {
          push(slongsh, L32, D0);
          d0_pushed = 1;
       }

       make_comment("change variety bottom -> rreg");
       change_var_sh(lsha, sha, bottom, rreg);

       if (d0_pushed)
          pop(slongsh,L32,D0);

       breg = rreg;
    } else {
        move(sha, top, qreg);
        if (whereis(bottom) == Areg || whereis(bottom) == Freg) {
            if (d1_pending) {
                regsinproc |= regmsk(REG_D1);
                if (D1_is_special) {
                    save_d1 = 1;
                    push(slongsh, L32, D1);
                }
            }
            move(sha, bottom, rreg);
            breg = rreg;
        } else {
            breg = bottom;
        }
    }

    if (have_overflow()) {
       if (save_d1) {
          pop(slongsh,L32,D1);
       }
       cmp_zero(sha, sz, breg);
       test_overflow2(m_beq);
       if (save_d1) {
          push(slongsh,L32,D1);
       }
    }

    /* Keep the denominator in form 1 */
    if (form == 1 && !b_const)push(slongsh, L32, breg);

    /* Create the actual division instruction */
    if (type == DIV && form != 1) {
        long qn = reg(qreg.wh_regs);
        int instr = (sg ? m_divsl : m_divul);
        mach_op *op1 = operand(L32, breg);
        mach_op *op2 = make_register(qn);
        make_instr(instr, op1, op2, regmsk(qn));
    } else {
        long qn = reg(qreg.wh_regs);
        long rn = reg(rreg.wh_regs);
        int instr = (sg ? m_divsll : m_divull);
        mach_op *op1 = operand(L32, breg);
        mach_op *op2 = make_reg_pair(rn, qn);
        make_instr(instr, op1, op2,(regmsk(qn) | regmsk(rn)));
    }
    if (have_overflow()) {
      if (save_d1) {
        pop(slongsh,L32,D1);
      }
      if (form == 1 && !b_const) {
        dec_stack(-32);
      }
      test_overflow(ON_SHAPE(sha));
      if (form == 1 && !b_const) {
        dec_stack(32);
      }


    }


    /* Apply hacks for form 1 */
    if (form == 1 && is_signed(sha)) {
        mach_op *op1, *op2;
        long lab1 = next_lab();
        long lab2 = next_lab();
        long qn = reg(qreg.wh_regs);
        long rn = reg(rreg.wh_regs);
        if (!b_const) {
            op1 = make_indirect(REG_SP, 0);
            make_instr(m_tstl, op1, null, 0);
            make_jump(m_bge, lab1);
        }

        /* Denominator is negative ? */
        if (!(b_const && v >= 0)) {
            op1 = make_register(rn);
            make_instr(m_tstl, op1, null, 0);
            make_jump(m_ble, lab2);
            if (type != REM) {
                op1 = make_value(1);
                op2 = make_register(qn);
                make_instr(m_subql, op1, op2, regmsk(qn));
            }
            if (type != DIV) {
                if (b_const) {
                    op1 = make_value(v);
                } else {
                    op1 = make_indirect(REG_SP, 0);
                }
                op2 = make_register(rn);
                make_instr(m_addl, op1, op2, regmsk(rn));
            }
            if (!b_const)make_jump(m_bra, lab2);
        }

        /* Denominator is positive ? */
        if (!(b_const && v < 0)) {
            if (!b_const)make_label(lab1);
            op1 = make_register(rn);
            make_instr(m_tstl, op1, null, 0);
            make_jump(m_bge, lab2);
            if (type != REM) {
                op1 = make_value(1);
                op2 = make_register(qn);
                make_instr(m_subql, op1, op2, regmsk(qn));
            }
            if (type != DIV) {
                if (b_const) {
                    op1 = make_value(v);
                } else {
                    op1 = make_indirect(REG_SP, 0);
                }
                op2 = make_register(rn);
                make_instr(m_addl, op1, op2, regmsk(rn));
            }
        }

        make_label(lab2);
        if (!b_const)dec_stack(-32);
    }

    /* Move results into place */
    if (sz == 32) {
        if (type != REM)move(sha, qreg, quot);
        if (type != DIV)move(sha, rreg, rem);
    } else {
        if (type != REM)change_var_sh(sha, lsha, qreg, quot);
        if (type != DIV)change_var_sh(sha, lsha, rreg, rem);
    }

    /* Restore D1 */
    if (save_d1) {
        pop(slongsh, L32, D1);
        debug_warning("D1 saved on stack during division");
    }
    have_cond = 0;
    return;
}


/*
    DIVISION INSTRUCTION - FORM ONE

    The value top of shape sha is divided by bottom and the result is
    stored in dest.  This is the alternative division operation.
*/

void div1
(shape sha, where bottom, where top, where dest)
{
    euclid(sha, bottom, top, dest, zero, DIV, 1);
    return;
}


/*
    DIVISION INSTRUCTION - FORM TWO

    The value top of shape sha is divided by bottom and the result is
    stored in dest.  This is the standard division operation.
*/

void div2
(shape sha, where bottom, where top, where dest)
{
    euclid(sha, bottom, top, dest, zero, DIV, 2);
    return;
}


/*
    REMAINDER INSTRUCTION - FORM ONE

    The value top of shape sha is divided by bottom and the remainder is
    stored in dest.  This is the alternative remainder operation.
*/

void rem1
(shape sha, where bottom, where top, where dest)
{
    euclid(sha, bottom, top, zero, dest, REM, 1);
    return;
}


/*
    REMAINDER INSTRUCTION - FORM TWO

    The value top of shape sha is divided by bottom and the remainder is
    stored in dest.  This is the standard remainder operation.
*/

void rem2
(shape sha, where bottom, where top, where dest)
{
    euclid(sha, bottom, top, zero, dest, REM, 2);
    return;
}


/*
    DO AN EXACT DIVISION

    The value top is divided by bottom and the result is stored in dest.
*/

void exactdiv
(shape sha, where bottom, where top, where dest)
{
    euclid(slongsh, bottom, top, dest, zero, DIV, 2);
    return;
}


/*
    DO A MAXIMUM OR MINIMUM INSTRUCTION
*/

static void maxmin
(shape sha, where a1, where a2, where dest, int tst)
{
    where d;
    bool sw;
    long sz = shape_size(sha);
    long lab = next_lab();
    exp jt = simple_exp(0);
    ptno(jt) = lab;
    if (whereis(dest) == Dreg && !interfere(a1, dest) &&
         !interfere(a2, dest)) {
        d = dest;
    } else {
        d = D0;
    }
    make_comment("maxmin ...");
    move(sha, a1, d);
    sw = cmp(sha, d, a2, tst);
    branch(tst, jt, is_signed(sha), sw, 0);
    move(sha, a2, d);
    make_label(lab);
    move(sha, d, dest);
    make_comment("maxmin done");
    return;
}


/*
    DO A MAXIMUM INSTRUCTION
*/

void maxop
(shape sha, where a1, where a2, where dest)
{
    maxmin(sha, a1, a2, dest, tst_ge);
    return;
}


/*
    DO A MINIMUM INSTRUCTION
*/

void minop
(shape sha, where a1, where a2, where dest)
{
    maxmin(sha, a1, a2, dest, tst_le);
    return;
}


/*
    DO AN ABSOLUTE OPERATION
*/

void absop
(shape sha, where a, where dest)
{
    if (is_signed(sha)) {
        where d;
        bool sw;
        long lab = next_lab();
        exp jt = simple_exp(0);
        ptno(jt) = lab;
        if (whereis(dest) == Dreg) {
            d = dest;
        } else {
            d = D0;
        }
        move(sha, a, d);
        sw = cmp(sha, d, zero, tst_ge);
        branch(tst_ge, jt, 1, sw, 0);
        negate(sha, d, d);
        make_label(lab);
        move(sha, d, dest);
    } else {
        move(sha, a, dest);
    }
    return;
}