/*
 * Copyright (c) 1993-2018, NVIDIA CORPORATION.  All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

/** \file
 * \brief Register allocation performed by the expander at opt level 1
 */

#include "expreg.h"
#include "gbldefs.h"
#include "error.h"
#include "global.h"
#include "symtab.h"
#include "ili.h"
#include "expand.h"
#include "regutil.h"
#include "machreg.h"
#include "machar.h"

static int ilt;

static LST *list; /* linked list of ili added to entry blk */

static void assign1(int rtype);

/** \brief Add register candidate
 *
 * routine to add candidates to the register candidate lists of
 * the register module.  This routine is valid for opt 1 only.
 * This routine is called only for load and store ILI and will
 * eliminate a candidate which depend on their types, storage
 * class, etc.
 */
void
exp_rcand(int ilix, int nmex)
{
  register int sym;

  if (NME_TYPE(nmex) == NT_VAR                         /* var names only */
      && TY_ISSCALAR(DTY(DTYPEG(sym = NME_SYM(nmex)))) /* scalars only */
      && IS_LCL_OR_DUM(sym)                            /* autos, args or regs */
      && !VOLG(sym)                                    /* not volatile */
      && SOCPTRG(sym) == 0 /* not equivalenced */
      ) {
    addrcand(ilix);
    /* If a variable is loaded or stored in a critical section or a
     * parallel section, set the candidate's IGNORE flag.  This prevents
     * a register from being assigned.
     * Tprs 2077 & 2127 - can't assign registers to variables in a
     * parallel region (could restrict this to shared variables).
     */
    if (NME_RAT(nmex) &&
        (bihb.csfg || bihb.parsectfg || bihb.parfg || bihb.taskfg))
      RCAND_IGNORE(NME_RAT(nmex)) = 1;
  }
}

/***********************************************************************/

/** \brief Control the assignment of the registers at opt level 1
 */
void
reg_assign1(void)
{
  register int first_rat, /* first entry of the block's RAT  */
      ilix,               /* ili index of the candidate */
      entr,               /* entry symbol of the function */
      rtype,              /* register type */
      cand;               /* register candidate */
  int null_rat;
  int funcbih;
  int iltt; /* local tmp for ilt */
  int sym;
  LST *save;
  int tbih;

  funcbih = BIHNUMG(gbl.currsub);
  rdilts(funcbih);           /* fetch the entry block */
  entr = BIH_LABEL(funcbih); /* get the entry symbol */
  list = 0;
  ilt = ILT_PREV(0); /* locate the last ilt in the block */

#if DEBUG
  if (EXPDBG(8, 12))
    fprintf(gbl.dbgfil,
            "\n***** Register Information for Function \"%s\" *****\n",
            getprint(entr));
  if (EXPDBG(8, 4))
    dmprcand();
#endif

  GET_RAT(first_rat); /* fetch a RAT item which will be
                       * the header of the table
                       */
  /*
   * assign the data registers from the allowed register set.
   */
  assign1(RATA_IR);
  /*
   * assign the address registers from the allowed set.
   */
  assign1(RATA_AR);
  /*
   * assign the single precision registers from the allowed set.
   */
  assign1(RATA_SP);
  /*
   * assign the double precision registers from the allowed set.
   */
  assign1(RATA_DP);
  /*
   * assign the double integer registers from the allowed set.
   */
  assign1(RATA_KR);
  /*
   * communicate to the scheduler the first global register assigned
   * for each register class -- note that this will be the physical register
   * number; it reflects the number of registers assigned from the physical
   * set mapped from the generic register set. Because two or more generic
   * register sets can map to a single register set, this information
   * can only be computed after all of the assignments are done.
   *
   */
  mr_end();
  /*
   * fill in the VAL field of the header entry for this RAT with
   * the number of assignments.
   */
  RAT_VAL(first_rat) = ratb.stg_avail - first_rat - 1;

#if DEBUG
  if (EXPDBG(8, 8)) {
    dmprat(first_rat);
    fprintf(gbl.dbgfil,
            "   first_dr:%3d, first_ar:%3d, first_sp:%3d, first_dp:%3d\n",
            aux.curr_entry->first_dr, aux.curr_entry->first_ar,
            aux.curr_entry->first_sp, aux.curr_entry->first_dp);
  }
#endif
/*
 * link the BIH of the entry block to the RAT.  this block is written
 * out.
 */
  BIH_ASSN(BIH_NEXT(funcbih)) = first_rat;
  wrilts(funcbih);
  /*
   * add ilis that were added to the main entry to any other entries
   */
  if (list) {
    save = list;
    for (sym = SYMLKG(gbl.entries); sym != NOSYM; sym = SYMLKG(sym)) {
      rdilts(tbih = BIHNUMG(sym));
      GET_RAT(null_rat);
      RAT_VAL(null_rat) = 0;
      BIH_ASSN(tbih) = null_rat;
      /*
       * if we copied params, we do this mvxx stuff in block after the
       * entry block, so scheduler loads up dummies after copy to temp
       * param list has been done
       */
      if (COPYPRMSG(sym)) {
        wrilts(tbih);
        tbih = exp_addbih(tbih);
        rdilts(tbih);
        BIH_ASSN(tbih) = null_rat;
      }
      iltt = ILT_PREV(0);
      list = save;
      while (list) {
        iltt = addilt(iltt, list->item);
        list = list->next;
      }
      BIH_ASSN(BIH_NEXT(tbih)) = first_rat;
      wrilts(tbih);
    }
    freearea(LST_AREA);
  }
  /*
   * dummies that have stores into  and that are assigned registers , must
   * be stored back at exit
   */
  storedums(expb.curbih, first_rat);

  /* for the routine's exit block, assign a null rat table  */
  GET_RAT(null_rat);
  RAT_VAL(null_rat) = 0;
  BIH_ASSN(expb.curbih) = null_rat;

  /*
   * go through the candidate lists to clean up the back pointers.
   * also, reset the register candidate store area.
   */
  endrcand();

}

