/* 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 "Opcodes.h"
#include "Utility.h"
#include "Code.h"
#include "Zone.h"
#include "Array.h"
#include "Strings.h"
#include "CompactBuffer.h"
#include "Structures.h"
#include "Modules.h"
#include "Heap-inl.h"

using namespace ke;

OpInfo ke::OpcodeInfo[] =
{
#define OPDEF(name, 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, Handle<FixedArray> strings, unsigned maxStackDepth,
          unsigned maxLoopDepth, Handle<ByteArray> safepoints, Handle<ByteArray> sourcemap)
{
    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_ = NULL;
    code->safepoints_ = safepoints;
    code->sourcemap_ = sourcemap;
    code->lineNumber_ = 0;
    code->strings_ = strings;
    code->maxLoopDepth_ = maxLoopDepth;
    return code;
}

static int
CompareSafepoints(const void *a, const void *b)
{
    const unsigned *sa = reinterpret_cast<const unsigned *>(a);
    const unsigned *sb = reinterpret_cast<const unsigned *>(b);
    if (*sa > *sb)
        return 1;
    if (*sa == *sb)
        return 0;
    return -1;
}

uint8 *
Code::safepointAt(unsigned pcOffset)
{
    // Only specific opcodes may have safepoints. Asking for a safepoint at
    // any other position indicates a failure to mark that position as a
    // safepoint, which is a bug.
#ifndef NDEBUG
    Opcode op = Opcode(*(start() + pcOffset));
    assert(op == OP_CALL ||
           op == OP_NEWFIXED ||
           op == OP_NEWEMPTY ||
           op == OP_NEWSIZED ||
           op == OP_VARREF_LIFO ||
           op == OP_ARGREF_LIFO ||
           op == OP_SLOTREF_LIFO ||
           op == OP_COPYARRAY ||
           op == OP_CALLNATIVE ||
           op == OP_PUBLIC);
#endif

    if (!safepoints_)
        return NULL;

    size_t bytesPerSafepoint = BytesPerSafepoint(maxStackDepth());
    assert(!(safepoints_->length() % bytesPerSafepoint));

    size_t count = safepoints_->length() / bytesPerSafepoint;

    void *result = bsearch(&pcOffset,
                           safepoints_->raw(),
                           count,
                           bytesPerSafepoint,
                           CompareSafepoints);

    if (!result)
        return NULL;

    assert(*reinterpret_cast<unsigned *>(result) == pcOffset);

    return Address(result) + sizeof(unsigned);
}

SourcePosition
Code::pcToSource(unsigned pc)
{
    assert(pc < bytecode_->length());

    CompactBufferReader reader(sourcemap_->raw(), sourcemap_->raw() + sourcemap_->length());

    unsigned line = 1;
    unsigned col = 0;
    unsigned curPc = 0;
    while (!reader.done()) {
        curPc += reader.readUint32();
        if (curPc > pc)
            break;

        line = reader.readUint32();
        col = reader.readUint32();
    }

    return SourcePosition(lineNumber_ + line, col);
}

// All jumps must be the same length.
STATIC_ASSERT(OP_JT_LENGTH == OP_JUMP_LENGTH);
STATIC_ASSERT(OP_JF_LENGTH == OP_JUMP_LENGTH);

void
ke::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_RETURNVOID:
          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_CVT_I2B:
          case OP_POP:
          case OP_GETELEM:
          case OP_SETELEM:
          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_COPYARRAY:
          case OP_COPYSTRUCT:
          case OP_DUP:
          case OP_DUP2:
          case OP_SWAP:
          case OP_ROLL3:
          case OP_PICK2:
          case OP_PICK3:
          case OP_CALL:
          case OP_LT:
          case OP_LE:
          case OP_GE:
          case OP_GT:
          case OP_EQ:
          case OP_NE:
          case OP_LT_F:
          case OP_LE_F:
          case OP_GE_F:
          case OP_GT_F:
          case OP_EQ_F:
          case OP_NE_F:
          case OP_BITNOT:
          case OP_NOT:
          case OP_NOT_F:
          case OP_NEG:
          case OP_NEG_F:
          case OP_MOD:
          case OP_BITCAST:
          case OP_TRUE:
          case OP_FALSE:
          case OP_SIZEOF:
          case OP_PUBLIC:
          case OP_LEAVELOOP:
          case OP_DUPARRAY:
          case OP_DEPARRAY:
            break;

          case OP_ENTERLOOP:
            if (pc[1])
              fprintf(fp, "; lifo region");
            break;

          case OP_STOP:
            fprintf(fp, "\n");
            break;

          case OP_INT:
            fprintf(fp, "%d", READ_INT32(pc));
            break;

          case OP_FLOAT:
            fprintf(fp, "%f", READ_FLOAT(pc));
            break;

          case OP_JT:
          case OP_JF:
          case OP_JUMP:
          case OP_CONTINUE:
          case OP_BREAK:
            fprintf(fp, "%u (%+d)", unsigned((pc + READ_INT32(pc)) - code->start()), READ_INT32(pc));
            break;

          case OP_NEWFIXED:
          case OP_NEWEMPTY:
          case OP_NEWSIZED:
          {
            Heap::Tenure tenure = (Heap::Tenure)pc[1];
            //ArrayMap *map = ArrayMap::cast(code->map(READ_UINT32(pc + 1)));
            if (tenure == Heap::Tenure_Stack)
                fprintf(fp, " ; lifo");
            else if (tenure == Heap::Tenure_Old)
                fprintf(fp, " ; pretenured");
            else if (tenure == Heap::Tenure_Default)
                fprintf(fp, " ; newborn");
            break;
          }

          case OP_NEWSTRUCT:
          {
            Heap::Tenure tenure = (Heap::Tenure)pc[1];
            StructMap *map = StructMap::cast(code->map(READ_UINT32(pc + 1)));
            fprintf(fp, "%s", StructType::cast(map->type())->name()->chars());
            if (tenure == Heap::Tenure_Stack)
                fprintf(fp, " ; lifo");
            else if (tenure == Heap::Tenure_Old)
                fprintf(fp, " ; pretenured");
            else if (tenure == Heap::Tenure_Default)
                fprintf(fp, " ; newborn");
            break;
          }

          case OP_GETLOCAL:
          case OP_SETLOCAL:
          case OP_GETARG:
          case OP_SETARG:
          case OP_SAVELIFO:
          case OP_UNWINDLIFO:
          case OP_VARREF_LIFO:
          case OP_ARGREF_LIFO:
          case OP_OBJECT:
          case OP_GETFIELD:
          case OP_SETFIELD:
            fprintf(fp, "%u", READ_UINT32(pc));
            break;

          case OP_CALLNATIVE:
          {
            unsigned argc = READ_UINT32(pc);
            fprintf(fp, " ; %u args", argc);
            break;
          }

          case OP_TABLESWITCH:
          {
            int low = READ_TABLESWITCH_LOW(pc);
            int high = READ_TABLESWITCH_HIGH(pc);
            fprintf(fp, "%d, %d\n", low, high);
            break;
          }

          case OP_GETGLOBAL:
          case OP_SETGLOBAL:
          {
            unsigned index = READ_UINT32(pc);
            fprintf(fp, "%s", String::cast(code->strings()->at(index))->chars());
            break;
          }

          case OP_IMPORT:
          {
            unsigned index = READ_UINT32(pc);
            FixedArray *path = code->module()->importPathAt(index);
            fprintf(fp, " ; ");
            for (unsigned i = 0; i < path->length(); i++) {
                fprintf(fp, "%s", String::cast(path->at(i))->chars());
                if (i != path->length() - 1)
                    fprintf(fp, ".");
            }
            break;
          }

          default:
            // Unknown opcode!
            assert(false);
            return;
        }

        if (op == OP_STOP)
            break;

        fprintf(fp, "\n");
        pc += OpcodeInfo[op].length;
        assert(pc < code->start() + code->length());
    }

    fprintf(fp, "\nSafepoints:\n");
    ByteArray *safepoints = code->safepoints();
    if (safepoints) {
        size_t nsafepoints = safepoints->length() / Code::BytesPerSafepoint(code->maxStackDepth());
        for (size_t i = 0; i < nsafepoints; i++) {
            Address address = safepoints->raw() + Code::BytesPerSafepoint(code->maxStackDepth()) * i;
            fprintf(fp, "%4d: ", *reinterpret_cast<unsigned *>(address));
            unsigned char *bits = (unsigned char *)(address + sizeof(unsigned));
            for (unsigned j = 0; j < code->maxStackDepth(); j++) {
                if (bits[j / 8] & (1 << (j % 8)))
                    fprintf(fp, "%d ", j);
            }
            fprintf(fp, "\n");
        }
    }
}
