/* vim: set ts=4 sw=4 tw=99 et:
 *
 * Copyright (C) 2012 David Anderson
 *
 * This file is part of JITCraft.
 *
 * JITCraft is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 * 
 * Foobar is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * JITCraft. If not, see http://www.gnu.org/licenses/.
 */
#include <new>
#include <stdlib.h>
#include <string.h>
#include "Stack.h"
#include "Zone.h"
#include "Strings.h"
#include "Modules.h"
#include "NativeTable.h"
#include "Heap-inl.h"

using namespace ke;

Stack::Stack(Zone *zone)
  : zone_(zone),
    buffer_(NULL),
    end_(NULL)
{
}

Stack::~Stack()
{
    free(buffer_);
}

bool
Stack::initialize()
{
    buffer_ = (char *)malloc(STACK_SIZE);
    if (!buffer_)
        return false;

    top_.sp = reinterpret_cast<LocalSlot *>(buffer_);
    top_.pc = NULL;
    top_.fp = NULL;
    end_ = buffer_ + STACK_SIZE;
    return true;
}

InvokeArgs::~InvokeArgs()
{
    stack_->popInvokeFrame(*this);
}

NativeArgs::~NativeArgs()
{
    stack_->popNativeFrame(*this);
}

static inline size_t
SizeForCodeFrame(Code *code)
{
    return sizeof(Frame) + 
           sizeof(void *) * (code->maxLoopDepth()) +
           sizeof(LocalSlot) * (code->nlocals() + code->maxStackDepth());
}

bool
Stack::pushInvokeFrame(Handle<Function> fun, InvokeArgs &args)
{
    size_t nbytes = SizeForCodeFrame(fun->code()) +
                    sizeof(LocalSlot) * fun->nargs();
    if (reinterpret_cast<char *>(top_.sp) + nbytes >= end_) {
        assert(false);
        return false;
    }

    memset(top_.sp, 0, sizeof(LocalSlot) * fun->nargs());
    args.args_ = top_.sp;
    args.nargs_ = fun->nargs();
    args.stack_ = this;
    top_.sp += fun->nargs();
    
    Frame *frame = reinterpret_cast<Frame *>(top_.sp);
    new (frame) Frame(top_.fp, top_.pc, fun, fun->code(), zone_->heap().lifoPosition());
    
    top_.pc = frame->code()->start();
    top_.sp = frame->stackBase();
    top_.fp = frame;

    return true;
}

bool
Stack::pushModuleFrame(Handle<Module> module, InvokeArgs &args)
{
    size_t nbytes = SizeForCodeFrame(module->code());
    if (reinterpret_cast<char *>(top_.sp) + nbytes >= end_) {
        assert(false);
        return false;
    }

    args.args_ = top_.sp;
    args.nargs_ = 0;
    args.stack_ = this;
    
    Frame *frame = reinterpret_cast<Frame *>(top_.sp);
    new (frame) Frame(top_.fp, top_.pc, NULL, module->code(), zone_->heap().lifoPosition());
    
    top_.pc = frame->code()->start();
    top_.sp = frame->stackBase();
    top_.fp = frame;

    return true;
}

bool
Stack::pushNativeFrame(Handle<Native> native, NativeArgs &args, unsigned argc)
{
    size_t nbytes = sizeof(intptr_t) * argc + sizeof(Frame);
    if (reinterpret_cast<char *>(top_.sp) + nbytes >= end_) {
        assert(false);
        return false;
    }

    memset(top_.sp, 0, sizeof(intptr_t) * argc);
    args.argsBase_ = top_.sp;
    args.stack_ = this;
    top_.sp = reinterpret_cast<LocalSlot *>(reinterpret_cast<intptr_t *>(top_.sp) + argc);

    Frame *frame = reinterpret_cast<Frame *>(top_.sp);
    new (frame) Frame(top_.fp, top_.pc, native, zone_->heap().lifoPosition());

    top_.pc = NULL;
    top_.sp = reinterpret_cast<LocalSlot *>(frame + 1);
    top_.fp = frame;

    return true;
}

Frame *
Stack::pushInlineFrame(Handle<Function> fun)
{
    Code *code = fun->code();
    size_t nbytes = SizeForCodeFrame(code);
    if (reinterpret_cast<char *>(top_.sp) + nbytes >= end_) {
        zone_->reportError(Message_TooMuchRecursion);
        return NULL;
    }

    Frame *frame = reinterpret_cast<Frame *>(top_.sp);
    new (frame) Frame(top_.fp, top_.pc, fun, code, zone_->heap().lifoPosition());
    memset(frame->locals(), 0, sizeof(LocalSlot) * code->nlocals());

    top_.pc = frame->code()->start();
    top_.sp = frame->stackBase();
    top_.fp = frame;

    return frame;
}


