/*
    Ypsilon Scheme System
    Copyright (c) 2004-2008 Y.FUJITA / LittleWing Company Limited.
    See license.txt for terms and conditions of use
*/

#include "core.h"
#include "hash.h"
#include "list.h"
#include "arith.h"
#include "interpreter.h"
#include "vm.h"
#include "port.h"
#include "printer.h"

#define REPORT_REMEMBER_SET     0

#if USE_PARALLEL_VM
void
Interpreter::init(VM* root, int n)
{
    if (n > MAX_VIRTUAL_MACHINE) n = MAX_VIRTUAL_MACHINE;
    m_lock.init();
    m_uuid_lock.init();
    m_remember_set.init(64);
    m_capacity = n;
    m_table = new vm_table_rec_t* [m_capacity];
    for (int i = 0; i < m_capacity; i++) {
        m_table[i] = new vm_table_rec_t;
        m_table[i]->interp = this;
        m_table[i]->notify.init();
        m_table[i]->state = VM_STATE_FREE;
        m_table[i]->name[0] = 0;
    }
    root->m_interp = this;
    root->m_parent = NULL;
    root->m_id = 0;
    root->m_child = 0;
    m_table[0]->interp = this;
    m_table[0]->state = VM_STATE_ACTIVE;
    m_table[0]->vm = root;
    m_table[0]->parent = VM_PARENT_NONE;
    m_table[0]->param = scm_nil;
    snprintf(m_table[0]->name, sizeof(m_table[0]->name), "<root>");
    m_live = 1;
}

void
Interpreter::destroy()
{
    m_lock.destroy();
    m_uuid_lock.destroy();
    delete [] m_table;
}

int
Interpreter::spawn(VM* parent, scm_closure_t func, int argc, scm_obj_t argv[])
{
    scoped_lock lock(m_lock);
    for (int i = 0; i < m_capacity; i++) {
        if (m_table[i]->state == VM_STATE_FREE) {
            object_heap_t* heap = new object_heap_t;
            int heap_limit = DEFAULT_HEAP_LIMIT * 1024 * 1024;
            int heap_init = 1 * 1024 * 1024;
            heap->init_child(heap_limit, heap_init, parent->m_heap);
            VM* vm = new VM;
            vm->m_heap = heap;
            vm->m_stack_size = VM_STACK_BYTESIZE;
            vm->m_stack_top = (scm_obj_t*)vm->m_heap->allocate(vm->m_stack_size, false, false);
            vm->m_stack_limit = (scm_obj_t*)((intptr_t)vm->m_stack_top + vm->m_stack_size);
            memset(vm->m_stack_top, 0, vm->m_stack_size);
            vm->m_to_stack_top = (scm_obj_t*)vm->m_heap->allocate(vm->m_stack_size, false, false);
            vm->m_to_stack_limit = (scm_obj_t*)((intptr_t)vm->m_to_stack_top + vm->m_stack_size);
            memset(vm->m_to_stack_top, 0, vm->m_stack_size);
            vm->m_interp = parent->m_interp;
            vm->m_parent = parent;
            vm->m_id = i;
            vm->m_child = 0;
            vm->m_bootport = (scm_port_t)scm_unspecified;
            vm->m_current_environment = parent->m_current_environment;
            vm->m_current_input = parent->m_current_input;
            vm->m_current_output = parent->m_current_output;
            vm->m_current_error = parent->m_current_error;
            vm->m_current_source_comments = scm_false;
            vm->m_current_exception_handler = scm_false;
            vm->m_current_dynamic_environment = clone_weakhashtable(vm->m_heap, parent->m_current_dynamic_environment, false);
            vm->m_current_dynamic_wind_record = scm_nil;
            vm->m_recursion_level = 0;
            vm->m_shared_object_errno = 0;
            vm->m_shared_object_win32_lasterror = 0;
            memcpy(&vm->flags, &parent->flags, sizeof(parent->flags));
            vm->run(true);
            vm->reset();
            for (int n = 0; n < argc; n++) vm->m_sp[n] = argv[n];
            vm->m_sp += argc;
            vm_env_t env = (vm_env_t)vm->m_sp;
            env->count = argc;
            env->up = func->env;
            vm->m_sp = vm->m_fp = (scm_obj_t*)(env + 1);
            vm->m_pc = func->code;
            vm->m_env = &env->up;
            scm_obj_t context = scm_nil;
            if (argc > 0) {
                for (int n = argc - 1; n >= 0; n--) context = make_pair(parent->m_heap, argv[n], context);
                context = make_pair(parent->m_heap, func, context);
            } else {
                context = make_list(parent->m_heap, 1, func);
            }
            m_table[i]->param = make_list(parent->m_heap,
                                          5,
                                          context,
                                          parent->m_current_environment,
                                          parent->m_current_input,
                                          parent->m_current_output,
                                          parent->m_current_error);
            m_table[i]->parent = parent->m_id;
            m_table[i]->vm = vm;
            m_table[i]->id = i;
            m_table[i]->state = VM_STATE_ACTIVE;
            if (func->doc == scm_nil) {
                snprintf(m_table[i]->name, sizeof(m_table[i]->name), "[%p]", func);
            } else {
                const char* name = "";
                if (SYMBOLP(func->doc)) name = ((scm_symbol_t)func->doc)->name;
                if (STRINGP(func->doc)) name = ((scm_string_t)func->doc)->name;
                snprintf(m_table[i]->name, sizeof(m_table[i]->name), "%s", name);
            }
            m_live = m_live + 1;
            parent->m_child++;
            thread_start(mutator_thread, m_table[i]);
            return i;
        }
    }
    return -1;
}

