/*****
 * coder.cc
 * Andy Hammerlindl 2004/11/06
 *
 * Handles encoding of syntax into programs.  It's methods are called by
 * abstract syntax objects during translation to construct the virtual machine
 * code.
 *****/

#include <utility>

#include "errormsg.h"
#include "coder.h"
#include "genv.h"
#include "entry.h"
#include "builtin.h"

using namespace sym;
using namespace types;

namespace trans {

namespace {
function *inittype();
function *bootuptype();
}

vm::lambda *newLambda(string name) {
  assert(!name.empty());
  vm::lambda *l = new vm::lambda;
#ifdef DEBUG_FRAME
  l->name = name;
#endif
  return l;
}


// Used purely for global variables and static code blocks of file
// level modules.
coder::coder(position pos, string name, modifier sord)
#ifdef SIMPLE_FRAME
  : level(frame::indirect_frame(name)),
#else
    : level(new frame(name, 0, 0)),
#endif
    recordLevel(0),
    recordType(0),
    isCodelet(false),
    l(newLambda(name)),
    funtype(bootuptype()),
    parent(0),
    sord(sord),
    perm(DEFAULT_PERM),
    program(new vm::program),
    curPos(pos)
{
  sord_stack.push(sord);
}

// Defines a new function environment.
coder::coder(position pos, string name, function *t, coder *parent,
             modifier sord, bool reframe)
  : level(reframe ? new frame(name,
                              parent->getFrame(),
                              t->sig.getNumFormals()) :
          parent->getFrame()),
    recordLevel(parent->recordLevel),
    recordType(parent->recordType),
    isCodelet(!reframe),
    l(newLambda(name)),
    funtype(t),
    parent(parent),
    sord(sord),
    perm(DEFAULT_PERM),
    program(new vm::program),
    curPos(pos)
{
  sord_stack.push(sord);
}

// Start encoding the body of the record.  The function being encoded
// is the record's initializer.
coder::coder(position pos, record *t, coder *parent, modifier sord)
  : level(t->getLevel()),
    recordLevel(t->getLevel()),
    recordType(t),
    isCodelet(false),
    l(t->getInit()),
    funtype(inittype()),
    parent(parent),
    sord(sord),
    perm(DEFAULT_PERM),
    program(new vm::program),
    curPos(pos)
{
  sord_stack.push(sord);
}

coder coder::newFunction(position pos, string name, function *t, modifier sord)
{
  return coder(pos, name, t, this, sord);
}

coder coder::newCodelet(position pos)
{
  return coder(pos, "<codelet>", new function(primVoid()), this,
               DEFAULT_DYNAMIC, false);
}

record *coder::newRecord(symbol id)
{
  frame *underlevel = getFrame();

  frame *level = new frame(id, underlevel, 0);

  record *r = new record(id, level);

