//
// coder.cpp
// Code generation implementation
// F.J. Alberti
//
// FA   17/05/2001   Alignment
// FA   07/08/2001   Implement writeString() method
// FA   12/11/2001   Replace SCOL_DEBUGGER_AWARE by INCLUDE_DEBUGGER
// FA   22/11/2001   Bug correction: backPatch() did not test the writer state!
//                   (BackPatch() method was moved here from header file)
//
// Remark:
//   Review error management
//

#include "compiler/coder.h"
#include "opcode.h"
#include "align.h"
#include <string.h>


// Writer state constants
const bool BytecodeStream::kWriterStateEnabled  = true;
const bool BytecodeStream::kWriterStateDisabled = false;

// Bytecode stream capacity
const uint32 kCodeCapacity = 128*1024; // 128k

// The main bytecode stream
BytecodeStream theCode(kCodeCapacity);


// Restrictions
static const uint kMaxOffset     = 16777216;   // 2^24
static const uint kMaxFields     = 256;        // 2^8
static const uint kMaxLocals     = 256;        // 2^8
static const uint kMaxReferences = 65536;      // 2^16
static const uint kMaxTupleSize  = 256;        // 2^8
static const uint kMaxArraySize  = 1073741823; // 2^30-1 (the largest positive SCOL integer)



#if defined(SCOL_DISASSEMBLER)
#define BYTECODE(mnemonic, operandType)  { mnemonic, operandType }
#else
#define BYTECODE(mnemonic, operandType)  { operandType }
#endif

// Bytecode operand types
enum OperandType {
  kOperandNone,     // no direct operand
  kOperandByte,
  kOperandInt16,    
  kOperandInt32,
  kOperandFloat,
  kOperandOffset,
  kOperandString
};

