#import "gdb.h"
#import <mach/thread_status.h>

extern thread_t current_thread;
extern RegionManager *regionManager;

/*
 * GDB and MACH have different ideas on the order which registers should
 * be stored.
 *
 * IU registers:
 *   GDB order:
 *	eax, ecx, edx, ebx, ebp, esp, esi, edi, eip, ps, cs, ss, ds, es, fs, gs
 *   MACH order:
 *	eax, ebx, ecx, edx, edi, esi, ebp, esp, ss, eflags, eip,
 *	cs, ds, es, fs, gs
 *
 * FP registers:
 *   GDB order:
 *	fst0, fst1, fst2, fst3, fst4, fst5, fst6, fst7, fctrl, fstat, ftag,
 *	fip, fcs, fopoff, fopsel
 *
 *   MACH order:
 *	(uses the structure defined in mach/i386/thread_status.h)
 */

/*
 * IU register ordering
 */
static int offset[] = {
	0, 2, 3, 1, 7, 6, 5, 4, 10, 9, 11, 8, 12, 13, 14, 15
};

void
copy_registers_in(regsState)
     i386_thread_state_t *regsState;
{
  unsigned int *r = (unsigned int *) registers;
  unsigned int *rs = (unsigned int *) regsState;
  int i;

  for (i = 0; i < FP0_REGNUM; i++)
    r[i] = rs[offset[i]];
  return;
}

void
copy_registers_out(regsState)
     i386_thread_state_t *regsState;
{
  unsigned int *r = (unsigned int *) registers;
  unsigned int *rs = (unsigned int *) regsState;
  int i;

  for (i = 0; i < FP0_REGNUM; i++)
    rs[offset[i]] = r[i];
  return;
}


void
copy_fp_registers_in(fpState)
     i386_thread_fpstate_t *fpState;
{
  /* fp stack */
  memcpy((char *) &registers[REGISTER_BYTE(FP0_REGNUM)],
	 (char *) &fpState->stack, 
	 sizeof(fp_stack_t));

  /* fctrl, fstat, ftag */
  *(unsigned short *)&registers[REGISTER_BYTE(FPC_REGNUM)] =
    *((unsigned short *) &fpState->environ.control);
  *(unsigned short *)&registers[REGISTER_BYTE(FPC_REGNUM+1)] =
    *((unsigned short *) &fpState->environ.status);
  *(unsigned short *)&registers[REGISTER_BYTE(FPC_REGNUM+2)] =
    *((unsigned short *) &fpState->environ.tag);

  /* fip */
  *(unsigned *)&registers[REGISTER_BYTE(FPC_REGNUM+3)] = fpState->environ.ip;

  /* fcs, fdp, fds - don't copy opcode */
  *(unsigned short *)&registers[REGISTER_BYTE(FPC_REGNUM+4)] =
    *((unsigned short *) &fpState->environ.cs);
  *(unsigned short *)&registers[REGISTER_BYTE(FPC_REGNUM+5)] = 
    fpState->environ.dp;
  *(unsigned short *)&registers[REGISTER_BYTE(FPC_REGNUM+6)] =
    *((unsigned short *) &fpState->environ.ds);

  return;
}


void
copy_fp_registers_out(fpState)
     i386_thread_fpstate_t *fpState;
{
  /* fp stack */
  memcpy((char *) &fpState->stack,
	 (char *) &registers[REGISTER_BYTE(FP0_REGNUM)],
	 sizeof(fp_stack_t));

  /* fctrl, fstat, ftag, fip */
  *((unsigned short *) &fpState->environ.control) =
    *((unsigned short *) &registers[REGISTER_BYTE(FPC_REGNUM)]);
  *((unsigned short *) &fpState->environ.status) =
    *((unsigned short *) &registers[REGISTER_BYTE(FPC_REGNUM+1)]);
  *((unsigned short *) &fpState->environ.tag) =
    *((unsigned short *) &registers[REGISTER_BYTE(FPC_REGNUM+2)]);

  /* fip */
  fpState->environ.ip =
    *((unsigned int *) &registers[REGISTER_BYTE(FPC_REGNUM+3)]);

  /* fcs, opcode, fdp, fds */
  *((unsigned short *) &fpState->environ.cs) =
    *((unsigned short *) &registers[REGISTER_BYTE(FPC_REGNUM + 4)]);
  fpState->environ.opcode = 0;
  fpState->environ.dp =
    *((unsigned int *) &registers[REGISTER_BYTE(FPC_REGNUM + 5)]);
  *((unsigned short *) &fpState->environ.ds) =
    *(unsigned short *) &registers[REGISTER_BYTE(FPC_REGNUM + 6)];

  return;
}