  return r;
}

coder coder::newRecordInit(position pos, record *r, modifier sord)
{
  return coder(pos, r, this, sord);
}

#ifdef DEBUG_BLTIN
void assertBltinLookup(inst::opcode op, item it)
{
  if (op == inst::builtin) {
    string name=lookupBltin(vm::get<vm::bltin>(it));
    assert(!name.empty());
  }
}
#endif


void coder::encodePop()
{
  if (isStatic() && !isTopLevel()) {
    assert(parent);
    parent->encodePop();
  }
  else {
#ifdef COMBO
    vm::program::label end = program->end();
    --end;
    inst& lastInst = *end;
    if (lastInst.op == inst::varsave) {
      lastInst.op = inst::varpop;
      return;
    }
    if (lastInst.op == inst::fieldsave) {
      lastInst.op = inst::fieldpop;
      return;
    }
    // TODO: push+pop into no op.
#endif

    // No combo applicable.  Just encode a usual pop.
    encode(inst::pop);
  }
}


bool coder::encode(frame *f)
{
  frame *toplevel = getFrame();

  if (f == 0) {
    encode(inst::constpush,(item)0);
    return true;
  }
  else if (f == toplevel) {
    encode(inst::pushclosure);
    return true;
  }
  else {
    encode(inst::varpush,toplevel->parentIndex());
    return encode(f, toplevel->getParent());
  }
}

bool coder::encode(frame *dest, frame *top)
{
  if (dest == 0) {
    // Change to encodePop?
    encode(inst::pop);
    encode(inst::constpush,(item)0);
  }
  else {
    frame *level = top;
    while (level != dest) {
      if (level == 0) {
        // Frame request was in an improper scope.
        return false;
      }

      encode(inst::fieldpush, level->parentIndex());

      level = level->getParent();
    }
  }

  //cerr << "succeeded\n";
  return true;
}

vm::program::label coder::encodeEmptyJump(inst::opcode op)
{
  // Get the end position before encoding the label.  Once encoded, this will
  // point to the instruction.
  vm::program::label pos = program->end();

  encode(op);

  return pos;
}

void replaceEmptyJump(vm::program::label from, vm::program::label to)
{
  from->ref = to;
}

label coder::defNewLabel()
{
  if (isStatic())
    return parent->defNewLabel();

  label l = new label_t();
  assert(!l->location.defined());
  assert(!l->firstUse.defined());
  return defLabel(l);
}

label coder::defLabel(label label)
{
  if (isStatic())
    return parent->defLabel(label);

  //cout << "defining label " << label << endl;

  assert(!label->location.defined());
  //vm::program::label here = program->end();
  label->location = program->end();
  assert(label->location.defined());

  if (label->firstUse.defined()) {
    replaceEmptyJump(label->firstUse, program->end());
    //vm::printInst(cout, label->firstUse, program->begin());
    //cout << endl;

    if (label->moreUses) {
      typedef label_t::useVector useVector;
      useVector& v = *label->moreUses;
      for (useVector::iterator p = v.begin(); p != v.end(); ++p) {
        replaceEmptyJump(*p, program->end());
      }
    }
  }

  return label;
}


void coder::useLabel(inst::opcode op, label label)
{
  if (isStatic())
    return parent->useLabel(op,label);

  if (label->location.defined()) {
    encode(op, label->location);
  } else {
    if (label->firstUse.defined()) {
      // Store additional uses in the moreUses array.
      if (!label->moreUses)
        label->moreUses = new label_t::useVector;
      label->moreUses->push_back(encodeEmptyJump(op));
    }
    else {
      label->firstUse = encodeEmptyJump(op);
      assert(label->firstUse.defined());
      assert(!label->location.defined());
    }
  }
}
label coder::fwdLabel()
{
  if (isStatic())
    return parent->fwdLabel();

  // Create a new label without specifying its position.
  label l = new label_t();
  assert(!l->location.defined());
  assert(!l->firstUse.defined());

  //cout << "forward label " << l << endl;

  return l;
}

bool coder::usesClosureSinceLabel(label l)
{
  assert(l->location.defined());
  for (vm::program::label i = l->location; i != program->end(); ++i)
    if (i->op == inst::pushclosure)
      return true;
  return false;
}

void coder::encodePatch(label from, label to)
{
  assert(from->location.defined());
  assert(to->location.defined());

  assert(from->location->op == inst::nop);

  from->location->op = inst::jmp;
  from->location->ref = to->location;
}

void coder::markPos(position pos)
{
  curPos = pos;
}

// When translating the function is finished, this ties up loose ends
// and returns the lambda.
vm::lambda *coder::close() {
  // These steps must be done dynamically, not statically.
  sord = EXPLICIT_DYNAMIC;
  sord_stack.push(sord);

  // Add a return for void types; may be redundant.
  if (funtype->result->kind == types::ty_void)
    encode(inst::ret);

  l->code = program;

  l->parentIndex = level->parentIndex();

  l->framesize = level->size();

  sord_stack.pop();
  sord = sord_stack.top();

  return l;
}

void coder::closeRecord()
{
  // Put record into finished state.
  encode(inst::pushclosure);
  close();
}

bool coder::isRecord()
{
  return (funtype==inittype());
}

namespace {
function *inittype()
{
  static function t(types::primVoid());
  return &t;
}

function *bootuptype()
{
  static function t(types::primVoid());
  return &t;
}
} // private

bool coder::encodeParent(position pos, trans::tyEntry *ent) {
  record *r = dynamic_cast<record *>(ent->t);
  if (!r) {
    em.compiler(pos);
    em << "type '" << *ent->t << "' is not a structure";
    return false;
  }
  assert(r);

  // The level needed on which to allocate the record.
  frame *level = r->getLevel()->getParent();

  if (ent->v) {
    // Put the record on the stack.  For instance, in code like
    //   access imp;
    //   new imp.t;
    // we are putting the instance of imp on the stack, so we can use it to
    // allocate an instance of imp.t.
    ent->v->encode(trans::READ, pos, *this);

    // Adjust to the right frame.  For instance, in the last new in
    //   struct A {
    //     struct B {
    //       static struct C {}
    //     }
    //     B b=new B;
    //   }
    //   A a=new A;
    //   new a.b.C;
    // we push a.b onto the stack, but need a as the enclosing frame for
    // allocating an instance of C.
    record* q= dynamic_cast<record*>(ent->v->getType());
    assert(q);
    return encode(level, q->getLevel());
  } else
    return encode(level);
}

} // namespace trans