const struct {
#if defined(SCOL_DISASSEMBLER)
  const char* mnemonic;                // opcode mnemonic
#endif
  OperandType operandType;             // type of operand (see above)
} bytecode[] = {
  /* 0x00 */
  BYTECODE("nop",        kOperandNone  ),
  BYTECODE("drop",       kOperandNone  ),
  BYTECODE("dup",        kOperandNone  ),
  BYTECODE("pushnil",    kOperandNone  ),
  BYTECODE("goto",       kOperandOffset),
  BYTECODE("iftrue",     kOperandOffset),
  BYTECODE("iffalse",    kOperandOffset),
  BYTECODE("execcall",   kOperandNone  ),
  BYTECODE("call",       kOperandNone  ),
  BYTECODE("tailcall",   kOperandNone  ),
  BYTECODE("callnative", kOperandNone  ),
  BYTECODE("return",     kOperandNone  ),
  BYTECODE("newfun",     kOperandNone  ),
  BYTECODE("pushstr",    kOperandString),
  BYTECODE("getlocal",   kOperandByte  ),
  BYTECODE("getlocal0",  kOperandNone  ),
  /* 0x10 */
  BYTECODE("getlocal1",  kOperandNone  ),
  BYTECODE("getlocal2",  kOperandNone  ),
  BYTECODE("getlocal3",  kOperandNone  ),
  BYTECODE("getlocal4",  kOperandNone  ),
  BYTECODE("getlocal5",  kOperandNone  ),
  BYTECODE("getlocal6",  kOperandNone  ),
  BYTECODE("getlocal7",  kOperandNone  ),
  BYTECODE("setlocal",   kOperandByte  ),
  BYTECODE("setlocal0",  kOperandNone  ),
  BYTECODE("setlocal1",  kOperandNone  ),
  BYTECODE("setlocal2",  kOperandNone  ),
  BYTECODE("setlocal3",  kOperandNone  ),
  BYTECODE("setlocal4",  kOperandNone  ),
  BYTECODE("setlocal5",  kOperandNone  ),
  BYTECODE("setlocal6",  kOperandNone  ),
  BYTECODE("setlocal7",  kOperandNone  ),
  /* 0x20 */
  BYTECODE("getglobal",  kOperandInt16 ),
  BYTECODE("getglobal0", kOperandNone  ),
  BYTECODE("getglobal1", kOperandNone  ),
  BYTECODE("getglobal2", kOperandNone  ),
  BYTECODE("getglobal3", kOperandNone  ),
  BYTECODE("getglobal4", kOperandNone  ),
  BYTECODE("getglobal5", kOperandNone  ),
  BYTECODE("getglobal6", kOperandNone  ),
  BYTECODE("getglobal7", kOperandNone  ),
  BYTECODE("setglobal",  kOperandInt16 ),
  BYTECODE("setglobal0", kOperandNone  ),
  BYTECODE("setglobal1", kOperandNone  ),
  BYTECODE("setglobal2", kOperandNone  ),
  BYTECODE("setglobal3", kOperandNone  ),
  BYTECODE("setglobal4", kOperandNone  ),
  BYTECODE("setglobal5", kOperandNone  ),
  /* 0x30 */
  BYTECODE("setglobal6", kOperandNone  ),
  BYTECODE("setglobal7", kOperandNone  ),
  BYTECODE("newarray",   kOperandNone  ),
  BYTECODE("arraysize",  kOperandNone  ),
  BYTECODE("arrayget",   kOperandNone  ),
  BYTECODE("arrayset",   kOperandNone  ),
  BYTECODE("arraycheck", kOperandNone  ),
  BYTECODE("newtuple",   kOperandByte  ),
  BYTECODE("newtuple1",  kOperandNone  ),
  BYTECODE("newtuple2",  kOperandNone  ),
  BYTECODE("getfield",   kOperandByte  ),
  BYTECODE("getfield0",  kOperandNone  ),
  BYTECODE("getfield1",  kOperandNone  ),
  BYTECODE("getfield2",  kOperandNone  ),
  BYTECODE("getfield3",  kOperandNone  ),
  BYTECODE("getfield4",  kOperandNone  ),
  /* 0x40 */
  BYTECODE("getfield5",  kOperandNone  ),
  BYTECODE("getfield6",  kOperandNone  ),
  BYTECODE("getfield7",  kOperandNone  ),
  BYTECODE("setfield",   kOperandByte  ),
  BYTECODE("setfield0",  kOperandNone  ),
  BYTECODE("setfield1",  kOperandNone  ),
  BYTECODE("setfield2",  kOperandNone  ),
  BYTECODE("setfield3",  kOperandNone  ),
  BYTECODE("setfield4",  kOperandNone  ),
  BYTECODE("setfield5",  kOperandNone  ),
  BYTECODE("setfield6",  kOperandNone  ),
  BYTECODE("setfield7",  kOperandNone  ),
  BYTECODE("pushint",    kOperandInt32 ),
  BYTECODE("pushint0",   kOperandNone  ),
  BYTECODE("pushint1",   kOperandNone  ),
  BYTECODE("pushint2",   kOperandNone  ),
  /* 0x50 */
  BYTECODE("pushbyte",   kOperandByte  ),
  BYTECODE("intadd",     kOperandNone  ),
  BYTECODE("intadd1",    kOperandNone  ),
  BYTECODE("intsub",     kOperandNone  ),
  BYTECODE("intsub1",    kOperandNone  ),
  BYTECODE("intmul",     kOperandNone  ),
  BYTECODE("intdiv",     kOperandNone  ),
  BYTECODE("intmod",     kOperandNone  ),
  BYTECODE("intneg",     kOperandNone  ),
  BYTECODE("intand",     kOperandNone  ),
  BYTECODE("intor",      kOperandNone  ),
  BYTECODE("intxor",     kOperandNone  ),
  BYTECODE("intnot",     kOperandNone  ),
  BYTECODE("intshl",     kOperandNone  ),
  BYTECODE("intshr",     kOperandNone  ),
  BYTECODE("inteq",      kOperandNone  ),
  /* 0x60 */
  BYTECODE("intne",      kOperandNone  ),
  BYTECODE("intlt",      kOperandNone  ),
  BYTECODE("intgt",      kOperandNone  ),
  BYTECODE("intle",      kOperandNone  ),
  BYTECODE("intge",      kOperandNone  ),
  BYTECODE("booland",    kOperandNone  ),
  BYTECODE("boolor",     kOperandNone  ),
  BYTECODE("boolnot",    kOperandNone  ),
  BYTECODE("pushfloat",  kOperandFloat ),
  BYTECODE("pushfloat0", kOperandNone  ),
  BYTECODE("pushfloat1", kOperandNone  ),
  BYTECODE("int2float",  kOperandNone  ),
  BYTECODE("float2int",  kOperandNone  ),
  BYTECODE("floatadd",   kOperandNone  ),
  BYTECODE("floatsub",   kOperandNone  ),
  BYTECODE("floatmul",   kOperandNone  ),
  /* 0x70 */
  BYTECODE("floatdiv",   kOperandNone  ),
  BYTECODE("floatneg",   kOperandNone  ),
  BYTECODE("floateq",    kOperandNone  ),
  BYTECODE("floatne",    kOperandNone  ),
  BYTECODE("floatlt",    kOperandNone  ),
  BYTECODE("floatgt",    kOperandNone  ),
  BYTECODE("floatle",    kOperandNone  ),
  BYTECODE("floatge",    kOperandNone  ),
  BYTECODE("newcomm",    kOperandNone  ),
  BYTECODE("newcommvar", kOperandNone  ),
  BYTECODE("break",      kOperandNone  ),
  BYTECODE("assert",     kOperandInt32 )
};


