/**********************************************************************
 * pgen.c
 * P-Code generation logic
 *
 *   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 <string.h>
#include <errno.h>

#include "config.h"   /* Configuration */
#include "keywords.h" /* Standard types */
#include "pasdefs.h"  /* Common types */
#include "ptdefs.h"   /* Token / symbol table definitions */
#include "podefs.h"   /* Logical opcode definitions */
#include "pedefs.h"   /* error code definitions */

#include "pas.h"      /* Global variables */
#include "poff.h"     /* For POFF file format */
#include "pofflib.h"  /* For poff*() functions*/
#include "pinsn.h"    /* (DEBUG only) */
#include "perr.h"     /* error() */

#include "pproc.h"    /* for actualParameterSize */
#include "pgen.h"     /* (to verify prototypes in this file) */

/**********************************************************************
 * Pre-processor Definitions
 **********************************************************************/

#define UNDEFINED_LEVEL (-1)
#define INVALID_PCODE   (-1)

#define LEVEL_DEFINED(l)  ((int32_t)(l) >= 0)
#define PCODE_VALID(p)    ((int32_t)(p) >= 0)

/**********************************************************************
 * Global Variables
 **********************************************************************/

/**********************************************************************
 * Private Variables
 **********************************************************************/

static int32_t  g_currentStackLevelReference  = UNDEFINED_LEVEL;
static uint32_t g_nStackLevelReferenceChanges = 0;

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

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

/***********************************************************************/
/* Generate a stack reference opcode to a global variable residing at
 * static nesting level zero.
 */

static void
pas_GenerateLevel0StackReference(enum pcode_e eOpCode, STYPE *pVar)
{
  /* Sanity checking.  Double check nesting level and also since this is
   * a level zero reference, then the offset must be positive
   */

  if ((pVar->sLevel != 0) || (pVar->sParm.v.offset < 0))
    {
      error(eHUH);
    }
  else
    {
      /* Generate the P-code */

      insn_GenerateDataOperation(eOpCode, pVar->sParm.v.offset);

      /* If the variable is undefined, also generate a relocation
       * record.
       */

      if ((pVar->sParm.v.flags & SVAR_EXTERNAL) != 0)
        {
          (void)poffAddRelocation(poffHandle, RLT_LDST,
                                  pVar->sParm.v.symIndex, 0);
        }
    }
}

/***********************************************************************/
/* There are some special P-codes for accessing stack data at static
 * nesting level 0.  Check if the specified opcode is one of those.  If
 * so, return the mapped opcode.  Otherwise, return INVALID_PCODE.
 */

static int32_t
pas_GetLevel0Opcode(enum pcode_e eOpCode)
{
  switch (eOpCode)
    {
    case opLDS:   return opLD;
    case opLDSH:  return opLDH;
    case opLDSB:  return opLDB;
    case opLDSM:  return opLDM;
    case opSTS:   return opST;
    case opSTSB:  return opSTB;
    case opSTSM:  return opSTM;
    case opLDSX:  return opLDX;
    case opLDSXB: return opLDXB;
    case opLDSXM: return opLDXM;
    case opSTSX:  return opSTX;
    case opSTSXB: return opSTXB;
    case opSTSXM: return opSTXM;
    case opLAS:   return opLA;
    case opLASX:  return opLAX;
    default:      return INVALID_PCODE;
    }
}

/***********************************************************************/
/* A new static nesting level has been encountered.  Check if we need
 * to reset the level stack pointer (LSP) register (assuming that the
 * architecutre has one).
 */

static void
pas_SetLevelStackPointer(uint32_t dwLevel)
{
  if (dwLevel != g_currentStackLevelReference)
    {
      /* Set the level stack pointer (LSP) register */

      insn_SetStackLevel(dwLevel);

      /* Remember the setting so that we do not reset the LSP until
       * the level changes (or it is invalidated).
       */

      g_currentStackLevelReference = dwLevel;
      g_nStackLevelReferenceChanges++;
    }
}

/***********************************************************************
 * Public Functions
 ***********************************************************************/

/***********************************************************************/
/* Return the current setting of the level stack pointer (LSP) register
 * -- assuming that the underlying architecure may have one.
 */

