/*
 * Copyright (c) 2013, Red Hat Inc.
 * All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

#ifdef BUILTIN_SIM

#include <stdio.h>
#include <sys/types.h>
#include "asm/assembler.hpp"
#include "asm/assembler.inline.hpp"
#include "runtime/sharedRuntime.hpp"
#include "../../../../../../simulator/cpustate.hpp"
#include "../../../../../../simulator/simulator.hpp"

/*
 * a routine to initialise and enter ARM simulator execution when
 * calling into ARM code from x86 code.
 *
 * we maintain a simulator per-thread and provide it with 8 Mb of
 * stack space
 */
#define SIM_STACK_SIZE (1024 * 1024) // in units of u_int64_t

extern "C" u_int64_t get_alt_stack()
{
  return AArch64Simulator::altStack();
}

extern "C" void setup_arm_sim(void *sp, u_int64_t calltype)
{
  // n.b. this function runs on the simulator stack so as to avoid
  // simulator frames appearing in between VM x86 and ARM frames. note
  // that arfgument sp points to the old (VM) stack from which the
  // call into the sim was made. The stack switch and entry into this
  // routine is handled by x86 prolog code planted in the head of the
  // ARM code buffer which the sim is about to start executing (see
  // aarch64_linkage.S).
  //
  // The first ARM instruction in the buffer is identified by fnptr
  // stored at the top of the old stack. x86 register contents precede
  // fnptr. preceding that are the fp and return address of the VM
  // caller into ARM code. any extra, non-register arguments passed to
  // the linkage routine precede the fp (this is as per any normal x86
  // call wirth extra args).
  //
  // note that the sim creates Java frames on the Java stack just
  // above sp (i.e. directly above fnptr). it sets the sim FP register
  // to the pushed fp for the caller effectively eliding the register
  // data saved by the linkage routine.
  //
  // x86 register call arguments are loaded from the stack into ARM
  // call registers. if extra arguments occur preceding the x86
  // caller's fp then they are copied either into extra ARM registers
  // (ARM has 8 rather than 6 gp call registers) or up the stack
  // beyond the saved x86 registers so that they immediately precede
  // the ARM frame where the ARM calling convention expects them to
  // be.
  //
  // n.b. the number of register/stack values passed to the ARM code
  // is determined by calltype
  //
  // +--------+  
  // | fnptr  |  <--- argument sp points here
  // +--------+  |
  // | rax    |  | return slot if we need to return a value
  // +--------+  |
  // | rdi    |  increasing
  // +--------+  address
  // | rsi    |  |
  // +--------+  V
  // | rdx    |
  // +--------+
  // | rcx    |
  // +--------+
  // | r8     |
  // +--------+
  // | r9     |
  // +--------+
  // | xmm0   |
  // +--------+
  // | xmm1   |
  // +--------+
  // | xmm2   |
  // +--------+
  // | xmm3   |
  // +--------+
  // | xmm4   |
  // +--------+
  // | xmm5   |
  // +--------+
  // | xmm6   |
  // +--------+
  // | xmm7   |
  // +--------+
  // | fp     |
  // +--------+
  // | caller |
  // | ret ip |
  // +--------+
  // | arg0   | <-- any extra call args start here
  // +--------+     offset = 18 * wordSize
  // | . . .  |     (i.e. 1 * calladdr + 1 * rax  + 6 * gp call regs
  //                      + 8 * fp call regs + 2 * frame words)
  //
  // we use a unique sim/stack per thread
  const int cursor2_offset = 18;
  const int fp_offset = 16;
  u_int64_t *cursor = (u_int64_t *)sp;
  u_int64_t *cursor2 = ((u_int64_t *)sp) + cursor2_offset;
  u_int64_t *fp = ((u_int64_t *)sp) + fp_offset;
  int gp_arg_count = calltype & 0xf;
  int fp_arg_count = (calltype >> 4) & 0xf;
  int return_type = (calltype >> 8) & 0x3;
  AArch64Simulator *sim = AArch64Simulator::get_current(UseSimulatorCache, DisableBCCheck);
  // save previous cpu state in case this is a recursive entry
  CPUState saveState = sim->getCPUState();
  // set up initial sim pc, sp and fp registers
  sim->init(*cursor++, (u_int64_t)sp, (u_int64_t)fp);
  u_int64_t *return_slot = cursor++;

  // if we need to pass the sim extra args on the stack then bump
  // the stack pointer now
  u_int64_t *cursor3 = (u_int64_t *)sim->getCPUState().xreg(SP, 1);
  if (gp_arg_count > 8) {
    cursor3 -= gp_arg_count - 8;
  }
  if (fp_arg_count > 8) {
    cursor3 -= fp_arg_count - 8;
  }
  sim->getCPUState().xreg(SP, 1) = (u_int64_t)(cursor3++);

  for (int i = 0; i < gp_arg_count; i++) {
    if (i < 6) {
      // copy saved register to sim register
      GReg reg = (GReg)i;
      sim->getCPUState().xreg(reg, 0) = *cursor++;
    } else if (i < 8) {
      // copy extra int arg to sim register
      GReg reg = (GReg)i;
      sim->getCPUState().xreg(reg, 0) = *cursor2++;
    } else {
      // copy extra fp arg to sim stack      
      *cursor3++ = *cursor2++;
    }
  }
  for (int i = 0; i < fp_arg_count; i++) {
    if (i < 8) {
      // copy saved register to sim register
      GReg reg = (GReg)i;
      sim->getCPUState().xreg(reg, 0) = *cursor++;
    } else {
      // copy extra arg to sim stack      
      *cursor3++ = *cursor2++;
    }
  }
  AArch64Simulator::status_t return_status = sim->run();
  if (return_status != AArch64Simulator::STATUS_RETURN){
    sim->simPrint0();
    fatal("invalid status returned from simulator.run()\n");
  }
  switch (return_type) {
  case MacroAssembler::ret_type_void:
  default:
    break;
  case MacroAssembler::ret_type_integral:
  // this overwrites the saved r0
    *return_slot = sim->getCPUState().xreg(R0, 0);
    break;
  case MacroAssembler::ret_type_float:
    *(float *)return_slot = sim->getCPUState().sreg(V0);
    break;
  case MacroAssembler::ret_type_double:
    *(double *)return_slot = sim->getCPUState().dreg(V0);
    break;
  }
  // restore incoimng cpu state
  sim->getCPUState() = saveState;
}

#endif