// BytecodeStream method implementation

BytecodeStream::BytecodeStream(uint32 capacity)
  : _capacity   (capacity), 
    _offset     (0), 
    _lastOffset (0), 
    _state      (kWriterStateEnabled), 
    err         (MERROK)
{
  if ((_stream = new byte[_capacity]) == 0)
    err = MERRMEM;
}


BytecodeStream::~BytecodeStream()
{
}


int BytecodeStream::_check()
{
  if (_offset >= _capacity) {
    MMechostr(MSKRUNTIME, "(!) The temporary bytecode pool is not sufficiently \
      large (%d bytes, by default)\n", kCodeCapacity);
    err = MERRMEM;
    return err; 
  }
  return MERROK;
}


uint32 BytecodeStream::writeByte(byte n)
{
  if (_check() || _state == kWriterStateDisabled)
    return 0;
  _stream[_offset] = n;
  _lastOffset = _offset;
  _offset += ALIGNMENT(1);
  return _lastOffset;
}


uint32 BytecodeStream::writeInt16(uint16 n)
{
  if (_check() || _state == kWriterStateDisabled)
    return 0;
  *(uint16*)&_stream[_offset] = n;
  _lastOffset = _offset;
  _offset += ALIGNMENT(2);
  return _lastOffset;
}


uint32 BytecodeStream::writeInt32(uint32 n)
{
  if (_check() || _state == kWriterStateDisabled)
    return 0;
  *(uint32*)&_stream[_offset] = n;
  _lastOffset = _offset;
  _offset += ALIGNMENT(4);
  return _lastOffset;
}


uint32 BytecodeStream::writeFloat(float32 x)
{
  if (_check() || _state == kWriterStateDisabled)
    return 0;
  *(float32*)&_stream[_offset] = x;
  _lastOffset = _offset;
  _offset += ALIGNMENT(4);
  return _lastOffset;  
}


uint32 BytecodeStream::writeString(const char* s)
{
  // This is not very efficient: if ALIGNMENT is 4 (as in PS2), each
  // individual character is coded as a word!
  uint32 beg = writeHole(); // write 32-bit length
  if (err) 
    return 0;
  uint32 len = 0;
  while (*s) {
    writeByte(*s++);
    if (err) 
      return 0;
    len++;
  }
  backPatch(beg, len);
  return beg;
}


void BytecodeStream::backPatch(uint32 offset, uint32 n)
{
  //assert(offset >= 0 && offset < size()-sizeof(uint32));
  if (_state == kWriterStateEnabled)
    *(uint32*)&_stream[offset] = n;
}


void BytecodeStream::reset()
{
  _offset = _lastOffset = 0;  // the error code is not reset
}


int BytecodeStream::save(mmachine m) const
{
  static uint32 total = 0;

  total += _offset;
  MMechostr(MSKTRACE, "%d bytes generated (for a total of %d bytes)\n", _offset, total);
  int s = MMmalloc(m, _offset/sizeof(int)+(_offset%sizeof(int) > 0), TYPEBUF);
  if (s == NIL)
    return s;
  memcpy((byte*)&m->tape[s+SizeHeader], _stream, _offset);
  return (s<<1)+1;  // use <...>|0x00000001
}


void BytecodeStream::optimise()
// Support for tail call optimisations
{
#if !defined(INCLUDE_DEBUGGER)
  uint32 offset = 0;

  while (offset < size()) {
next:
    byte opcode = _stream[offset];
    offset += ALIGNMENT(1);
    // If it is a function call...
    if (opcode == kCall) {
      // ... save offset to patch it later with a kTailcall, if possible
      uint32 callOffset = offset-ALIGNMENT(1);
      // If next instruction is a 'goto' continue scan at target offset
      while ((opcode = _stream[offset]) == kGoto) {
        offset += ALIGNMENT(1);
        uint target = *(uint32*)&_stream[offset];  // target offset
        if (target < offset) {
          offset += 4;
          goto next;
        } else
          offset = *(uint32*)&_stream[offset];
      }
      // If next instruction is a kReturn, replace it with a kTailcall;
      // otherwise optimisation is not applicable
      if (opcode == kReturn)
        _stream[callOffset] = kTailcall;
      offset += ALIGNMENT(1);
    }
    // Skip operand
    switch (bytecode[opcode].operandType) {
      case kOperandNone:
        break;
      case kOperandByte:
        offset += ALIGNMENT(1);
        break;
      case kOperandInt16:
        offset += ALIGNMENT(2);
        break;
      case kOperandInt32:
      case kOperandOffset:
      case kOperandFloat:
        offset += ALIGNMENT(4);
        break;
      case kOperandString: {
        uint32 len = *(uint32*)&_stream[offset];
        offset += len*ALIGNMENT(1)+4;
        break;
      }
    } // switch
  } // while
#endif
}


