/**********************************************************************
 *  psopt.c
 *  String Stack Optimizaitons
 *
 *   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.
 *
 **********************************************************************/

/* The statement generation logic generates a PUSHS and POPS around
 * every statement.  These instructions save and restore the string
 * stack pointer registers.  However, only some statements actually
 * modify the string stack.  So the first major step in the optimatization
 * process is to retain only PUSHS and POPS statements that are
 * actually required.
 */

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

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

#include "keywords.h"
#include "pdefs.h"
#include "pedefs.h"
#include "pinsn16.h"
#include "pxdefs.h"

#include "popt.h"
#include "psopt.h"

/**********************************************************************
 * Definitions
 **********************************************************************/

#define PBUFFER_SIZE 1024
#define NPBUFFERS       8

/**********************************************************************
 * Private Data
 **********************************************************************/

static uint8_t *pbuffer[NPBUFFERS];
static int      nbytes_in_pbuffer[NPBUFFERS];
static int      current_level = -1;
static int      inch;

/**********************************************************************
 * Private Function Prototypes
 **********************************************************************/

static inline void putbuf(int c, poffProgHandle_t poffProgHandle);
static inline void flushc(int c, poffProgHandle_t poffProgHandle);
static inline void flushbuf(poffProgHandle_t poffProgHandle);
static void dopush(poffHandle_t poffHandle, poffProgHandle_t poffProgHandle);
static void dopop(poffHandle_t poffHandle, poffProgHandle_t poffProgHandle);

/**********************************************************************
 * Private Inline Functions
 **********************************************************************/

static inline void putbuf(int c, poffProgHandle_t poffProgHandle)
{
  int dlvl = current_level;

  if (dlvl < 0)
    {
      /* No PUSHS encountered.  Write byte directly to output */

      poffAddTmpProgByte(poffProgHandle, (uint8_t)c);
    }
  else
    {
      /* PUSHS encountered.  Write byte into buffer associated with
       * nesting level.
       */

      int idx                 = nbytes_in_pbuffer[dlvl];
      uint8_t *dest             = pbuffer[dlvl] + idx;
      *dest                   = c;
      nbytes_in_pbuffer[dlvl] = idx + 1;
    }
}

static inline void flushc(int c, poffProgHandle_t poffProgHandle)
{
  if (current_level > 0)
    {
      /* Nested PUSHS encountered. Write byte into buffer associated
       * with the previous nesting level.
       */

      int dlvl                = current_level - 1;
      int idx                 = nbytes_in_pbuffer[dlvl];
      uint8_t *dest             = pbuffer[dlvl] + idx;
      *dest                   = c;
      nbytes_in_pbuffer[dlvl] = idx + 1;
    }
  else
    {
      /* Only one PUSHS encountered.  Write directly to the output
       * buffer
       */

      poffAddTmpProgByte(poffProgHandle, (uint8_t)c);
    }
}

static inline void flushbuf(poffProgHandle_t poffProgHandle)
{
  uint16_t errCode;
  int slvl = current_level;

  if (nbytes_in_pbuffer[slvl] > 0)
    {
      if (current_level > 0)
        {
          /* Nested PUSHS encountered. Flush buffer into buffer associated
           * with the previous nesting level.
           */

          int dlvl    = slvl - 1;
          uint8_t *src  = pbuffer[slvl];
          uint8_t *dest = pbuffer[dlvl] + nbytes_in_pbuffer[dlvl];

          memcpy(dest, src, nbytes_in_pbuffer[slvl]);
          nbytes_in_pbuffer[dlvl] += nbytes_in_pbuffer[slvl];
        }
      else
        {
          /* Only one PUSHS encountered.  Flush directly to the output
           * buffer
           */

          errCode = poffWriteTmpProgBytes(pbuffer[0], nbytes_in_pbuffer[0],
                                          poffProgHandle);

          if (errCode != eNOERROR)
            {
              printf("Error writing to file: %d\n", errCode);
              exit(1);
            }
        }
    }
  nbytes_in_pbuffer[slvl] = 0;
}

/**********************************************************************
 * Private Functions
 **********************************************************************/