thread_main_t
Interpreter::mutator_thread(void* param)
{
    vm_table_rec_t* table_rec = (vm_table_rec_t*)param;
    Interpreter* interp = table_rec->interp;
    VM* vm = table_rec->vm;
    set_current_vm(vm);

loop:
    try {
        vm->run(false);
    } catch (vm_exit_t& e) {
        exit(e.m_code);
    } catch (vm_exception_t& e) {
        vm->backtrace(vm->m_current_error);
    } catch (io_exception_t& e) {
        if (e.m_err == EINTR) goto loop;
        if (e.m_err == EIO) goto loop;
        fatal("fatal in thread(0x%x): unexpected io_expecption_t(%d, %s)", vm, e.m_err, e.m_message);
    } catch (reader_exception_t& e) {
        fatal("fatal in thread(0x%x): unhandled exception reader_expecption_t(%s)", vm, e.m_message);
    } catch (io_codec_exception_t& e) {
        fatal("fatal in thread(0x%x): unhandled exception io_codec_exception_t(%d, %s)", vm, e.m_operation, e.m_message);
    } catch (vm_escape_t& e) {
        fatal("fatal in thread(0x%x): unhandled exception vm_escape_t, maybe (escape) procedure in bad context", vm);
    } catch (vm_continue_t& e) {
        fatal("fatal in thread(0x%x): unhandled exception vm_continue_t, maybe (escape) procedure in bad context", vm);
    } catch (int code) {
        fatal("fatal in thread(0x%x): unexpected exception (errno %d, %s)", vm, code, strerror(code));
    } catch (...) {
        fatal("fatal in thread(0x%x): unknown exception", vm);
    }
    vm->m_heap->m_collector_lock.lock();
    vm->m_heap->m_collector_terminating = true;
    vm->m_heap->m_collector_wake.signal();
    vm->m_heap->m_collector_lock.unlock();
    while (true) {
        vm->m_heap->m_collector_lock.lock();
        if (vm->m_heap->m_collector_terminating == false) {
            vm->m_heap->m_collector_lock.unlock();
            break;
        }
        while (vm->m_heap->m_stop_the_world) {
            vm->m_heap->m_mutator_stopped = true;
            vm->m_heap->m_collector_wake.signal();
            vm->m_heap->m_mutator_wake.wait(vm->m_heap->m_collector_lock);
            vm->m_heap->m_mutator_stopped = false;
        }
        vm->m_heap->m_collector_wake.signal();
        vm->m_heap->m_collector_lock.unlock();
        thread_yield();
    }
    {
        scoped_lock lock(interp->m_lock);
        interp->m_table[table_rec->parent]->vm->m_child--;
        interp->m_remember_set.clear(1 << vm->m_id);

    wait_again:
        for (int i = 0; i < interp->m_capacity; i++) {
            switch (interp->m_table[i]->state) {
            case VM_STATE_FREE:
                break;
            case VM_STATE_ACTIVE:
            case VM_STATE_BLOCK:
            case VM_STATE_SYNC:
                if (interp->m_table[i]->parent == table_rec->id) {
                    table_rec->state = VM_STATE_SYNC;
                    table_rec->notify.wait(interp->m_lock);
                    goto wait_again;
                }
                break;
            }
        }
    }
    vm->m_heap->destroy();
    delete vm->m_heap;
    delete vm;
    interp->m_lock.lock();
    table_rec->state = VM_STATE_FREE;
    interp->m_live = interp->m_live - 1;
    interp->m_table[table_rec->parent]->notify.signal();
    interp->m_lock.unlock();
    return NULL;
}