int32_t pas_GetCurrentStackLevel(void)
{
  return g_currentStackLevelReference;
}

/***********************************************************************/
/* Invalidate the current stack level register setting.  This will cause
 * us to reset the LSP when the next stack level reference is encountered.
 */

void pas_InvalidateCurrentStackLevel(void)
{
  g_currentStackLevelReference = UNDEFINED_LEVEL;
  g_nStackLevelReferenceChanges++;
}

/***********************************************************************/
/* Set the stack level pointer to known value.  This is done when in
 * while and for loop processing.  The value of the LSP will be that
 * as sampled at the top of the lop not necessarily the value at the
 * bottom of the loop.
 */

void pas_SetCurrentStackLevel(int32_t dwLsp)
{
  g_currentStackLevelReference = dwLsp;
  g_nStackLevelReferenceChanges++;
}

/***********************************************************************/
/* Get the number of changes made to the level stack pointer.  This is
 * useful by compiler logic to determine if the stack level pointer was
 * ever changed by any logic path.
 */

uint32_t pas_GetNStackLevelChanges(void)
{
  return g_nStackLevelReferenceChanges;
}

/***********************************************************************/
/* Generate the most simple of all P-codes */

void pas_GenerateSimple(enum pcode_e eOpCode)
{
  insn_GenerateSimple(eOpCode);
}

/***********************************************************************/
/* Generate a P-code with a single data argument */

void pas_GenerateDataOperation(enum pcode_e eOpCode, int32_t dwData)
{
  insn_GenerateDataOperation(eOpCode, dwData);
}

/***********************************************************************/
/* This function is called just before a multiple register operation is
 * is generated.  This should generate logic to specify the size of the
 * multiple register operation (in bytes, not registers). This may translate
 * into different operations on different architectures.  Typically,
 * this would generate a push of the size onto the stack or, perhaps,
 * setting of a dedicated count register.
 */

void pas_GenerateDataSize(int32_t dwDataSize)
{
  insn_GenerateDataSize(dwDataSize);
}

/***********************************************************************/
/* Generate a floating point operation */

void pas_GenerateFpOperation(uint8_t fpOpcode)
{
  insn_GenerateFpOperation(fpOpcode);
}

/***********************************************************************/
/* Generate an IO operation */

void pas_GenerateIoOperation(uint16_t ioOpcode, uint16_t fileNumber)
{
  insn_GenerateIoOperation(ioOpcode, fileNumber);
}

/***********************************************************************/
/* Generate a psuedo call to a built-in, standard pascal function */

void pas_BuiltInFunctionCall(uint16_t libOpcode)
{
  insn_BuiltInFunctionCall(libOpcode);
}

/***********************************************************************/
/* Generate a reference to data on the data stack using the specified
 * level and offset.
 */

void pas_GenerateLevelReference(enum pcode_e eOpCode, uint16_t wLevel,
                                int32_t dwOffset)
{
  /* Is this variable declared at level 0 (i.e., it has global scope)
   * that is being offset via a nesting level?
   */

  if (wLevel == 0)
    {
      int32_t level0Opcode = pas_GetLevel0Opcode(eOpCode);
      if (PCODE_VALID(level0Opcode))
        {
          insn_GenerateDataOperation(level0Opcode, dwOffset);
          return;
        }
    }

  /* We get here if the reference is at some static nesting level
   * other that zero OR if there is no special PCode to reference
   * data at static nesting level 0 for this operation.
   *
   * Check if we have to change the level stack pointer (LSP) register
   * (assuming that the architecture has one).
   */

  pas_SetLevelStackPointer(wLevel);

  /* Then generate the opcode passing the level in the event that the
   * architecture does not have an LSP.
   */

  insn_GenerateLevelReference(eOpCode, wLevel, dwOffset);
}

/***********************************************************************/
/* Generate a stack reference opcode, handling references to undefined
 * stack offsets.
 */

