/**********************************************************************
 * pcopt.c
 * Constant Expression Optimizations
 *
 *   Copyright (C) 2008-2009 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <spudmonkey@racsa.co.cr>
 *
 * 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 NuttX 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 OWNER 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.
 *
 **********************************************************************/

/**********************************************************************
 * Included Files
 **********************************************************************/

#include <stdint.h>
#include <stdio.h>

#include "keywords.h"
#include "pdefs.h"
#include "pinsn32.h"

#include "paslib.h"
#include "popt.h"
#include "polocal.h"
#include "pcopt.h"

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

int unaryOptimize(void)
{
  register uint32_t temp;
  register int i;
  int nchanges = 0;

  TRACE(stderr, "[unaryOptimize]");

  /* At least two pcodes are need to perform unary optimizations */

  i = 0;
  while (i < nops-1)
    {
      /* Check for a constant value being pushed onto the stack */

      if (GETOP(pptr[i]) == oPUSH)
        {
          switch (GETOP(pptr[i+1]))
            {
              /* Delete unary operators on constants */
            case oNEG   :
              PUTARG(pptr[i], -GETARG(pptr[i]));
              deletePcode(i+1);
              nchanges++;
              break;

            case oABS   :
              if ((int32_t)GETARG(pptr[i]) < 0)
                PUTARG(pptr[i], -(int32_t)GETARG(pptr[i]));
              deletePcode(i+1);
              nchanges++;
              break;

            case oINC   :
              PUTARG(pptr[i], GETARG(pptr[i]) + 1);
              deletePcode(i+1);
              nchanges++;
              break;

            case oDEC   :
              PUTARG(pptr[i], GETARG(pptr[i]) - 1);
              deletePcode(i+1);
              nchanges++;
              break;

            case oNOT   :
              PUTARG(pptr[i], ~GETARG(pptr[i]));
              PUTARG(pptr[i], ~(GETARG(pptr[i])));
              deletePcode(i+1);
              nchanges++;
              break;

              /* Simplify binary operations on constants */

            case oADD :
              if (GETARG(pptr[i]) == 0)
                {
                  deletePcodePair(i, (i+1));
                  nchanges++;
                } /* end if */
              else if (GETARG(pptr[i]) == 1)
                {
                  PUTOP(pptr[i+1], oINC);
                  deletePcode(i);
                  nchanges++;
                } /* end else if */
              else if (GETARG(pptr[i]) == ARGONES)
                {
                  PUTOP(pptr[i+1], oDEC);
                  deletePcode(i);
                  nchanges++;
                } /* end else if */
              else i++;
              break;

            case oSUB :
              if (GETARG(pptr[i]) == 0)
                {
                  deletePcodePair(i, (i+1));
                  nchanges++;
                } /* end if */
              else if (GETARG(pptr[i]) == 1)
                {
                  PUTOP(pptr[i+1], oDEC);
                  deletePcode(i);
                  nchanges++;
                } /* end else if */
              else if (GETARG(pptr[i]) == ARGONES)
                {
                  PUTOP(pptr[i+1], oINC);
                  deletePcode(i);
                  nchanges++;
                } /* end else if */
              else i++;
              break;

            case oMUL :
            case oDIV :
              temp = 0;
              switch (GETARG(pptr[i]))
                {
                case 1 :
                  deletePcodePair(i, (i+1));
                  nchanges++;
                  break;
                case 16384 : temp++;
                case  8192 : temp++;
                case  4096 : temp++;
                case  2048 : temp++;
                case  1024 : temp++;
                case   512 : temp++;
                case   256 : temp++;
                case   128 : temp++;
                case    64 : temp++;
                case    32 : temp++;
                case    16 : temp++;
                case     8 : temp++;
                case     4 : temp++;
                case     2 : temp++;
                  PUTARG(pptr[i], temp);
                  if (GETOP(pptr[i+1]) == oMUL)
                    PUTOP(pptr[i+1], oSLL);
                  else
                    PUTOP(pptr[i+1], oSRA);
                  nchanges++;
                  i++;
                  break;

                default :
                  i++;
                  break;
                } /* end switch */
              break;

            case oSLL :
            case oSRL :
            case oSRA :
            case oOR  :
              if (GETARG(pptr[i]) == 0)
                {
                  deletePcodePair(i, (i+1));
                  nchanges++;
                } /* end if */
              else i++;
              break;

            case oAND :
              if (GETARG(pptr[i]) == ARGONES)
                {
                  deletePcodePair(i, (i+1));
                  nchanges++;
                } /* end if */
              else i++;
              break;

              /* Delete comparisons of constants to zero */

            case oEQUZ  :
              if (GETARG(pptr[i]) == 0)
                PUTARG(pptr[i], -1);
              else
                PUTARG(pptr[i], 0);
              deletePcode(i+1);
              nchanges++;
              break;

            case oNEQZ  :
              if (GETARG(pptr[i]) != 0)
                PUTARG(pptr[i], -1);
              else
                PUTARG(pptr[i], 0);
              deletePcode(i+1);
              nchanges++;
              break;

            case oLTZ   :
              if ((int32_t)GETARG(pptr[i]) < 0)
                PUTARG(pptr[i], -1);
              else
                PUTARG(pptr[i], 0);
              deletePcode(i+1);
              nchanges++;
              break;
                              
            case oGTEZ  :
              if ((int32_t)GETARG(pptr[i]) >= 0)
                PUTARG(pptr[i], -1);
              else
                PUTARG(pptr[i], 0);
              deletePcode(i+1);
              nchanges++;
              break;

            case oGTZ   :
              if (GETARG(pptr[i]) > 0)
                PUTARG(pptr[i], -1);
              else
                PUTARG(pptr[i], 0);
              deletePcode(i+1);
              nchanges++;
              break;
                                                                                         
            case oLTEZ :
              if (GETARG(pptr[i]) <= 0)
                PUTARG(pptr[i], -1);
              else
                PUTARG(pptr[i], 0);
              deletePcode(i+1);
              nchanges++;
              break;

              /*  Simplify comparisons with certain constants */

            case oEQU   :
              if (GETARG(pptr[i]) == 0)
                {
                  PUTOP(pptr[i+1],oEQUZ);
                  deletePcode(i);
                  nchanges++;
                } /* end if */
              else if (GETARG(pptr[i]) == 1)
                {
                  PUTOP(pptr[i], oDEC);
                  PUTARG(pptr[i], 0);
                  PUTOP(pptr[i+1], oEQUZ);
                  nchanges++;
                } /* end else if */
              else if ((int32_t)GETARG(pptr[i]) == -1)
                {
                  PUTOP(pptr[i], oINC);
                  PUTARG(pptr[i], 0);
                  PUTOP(pptr[i+1], oEQUZ);
                  nchanges++;
                } /* end else if */
              else i++;
              break;

            case oNEQ   :
              if (GETARG(pptr[i]) == 0)
                {
                  PUTOP(pptr[i+1], oNEQZ);
                  deletePcode(i);
                  nchanges++;
                } /* end if */
              else if (GETARG(pptr[i]) == 1)
                {
                  PUTOP(pptr[i], oDEC);
                  PUTARG(pptr[i], 0);
                  PUTOP(pptr[i+1], oNEQZ);
                  nchanges++;
                } /* end else if */
              else if ((int32_t)GETARG(pptr[i]) == -1)
                {
                  PUTOP(pptr[i], oINC);
                  PUTARG(pptr[i], 0);
                  PUTOP(pptr[i+1], oNEQZ);
                  nchanges++;
                } /* end else if */
              else i++;
              break;

            case oLT    :
              if (GETARG(pptr[i]) == 0)
                {
                  PUTOP(pptr[i+1], oLTZ);
                  deletePcode(i);
                  nchanges++;
                } /* end if */
              else if (GETARG(pptr[i]) == 1)
                {
                  PUTOP(pptr[i], oDEC);
                  PUTARG(pptr[i], 0);
                  PUTOP(pptr[i+1], oLTZ);
                  nchanges++;
                } /* end else if */
              else if ((int32_t)GETARG(pptr[i]) == -1)
                {
                  PUTOP(pptr[i], oINC);
                  PUTARG(pptr[i], 0);
                  PUTOP(pptr[i+1], oLTZ);
                  nchanges++;
                } /* end else if */
              else i++;
              break;

            case oGTE   :
              if (GETARG(pptr[i]) == 0)
                {
                  PUTOP(pptr[i+1], oGTEZ);
                  deletePcode(i);
                  nchanges++;
                } /* end if */
              else if (GETARG(pptr[i]) == 1)
                {
                  PUTOP(pptr[i], oDEC);
                  PUTARG(pptr[i], 0);
                  PUTOP(pptr[i+1], oGTEZ);
                  nchanges++;
                } /* end else if */
              else if ((int32_t)GETARG(pptr[i]) == -1)
                {
                  PUTOP(pptr[i], oINC);
                  PUTARG(pptr[i], 0);
                  PUTOP(pptr[i+1], oGTEZ);
                  nchanges++;
                } /* end else if */
              else i++;
              break;

            case oGT    :
              if (GETARG(pptr[i]) == 0)
                {
                  PUTOP(pptr[i+1], oGTZ);
                  deletePcode(i);
                  nchanges++;
                } /* end if */
              else if (GETARG(pptr[i]) == 1)
                {
                  PUTOP(pptr[i], oDEC);
                  PUTARG(pptr[i], 0);
                  PUTOP(pptr[i+1], oGTZ);
                  nchanges++;
                } /* end else if */
              else if ((int32_t)GETARG(pptr[i]) == -1)
                {
                  PUTOP(pptr[i], oINC);
                  PUTARG(pptr[i], 0);
                  PUTOP(pptr[i+1], oGTZ);
                  nchanges++;
                } /* end else if */
              else i++;
              break;

            case oLTE   :
              if (GETARG(pptr[i]) == 0)
                {
                  PUTOP(pptr[i+1], oLTEZ);
                  deletePcode(i);
                  nchanges++;
                } /* end if */
              else if (GETARG(pptr[i]) == 1)
                {
                  PUTOP(pptr[i], oDEC);
                  PUTARG(pptr[i], 0);
                  PUTOP(pptr[i+1], oLTEZ);
                  nchanges++;
                } /* end else if */
              else if ((int32_t)GETARG(pptr[i]) == -1)
                {
                  PUTOP(pptr[i], oINC);
                  PUTARG(pptr[i], 0);
                  PUTOP(pptr[i+1], oLTEZ);
                  nchanges++;
                } /* end else if */
              else i++;
              break;

              /* Simplify or delete condition branches on constants */

            case oJEQUZ :
              if (GETARG(pptr[i]) == 0)
                {
                  PUTOP(pptr[i+1], oJMP);
                  deletePcode(i);
                } /* end if */
              else 
                deletePcodePair(i, (i+1));
              nchanges++;
              break;

            case oJNEQZ :
              if (GETARG(pptr[i]) != 0)
                {
                  PUTOP(pptr[i+1], oJMP);
                  deletePcode(i);
                } /* end if */
              else 
                deletePcodePair(i, (i+1));
              nchanges++;
              break;

            case oJLTZ  :
              if ((int32_t)GETARG(pptr[i]) < 0)
                {
                  PUTOP(pptr[i+1], oJMP);
                  deletePcode(i);
                } /* end if */
              else 
                deletePcodePair(i, (i+1));
              nchanges++;
              break;

            case oJGTEZ :
              if ((int32_t)GETARG(pptr[i]) >= 0)
                {
                  PUTOP(pptr[i+1], oJMP);
                  deletePcode(i);
                } /* end if */
              else 
                deletePcodePair(i, (i+1));
              nchanges++;
              break;

            case oJGTZ  :
              if (GETARG(pptr[i]) > 0)
                {
                  PUTOP(pptr[i+1], oJMP);
                  deletePcode(i);
                } /* end if */
              else 
                deletePcodePair(i, (i+1));
              nchanges++;
              break;

            case oJLTEZ :
              if (GETARG(pptr[i]) <= 0)
                {
                  PUTOP(pptr[i+1], oJMP);
                  deletePcode(i);
                } /* end if */
              else 
                deletePcodePair(i, (i+1));
              nchanges++;
              break;

            default     :
              i++;
              break;
            } /* end switch */
        } /* end if */

          /* Delete multiple modifications of DSEG pointer */

      else if (GETOP(pptr[i]) == oINDS)
        {
          if (GETOP(pptr[i+1]) == oINDS)
            {
              PUTARG(pptr[i], GETARG(pptr[i] + GETARG(pptr[i+1])));
              deletePcode(i+1);
            } /* end if */
          else i++;
        } /* end else if */
      else i++;
    } /* end while */

  return (nchanges);

} /* end unaryOptimize */

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