void
Interpreter::update(VM* vm, int state)
{
    Interpreter* interp = vm->m_interp;
    scoped_lock lock(interp->m_lock);
    vm_table_rec_t** table = interp->m_table;
    for (int i = 0; i < interp->m_capacity; i++) {
        vm_table_rec_t* rec = table[i];
        if (rec->vm == vm) {
            rec->state = state;
            break;
        }
    }
}

void
Interpreter::display_status(VM* vm)
{
    scm_port_t port = vm->m_current_output;
    scoped_lock lock1(port->lock);
    Interpreter* interp = vm->m_interp;
    scoped_lock lock2(interp->m_lock);
    vm_table_rec_t** table = interp->m_table;
    int name_pad = 8;
    for (int i = 0; i < interp->m_capacity; i++) {
        vm_table_rec_t* rec = table[i];
        int n = strlen(rec->name);
        if (n > name_pad) name_pad = n;
    }
#if ARCH_LP64
    port_puts(port, "\n  VM ADRS           STATUS CT ID");
#else
    port_puts(port, "\n  VM ADRS       STATUS CT ID");
#endif
    for (int c = 0; c < name_pad - 2; c++) port_puts(port, " ");
    port_puts(port, "  MEM\n");
    for (int i = 0; i < interp->m_capacity; i++) {
        vm_table_rec_t* rec = table[i];
        const char* stat = "unknown";
        scm_obj_t param = scm_nil;
        switch (rec->state) {
            case VM_STATE_FREE:
                continue;
            case VM_STATE_ACTIVE:
                stat = "active";
                param = rec->param;
                break;
            case VM_STATE_BLOCK:
                stat = "block";
                param = rec->param;
                break;
            case VM_STATE_SYNC:
                stat = "wait";
                param = rec->param;
                break;
            default: break;
        }
        port_format(port, "  %2d 0x%08lx %-6s %2d %s", i, rec->vm, stat, rec->vm->m_child, rec->name);
        int pad = name_pad - strlen(rec->name);
        for (int c = 0; c < pad; c++) port_puts(port, " ");
        port_format(port, "  %d/%dM", rec->vm->m_heap->m_pool_watermark * OBJECT_SLAB_SIZE / 1024 / 1024, rec->vm->m_heap->m_pool_size / 1024 / 1024);
        port_puts(port, "\n");
    }
#if REPORT_REMEMBER_SET
    m_remember_set.display_status(vm);
#else
    port_puts(port, "\n");
#endif
    }

void
Interpreter::snapshot(VM* vm, bool retry)
{
    scoped_lock lock(m_lock);
    int id = vm->m_id;
    for (int i = 0; i < m_capacity; i++) {
        switch (m_table[i]->state) {
            case VM_STATE_ACTIVE:
            case VM_STATE_BLOCK:
            if (m_table[i]->parent == id) vm->m_heap->enqueue_root(m_table[i]->param);
            break;
        }
    }
    for (int i = 0; i < m_capacity; i++) {
        if (m_table[i]->state == VM_STATE_FREE) continue;
        if (m_table[i]->parent == id) {
            m_remember_set.snapshot(vm, retry);
            return;
        }
    }
}

bool
Interpreter::primordial(int id)
{
    scoped_lock lock(m_lock);
    return (m_table[id]->parent == VM_PARENT_NONE);
}

void
Interpreter::set_thread_name(int id, const char* name)
{
    scoped_lock lock(m_lock);
    strncpy(m_table[id]->name, name, sizeof(m_table[id]->name));
}

void
Interpreter::get_thread_name(int id, char* name, int len)
{
    scoped_lock lock(m_lock);
    strncpy(name, m_table[id]->name, len);
}

