/* Interpreter for JVM subset. */

#include <stdio.h>
#include "strings.h"
#include "assert.h"
#include "basics.h"
#include "class.h"
#include "bytecode.h"

#define STACKSIZE 32768

/* Globals */

/* The class we're interpreting. */
Class this_class;

/* The stack (grows up) */
u4 stack[STACKSIZE];

/* The stack pointer. 
   Invariant: sp points to highest occupied slot. */
u4* sp = stack-1;   

/* The stack limit. */
u4 *limit = stack+STACKSIZE;

/* Interpret a method. */
interp(Method method) {
  u1 *pc = method.code;  /* program counter */
  u2 nargs = count_args(get_utf8(this_class,method.descriptor_index));
  u4 *locals = sp - nargs + 1; /* address of local #0 */
  sp = locals + method.max_locals - 1;  /* allocate space for locals */
  if (sp + method.max_stack >= limit) /* check there is room for operand stack. */
    die("stack overflow");
  while (1) {
    switch (*pc) {
    case BIPUSH:
      {	
	*(++sp) = (int8)(pc[1]);
        pc+=2;
	break; 
      }
    case DUP: 
      {	
	sp++;
	*sp = *(sp-1);
	pc++;
	break;
      }
    case GETSTATIC:
      { 
	u2 index = (pc[1] << 8) | pc[2];
	/* kludge for java.lang.System.out */
	if ((strcmp (get_field_class(this_class,index),"java/lang/System") == 0) &&
	    (strcmp (get_field_name(this_class,index),"out") == 0)) 
	  sp++;  /* just push an empty slot */
	else 
	  die("unimplemented instruction GETSTATIC");
	pc+=3;
	break;
      }
    case GOTO:
      {
	int16 offset = (pc[1] << 8) | pc[2];
	pc += offset;
	break;
      }
    case GOTO_W:
      { 
	int32 offset = (pc[1] << 24) | (pc[2] << 16) | (pc[3] << 8) | pc[4];
	pc += offset;
	break;
      }
    case IADD:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*sp);
	*sp = (u4) (v1+v2);
	pc++;
	break;
      }
    case IAND:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*sp);
	*sp = (u4) (v1&v2);
	pc++;
	break;
      }
    case ICONST_M1:
      {
	*(++sp) = (u4) (-1);
	pc++;
	break;
      }
    case ICONST_0:
      {
	*(++sp) = (u4) 0;
	pc++;
	break;
      }
    case ICONST_1:
      {
	*(++sp) = (u4) 1;
	pc++;
	break;
      }
    case ICONST_2:
      {
	*(++sp) = (u4) 2;
	pc++;
	break;
      }
    case ICONST_3:
      {
	*(++sp) = (u4) 3;
	pc++;
	break;
      }
    case ICONST_4:
      {
	*(++sp) = (u4) 4;
	pc++;
	break;
      }
    case ICONST_5:
      {
	*(++sp) = (u4) 5;
	pc++;
	break;
      }
    case IDIV:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*sp);
	*sp = (u4) (v1/v2);
	pc++;
	break;
      }
    case IF_ICMPEQ:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*(sp--));
	int16 offset = (pc[1] << 8) | pc[2];
	if (v1 == v2)
	  pc += offset;
	else
	  pc += 3;
	break;
      }
    case IF_ICMPGE:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*(sp--));
	int16 offset = (pc[1] << 8) | pc[2];
	if (v1 >= v2)
	  pc += offset;
	else
	  pc += 3;
	break;
      }
    case IF_ICMPGT:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*(sp--));
	int16 offset = (pc[1] << 8) | pc[2];
	if (v1 > v2)
	  pc += offset;
	else
	  pc += 3;
	break;
      }
    case IF_ICMPLE:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*(sp--));
	int16 offset = (pc[1] << 8) | pc[2];
	if (v1 <= v2)
	  pc += offset;
	else
	  pc += 3;
	break;
      }
    case IF_ICMPLT:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*(sp--));
	int16 offset = (pc[1] << 8) | pc[2];
	if (v1 < v2)
	  pc += offset;
	else
	  pc += 3;
	break;
      }
    case IF_ICMPNE:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*(sp--));
	int16 offset = (pc[1] << 8) | pc[2];
	if (v1 != v2)
	  pc += offset;
	else
	  pc += 3;
	break;
      }
    case IFEQ:
      {
	int32 v = (int32) (*(sp--));
	int16 offset = (pc[1] << 8) | pc[2];
	if (v == 0)
	  pc += offset;
	else
	  pc += 3;
	break;
      }
    case IFGE:
      {
	int32 v = (int32) (*(sp--));
	int16 offset = (pc[1] << 8) | pc[2];
	if (v >= 0)
	  pc += offset;
	else
	  pc += 3;
	break;
      }
    case IFGT:
      {
	int32 v = (int32) (*(sp--));
	int16 offset = (pc[1] << 8) | pc[2];
	if (v > 0)
	  pc += offset;
	else
	  pc += 3;
	break;
      }
    case IFLE:
      {
	int32 v = (int32) (*(sp--));
	int16 offset = (pc[1] << 8) | pc[2];
	if (v <= 0)
	  pc += offset;
	else
	  pc += 3;
	break;
      }
    case IFLT:
      {
	int32 v = (int32) (*(sp--));
	int16 offset = (pc[1] << 8) | pc[2];
	if (v < 0)
	  pc += offset;
	else
	  pc += 3;
	break;
      }
    case IFNE:
      {
	int32 v = (int32) (*(sp--));
	int16 offset = (pc[1] << 8) | pc[2];
	if (v != 0)
	  pc += offset;
	else
	  pc += 3;
	break;
      }
    case IINC:
      {
	u1 index = pc[1];
	int8 incr = (int8) (pc[2]);
	locals[index] += incr;
	pc += 3;
	break;
      }
    case ILOAD:
      {
	u1 index = pc[1];
	*(++sp) = locals[index];
	pc += 2;
	break;
      }
    case ILOAD_0:
      {
	*(++sp) = locals[0];
	pc ++;
	break;
      }
    case ILOAD_1:
      {
	*(++sp) = locals[1];
	pc ++;
	break;
      }
    case ILOAD_2:
      {
	*(++sp) = locals[2];
	pc ++;
	break;
      }
    case ILOAD_3:
      {
	*(++sp) = locals[3];
	pc ++;
	break;
      }
    case IMUL:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*sp);
	*sp = (u4) (v1*v2);
	pc++;
	break;
      }
    case INEG:
      {
	int32 v = (int32) (*sp);
	*sp = (u4) (-v);
	pc++;
	break;
      }
    case INVOKESTATIC:
      {
	u2 index = (pc[1] << 8) | pc[2];
	char *method_class = get_method_class(this_class,index);
	/* only support static calls into this class */
	if ((strcmp(method_class,get_class(this_class,this_class.class_index)) == 0)) {
	  Method m = find_method(this_class,
				 get_method_name(this_class,index),
				 get_method_type(this_class,index));
	  interp(m);
	} else
	  die ("unimplemented INVOKESTATIC to method in different class %s", method_class);
	pc +=3;
	break;
      }
    case INVOKEVIRTUAL:
      { 
	u2 index = (pc[1] << 8) | pc[2];
	/* kludge for java.io.PrintStream.print */
	if ((strcmp(get_method_class(this_class,index),"java/io/PrintStream") == 0)
	    && (strcmp(get_method_name(this_class,index),"print") == 0)) {
	  char *type = get_method_type(this_class,index);
	  if (strcmp(type,"(Ljava/lang/String;)V") == 0) {
	    /* arg is string */
	    printf("%s",(char *)(*(sp--)));
	  } else if (strcmp(type,"(I)V") == 0) {
	    /* arg is int */
	    printf("%i",(int32)(*(sp--)));
	  } else 
	    die ("unimplemented PrintStream.print method with signature %s", type);
	  sp--;  /* pop the empty slot corresponding to the object argument */
	} else 
	  die ("unimplemented instruction INVOKEVIRTUAL");
	pc+=3;
	break;
      }
    case IOR:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*sp);
	*sp = (u4) (v1|v2);
	pc++;
	break;
      }
    case IREM:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*sp);
	*sp = (u4) (v1%v2);
	pc++;
	break;
      }
    case IRETURN:
      { 
	uint32 v = *sp;
	sp = locals;
	*sp = v;
	return;
      }
    case ISTORE:
      {
	u1 index = pc[1];
	locals[index] = *(sp--);
	pc += 2;
	break;
      }
    case ISTORE_0:
      {
	locals[0] = *(sp--);
	pc++;
	break;
      }
    case ISTORE_1:
      {
	locals[1] = *(sp--);
	pc++;
	break;
      }
    case ISTORE_2:
      {
	locals[2] = *(sp--);
	pc++;
	break;
      }
    case ISTORE_3:
      {
	locals[3] = *(sp--);
	pc++;
	break;
      }
    case ISUB:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*sp);
	*sp = (u4) (v1-v2);
	pc++;
	break;
      }
    case IXOR:
      {
	int32 v2 = (int32) (*(sp--));
	int32 v1 = (int32) (*sp);
	*sp = (u4) (v1^v2);
	pc++;
	break;
      }
    case LDC:
      {
	u1 index = pc[1];
	u1 tag = get_constant_tag(this_class,index);
	if (tag == CONSTANT_String) 
	  *(++sp) = (u4) (get_string(this_class,index));
	else if (tag == CONSTANT_Integer) 
	  *(++sp) = (u4) (get_integer(this_class,index));
	else 
	  die ("unsupported constant type %d", tag);
	pc+=2;
	break;
      }
    case LDC_W:
      {
	u2 index = (pc[1] << 8) | pc[2];
	u1 tag = get_constant_tag(this_class,index);
	if (tag == CONSTANT_String) 
	  *(++sp) = (u4) (get_string(this_class,index));
	else if (tag == CONSTANT_Integer) 
	  *(++sp) = (u4) (get_integer(this_class,index));
	else 
	  die ("unsupported constant type %d", tag);
	pc+=3;
	break;
      }
    case NOP: 
      {
	pc++;
	break;
      }
    case POP:
      {
	sp--;
	pc++;
	break;
      }
    case POP2:
      {
	sp -= 2;
	pc++;
	break;
      }
    case RETURN:
      {
	sp = locals - 1;
	return;
      }
    case SIPUSH:
      {
	*(++sp) = (int16) ((pc[1] << 8) | pc[2]);
	pc +=3;
	break;
      }
    case SWAP:
      {
	u4 t = *sp;
	*sp = *(sp-1);
	*(sp-1) = t;
	pc++;
	break;
      }
    default: 
      die("unimplemented instruction code %i", *pc);
    }
  }
  /* supposedly can't fall off the end */
  assert(false);
}

int main(int argc, char **argv) {
  char file[255];
  FILE *cf;
  Method main_method;
  strcpy(file,*++argv);
  strcat(file,".class"); 
  cf = fopen(file,"r");
  read_class(cf,&this_class);
  fclose(cf);
  main_method = find_method(this_class,"main","([Ljava/lang/String;)V");
  interp(main_method);
  return 0;
}