void
fetch_inferior_registers (regno)
{
  int i;
  extern char registers[];
  extern char register_valid[];

  if (current_thread) {
    if (regno < FP0_REGNUM || regno == -1) {
      i386_thread_state_t regsState;
      unsigned regsCount = i386_THREAD_STATE_COUNT;
      mach_call(thread_get_state(current_thread,
				 i386_THREAD_STATE,
				 (thread_state_t)&regsState, 
				 &regsCount),
		"thread_get_state", "fetch_inferior_registers");

      copy_registers_in(&regsState);
      for (i = 0; i < FP0_REGNUM; i++)
	register_valid[i] = 1;
    }
    /* get FP registers only if we have to */
    if (regno >= FP0_REGNUM || regno == -1) {
      i386_thread_fpstate_t fpState;
      unsigned fpCount = i386_THREAD_FPSTATE_COUNT;
      mach_call(thread_get_state(current_thread,
				 i386_THREAD_FPSTATE,
				 (thread_state_t)&fpState, 
				 &fpCount),
		"thread_get_state", "fetch_inferior_registers");
      copy_fp_registers_in(&fpState);
      for (i = FP0_REGNUM; i < NUM_REGS; i++)
	register_valid[i] = 1;
    }
  }
}

/* Store our register values back into the inferior.
   If REGNO is -1, do this for all registers.
   Otherwise, REGNO specifies which register (so we can save time).  */
void
store_inferior_registers(regno)
{
  i386_thread_state_t regsState;
  i386_thread_fpstate_t fpState;
  unsigned regsCount = i386_THREAD_STATE_COUNT;
  unsigned fpCount = i386_THREAD_FPSTATE_COUNT;
  extern char registers[];

  if (current_thread) {
    validateRegister(regno);
    if (!stopped_in_ptrace)
      mach_call(thread_abort(current_thread),
		"thread_abort", "store_inferior_registers");
    if (regno < FP0_REGNUM || regno == -1) {
      i386_thread_state_t regsState;
      unsigned regsCount = i386_THREAD_STATE_COUNT;
      copy_registers_out(&regsState);
      mach_call(thread_set_state(current_thread,
				 i386_THREAD_STATE,
				 (thread_state_t)&regsState, 
				 regsCount),
		"thread_set_state", "store_inferior_registers");
    }
    /* set FP registers only if we have to */
    if (regno >= FP0_REGNUM || regno == -1) {
      i386_thread_fpstate_t fpState;
      unsigned fpCount = i386_THREAD_FPSTATE_COUNT;
      copy_fp_registers_out(&fpState);
      mach_call(thread_set_state(current_thread,
				 i386_THREAD_FPSTATE,
				 (thread_state_t)&fpState, 
				 fpCount),
		"thread_set_state", "store_inferior_registers");
    }
  }
}