static void dopush(poffHandle_t poffHandle, poffProgHandle_t poffProgHandle)
{
  int opcode;

  while (inch != EOF)
    {
      /* Search for a PUSHS opcode */

      if (inch != oPUSHS)
        {
          /* Its not PUSHS, just echo to the output file/buffer */

          putbuf(inch, poffProgHandle);

          /* Get the next byte from the input stream */

          opcode = inch;
          inch = poffGetProgByte(poffHandle);

          /* Check for an 8-bit argument */

          if ((opcode & o8) != 0)
            {
              /* Echo the 8-bit argument */

              putbuf(inch, poffProgHandle);
              inch = poffGetProgByte(poffHandle);
            }

          /* Check for a 16-bit argument */

          if ((opcode & o16) != 0)
            {
              /* Echo the 16-bit argument */

              putbuf(inch, poffProgHandle);
              inch = poffGetProgByte(poffHandle);
              putbuf(inch, poffProgHandle);
              inch = poffGetProgByte(poffHandle);
            }
        }
      else
        {
          /* We have found PUSHS.  No search for the next occurrence
           * of either and instruction that increments the string
           * stack or for the matching POPS
           */

          current_level++;
          dopop(poffHandle, poffProgHandle);
          current_level--;
        }
    }
}

static void dopop(poffHandle_t poffHandle, poffProgHandle_t poffProgHandle)
{
  int opcode;
  int arg16;
  int arg16a;
  int arg16b;

  /* We have found PUSHS.  No search for the next occurrence
   * of either and instruction that increments the string
   * stack or for the matching POPS
   */

  /* Skip over the PUSHS for now */

  inch = poffGetProgByte(poffHandle);

  while (inch != EOF)
    {
      /* Did we encounter another PUSHS? */

      if (inch == oPUSHS)
        {
          /* Yes... recurse to handle it */

          current_level++;
          dopop(poffHandle, poffProgHandle);
          current_level--;
        }

      else if (inch == oPOPS)
        {
          /* Flush the buffered data without the PUSHS */

          flushbuf(poffProgHandle);

          /* And discard the matching POPS */

          inch = poffGetProgByte(poffHandle);
          break;
        }
      else if (inch == oLIB)
        {
          /* Get the 16-bit argument */

          putbuf(inch, poffProgHandle);
          arg16a = poffGetProgByte(poffHandle);
          putbuf(arg16a, poffProgHandle);
          arg16b = poffGetProgByte(poffHandle);
          putbuf(arg16b, poffProgHandle);
          arg16  = (arg16a << 8) | arg16b;
          inch   = poffGetProgByte(poffHandle);

          /* Is it LIB MKSTK? MKSTKSTR? or MKSTKC? */

          if ((arg16 == lbMKSTK) ||
              (arg16 == lbMKSTKSTR) ||
              (arg16 == lbMKSTKC))
            {
              /* Flush the buffered data with the PUSHS */

              flushc(oPUSHS, poffProgHandle);
              flushbuf(poffProgHandle);

              /* And break out of the loop to search for
               * the next PUSHS
               */

              break;
            }
        }
      else
        {
          /* Something else.  Put it in the buffer */

          putbuf(inch, poffProgHandle);

          /* Get the next byte from the input stream */

          opcode = inch;
          inch = poffGetProgByte(poffHandle);

          /* Check for an 8-bit argument */

          if ((opcode & o8) != 0)
            {
              /* Buffer the 8-bit argument */

              putbuf(inch, poffProgHandle);
              inch = poffGetProgByte(poffHandle);
            }

          /* Check for a 16-bit argument */

          if ((opcode & o16) != 0)
            {
              /* Buffer the 16-bit argument */

              putbuf(inch, poffProgHandle);
              inch = poffGetProgByte(poffHandle);
              putbuf(inch, poffProgHandle);
              inch = poffGetProgByte(poffHandle);
            }
        }
    }
}

/**********************************************************************
 * Global Functions
 **********************************************************************/

void stringStackOptimize(poffHandle_t poffHandle,
                         poffProgHandle_t poffProgHandle)
{
  int i;

  /* Allocate an array of buffers to hold pcode data */

  for (i = 0; i < NPBUFFERS; i++)
    {
      pbuffer[i] = (uint8_t*)malloc(PBUFFER_SIZE);
      if (pbuffer[i] == NULL)
        {
          printf("Failed to allocate pcode buffer\n");
          exit(1);
        }
      nbytes_in_pbuffer[i] = 0;
    }

  /* Prime the search logic */

  inch = poffGetProgByte(poffHandle);
  current_level = -1;

  /* And parse the input file to the output file, removing unnecessary string
   * stack operations.
   */

  dopush(poffHandle, poffProgHandle);

  /* Release the buffers */

  for (i = 0; i < NPBUFFERS; i++)
    {
      free(pbuffer[i]);
      pbuffer[i] = NULL;
      nbytes_in_pbuffer[i] = 0;
    }
}

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