#if defined(SCOL_DISASSEMBLER)
void BytecodeStream::dumpAssembler()
{
  uint32 offset = 0;

  while (offset < size()) {
    byte opcode = _stream[offset];
    MMechostr(MSKTRACE, "%-8d\t%s", offset, bytecode[opcode].mnemonic);
    offset += ALIGNMENT(1);
    switch (bytecode[opcode].operandType) {
      case kOperandNone:
        MMechostr(MSKTRACE, "\n");
        break;
      case kOperandByte:
        MMechostr(MSKTRACE, " #%d\n", _stream[offset]);
        offset += ALIGNMENT(1);
        break;
      case kOperandInt16:
        MMechostr(MSKTRACE, " #%d\n", *(uint16*)&_stream[offset]);
        offset += ALIGNMENT(2);
        break;
      case kOperandInt32:
        MMechostr(MSKTRACE, " #%d\n", *(int32*)&_stream[offset]); // print it as signed
        offset += ALIGNMENT(4);
        break;
      case kOperandFloat:
        MMechostr(MSKTRACE, " #%f\n", *(float32*)&_stream[offset]);
        offset += ALIGNMENT(4);
        break;
      case kOperandOffset:
        MMechostr(MSKTRACE, " %d\n", *(uint32*)&_stream[offset]);
        offset += ALIGNMENT(4);
        break;        
      case kOperandString: {
        uint32 len = *(uint32*)&_stream[offset];
        MMechostr(MSKTRACE, " #%d\n", len);
        offset += len*ALIGNMENT(1)+4;
        break;
      }
    }
  }
}
#endif


int writePushint(int32 n)
{
  if (n >= 0 && n <= 2) {
    CODER_WRITEOPCODE(kPushint0+n)
  } else {
    CODER_WRITEOPCODE(kPushint)
    CODER_WRITEINT32((uint32)n)
  }
  return MERROK;
}


int writePushfloat(float32 x)
{
  if (x == 0.0) {
    CODER_WRITEOPCODE(kPushfloat0)
  } else if (x == 1.0) {
    CODER_WRITEOPCODE(kPushfloat1)
  } else {
    CODER_WRITEOPCODE(kPushfloat)
    CODER_WRITEFLOAT(x)
  }
  return MERROK;
}


int writeNewtuple(byte size)
{
  switch (size) {
    case 1:
      CODER_WRITEOPCODE(kNewtuple1)
      break;
    case 2:
      CODER_WRITEOPCODE(kNewtuple2)
      break;
    default:
      if (size > kMaxTupleSize) {
        // This message should not go here
        MMechostr(MSKRUNTIME, "(!) Structures or tuples may not contain more than %d fields\n", kMaxTupleSize);
        return MERRTYP;
      }
      CODER_WRITEOPCODE(kNewtuple)
      CODER_WRITEBYTE(size)
  }
  return MERROK;
}


int writeAccessor(Accessor accessor, bool getter, uint32 n)
{
  switch (accessor) {
    case kAccessorLocal:
      if (n >= kMaxLocals) {
        MMechostr(MSKRUNTIME, "(!) Function uses more than %d local variables\n", kMaxLocals);
        return MERRTYP;
      }
      if (n <= 7)
        CODER_WRITEOPCODE(getter ? kGetlocal0+n : kSetlocal0+n)
      else {
        CODER_WRITEOPCODE(getter ? kGetlocal    : kSetlocal)
        CODER_WRITEBYTE(n)
      }
      break;

	case kAccessorGlobal:
      if (n >= kMaxReferences) {
        MMechostr(MSKRUNTIME, "(!) Function references more than %d global variables\n", kMaxReferences);
        return MERRTYP;
      }
      if (n <= 7)
        CODER_WRITEOPCODE(getter ? kGetglobal0+n : kSetglobal0+n)
      else {
        CODER_WRITEOPCODE(getter ? kGetglobal    : kSetglobal)
        CODER_WRITEINT16(n)
      }
	  break;

	case kAccessorField:
      if (n >= kMaxFields) {
        MMechostr(MSKRUNTIME, "(!) Structures or tuples may not contain more than %d fields\n", kMaxFields);
        return MERRTYP;
      }
      if (n <= 7)
        CODER_WRITEOPCODE(getter ? kGetfield0+n : kSetfield0+n)
      else {
	    CODER_WRITEOPCODE(getter ? kGetfield    : kSetfield)
        CODER_WRITEBYTE(n)
      }
	  break;

    case kAccessorArray:
      CODER_WRITEOPCODE(getter ? kArrayget : kArrayset)
      break;
  }
  return MERROK;
}