Frame *
Stack::popInlineFrame()
{
    zone_->heap().unwindLifo(top_.fp->lifoPosition());

    Frame *prev = top_.fp->prev();
    Function *fun = top_.fp->fun();

    top_.pc = top_.fp->prevpc();
    top_.sp = reinterpret_cast<LocalSlot *>(top_.fp) - fun->nargs();
    top_.fp = prev;
    return prev;
}

void
Stack::popInvokeFrame(InvokeArgs &args)
{
    zone_->heap().unwindLifo(top_.fp->lifoPosition());

    top_.pc = top_.fp->prevpc();
    top_.sp = args.args_;
    top_.fp = top_.fp->prev();
}

void
Stack::popNativeFrame(NativeArgs &args)
{
    zone_->heap().unwindLifo(top_.fp->lifoPosition());

    top_.pc = top_.fp->prevpc();
    top_.sp = args.argsBase_;
    top_.fp = top_.fp->prev();
}

void
Frame::enterLoop(Zone *zone)
{
    assert(loopDepth_ < code_->maxLoopDepth());
    loopStack()[loopDepth_] = zone->heap().lifoPosition();
    loopDepth_++;
}

void
Frame::leaveLoop(Zone *zone)
{
    assert(loopDepth_ > 0);
    loopDepth_--;
    zone->heap().unwindLifo(loopStack()[loopDepth_]);
}

void
Frame::accept(VirtualObjectVisitor *visitor)
{
    visitor->visit(reinterpret_cast<Object **>(&callee_));
    visitor->visit(reinterpret_cast<Object **>(&code_));
}

static void
TraceFunctionFrame(VirtualObjectVisitor *visitor, Frame *fp, LocalSlot *sp, bytecode *pc)
{
    Code *code = fp->code();
    LocalSlot *slots = fp->locals();

    // Trace local variables.
    for (unsigned i = 0; i < code->nlocals(); i++) {
        Type *type = code->localType(i);
        if (type->isTraceable())
            visitor->visit(&slots[i].obj);
    }

    // Trace arguments, if a function call. Note that this means we usually
    // mark arguments twice, once on the caller side and once on the callee.
    // This makes it easier to deal with invoke frames which have no caller.
    if (Function *fun = fp->maybeFun()) {
        for (unsigned i = 0; i < fun->nargs(); i++) {
            Type *type = fun->argType(i);
            if (type->isTraceable())
                visitor->visit(&fp->arg(i).obj);
        }
    }

    // Finally, trace stack slots. Not every gc-point in the bytecode will
    // have a safepoint; since they are actually uncommon, we don't emit
    // them unless at least one stack slot would be marked.
    if (uint8 *safepoint = code->safepointAt(code->pcToOffset(pc))) {
        LocalSlot *base = fp->stackBase();
        unsigned stackDepth = unsigned(sp - fp->stackBase());
        assert(stackDepth <= code->maxStackDepth());

        for (unsigned i = 0; i < stackDepth; i++) {
            if (safepoint[i / 8] & (1 << (i % 8)))
                visitor->visit(&base[i].obj);
        }
    }
}

void
Stack::accept(VirtualObjectVisitor *visitor)
{
    LocalSlot *sp = top_.sp;
    bytecode *pc = top_.pc;

    for (Frame *frame = top_.fp; frame; frame = frame->prev()) {
        if (!frame->isNative())
            TraceFunctionFrame(visitor, frame, sp, pc);
        frame->accept(visitor);

        sp = reinterpret_cast<LocalSlot *>(frame);
        pc = frame->prevpc();
    }
}

bool
Stack::isInCallStack(Handle<Module> module)
{
    for (Frame *frame = top_.fp; frame; frame = frame->prev()) {
        if (frame->code()->module() == module)
            return true;
    }
    return false;
}

StackIter::StackIter(Zone *zone)
  : pc_(zone->stack().top()->pc),
    fp_(zone->stack().top()->fp)
{
}

bool
StackIter::done() const
{
    return !fp_;
}

const char *
StackIter::function() const
{
    if (fp_->isNative())
        return ZONE()->nativeTable()->name(fp_->native()->index())->chars();
    if (Function *fun = fp_->maybeFun())
        return fun->name()->chars();
    return "<toplevel>";
}

unsigned
StackIter::lineNumber() const
{
    if (fp_->isNative())
        return 0;
    if (fp_->maybeFun()) {
        unsigned offs = fp_->code()->pcToOffset(pc_);
        return fp_->code()->pcToSource(offs).line;
    }
    return fp_->code()->lineNumber();
}

const char *
StackIter::source() const
{
    if (fp_->isNative())
        return "<native>";
    return fp_->code()->module()->path()
           ? fp_->code()->module()->path()->chars()
           : "(anonymous)";
}

void
StackIter::prev()
{
    pc_ = fp_->prevpc();
    fp_ = fp_->prev();
}
