/* 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/.
 */
#ifndef _include_bytecode_emitter_h_
#define _include_bytecode_emitter_h_

#include <assert.h>
#include <limits.h>
#include <vector>
#include "Opcodes.h"
#include "NameBinding.h"
#include "Handles.h"

namespace jc {

class Type;
class Map;

typedef std::vector<ScopedRoot<Object> *> ObjectList;

class BytecodeEmitter
{
    bytecode *buffer_;
    bytecode *buffer_pos_;
    bytecode *buffer_end_;
    bytecode inline_buffer_[512];
    bool oom_;
    unsigned maxStackDepth_;
    unsigned stackDepth_;
    ObjectList objects_;

  private:
    static const size_t MAX_LENGTH = INT_MAX / 2;

  private:
    bool growBuffer(size_t bytes);

    // Checking the return value is not necessary, since the inline buffer is
    // used as a ballast.
    bool ensure(size_t bytes) {
        if (buffer_pos_ + bytes > buffer_end_)
            return growBuffer(bytes);
        return true;
    }

    void writeUint8(unsigned value) {
        assert(value >= 0 && value <= UCHAR_MAX);
        *buffer_pos_++ = value;
    }
    void writeFloat(float value) {
        *(float *)buffer_pos_ = value;
        buffer_pos_ += sizeof(float);
    }
    void writeInt32(int value) {
        *(int *)buffer_pos_ = value;
        buffer_pos_ += sizeof(int);
    }
    void writeUint32(unsigned value) {
        *(unsigned *)buffer_pos_ = value;
        buffer_pos_ += sizeof(unsigned);
    }
    void writeOp(Opcode op) {
        writeUint8(op);
        stackDepth_ += OpcodeInfo[op].ndefs;
        if (OpcodeInfo[op].nuses != -1)
            stackDepth_ -= OpcodeInfo[op].nuses;
        if (stackDepth_ > maxStackDepth_)
            maxStackDepth_ = stackDepth_;
    }
    void writeObject(Object *obj) {
        writeUint32(objects_.size());
        ScopedRoot<Object> *root = new ScopedRoot<Object>(obj);
        if (!root)
            oom_ = true;
        objects_.push_back(root);
    }

    int position() const {
        assert(buffer_pos_ - buffer_ < INT_MAX / 2);
        return int(buffer_pos_ - buffer_);
    }

  public:
    BytecodeEmitter();

    bytecode *buffer() const {
        return buffer_;
    }
    unsigned length() const {
        return unsigned(buffer_pos_ - buffer_);
    }
    bool oom() const {
        return oom_;
    }
    unsigned stackDepth() const {
        return stackDepth_;
    }
    unsigned maxStackDepth() const {
        return maxStackDepth_;
    }
    const ObjectList *objects() const {
        return &objects_;
    }

    void drop(unsigned amt) {
        assert(amt <= stackDepth_);
        stackDepth_ -= amt;
    }

  public:
    // A label represents a local control-flow target in the bytecode. A label
    // may be in three states:
    //
    // (1) Empty - the label is not used yet.
    // (2) Pending - the label has incoming jumps, but the actual location of
    //               is not yet known. The list of jumps is threaded through
    //               the four bytes reserved for each emitted jump target. In
    //               this case, the offset field is the location of the most
    //               recently emitted incoming jump.
    // (3) Bound   - the label's position has been pinned.
    //
    // A jump can be emitted to a label in any state. Binding a label
    // immediately backpatches all pending incoming jumps. A label must be
    // bound if it is used.
    class Label
    {
        int offset_ : 31;
        bool bound_ : 1;

        void setOffset(int offset) {
            offset_ = offset;
            assert(offset_ == offset);
        }

      public:
        static const int INVALID_OFFSET = -1;

      public:
        Label()
          : offset_(INVALID_OFFSET),
            bound_(false)
        {
        }
        ~Label()
        {
            assert(bound_ || offset_ == INVALID_OFFSET);
        }
        
        bool bound() const {
            return bound_;
        }
        int offset() const {
            return offset_;
        }
        void bind(int offset) {
            assert(!bound_);
            setOffset(offset);
            bound_ = true;
        }
        int link(int offset) {
            assert(!bound_);
            int old = offset_;
            setOffset(offset);
            return old;
        }
    };

  private:
    int patchJumpAt(int at, int to) {
        assert(at >= 0 && at < position());
        int old = *(int *)(buffer_ + at + 1);
        *(int *)(buffer_ + at + 1) = (to - at);
        return old;
    }

    void singleByteOp(Opcode op) {
        assert(OpcodeInfo[op].length == 1);
        ensure(OpcodeInfo[op].length);
        writeOp(op);
    }

  public:
    void int_(int value) {
        ensure(OP_INT);
        writeOp(OP_INT);
        writeInt32(value);
    }
    void return_() {
        ensure(OP_RETURN);
        writeOp(OP_RETURN);
    }
    void getlocal(unsigned local) {
        ensure(OP_GETLOCAL_LENGTH);
        writeOp(OP_GETLOCAL);
        writeUint32(local);
    }
    void setlocal(unsigned local) {
        ensure(OP_SETLOCAL_LENGTH);
        writeOp(OP_SETLOCAL);
        writeUint32(local);
    }
    void getarg(unsigned slot) {
        ensure(OP_GETARG_LENGTH);
        writeOp(OP_GETARG);
        writeUint32(slot);
    }
    void setarg(unsigned slot) {
        ensure(OP_SETARG_LENGTH);
        writeOp(OP_SETARG);
        writeUint32(slot);
    }

