/*******************************************************************************
 * Copyright (c) 2012 Wind River Systems, Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 * The Eclipse Public License is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 * You may elect to redistribute this code under either of these licenses.
 *
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/

/*
 * This module implements preparation of a new stack before calling a function.
 * This code is a stub. Downstream code will provide real implementation.
 */

#include <tcf/config.h>

#if ENABLE_Symbols && ENABLE_DebugContext

#include <assert.h>
#include <tcf/framework/myalloc.h>
#include <tcf/framework/cpudefs.h>
#include <tcf/services/funccall.h>

#define EM_386          3 /* Intel Architecture */
#define EM_X86_64      62 /* AMD x86-64 architecture */

static FunctionCallInfo * info = NULL;

static unsigned trace_cmds_max = 0;
static unsigned trace_cmds_cnt = 0;
static LocationExpressionCommand * trace_cmds = NULL;

static Symbol * func_type = NULL;
static Symbol * func_return_type = NULL;
static Symbol ** func_args = NULL;
static int func_args_cnt = 0;
static ContextAddress func_return_size = 0;
static int func_return_type_class = 0;
static ContextAddress * arg_size_formal = NULL;
static int * arg_class_formal = NULL;
static int * arg_class_actual = NULL;

static int reg_arg_ids[] = { 5, 4, 1, 2, 8, 9 };

static LocationExpressionCommand * add_command(int op) {
    LocationExpressionCommand * cmd = NULL;
    if (trace_cmds_cnt >= trace_cmds_max) {
        trace_cmds_max += 16;
        trace_cmds = (LocationExpressionCommand *)tmp_realloc(trace_cmds, trace_cmds_max * sizeof(LocationExpressionCommand));
    }
    cmd = trace_cmds + trace_cmds_cnt++;
    memset(cmd, 0, sizeof(*cmd));
    cmd->cmd = op;
    if (op == SFT_CMD_RD_MEM || op == SFT_CMD_WR_MEM) {
        cmd->args.mem.big_endian = info->scope.big_endian;
    }
    return cmd;
}

#if 0 /* Not used */
static LocationExpressionCommand * add_command_location(uint8_t * code, size_t code_size) {
    LocationExpressionCommand * cmd = NULL;
    cmd = add_command(SFT_CMD_LOCATION);
    cmd->args.loc.code_addr = code;
    cmd->args.loc.code_size = code_size;
    cmd->args.loc.reg_id_scope = info->scope;
    cmd->args.loc.addr_size =  info->scope.elf64? 64 : 32;
    return cmd;
}
#endif

static int get_stack_pointer_register_id(void) {
    switch (info->scope.machine) {
    case EM_386: return 4;
    case EM_X86_64: return 7;
    }
    return -1;
}

static int get_return_value_register_id(void) {
    switch (info->scope.machine) {
    case EM_386: return 0;
    case EM_X86_64: return 0;
    }
    return -1;
}

static RegisterDefinition * find_register(int id) {
    if (id < 0) return NULL;
    return get_reg_by_id(info->ctx, id, &info->scope);
}

