/*****
* 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