struct type *
i386_register_virtual_type(regno)
{
  switch (regno) {
      /* eax, ecx, edx, ebx, esi, edi, eflags */
    case 0: case 1: case 2: case 3:
    case 6: case 7: case 9:
      return builtin_type_int;

      /* cs, ss, ds, es, fs, gs, fctrl, fstat, ftag, fcs, fds */
    case 10: case 11: case 12:
    case 13: case 14: case 15:
    case 24: case 25: case 26:
    case 28: case 30:
      return builtin_type_unsigned_short;

      /* eip, ebp, esp, fip, fdp */
    case 4: case 5: case 8:
    case 27: case 29:
      return lookup_pointer_type(builtin_type_void);
	
      /* fst0 - fst7 */
    case 16: case 17: case 18: case 19:
    case 20: case 21: case 22: case 23:
      return builtin_type_double;
  }
}

int getFirstTwoIntArgs(first, second)
     unsigned *first, *second;
{
  unsigned *sp = (unsigned *)read_register(SP_REGNUM);
  unsigned *argsPtr = [regionManager pointerFor: sp + 1
				       withSize: 2 * sizeof(CORE_ADDR)];

  if (argsPtr) {
    *first = *(argsPtr);
    *second = *(argsPtr + 1);
    return YES;
  } else {
    return NO;
  }
}

CORE_ADDR getBranch(pc)
     CORE_ADDR pc;
{
  unsigned char *jmpSlot;
  jmpSlot = [regionManager pointerFor: (void *)pc withSize: 5];
  return pc + 5 + *(CORE_ADDR *)(jmpSlot + 1);
}

#define JMPTABLEOFFSET 0x54
#define JMPOPCODE_I386  0xe9
#define TRAPOPCODE_I386 0xf4

void *
jmpTableEnd(sectAddr, sect)
     CORE_ADDR sectAddr;
     struct section *sect;
{
  struct _JmpSlot {
    unsigned char opcode;
    unsigned int arg;
  } *jmpSlot;
  void *endOfText;
  BOOL foundEnd = NO;
  
  for (jmpSlot = (struct _JmpSlot *)(sectAddr + JMPTABLEOFFSET),
       endOfText = (void *)(sectAddr + sect->size);
       !foundEnd && (jmpSlot < (struct _JmpSlot *)endOfText);
       ((char *)jmpSlot)+=5) {
    if ((jmpSlot->opcode != JMPOPCODE_I386)
         && (jmpSlot->opcode != TRAPOPCODE_I386))
      foundEnd = YES;
  }
  return (void *) (sect->addr + (((void *) jmpSlot - 5)- sectAddr));
}

cpu_type_t cpuType() 
{
  return CPU_TYPE_I386;
}

#pragma CC_OPT_OFF
float
convertRealToFloat(real)
     struct fp_data_reg *real;
{
  float dst;

  asm ("fldtl %0" : : "g" (*real) : "st");
  asm ("fsts %0" : "=m" (dst));
  return dst;
}

double
convertRealToDouble(real)
     struct fp_data_reg *real;
{
  double dst;

  asm ("fldtl %0" : : "g" (*real) : "st");
  asm ("fstl %0" : "=m" (dst));
  return dst;
}

#pragma CC_OPT_ON

void 
extractReturnValue(type, regBuf, valBuf)
     struct type *type;
     const char *regBuf;
     char *valBuf;
{
  if (type->code == TYPE_CODE_FLT) {
    fp_status_t status       = *(fp_status_t *)
      &registers[REGISTER_BYTE(FPC_REGNUM+1)];
    struct fp_data_reg *reg =  (struct fp_data_reg *)
      &regBuf[REGISTER_BYTE(FP0_REGNUM)];

    reg += ((status.tos + 1) % 8);
    if (TYPE_LENGTH(type) == 4) {
      *(float *)  valBuf = convertRealToFloat(reg);
    } else {
      *(double *) valBuf = convertRealToDouble(reg);
    }
  } else if ((type->code == TYPE_CODE_STRUCT) && (type->length == 8)) {
    *(unsigned int *) valBuf      = *(unsigned int *) regBuf;
    *(unsigned int *)(valBuf + 4) = *(unsigned int *)(regBuf + 8);
  } else {
    bcopy (regBuf, valBuf, TYPE_LENGTH (type));
  }
}