static int c_call_cmds(void) {
    unsigned i;
    unsigned sp_offs = 0;
    int * reg_arg = (int *)tmp_alloc_zero(sizeof(int) * info->args_cnt);

    RegisterDefinition * reg_rv = find_register(get_return_value_register_id());
    RegisterDefinition * reg_sp = find_register(get_stack_pointer_register_id());
    RegisterDefinition * reg_pc = get_PC_definition(info->ctx);

    if (reg_sp == NULL) {
        set_errno(ERR_OTHER, "Don't know stack pointer register");
        return -1;
    }
    if (reg_pc == NULL) {
        set_errno(ERR_OTHER, "Don't know instruction pointer register");
        return -1;
    }
    if (reg_rv == NULL) {
        set_errno(ERR_OTHER, "Don't know function return value register");
        return -1;
    }

    info->stak_pointer = reg_sp;

    if (info->scope.machine == EM_386) {
        info->stack_alignment = 8;
    }
    else if (info->scope.machine == EM_X86_64) {
        unsigned reg_args_cnt = 0;

        info->stack_alignment = 16;
        info->red_zone_size = 128;

        /* Assign arguments to registers */
        for (i = 0; i < info->args_cnt; i++) {
            unsigned arg_no = i;
            switch (arg_class_actual[arg_no]) {
            case TYPE_CLASS_CARDINAL:
            case TYPE_CLASS_INTEGER:
            case TYPE_CLASS_POINTER:
            case TYPE_CLASS_ENUMERATION:
            case TYPE_CLASS_ARRAY:
                switch (arg_class_formal[arg_no]) {
                case TYPE_CLASS_CARDINAL:
                case TYPE_CLASS_INTEGER:
                case TYPE_CLASS_POINTER:
                case TYPE_CLASS_ENUMERATION:
                case TYPE_CLASS_ARRAY:
                    if (reg_args_cnt < sizeof(reg_arg_ids) / sizeof(int)) {
                        add_command(SFT_CMD_ARG)->args.arg_no = FUNCCALL_ARG_ARGS + arg_no;
                        add_command(SFT_CMD_WR_REG)->args.reg = find_register(reg_arg_ids[reg_args_cnt++]);
                        reg_arg[i] = 1;
                    }
                    break;
                }
                break;
            }
        }
    }

    /* Push arguments to call stack */
    for (i = 0; i < info->args_cnt; i++) {
        unsigned arg_no = info->args_cnt - i - 1;
        if (reg_arg[arg_no]) continue;
        switch (arg_class_actual[arg_no]) {
        case TYPE_CLASS_CARDINAL:
        case TYPE_CLASS_INTEGER:
        case TYPE_CLASS_POINTER:
        case TYPE_CLASS_ENUMERATION:
        case TYPE_CLASS_ARRAY:
            switch (arg_class_formal[arg_no]) {
            case TYPE_CLASS_CARDINAL:
            case TYPE_CLASS_INTEGER:
            case TYPE_CLASS_POINTER:
            case TYPE_CLASS_ENUMERATION:
            case TYPE_CLASS_ARRAY:
                sp_offs += (unsigned)arg_size_formal[arg_no];
                while (sp_offs % (info->scope.elf64 ? 8 : 4) != 0) sp_offs++;
                add_command(SFT_CMD_RD_REG)->args.reg = reg_sp;
                add_command(SFT_CMD_NUMBER)->args.num = sp_offs;
                add_command(SFT_CMD_SUB);
                add_command(SFT_CMD_ARG)->args.arg_no = FUNCCALL_ARG_ARGS + arg_no;
                add_command(SFT_CMD_WR_MEM)->args.mem.size = (size_t)arg_size_formal[arg_no];
                break;
            default:
                set_errno(ERR_OTHER, "Unsupported argument type");
                return -1;
            }
            break;
        default:
            set_errno(ERR_OTHER, "Unsupported argument type");
            return -1;
        }
    }

    /* Push current PC to the stack as return address */
    sp_offs += reg_pc->size;
    add_command(SFT_CMD_RD_REG)->args.reg = reg_sp;
    add_command(SFT_CMD_NUMBER)->args.num = sp_offs;
    add_command(SFT_CMD_SUB);
    add_command(SFT_CMD_RD_REG)->args.reg = reg_pc;
    add_command(SFT_CMD_WR_MEM)->args.mem.size = reg_pc->size;

    /* Update stack pointer register */
    add_command(SFT_CMD_RD_REG)->args.reg = reg_sp;
    add_command(SFT_CMD_NUMBER)->args.num = sp_offs;
    add_command(SFT_CMD_SUB);
    add_command(SFT_CMD_WR_REG)->args.reg = reg_sp;

    /* Execute the call */
    add_command(SFT_CMD_FCALL);

    /* Get function return value */
    if (func_return_size == 0) return 0;
    switch (func_return_type_class) {
    case TYPE_CLASS_CARDINAL:
    case TYPE_CLASS_INTEGER:
    case TYPE_CLASS_POINTER:
    case TYPE_CLASS_ENUMERATION:
    case TYPE_CLASS_ARRAY:
        add_command(SFT_CMD_RD_REG)->args.reg = reg_rv;
        break;
    default:
        set_errno(ERR_OTHER, "Unsupported return type");
        return -1;
    }
    return 0;
}

