/* 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 <stdlib.h>
#include <string.h>
#include "../Utility.h"
#include "../Types.h"
#include "../Zone.h"
#include "../Strings.h"
#include "../Zone.h"
#include "BytecodeEmitter.h"
#include "Symbols.h"
#include "Scopes.h"
#include "../Spaces-inl.h"
#include "../Heap-inl.h"

using namespace ke;

StringTable::StringTable()
  : set_(ZoneAllocatorPolicy()),
    initialized_(false)
{
}

bool
StringTable::initialize()
{
    assert(!initialized_);
    initialized_ = true;
    if (!set_.init(16))
        return false;
    return true;
}

bool
StringTable::add(Handle<String> name, unsigned *indexp)
{
    if (!initialized_ && !initialize())
        return false;

    Set::Insert p = set_.findForAdd(*name);
    if (p.found()) {
        *indexp = p->index;
        return true;
    }

    if (strings_.length() > INT_MAX) {
        ZONE()->reportAllocationOverflow();
        return false;
    }

    Entry e(name, strings_.length());
    if (!strings_.append(e))
        return false;

    if (!set_.add(p, e))
        return false;

    *indexp = e.index;

    return true;
}

void
StringTable::accept(VirtualObjectVisitor *visitor)
{
    for (size_t i = 0; i < strings_.length(); i++)
        visitor->visit(reinterpret_cast<Object **>(&strings_[i].string));
}

BytecodeEmitter::BytecodeEmitter(PoolAllocator &pool)
  : AutoRooted(ZONE()),
    pool_(pool),
    buffer_(inline_buffer_),
    buffer_pos_(buffer_),
    buffer_end_(buffer_pos_ + ARRAY_LENGTH(inline_buffer_)),
    oom_(false),
    maxStackDepth_(0),
    loopDepth_(0),
    maxLoopDepth_(0)
{
    lastCodePosition_ = 0;
    firstLine_ = 1;
}

BytecodeEmitter::~BytecodeEmitter()
{
    for (size_t i = 0; i < oopRecords_.length(); i++)
        delete oopRecords_[i];
}

void
BytecodeEmitter::accept(VirtualObjectVisitor *visitor)
{
    for (size_t i = 0; i < objects_.length(); i++)
        visitor->visit(&objects_[i]);

    strings_.accept(visitor);
}

bool
BytecodeEmitter::growBuffer(size_t nbytes)
{
    if (oom_) {
        buffer_pos_ = buffer_;
        return false;
    }

    size_t newSize = size_t((buffer_end_ - buffer_) * 2);
    if (newSize >= MAX_LENGTH) {
        oom_ = true;
        buffer_pos_ = buffer_;
        ZONE()->reportOutOfMemory();
        return false;
    }

    bytecode *newBuffer;
    if (buffer_ == inline_buffer_)
        newBuffer = (bytecode *)malloc(newSize);
    else
        newBuffer = (bytecode *)realloc(buffer_, newSize);

    // If we can't allocate a buffer, reset the buffer to the start and set
    // a flag to indicate out-of-memory.
    if (!newBuffer) {
        oom_ = true;
        buffer_pos_ = buffer_;
        ZONE()->reportOutOfMemory();
        return false;
    }

    if (buffer_ == inline_buffer_)
        memcpy(newBuffer, inline_buffer_, (buffer_pos_ - inline_buffer_));

    buffer_pos_ = newBuffer + size_t(buffer_pos_ - buffer_);
    buffer_ = newBuffer;
    buffer_end_ = buffer_ + newSize;
    assert(buffer_pos_ + nbytes < buffer_end_);

    return true;
}

void
BytecodeEmitter::recordOops()
{
    OopRecord *record = new OopRecord(oopMap_.length(), position());

    if (!record->slots.ensure(record->depth)) {
        oom_ = true;
        return;
    }

    bool hadOops = false;
    for (size_t i = 0; i < oopMap_.length(); i++) {
        bool bit = oopMap_[i];
        record->slots.infallibleAppend(bit);
        hadOops |= bit;
    }

    if (!hadOops) {
        delete record;
        return;
    }

    oom_ &= oopRecords_.append(record);
}

void
BytecodeEmitter::note_position(const SourcePosition &pos)
{
    assert(pos.line > 0);
    assert(unsigned(pos.line) >= firstLine_);

    unsigned lineDelta = unsigned(pos.line) - firstLine_;
    unsigned pcDelta = unsigned(position()) - lastCodePosition_;

    sourcemap_.writeUint32(pcDelta);
    sourcemap_.writeUint32(lineDelta);
    sourcemap_.writeUint32(pos.col);

    lastCodePosition_ = unsigned(position());
}

void
BytecodeEmitter::getglobal(Symbol *sym)
{
    ensure(OP_GETGLOBAL_LENGTH);
    writeOp(OP_GETGLOBAL);
    writeUint32(addString(sym->name()));
    if (sym->type()->isTraceable())
        markOop(1);
}
void
BytecodeEmitter::setglobal(Symbol *sym)
{
    ensure(OP_SETGLOBAL_LENGTH);
    writeOp(OP_SETGLOBAL);
    writeUint32(addString(sym->name()));
    if (sym->type()->isTraceable())
        markOop(1);
}

void
BytecodeEmitter::getlocal(VariableSymbol *var)
{
    assert(var->storage() == VariableSymbol::Local);
    ensure(OP_GETLOCAL_LENGTH);
    writeOp(OP_GETLOCAL);
    writeUint32(var->slot());
    if (var->type()->isTraceable())
        markOop(1);
}

void
BytecodeEmitter::setlocal(VariableSymbol *var)
{
    assert(var->storage() == VariableSymbol::Local);
    ensure(OP_SETLOCAL_LENGTH);
    writeOp(OP_SETLOCAL);
    writeUint32(var->slot());
    if (var->type()->isTraceable())
        markOop(1);
}

void
BytecodeEmitter::getarg(VariableSymbol *var)
{
    assert(var->storage() == VariableSymbol::Arg);
    ensure(OP_GETARG_LENGTH);
    writeOp(OP_GETARG);
    writeUint32(var->slot());
    if (var->type()->isTraceable())
        markOop(1);
}

void
BytecodeEmitter::setarg(VariableSymbol *var)
{
    assert(var->storage() == VariableSymbol::Arg);
    ensure(OP_SETARG_LENGTH);
    writeOp(OP_SETARG);
    writeUint32(var->slot());
    if (var->type()->isTraceable())
        markOop(1);
}

bool
BytecodeEmitter::allocate(VariableSymbol *sym)
{
    assert(sym->isVariable());
    assert(sym->scope()->kind() == Scope::BLOCK);

    switch (sym->scope()->kind()) {
      case Scope::BLOCK:
      {
        // See overall comment on overflows.
        assert(locals_.length() < UINT_MAX);
        sym->allocate(VariableSymbol::Local, locals_.length());
        if (!locals_.append(sym))
            return false;
        break;
      }

      default:
        assert(false);
        return false;
    }

    return true;
}

void
BytecodeEmitter::newref(VariableSymbol *var)
{
    recordOops();
    if (var->storage() == VariableSymbol::Local) {
        ensure(OP_VARREF_LIFO_LENGTH);
        writeOp(OP_VARREF_LIFO);
    } else {
        assert(var->storage() == VariableSymbol::Arg);
        ensure(OP_ARGREF_LIFO_LENGTH);
        writeOp(OP_ARGREF_LIFO);
    }
    writeUint32(var->slot());
    markOop(1);
}

Code *
BytecodeEmitter::finish()
{
    stop();

    Zone *zone = ZONE();
    if (oom() || zone->hasPendingException())
        return NULL;

    Local<ByteArray> bytes(zone, ByteArray::New(zone, length(), Heap::Tenure_Old));
    if (!bytes)
        return NULL;
    memcpy(bytes->raw(), buffer(), length());

    Local<FixedArray> objects(zone, FixedArray::New(zone, objects_.length(), Heap::Tenure_Old));
    if (!objects)
        return NULL;

    for (size_t i = 0; i < objects_.length(); i++)
        objects->set(zone, i, objects_[i]);

    // If any stack slots hold gc pointers at potential failure points in the
    // bytecode, record them now. We do this by building a list of tuples;
    // each tuple contains the pc offset, and a bitset containing which stack
    // slots must be scanned by the gc.
    Local<ByteArray> safepoints(zone);
    if (oopRecords_.length()) {
        size_t bytesPerSafepoint = Code::BytesPerSafepoint(maxStackDepth());
        size_t gcmapSize = bytesPerSafepoint * oopRecords_.length();
        safepoints = ByteArray::New(zone, gcmapSize, Heap::Tenure_Old);
        if (!safepoints)
            return NULL;

        Address cursor = safepoints->raw();
        for (size_t i = 0; i < oopRecords_.length(); i++) {
            OopRecord *record = oopRecords_[i];
            assert(record->depth <= maxStackDepth());

            *reinterpret_cast<unsigned *>(cursor) = record->offset;

            unsigned char *bits = reinterpret_cast<unsigned char *>(cursor + sizeof(unsigned));
            for (unsigned i = 0; i < record->depth; i++)
                bits[i / 8] |= (1 << (i % 8));

            cursor += bytesPerSafepoint;
        }

        assert(cursor == safepoints->raw() + gcmapSize);
    }

    Local<ByteArray> sourcemap(zone);
    if (sourcemap_.length()) {
        sourcemap = ByteArray::New(zone, sourcemap_.length(), Heap::Tenure_Old);
        if (!sourcemap)
            return NULL;

        memcpy(sourcemap->raw(), sourcemap_.buffer(), sourcemap_.length());
    }

    Local<FixedArray> strings(zone, FixedArray::New(zone, strings_.stringCount(), Heap::Tenure_Old));
    if (!strings)
        return NULL;
    for (size_t i = 0; i < strings_.stringCount(); i++)
        strings->set(zone, i, strings_.stringAt(i));

    Local<FixedArray> locals(zone, FixedArray::New(zone, locals_.length(), Heap::Tenure_Old));
    if (!locals)
        return NULL;
    for (size_t i = 0; i < locals_.length(); i++)
        locals->set(zone, i, locals_[i]->type());

    Local<Code> code(zone);

    code = Code::New(zone,
                     bytes,
                     locals,
                     objects,
                     strings,
                     maxStackDepth(),
                     maxLoopDepth_,
                     safepoints,
                     sourcemap);

    return code;
}