void pas_GenerateStackReference(enum pcode_e eOpCode, STYPE *pVar)
{
  /* Is this variable declared at level 0 (i.e., it has global scope)
   * that is being offset via a nesting level?
   */

  if (pVar->sLevel == 0)
    {
      int32_t level0Opcode = pas_GetLevel0Opcode(eOpCode);
      if (PCODE_VALID(level0Opcode))
        {
          pas_GenerateLevel0StackReference(level0Opcode, pVar);
          return;
        }
    }

  /* We get here if the reference is at some static nesting level
   * other that zero OR if there is no special PCode to reference
   * data at static nesting level 0 for this operation.
   *
   * Check if we have to change the level stack pointer (LSP) register
   * (assuming that the architecture has one).
   */

  pas_SetLevelStackPointer(pVar->sLevel);

  /* Generate the P-Code at the defined offset and with the specified
   * static level offset (in case that the architecture does not have
   * an LSP)
   */

  insn_GenerateLevelReference(eOpCode, (level - pVar->sLevel),
                              pVar->sParm.v.offset);
}

/***********************************************************************/
/* Generate a procedure call and an associated relocation record if the
 * called procedure is external.
 */

void
pas_GenerateProcedureCall(STYPE *pProc)
{
  /* sLevel is the level at which the procedure was declared.  We need
   * to set the SLP to this value prior to the call (on some architectures
   * where the SLP is pushed onto the stack by the procedure
   * call).
   */

  pas_SetLevelStackPointer(pProc->sLevel);

  /* Then generate the procedure call (passing the level again for those
   * architectures that do not support the SLP.
   */

  insn_GenerateProcedureCall(pProc->sLevel, pProc->sParm.p.label);

  /* If the variable is undefined, also generate a relocation
   * record.
   */

#if 0 /* Not yet */
  if ((pVar->sParm.p.flags & SVAR_EXTERNAL) != 0)
    {
      /* For now */
# error "Don't know what last parameter should be"
      (void)poffAddRelocation(poffHandle, RLT_PCAL,
                              pVar->sParm.p.symIndex,
                              0);
    }
#endif

  /* Any logic after the procedure/function call return must assume
   * that the last level reference is unknown.
   */

  pas_InvalidateCurrentStackLevel();
}

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

void pas_GenerateLineNumber(uint16_t wIncludeNumber, uint32_t dwLineNumber)
{
  insn_GenerateLineNumber(wIncludeNumber, dwLineNumber);
}

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

void pas_GenerateDebugInfo(STYPE *pProc, uint32_t dwReturnSize)
{
  int i;

  /* Allocate a container to pass the proc information to the library */

  uint32_t nparms                      = pProc->sParm.p.nParms;
  poffLibDebugFuncInfo_t *pContainer = poffCreateDebugInfoContainer(nparms);

  /* Put the proc information into the container */

  pContainer->value   = pProc->sParm.p.label;
  pContainer->retsize = dwReturnSize;
  pContainer->nparms  = nparms;

  /* Add the argument information to the container */

   for (i = 0; i < nparms; i++)
     {
       pContainer->argsize[i] = actualParameterSize(pProc, i+1);
     }

   /* Add the contained information to the library */

   poffAddDebugFuncInfo(poffHandle, pContainer);

   /* Release the container */

   poffReleaseDebugFuncContainer(pContainer);
}

/***********************************************************************/
/* Generate description of a level 0 stack variable that can be
 * exported by a unit.
 */

void pas_GenerateStackExport(STYPE *pVar)
{
  poffLibSymbol_t symbol;

#if CONFIG_DEBUG
  /* Get the parent type of the variable */

  STYPE *typePtr = pVar->sParm.v.parent;

  /* Perform some sanity checking:
   * - Must have a parent type
   * - Must not be declared external
   * - Must be declared at static nesting level zero
   */

  if ((!typePtr) ||
      ((pVar->sParm.v.flags & SVAR_EXTERNAL) != 0) ||
      (pVar->sLevel != 0))
    {
      error(eSYMTABINTERNAL);
    }
#endif

  /* Create the symbol structure */

  symbol.type  = STT_DATA;
  symbol.align = STA_8BIT; /* for now */
  symbol.flags = STF_NONE;
  symbol.name  = pVar->sName;
  symbol.value = pVar->sParm.v.offset;
  symbol.size  = pVar->sParm.v.size;

  /* Add the symbol to the symbol table */

  (void)poffAddSymbol(poffHandle, &symbol);
}

/***********************************************************************/
/* Generate description of a level 0 stack variable that must be
 * imported by a program or unit from a unit.
 */