static void save_registers(void) {
    unsigned cnt = 0;
    RegisterDefinition * r;
    RegisterDefinition * regs = get_reg_definitions(info->ctx);
    for (r = regs; r->name != NULL; r++) {
        if (r->dwarf_id < 0) continue;
        if (r->size == 0) continue;
        cnt++;
    }
    info->saveregs = (RegisterDefinition **)tmp_alloc(sizeof(RegisterDefinition *) * cnt);
    for (r = regs; r->name != NULL; r++) {
        if (r->dwarf_id < 0) continue;
        if (r->size == 0) continue;
        info->saveregs[info->saveregs_cnt++] = r;
    }
    assert(info->saveregs_cnt == cnt);
}

int get_function_call_location_expression(FunctionCallInfo * arg_info) {
    unsigned i;
    int sym_class = SYM_CLASS_UNKNOWN;

    info = arg_info;
    trace_cmds_cnt = 0;
    trace_cmds_max = 0;
    trace_cmds = NULL;

    if (get_symbol_class(info->func, &sym_class) < 0) return -1;
    if (sym_class == SYM_CLASS_FUNCTION) {
        if (get_symbol_type(info->func, &func_type) < 0) return -1;
    }
    else if (sym_class == SYM_CLASS_TYPE) {
        func_type = (Symbol *)info->func;
    }
    else {
        set_errno(ERR_OTHER, "Invalid function symbol");
        return -1;
    }
    if (get_symbol_children(func_type, &func_args, &func_args_cnt) < 0) return -1;
    if (get_symbol_base_type(func_type, &func_return_type) < 0) return -1;
    if (get_symbol_size(func_return_type, &func_return_size) < 0) return -1;
    if (get_symbol_type_class(func_return_type, &func_return_type_class) < 0) return -1;

    arg_class_formal = (int *)tmp_alloc(sizeof(int) * info->args_cnt);
    arg_class_actual = (int *)tmp_alloc(sizeof(int) * info->args_cnt);
    arg_size_formal = (ContextAddress *)tmp_alloc(sizeof(ContextAddress) * info->args_cnt);
    for (i = 0; i < info->args_cnt; i++) {
        const Symbol * s = info->args[i];
        arg_class_formal[i] = TYPE_CLASS_INTEGER;
        arg_class_actual[i] = TYPE_CLASS_INTEGER;
        arg_size_formal[i] = info->scope.elf64 ? 8 : 4;
        /* If argument type is not given, assume 'int' */
        if (s != NULL && get_symbol_type_class(s, arg_class_actual + i) < 0) return -1;
        if (i < (unsigned)func_args_cnt) {
            if (get_symbol_type_class(func_args[i], arg_class_formal + i) < 0) return -1;
            if (get_symbol_size(func_args[i], arg_size_formal + i) < 0) return -1;
        }
    }

    switch (info->scope.os_abi) {
    case 0:
        if (c_call_cmds() < 0) return -1;
        break;
    default:
        set_errno(ERR_OTHER, "Unsupported ABI code");
        return -1;
    }

    if (trace_cmds_cnt > 0) {
        save_registers();
        info->cmds = trace_cmds;
        info->cmds_cnt = trace_cmds_cnt;
        return 0;
    }
    set_errno(ERR_OTHER, "Calling functions is not supported");
    return -1;
}

#endif /* ENABLE_Symbols && ENABLE_DebugContext */