/** \brief Assign registers for opt level 1.
 *
 * The possible set of registers to use satisfies the relation first_global <=
 * reg <= last_global, where the bounds are found in the mach_reg structure of
 * the reg structure for the given register type.  The registers are assigned
 * beginning at last_global and continues down to first_global.  The value
 * returned by the function is the number of registers assigned.
 *
 * \param rtype type of registers
 */
static void
assign1(int rtype)
{
  register int cand, /* candidate for a register */
      rat,           /* RAT for each assigned reg */
      ilix,          /* ili for the candidate */
      areg;          /* assigned register */

  int candl,  /* candidate list */
      nme,    /* NME for the candidate */
      sym,    /* ST for the candidate  */
      addr,   /* address ili for the candidate */
      ilitmp; /* ili temporary */

  /*
   * loop while there are candidates and registers available
   */
  candl = reg[rtype].rcand;
  for (;;) {
    /*
     * get a candidate; if one does not exist, exit the loop
     */
    if ((cand = getrcand(candl)) == 0)
      break;
    if (RCAND_IGNORE(cand))
      continue;
    if (!RCAND_ISILI(cand)) {
/*
 * if the candidate is not type ILI (implies that just stores
 * occurred, get the next candidate if it's not a regarg.
 * NOTE: C only.
 */
      continue;
    } else {
      /*
       * for the candidate, locate the load ili (ilix), the names entry
       * (nme), and the symbol (sym)
       */
      ilix = RCAND_VAL(cand);
      sym = NME_SYM(nme = ILI_OPND(ilix, 2));
      addr = ILI_OPND(ilix, 1);
    }
    /*
     * if the symbol has had its address taken, it cannot be assigned a
     * register
     */
    if (ADDRTKNG(sym))
      continue;
    if (gbl.internal > 1 && sym == aux.curr_entry->display) {
      /* printf("display temp %s cannot be assigned register\n",
             SYMNAME(sym));
       */
      continue;
    }
/*
 * if symbol is declared in an 'outer' scope, it cannot be assigned a
 * register - happens in a language with nested procedures (c++)
 */
    if (GSCOPEG(sym))
      continue;
    if (UPLEVELG(sym))
      continue;

    /*
     *for now, dissallow dummies if there are multiple entries
     */
    if (IS_DUM(sym)) {
      if (SYMLKG(gbl.entries) != NOSYM)
        continue;
    } else if (SCG(sym) == SC_LOCAL && DINITG(sym) &&
               (ADDRTKNG(sym) || RCAND_STORE(cand)) && gbl.rutype != RU_PROG)
      /*
       * if a local variable is data initialized, disallow it if it
       * has been stored; the thinking is that it falls into the
       * same category of a saved variable -- someday, may want
       * to override this if XBIT(124,0x80) is set (also optutil.c)
       */
      continue;
    /*
     * for C regargs, use arg register saved in address field;
     * for ftn use arg register saved in address field if it's the
     * symbol which represents the arg's address.  Otherwise, get a
     * register from the "global" set.
     */
    if (IS_REGARG(sym))
      areg = ADDRESSG(sym);
    else {
      /*
       * get a register from the global set
       */
      if ((areg = mr_getreg(rtype)) == NO_REG)
        break;
    }

    GET_RAT(rat) /* locate a rat entry */

    RAT_REG(rat) = ad1ili(RTYPE_DF(rtype), areg);

    RAT_RTYPE(rat) = rtype;             /* don't forget its rtype */
    RAT_ATYPE(rat) = RATA_NME;          /* opt 1 assigns nme's only */
    RAT_ADDR(rat) = addr;               /* locate addr ili of ref   */
    RAT_CONFL(rat) = 0;                 /* no conflict for opt 1    */
    RAT_STORE(rat) = RCAND_STORE(cand); /* copy up store flag       */
    RAT_VAL(rat) = nme;                 /* record nme assigned      */
    RAT_MSIZE(rat) = RCAND_MSIZE(cand); /* copy up mem size         */
                                        /*
                                         * if the symbol is an argument to the function, add a move ili of
                                         * the argument to its assigned register
                                         */
    /* for fortran, a REGARG sym has its address in the register, not
     * the value of the argument.  REDUC flag indicates that no extra
     * indirection is needed.
     */
    if (IS_DUM(sym)) {
      if (REGARGG(sym)) {
        if (!REDUCG(sym)) {
          /* we're preloading the dummy argument; we need to use
           * the arg register that's assigned to its address as the
           * address expression in the the load.
           */
          addr = ad1ili(IL_ARDF, ADDRESSG(sym));
          if (flg.endian &&
              (DTYPEG(sym) == DT_INT8 || DTYPEG(sym) == DT_LOG8)) {
            if (!XBIT(124, 0x400))
              /* 32bits of significance in 64 bits */
              addr = ad3ili(IL_AADD, addr, ad_aconi(4), 0);
          }
          RAT_ADDR(rat) = addr; /* switch addr expr */
          ilix = ad3ili(ILI_OPC(ilix), addr, ILI_OPND(ilix, 2),
                        ILI_OPND(ilix, 3));
          ilt = addilt(ilt, ilitmp = ad2ili(MV_RTYPE(rtype), ilix, areg));
          ADDNODE(list, ilitmp);
        }
      } else {
        ilt = addilt(ilt, ilitmp = ad2ili(MV_RTYPE(rtype), ilix, areg));
        ADDNODE(list, ilitmp);
      }
    } else {
      /*
       * FORTRAN only - if the symbol is local to the function and has
       * been data initialized, add a move ili of the argument to its
       * assigned register
       */
      if (DINITG(sym)) {
        ilt = addilt(ilt, ilitmp = ad2ili(MV_RTYPE(rtype), ilix, areg));
        ADDNODE(list, ilitmp);
      }
    }
  }
}