    void j(Opcode op, Label *label) {
        // Since jumps can skip around the stream, do not try to do anything
        // in a potentially truncated state.
        if (oom())
            return;

        assert(op >= OP_JL && op <= OP_JUMP);
        ensure(OP_JUMP_LENGTH);

        if (label->bound()) {
            // We can immediately emit this jump.
            int distance = label->offset() - position();
            writeOp(op);
            writeInt32(distance);
        } else {
            // We can't emit this jump yet. Add a placeholder.
            int old = label->link(position());
            writeOp(op);
            writeInt32(old);
        }
    }
    void bind(Label *label) {
        if (oom())
            return;

        assert(!label->bound());

        int offset = label->offset();
        while (offset != Label::INVALID_OFFSET)
            offset = patchJumpAt(offset, position());

        label->bind(position());
    }
    void jump(Label *label) {
        j(OP_JUMP, label);
    }

    void binary(Opcode op) {
        singleByteOp(op);
    }

    void pop() {
        singleByteOp(OP_POP);
    }
    void stop() {
        singleByteOp(OP_STOP);
    }
    void callglobal(unsigned index) {
        ensure(OP_CALLGLOBAL_LENGTH);
        writeOp(OP_CALLGLOBAL);
        writeUint32(index);
    }
    void getarray(PrimitiveType type) {
        switch (type) {
          case PrimitiveType_Float:
            singleByteOp(OP_GETARRAY_F);
            break;
          case PrimitiveType_Int32:
            singleByteOp(OP_GETARRAY_I);
            break;
          default:
            assert(type == PrimitiveType_Char);
            singleByteOp(OP_GETARRAY_C);
            break;
        }
    }
    void setarray(PrimitiveType type) {
        switch (type) {
          case PrimitiveType_Float:
            singleByteOp(OP_SETARRAY_F);
            break;
          case PrimitiveType_Int32:
            singleByteOp(OP_SETARRAY_I);
            break;
          default:
            assert(type == PrimitiveType_Char);
            singleByteOp(OP_SETARRAY_C);
            break;
        }
    }
    void getarray_o() {
        singleByteOp(OP_GETARRAY_O);
    }
    void bounds() {
        singleByteOp(OP_BOUNDS);
    }
    void float_(float value) {
        ensure(OP_FLOAT_LENGTH);
        writeOp(OP_FLOAT);
        writeFloat(value);
    }
    void cvt_i2f() {
        singleByteOp(OP_CVT_I2F);
    }
    void newarray_lifo(Handle<Map> map) {
        ensure(OP_NEWARRAY_LIFO_LENGTH);
        writeOp(OP_NEWARRAY_LIFO);
        writeObject(map);
    }
    void savelifo(unsigned slot) {
        ensure(OP_SAVELIFO_LENGTH);
        writeOp(OP_SAVELIFO);
        writeUint32(slot);
    }
    void unwindlifo(unsigned slot) {
        ensure(OP_UNWINDLIFO_LENGTH);
        writeOp(OP_UNWINDLIFO);
        writeUint32(slot);
    }
    void newref(Variable *var) {
        if (var->location() == Variable::STACK) {
            ensure(OP_VARREF_LIFO_LENGTH);
            writeOp(OP_VARREF_LIFO);
        } else {
            assert(var->location() == Variable::PARAMETER);
            ensure(OP_ARGREF_LIFO_LENGTH);
            writeOp(OP_ARGREF_LIFO);
        }
        writeUint32(var->slot());
    }
    void get(Variable *var) {
        if (var->location() == Variable::STACK) {
            getlocal(var->slot());
        } else {
            assert(var->location() == Variable::PARAMETER);
            getarg(var->slot());
        }
    }
    void set(Variable *var) {
        assert(!var->type()->isReference());
        if (var->location() == Variable::STACK) {
            setlocal(var->slot());
        } else {
            assert(var->location() == Variable::PARAMETER);
            setarg(var->slot());
        }
    }
    void slotref_lifo() {
        singleByteOp(OP_SLOTREF_LIFO);
    }
    void arrayref_lifo() {
        singleByteOp(OP_ARRAYREF_LIFO);
    }
    void getref(PrimitiveType type) {
        if (type == PrimitiveType_Int32) {
            singleByteOp(OP_GETREF_I);
        } else {
            assert(type == PrimitiveType_Float);
            singleByteOp(OP_GETREF_F);
        }
    }
    void setref(PrimitiveType type) {
        if (type == PrimitiveType_Int32) {
            singleByteOp(OP_SETREF_I);
        } else {
            assert(type == PrimitiveType_Float);
            singleByteOp(OP_SETREF_F);
        }
    }
    void object(Handle<Object> obj) {
        ensure(OP_OBJECT_LENGTH);
        writeOp(OP_OBJECT);
        writeObject(obj);
    }
    void arraycopy() {
        singleByteOp(OP_ARRAYCOPY);
    }
    void dup() {
        singleByteOp(OP_DUP);
    }
    void dup2() {
        singleByteOp(OP_DUP2);
    }
    void swap() {
        singleByteOp(OP_SWAP);
    }
    void roll3() {
        singleByteOp(OP_ROLL3);
    }
    void pick2() {
        singleByteOp(OP_PICK2);
    }
    void pick3() {
        singleByteOp(OP_PICK3);
    }
};

}

#endif // _include_bytecode_emitter_h_