static bool
subset_of_list(scm_obj_t lst, scm_obj_t elt)
{
    if (lst == scm_nil) return (elt == scm_nil);
    scm_obj_t fast = lst;
    scm_obj_t slow = fast;
    while (PAIRP(fast)) {
        if (fast == elt) return true;
        fast = CDR(fast);
        if (PAIRP(fast)) {
            if (fast == elt) return true;
            fast = CDR(fast);
            slow = CDR(slow);
            if (slow == fast) return false;
        } else {
            return false;
        }
    }
    return false;
}

void
Interpreter::remember(scm_obj_t lhs, scm_obj_t rhs)
{
    assert(lhs);
    if (CELLP(lhs)) {
        if (lhs == rhs) return;
        if (PAIRP(rhs)) {
            if (CDR(rhs) == lhs) return; // eliminate if rhs == (x . lhs)
            if (PAIRP(lhs) && subset_of_list(rhs, lhs)) return;  // eliminate if rhs == (x ... lhs)
        }
        scoped_lock lock(m_lock);
        uint32_t bits = 0;
        for (int i = 0; i < m_capacity; i++) {
            switch (m_table[i]->state) {
            case VM_STATE_ACTIVE:
            case VM_STATE_BLOCK:
                if (m_table[i]->parent != VM_PARENT_NONE) bits |= (1 << i);
                break;
            }
        }
        if (bits) m_remember_set.set(lhs, bits);
    }
}

remember_set_t::remember_set_t()
{
    m_elts = NULL;
}

remember_set_t::~remember_set_t()
{
    free(m_elts);
}

void
remember_set_t::init(int n)
{
    m_count = lookup_mutable_hashtable_size(n);
    m_live = 0;
    m_elts = (element_t*)malloc(sizeof(element_t) * m_count);
    for (int i = 0; i < m_count; i++) {
        m_elts[i].obj = scm_hash_free;
        m_elts[i].bits = 0;
    }
}

void
remember_set_t::set(scm_obj_t obj, uint32_t bits)
{
    int hash1 = address_hash1(obj, m_count);
    int hash2 = address_hash2(obj, m_count);
    int index = hash1;
    do {
        scm_obj_t tag = m_elts[index].obj;
        if (tag == obj) {
            m_elts[index].bits |= bits;
            return;
        }
        if (tag == scm_hash_free || tag == scm_hash_deleted) {
            m_elts[index].obj = obj;
            m_elts[index].bits = bits;
            m_live++;
            if (m_live >= HASH_BUSY_THRESHOLD(m_count)) rehash(lookup_mutable_hashtable_size(m_count));
            return;
        }
        index += hash2;
        if (index >= m_count) index -= m_count;
    } while (index != hash1);
    fatal("%s:%u remember set overflow", __FILE__, __LINE__);
}

void
remember_set_t::clear(uint32_t bits)
{
    int n = m_live;
    for (int i = 0; i < m_count; i++) {
        scm_obj_t tag = m_elts[i].obj;
        if (tag == scm_hash_free || tag == scm_hash_deleted) continue;
        m_elts[i].bits &= (~bits);
        if (m_elts[i].bits == 0) {
            m_elts[i].obj = scm_hash_deleted;
            m_live--;
        }
    }
    if (n != m_live) rehash(lookup_mutable_hashtable_size(m_live));
}

void
remember_set_t::rehash(int ncount)
{
    assert(ncount >= m_live);
    int save_count = m_count;
    element_t* save_elts = m_elts;
    m_count = ncount;
    m_elts = (element_t*)malloc(sizeof(element_t) * m_count);
    for (int i = 0; i < m_count; i++) {
        m_elts[i].obj = scm_hash_free;
        m_elts[i].bits = 0;
    }
    for (int i = 0; i < save_count; i++) {
        scm_obj_t obj = save_elts[i].obj;
        if (obj == scm_hash_free) continue;
        if (obj == scm_hash_deleted) continue;
        int hash1 = address_hash1(obj, m_count);
        int hash2 = address_hash2(obj, m_count);
        int index = hash1;
        do {
            if (m_elts[index].obj == scm_hash_free) {
                m_elts[index] = save_elts[i];
                break;
            }
            index += hash2;
            if (index >= m_count) index -= m_count;
            assert(index != hash1);
        } while (true);
    }
    free(save_elts);
}

