/*
 * 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 "Opcodes.h"
#include "Utility.h"
#include "Code.h"
#include "Zone.h"
#include "Array.h"
#include "Heap-inl.h"

using namespace jc;

OpInfo jc::OpcodeInfo[] =
{
#define OPDEF(name, number, text, length, uses, defs) \
    {text, length, uses, defs},
#include "Opcodes.tbl"
#undef OPDEF
};

Code *
Code::New(Zone *zone, Handle<ByteArray> bytecode, Handle<FixedArray> locals,
          Handle<FixedArray> objects, unsigned maxStackDepth, Handle<Module> module)
{
    Local<Code> code(zone, Code::cast(zone->allocate(MapKind_Code, sizeof(Code), Heap::Tenure_Old)));
    if (!code)
        return NULL;

    code->bytecode_ = bytecode;
    code->locals_ = locals;
    code->objects_ = objects;
    code->maxStackDepth_ = maxStackDepth;
    code->module_ = module;
    return code;
}

// All jumps must be the same length.
STATIC_ASSERT(OP_JL_LENGTH == OP_JUMP_LENGTH);
STATIC_ASSERT(OP_JLE_LENGTH == OP_JUMP_LENGTH);
STATIC_ASSERT(OP_JG_LENGTH == OP_JUMP_LENGTH);
STATIC_ASSERT(OP_JGE_LENGTH == OP_JUMP_LENGTH);
STATIC_ASSERT(OP_JEQ_LENGTH == OP_JUMP_LENGTH);
STATIC_ASSERT(OP_JNE_LENGTH == OP_JUMP_LENGTH);
STATIC_ASSERT(OP_JT_LENGTH == OP_JUMP_LENGTH);
STATIC_ASSERT(OP_JF_LENGTH == OP_JUMP_LENGTH);

void
jc::Disassemble(Code *code, FILE *fp)
{
    bytecode *pc = code->start();
    for (;;) {
        Opcode op = Opcode(*pc);

        fprintf(fp, "%-4d %s ", int(pc - code->start()), OpcodeInfo[op].name);

        switch (op) {
          case OP_NOP:
          case OP_RETURN:
          case OP_ADD:
          case OP_SUB:
          case OP_MUL:
          case OP_DIV:
          case OP_ADD_F:
          case OP_SUB_F:
          case OP_MUL_F:
          case OP_DIV_F:
          case OP_CVT_I2F:
          case OP_POP:
          case OP_GETARRAY_I:
          case OP_SETARRAY_I:
          case OP_GETARRAY_F:
          case OP_SETARRAY_F:
          case OP_GETARRAY_C:
          case OP_SETARRAY_C:
          case OP_GETARRAY_O:
          case OP_BOUNDS:
          case OP_SHL:
          case OP_SHR:
          case OP_USHR:
          case OP_BITAND:
          case OP_BITOR:
          case OP_BITXOR:
          case OP_GETREF_I:
          case OP_SETREF_I:
          case OP_GETREF_F:
          case OP_SETREF_F:
          case OP_SLOTREF_LIFO:
          case OP_ARRAYREF_LIFO:
          case OP_OBJECT:
          case OP_ARRAYCOPY:
          case OP_DUP:
          case OP_DUP2:
          case OP_SWAP:
          case OP_ROLL3:
          case OP_PICK2:
          case OP_PICK3:
            break;

          case OP_STOP:
            fprintf(fp, "\n");
            return;

          case OP_INT:
            fprintf(fp, "%d", READ_INT32(pc));
            break;

          case OP_FLOAT:
            fprintf(fp, "%f", READ_FLOAT(pc));
            break;

          case OP_JL:
          case OP_JLE:
          case OP_JG:
          case OP_JGE:
          case OP_JEQ:
          case OP_JNE:
          case OP_JT:
          case OP_JF:
          case OP_JUMP:
            fprintf(fp, "%u (%+d)", unsigned((pc + READ_INT32(pc)) - code->start()), READ_INT32(pc));
            break;

          case OP_NEWARRAY_LIFO:
          {
            // :TODO: use Local
            ArrayMap *map = ArrayMap::cast(code->map(READ_UINT32(pc)));
            fprintf(fp, "%s:", map->type()->innermost()->tag()
                               ? map->type()->innermost()->tag()
                               : "_");
            for (; map; map = map->contained()) {
                Type *type = map->type();
                fprintf(fp, "[%d]", type->elements());
            }
            break;
          }

          case OP_GETLOCAL:
          case OP_SETLOCAL:
          case OP_GETARG:
          case OP_SETARG:
          case OP_SAVELIFO:
          case OP_UNWINDLIFO:
          case OP_CALLGLOBAL:
          case OP_VARREF_LIFO:
          case OP_ARGREF_LIFO:
            fprintf(fp, "%u", READ_UINT32(pc));
            break;

          default:
            // Unknown opcode!
            assert(false);
            return;
        }

        fprintf(fp, "\n");
        pc += OpcodeInfo[op].length;
        assert(pc < code->start() + code->length());
    }
}