int binaryOptimize(void)
{
  register int i;
  int nchanges = 0;

  TRACE(stderr, "[binaryOptimize]");

  /* At least two pcodes are needed to perform the following binary */
  /* operator optimizations */

  i = 0;
  while (i < nops-2)
    {
      if (GETOP(pptr[i]) == oPUSH)
        {
          if (GETOP(pptr[i+1]) == oPUSH)
            {
              switch (GETOP(pptr[i+2]))
                {
                case oADD :
                  PUTARG(pptr[i], GETARG(pptr[i]) + GETARG(pptr[i+1]));
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oSUB :
                  PUTARG(pptr[i], GETARG(pptr[i]) - GETARG(pptr[i+1]));
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oMUL :
                  PUTARG(pptr[i], GETARG(pptr[i]) * GETARG(pptr[i+1]));
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oDIV :
                  PUTARG(pptr[i], GETARG(pptr[i]) / (int32_t)GETARG(pptr[i+1]));
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oMOD :
                  PUTARG(pptr[i], GETARG(pptr[i]) % GETARG(pptr[i+1]));
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oSLL :
                  PUTARG(pptr[i], GETARG(pptr[i]) << GETARG(pptr[i+1]));
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oSRL :
                  PUTARG(pptr[i], GETARG(pptr[i]) >> GETARG(pptr[i+1]));
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oSRA :
                  PUTARG(pptr[i], (int32_t)GETARG(pptr[i]) >> GETARG(pptr[i+1]));
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oOR  :
                  PUTARG(pptr[i], GETARG(pptr[i]) | GETARG(pptr[i+1]));
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oAND :
                  PUTARG(pptr[i], GETARG(pptr[i]) & GETARG(pptr[i+1]));
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oEQU :
                  if (GETARG(pptr[i]) == GETARG(pptr[i+1]))
                    PUTARG(pptr[i], -1);
                  else
                    PUTARG(pptr[i], 0);
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oNEQ :
                  if (GETARG(pptr[i]) != GETARG(pptr[i+1]))
                    PUTARG(pptr[i], -1);
                  else
                    PUTARG(pptr[i], 0);
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oLT  :
                  if ((int32_t)GETARG(pptr[i]) < (int32_t)GETARG(pptr[i+1]))
                    PUTARG(pptr[i], -1);
                  else
                    PUTARG(pptr[i], 0);
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oGTE :
                  if ((int32_t)GETARG(pptr[i]) >= (int32_t)GETARG(pptr[i+1]))
                    PUTARG(pptr[i], -1);
                  else
                    PUTARG(pptr[i], 0);
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oGT  :
                  if ((int32_t)GETARG(pptr[i]) > (int32_t)GETARG(pptr[i+1]))
                    PUTARG(pptr[i], -1);
                  else
                    PUTARG(pptr[i], 0);
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                case oLTE :
                  if ((int32_t)GETARG(pptr[i]) <= (int32_t)GETARG(pptr[i+1]))
                    PUTARG(pptr[i], -1);
                  else
                    PUTARG(pptr[i], 0);
                  deletePcodePair((i+1), (i+2));
                  nchanges++;
                  break;

                default   :
                  i++;
                  break;
                } /* end switch */
            } /* end if */

          /* A single (constant) pcode is sufficient to perform the */
          /* following binary operator optimizations */

          else if ((GETOP(pptr[i+1]) == oLDSH) || (GETOP(pptr[i+1]) == oLDSB) ||
                   (GETOP(pptr[i+1]) == oLAS)  || (GETOP(pptr[i+1]) == oLAC))
            {
              switch (GETOP(pptr[i+2]))
                {
                case oADD :
                  if (GETARG(pptr[i]) == 0)
                    {
                      deletePcodePair(i, (i+2));
                      nchanges++;
                    } /* end if */
                  else if (GETARG(pptr[i]) == 1)
                    {
                      PUTOP(pptr[i+2], oINC);
                      deletePcode(i);
                      nchanges++;
                    } /* end else if */
                  else if (GETARG(pptr[i]) == ARGONES)
                    {
                      PUTOP(pptr[i+2], oDEC);
                      deletePcode(i);
                      nchanges++;
                    } /* end else if */
                  else i++;
                  break;

                case oSUB :
                  if (GETARG(pptr[i]) == 0)
                    {
                      PUTOP(pptr[i], oNEG);
                      deletePcode(i);
                      nchanges++;
                    } /* end if */
                  else i++;
                  break;

                case oMUL :
                  {
                    int32_t stmp32 = 0;
                    switch (GETARG(pptr[i]))
                      {
                      case 1 :
                        deletePcodePair(i, (i+2));
                        nchanges++;
                        break;
                      case 16384 : stmp32++;
                      case  8192 : stmp32++;
                      case  4096 : stmp32++;
                      case  2048 : stmp32++;
                      case  1024 : stmp32++;
                      case   512 : stmp32++;
                      case   256 : stmp32++;
                      case   128 : stmp32++;
                      case    64 : stmp32++;
                      case    32 : stmp32++;
                      case    16 : stmp32++;
                      case     8 : stmp32++;
                      case     4 : stmp32++;
                      case     2 : stmp32++;
                        PUTOP(pptr[i],    GETOP(pptr[i+1]));
                        PUTARG(pptr[i],   GETARG(pptr[i+1]));
                        PUTOP(pptr[i+1],  oPUSH);
                        PUTARG(pptr[i+1], stmp32);
                        PUTOP(pptr[i+2],  oSLL);
                        nchanges++;
                        i++;
                        break;

                      default :
                        i++;
                        break;
                      }
                  }
                  break;

                case oOR  :
                  if (GETARG(pptr[i]) == 0)
                    {
                      deletePcodePair(i, (i+2));
                      nchanges++;
                    } /* end if */
                  else i++;
                  break;

                case oAND :
                  if (GETARG(pptr[i]) == ARGONES)
                    {
                      deletePcodePair(i, (i+2));
                      nchanges++;
                    } /* end if */
                  else i++;
                  break;

                case oEQU :
                  if (GETARG(pptr[i]) == 0)
                    {
                      PUTOP(pptr[i+2], oEQUZ);
                      deletePcode(i);
                      nchanges++;
                    } /* end if */
                  else i++;
                  break;

                case oNEQ :
                  if (GETARG(pptr[i]) == 0)
                    {
                      PUTOP(pptr[i+2], oNEQZ);
                      deletePcode(i);
                      nchanges++;
                    } /* end if */
                  else i++;
                  break;

                case oLT  :
                  if (GETARG(pptr[i]) == 0)
                    {
                      PUTOP(pptr[i+2], oGTEZ);
                      deletePcode(i);
                      nchanges++;
                    } /* end if */
                  else i++;
                  break;

                case oGTE :
                  if (GETARG(pptr[i]) == 0)
                    {
                      PUTOP(pptr[i+2], oLTZ);
                      deletePcode(i);
                      nchanges++;
                    } /* end if */
                  else i++;
                  break;

                case oGT  :
                  if (GETARG(pptr[i]) == 0)
                    {
                      PUTOP(pptr[i+2], oLTEZ);
                      deletePcode(i);
                      nchanges++;
                    } /* end if */
                  else i++;
                  break;

                case oLTE :
                  if (GETARG(pptr[i]) == 0)
                    {
                      PUTOP(pptr[i+2], oGTZ);
                      deletePcode(i);
                      nchanges++;
                    } /* end if */
                  else i++;
                  break;

                default   :
                  i++;
                  break;

                } /* end switch */
            } /* end else if */
          else i++;
        } /* end if */

      /* Misc improvements on binary operators */

      else if (GETOP(pptr[i]) == oNEG)
        {
          /* Negation followed by add is subtraction */

          if (GETOP(pptr[i+1]) == oADD)
            {
              PUTOP(pptr[i+1], oSUB);
              deletePcode(i);
              nchanges++;
            }

          /* Negation followed by subtraction is addition */

          else if (GETOP(pptr[i]) == oSUB)
            {
              PUTOP(pptr[i+1], oADD);
              deletePcode(i);
              nchanges++;
            }
          else i++;
        }
      else i++;
    } /* end while */

  return (nchanges);

} /* end binaryOptimize */

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