void pas_GenerateStackImport(STYPE *pVar)
{
  poffLibSymbol_t symbol;

#if CONFIG_DEBUG
  /* Get the parent type of the variable */

  STYPE *typePtr = pVar->sParm.v.parent;

  /* Perform some sanity checking
   * - Must have a parent type
   * - Must be declared external
   * - Must be declared at static nesting level zero
   */

  if ((!typePtr) ||
      ((pVar->sParm.v.flags & SVAR_EXTERNAL) == 0) ||
      (pVar->sLevel != 0))
    {
      error(eSYMTABINTERNAL);
    }
#endif

  /* Create the symbol structure */

  symbol.type  = STT_DATA;
  symbol.align = STA_8BIT; /* for now */
  symbol.flags = STF_UNDEFINED;
  symbol.name  = pVar->sName;
  symbol.value = pVar->sParm.v.offset; /* for now */
  symbol.size  = pVar->sParm.v.size;

  /* Add the symbol to the symbol table */

  pVar->sParm.v.symIndex = poffAddSymbol(poffHandle, &symbol);
}

/***********************************************************************/
/* Generate description of a level 0 procedure or function that can be
 * exported by a unit.
 */

void pas_GenerateProcExport(STYPE *pProc)
{
  poffLibSymbol_t symbol;

#if CONFIG_DEBUG
  /* Get the parent type of the function (assuming it is a function) */

  STYPE *typePtr = pProc->sParm.p.parent;

  /* Perform some sanity checking */

  /* Check for a function reference which must have a valid parent type */

  if ((pProc->sKind == sFUNC) && (typePtr != NULL));

  /* Check for a procedure reference which must not have a valid type */

  else  if ((pProc->sKind == sPROC) && (typePtr == NULL));

  /* Anything else is an error */

  else
    error(eSYMTABINTERNAL);

  /* The function / procedure should NOT be declared external and
   * only procedures declared at static nesting level zero can
   * be exported.
   */

  if (((pProc->sParm.p.flags & SPROC_EXTERNAL) != 0) ||
      (pProc->sLevel != 0))
    error(eSYMTABINTERNAL);
#endif

  /* Everthing looks okay.  Create the symbol structure */

  if (pProc->sKind == sPROC)
    symbol.type  = STT_PROC;
  else
    symbol.type  = STT_FUNC;

  symbol.align = STA_NONE;
  symbol.flags = STF_NONE;
  symbol.name  = pProc->sName;
  symbol.value = pProc->sParm.p.label;
  symbol.size  = 0;

  /* Add the symbol to the symbol table */

  (void)poffAddSymbol(poffHandle, &symbol);
}

/***********************************************************************/
/* Generate description of a level 0 procedure or function that must be
 * imported by a program or unit from a unit.
 */

void pas_GenerateProcImport(STYPE *pProc)
{
  poffLibSymbol_t symbol;

#if CONFIG_DEBUG
  /* Get the parent type of the function (assuming it is a function) */

  STYPE *typePtr = pProc->sParm.p.parent;

  /* Perform some sanity checking */

  /* Check for a function reference which must have a valid parent type */

  if ((pProc->sKind == sFUNC) && (typePtr != NULL));

  /* Check for a procedure reference which must not have a valid type */

  else  if ((pProc->sKind == sPROC) && (typePtr == NULL));

  /* Anything else is an error */

  else
    error(eSYMTABINTERNAL);

  /* The function / procedure should also be declared external and
   * only procedures declared at static nesting level zero can
   * be exported.
   */

  if (((pProc->sParm.p.flags & SPROC_EXTERNAL) == 0) ||
      (pProc->sLevel != 0))
    error(eSYMTABINTERNAL);
#endif

  /* Everthing looks okay.  Create the symbol structure */

  if (pProc->sKind == sPROC)
    symbol.type  = STT_PROC;
  else
    symbol.type  = STT_FUNC;

  symbol.align = STA_NONE;
  symbol.flags = STF_UNDEFINED;
  symbol.name  = pProc->sName;
  symbol.value = pProc->sParm.p.label;
  symbol.size  = 0;

  /* Add the symbol to the symbol table */

  pProc->sParm.p.symIndex = poffAddSymbol(poffHandle, &symbol);
}