void
remember_set_t::snapshot(VM* vm, bool retry)
{
    int n = m_live;
    for (int i = 0; i < m_count; i++) {
        scm_obj_t obj = m_elts[i].obj;
        if (m_elts[i].obj != scm_hash_free && m_elts[i].obj != scm_hash_deleted) {
            if (vm->m_heap->in_heap(obj)) {
                if (retry) {
                    vm->m_heap->enqueue_root(obj);
                    continue;
                }
                if (OBJECT_SLAB_TRAITS_OF(obj)->cache->state(obj)) {
                    m_elts[i].obj = scm_hash_deleted;
                    m_live--;
                    continue;
                }
                vm->m_heap->enqueue_root(obj);
            }
        }
    }
    if (n != m_live) rehash(lookup_mutable_hashtable_size(m_live));
}

void
remember_set_t::display_status(VM* vm)
{
    scm_port_t port = vm->m_current_output;
    scoped_lock lock(port->lock);
    int n = 0;
    for (int i = 0; i < m_count; i++) {
        if (m_elts[i].obj == scm_hash_free) continue;
        if (m_elts[i].obj == scm_hash_deleted) continue;
        n++;
    }
    port_format(port, "\n   [%d objects in remember set]\n", n);
}

/*
  Reference: A Universally Unique IDentifier (UUID) URN Namespace
             http://www.ietf.org/rfc/rfc4122.txt

      UUID                   = time-low "-" time-mid "-"
                               time-high-and-version "-"
                               clock-seq-and-reserved
                               clock-seq-low "-" node
      time-low               = 4hexOctet
      time-mid               = 2hexOctet
      time-high-and-version  = 2hexOctet
      clock-seq-and-reserved = hexOctet
      clock-seq-low          = hexOctet
      node                   = 6hexOctet
      hexOctet               = hexDigit hexDigit
      hexDigit =
            "0" / "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" /
            "a" / "b" / "c" / "d" / "e" / "f" /
            "A" / "B" / "C" / "D" / "E" / "F"
*/

static uint32_t rand8()
{
    uint32_t a = random();
    return (a & 0xff);
}

static uint32_t rand16()
{
    uint32_t a = random();
    uint32_t b = random();
    return ((a & 0xff) << 8) + (b & 0xff);
}

static uint32_t rand32()
{
    uint32_t a = random();
    uint32_t b = random();
    uint32_t c = random();
    return ((a & 0x3ff) << 22) + ((b & 0x7ff) << 11) + (c & 0x7ff);
}

static uint64_t rand48()
{
    uint64_t a = random();
    uint64_t b = random();
    uint64_t c = random();
    uint64_t d = random();
    return ((a & 0xfff) << 36) + ((b & 0xfff) << 24) + ((c & 0xfff) << 12) + (d & 0xfff);
}

void
Interpreter::generate_uuid(char* buf, int bufsize) // version 4
{
    assert(bufsize > 36);
    scoped_lock lock(m_uuid_lock);
    uint32_t time_low;                      // octet[0-3]
    uint16_t time_mid;                      // octet[4-5]
    uint16_t time_hi_and_version;           // octet[6-7]
    uint8_t clock_seq_hi_and_reserved;      // octet[8]
    uint8_t clock_seq_lo;                   // octet[9]
    uint64_t node;                          // octed[10-15]
    time_low = rand32();
    time_mid = rand16();
    time_hi_and_version = rand16();
    clock_seq_hi_and_reserved = rand8();
    clock_seq_lo = rand8();
    node = rand48();
    uint16_t version = 0x4000;              // octet[6] 0100 xxxx octet[7] xxxx xxxx
    time_hi_and_version = version + (time_hi_and_version & 0xfff);
    uint8_t variant = 0x80;                 // octet[8] 100x xxxx
    clock_seq_hi_and_reserved = variant + (clock_seq_hi_and_reserved & 0x1f);
    snprintf(buf,
             bufsize,
             "%08x-%04x-%04x-%02x%02x-%04x%08x",
             time_low,
             time_mid,
             time_hi_and_version,
             clock_seq_hi_and_reserved,
             clock_seq_lo,
             (uint32_t)(node >> 32),
             (uint32_t)node);
}

#endif
