/*
 * Optimized `switch' instruction dispatcher.
 * Copyright (c) 1998 New Generation Software (NGS) Oy
 *
 * Author: Markku Rossi <mtr@ngs.fi>
 */

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 */

/*
 * $Source: /tmp/cvsroot-15-2-2007/clamav-devel/libclamav/js/vmswitch.c,v $
 * $Id: vmswitch.c,v 1.2 2006/10/28 11:27:44 njh Exp $
 */
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif

#ifdef	CL_EXPERIMENTAL

#include "jsint.h"

/*
 * Types and definitions.
 */

#define SAVE_OP(a)				\
reloc[cp - fixed_code - 1] = &f->code[cpos];	\
f->code[cpos++].u.op = (a)

#define SAVE_INT8(a)	f->code[cpos++].u.i8 = (a)
#define SAVE_INT16(a)	f->code[cpos++].u.i16 = (a)
#define SAVE_INT32(a)	f->code[cpos++].u.i32 = (a)

#define ARG_INT32()	f->code[cpos].u.i32

#define READ_INT8(var)	(var) = (pc++)->u.i8
#define READ_INT16(var)	(var) = (pc++)->u.i16
#define READ_INT32(var)	(var) = (pc++)->u.i32

#define SETPC(ofs)		pc = (ofs)
#define SETPC_RELATIVE(ofs)	pc += (ofs)

#define CALL_USER_FUNC(f)	pc = ((Function *) (f))->code

#define DONE() goto done

#define ERROR(msg)		\
  do {				\
    JS_SAVE_REGS ();		\
    strcpy (vm->error, (msg));	\
    js_vm_error (vm);		\
    /* NOTREACHED */		\
  } while (0)


struct compiled_st
{
  union
  {
    void *ptr;
    JSUInt8 op;
    JSInt8 i8;
    JSInt16 i16;
    JSInt32 i32;
  } u;
};

typedef struct compiled_st Compiled;

/* Debug information. */
struct debug_info_st
{
  void *pc;
  unsigned int linenum;
};

typedef struct debug_info_st DebugInfo;

struct function_st
{
  JSHeapDestroyableCB destroy;

  char *name;
  Compiled *code;
  unsigned int length;

  struct
  {
    char *file;
    unsigned int num_info;
    DebugInfo *info;
  } debug;
};

typedef struct function_st Function;


/*
 * Prototypes for static functions.
 */

static void function_destroy (void *ptr);

static Function *link_code (JSVirtualMachine *vm, unsigned char *code,
			    unsigned int code_len,
			    unsigned int consts_offset,
			    unsigned char *debug_info,
			    unsigned int debug_info_len,
			    unsigned int code_offset);

static void execute_code (JSVirtualMachine *vm, JSNode *object, Function *f,
			  unsigned int argc, JSNode *argv);


/*
 * Global functions.
 */

int
js_vm_switch_exec (JSVirtualMachine *vm, JSByteCode *bc, JSSymtabEntry *symtab,
		   unsigned int num_symtab_entries,
		   unsigned int consts_offset,
		   unsigned int anonymous_function_offset,
		   unsigned char *debug_info, unsigned int debug_info_len,
		   JSNode *object, JSNode *func,
		   unsigned int argc, JSNode *argv)
{
  int i;
  unsigned int ui;
  Function *global_f = NULL;
  Function *f;
  unsigned char *code = NULL;
  char buf[512];

  if (bc)
    {
      /* Executing byte-code. */

      /* Find the code section. */
      for (i = 0; i < bc->num_sects; i++)
	if (bc->sects[i].type == JS_BCST_CODE)
	  code = bc->sects[i].data;
      assert (code != NULL);

      /* Enter all functions to the known functions of the VM. */
      for (i = 0; i < num_symtab_entries; i++)
	{
	  /* Link the code to our environment. */
	  f = link_code (vm, code + symtab[i].offset,
			 symtab[i + 1].offset - symtab[i].offset,
			 consts_offset, debug_info, debug_info_len,
			 symtab[i].offset);
	  f->name = js_strdup (vm, symtab[i].name);

	  if (strcmp (symtab[i].name, JS_GLOBAL_NAME) == 0)
	    global_f = f;
	  else
	    {
	      int is_anonymous = 0;

	      /* Check for the anonymous function. */
	      if (symtab[i].name[0] == '.' && symtab[i].name[1] == 'F'
		  && symtab[i].name[2] == ':')
		is_anonymous = 1;

	      if (vm->verbose > 3)
		{
		  sprintf (buf, "VM: link: %s(): start=%d, length=%d",
			   symtab[i].name, symtab[i].offset,
			   symtab[i + 1].offset - symtab[i].offset);
		  if (is_anonymous)
		    sprintf (buf + strlen (buf),
			     ", relocating with offset %u",
			     anonymous_function_offset);
		  strcat (buf, JS_HOST_LINE_BREAK);
		  js_iostream_write (vm->s_stderr, buf, strlen (buf));
		}

	      if (is_anonymous)
		{
		  sprintf (buf, ".F:%u",
			   (unsigned int) atoi (symtab[i].name + 3)
			   + anonymous_function_offset);
		  ui = js_vm_intern (vm, buf);
		}
	      else
		ui = js_vm_intern (vm, symtab[i].name);

	      vm->globals[ui].type = JS_FUNC;
	      vm->globals[ui].u.vfunction = js_vm_make_function (vm, f);
	    }
	}
    }
  else
    {
      /* Applying arguments to function. */

      if (func->type != JS_FUNC)
	{
	  sprintf (vm->error, "illegal function in apply");
	  return 0;
	}

      if (vm->verbose > 1)
	{
	  sprintf (buf, "VM: calling function%s",
		   JS_HOST_LINE_BREAK);
	  js_iostream_write (vm->s_stderr, buf, strlen (buf));
	}
      f = func->u.vfunction->implementation;

      execute_code (vm, object, f, argc, argv);
    }

  if (global_f)
    {
      if (vm->verbose > 1)
	{
	  sprintf (buf, "VM: exec: %s%s", global_f->name,
		   JS_HOST_LINE_BREAK);
	  js_iostream_write (vm->s_stderr, buf, strlen (buf));
	}

      /* Execute. */
      execute_code (vm, NULL, global_f, 0, NULL);
    }

  return 1;
}


const char *
js_vm_switch_func_name (JSVirtualMachine *vm, void *program_counter)
{
  int i;
  Function *f;
  Compiled *pc = program_counter;
  JSNode *sp = vm->sp;

  /* Check the globals. */
  for (i = 0; i < vm->num_globals; i++)
    if (vm->globals[i].type == JS_FUNC)
      {
	f = (Function *) vm->globals[i].u.vfunction->implementation;
	if (f->code < pc && pc < f->code + f->length)
	  return f->name;
      }

  /* No luck.  Let's try the stack. */
  for (sp++; sp < vm->stack + vm->stack_size; sp++)
    if (sp->type == JS_FUNC)
      {
	f = (Function *) sp->u.vfunction->implementation;
	if (f->code < pc && pc < f->code + f->length)
	  return f->name;
      }

  /* Still no matches.  This shouldn't be reached... ok, who cares? */
  return JS_GLOBAL_NAME;
}


const char *
js_vm_switch_debug_position (JSVirtualMachine *vm,
			     unsigned int *linenum_return)
{
  int i;
  Function *f;
  void *program_counter = vm->pc;
  Compiled *pc = vm->pc;
  JSNode *sp = vm->sp;
  unsigned int linenum = 0;

  /* Check the globals. */
  for (i = 0; i < vm->num_globals; i++)
    if (vm->globals[i].type == JS_FUNC)
      {
	f = (Function *) vm->globals[i].u.vfunction->implementation;
	if (f->code < pc && pc < f->code + f->length)
	  {
	  found:

	    /* Ok, found it. */
	    if (f->debug.file == NULL)
	      /* No debugging information available for this function. */
	      return NULL;

	    /* Find the correct pc position. */
	    for (i = 0; i < f->debug.num_info; i++)
	      {
		if (f->debug.info[i].pc > program_counter)
		  break;

		linenum = f->debug.info[i].linenum;
	      }

	    *linenum_return = linenum;
	    return f->debug.file;
	  }
      }

  /* No luck.  Let's try the stack. */
  for (sp++; sp < vm->stack + vm->stack_size; sp++)
    if (sp->type == JS_FUNC)
      {
	f = (Function *) sp->u.vfunction->implementation;
	if (f->code < pc && pc < f->code + f->length)
	  /* Found it. */
	  goto found;
      }

  /* Couldn't find the function we are executing. */
  return NULL;
}


/*
 * Static functions.
 */

static void
function_destroy (void *ptr)
{
  Function *f = ptr;

  js_free (f->name);
  js_free (f->code);

  if (f->debug.file)
    js_free (f->debug.file);
  if (f->debug.info)
    js_free (f->debug.info);
}


static Function *
link_code (JSVirtualMachine *vm, unsigned char *code, unsigned int code_len,
	   unsigned int consts_offset, unsigned char *debug_info,
	   unsigned int debug_info_len, unsigned int code_offset)
{
  unsigned char *cp, *end;
  JSInt32 i;
  Compiled **reloc;
  unsigned int cpos;
  Function *f;
  unsigned char *fixed_code;
  unsigned int ui;
  char *debug_filename = "unknown";
  char buf[512];

  /* Terminate the code with op `done'. */
  fixed_code = js_malloc (vm, code_len + 1);
  memcpy (fixed_code, code, code_len);
  fixed_code[code_len] = 1;	/* op `done' */

  cp = fixed_code;
  end = fixed_code + code_len + 1;

  /* Alloc function closure. */
  f = js_vm_alloc_destroyable (vm, sizeof (*f));
  f->destroy = function_destroy;

  /* Allocate space for our compiled code.  <length> is enought. */
  f->code = js_malloc (vm, (code_len + 1) * sizeof (Compiled));
  reloc = js_calloc (vm, code_len + 1, sizeof (Compiled *));

  /* Link phase 1: constants and symbols. */
  cpos = 0;
  while (cp < end)
    {
      switch (*cp++)
	{
	  /* include c1switch.h */
#include "c1switch.h"
	  /* end include c1switch.h */
	}
    }
  f->length = cpos;

  /* Link phase 2: relative jumps. */
  cp = fixed_code;
  cpos = 0;
  while (cp < end)
    {
      switch (*cp++)
	{
	  /* include c2switch.h */
#include "c2switch.h"
	  /* end include c2switch.h */
	}
    }
  /* Handle debug info. */
  if (debug_info)
    {
      unsigned int di_start = code_offset;
      unsigned int di_end = code_offset + code_len;
      unsigned int ln;

      for (; debug_info_len > 0;)
	{
	  switch (*debug_info)
	    {
	    case JS_DI_FILENAME:
	      debug_info++;
	      debug_info_len--;

	      JS_BC_READ_INT32 (debug_info, ui);
	      debug_info += 4;
	      debug_info_len -= 4;

	      f->debug.file = js_malloc (vm, ui + 1);
	      memcpy (f->debug.file, debug_info, ui);
	      f->debug.file[ui] = '\0';

	      debug_filename = f->debug.file;

	      debug_info += ui;
	      debug_info_len -= ui;
	      break;

	    case JS_DI_LINENUMBER:
	      JS_BC_READ_INT32 (debug_info + 1, ui);
	      if (ui > di_end)
		goto debug_info_done;

	      /* This belongs to us (maybe). */
	      debug_info += 5;
	      debug_info_len -= 5;

	      JS_BC_READ_INT32 (debug_info, ln);
	      debug_info += 4;
	      debug_info_len -= 4;

	      if (di_start <= ui && ui <= di_end)
		{
		  ui -= di_start;
		  f->debug.info = js_realloc (vm, f->debug.info,
					      (f->debug.num_info + 1)
					      * sizeof (DebugInfo));

		  f->debug.info[f->debug.num_info].pc = reloc[ui];
		  f->debug.info[f->debug.num_info].linenum = ln;
		  f->debug.num_info++;
		}
	      break;

	    default:
	      sprintf (buf,
		       "VM: unknown debug information type %d%s",
		       *debug_info, JS_HOST_LINE_BREAK);

	      js_iostream_write (vm->s_stderr, buf, strlen (buf));
	      js_iostream_flush (vm->s_stderr);

	      abort ();
	      break;
	    }
	}

    debug_info_done:
      if (f->debug.file == NULL)
	f->debug.file = js_strdup (vm, debug_filename);
    }

  js_free (reloc);
  js_free (fixed_code);

  return f;
}


/*
 * Execute byte code by using the `switch' dispatch technique.
 */

static void
execute_code (JSVirtualMachine *vm, JSNode *object, Function *f,
	      unsigned int argc, JSNode *argv)
{
  JSNode *sp;
  JSNode *fp;
  JSNode *function;
  JSNode builtin_result;
  Compiled *pc;
  JSInt32 i, j;
  JSInt8 i8;
  char buf[512];

  /* Create the initial stack frame by hand. */
  sp = vm->sp;

  /* Protect the function from gc. */
  JS_SP0->type = JS_FUNC;
  JS_SP0->u.vfunction = js_vm_make_function (vm, f);
  JS_PUSH ();

  /* Push arguments to the stack. */
  i = argc;
  for (i--; i >= 0; i--)
    {
      JS_COPY (JS_SP0, &argv[i]);
      JS_PUSH ();
    }

  /* This pointer. */
  if (object)
    JS_COPY (JS_SP0, object);
  else
    JS_SP0->type = JS_NULL;
  JS_PUSH ();

  /* Init fp and pc so our SUBROUTINE_CALL will work. */
  fp = NULL;
  pc = NULL;

  JS_SUBROUTINE_CALL (f);

  /* Ok, now we are ready to run. */

  while (1)
    {
      switch ((pc++)->u.op)
	{
	  /* include eswitch.h */
#include "eswitch.h"
	  /* end include eswitch.h */

	default:
	  sprintf (buf, "execute_code: unknown opcode %d%s",
		   (pc - 1)->u.op, JS_HOST_LINE_BREAK);
	  js_iostream_write (vm->s_stderr, buf, strlen (buf));
	  js_iostream_flush (vm->s_stderr);

	  abort ();
	  break;
	}
    }

 done:

  /* All done. */

  JS_COPY (&vm->exec_result, JS_SP1);
}
#endif	/*CL_EXPERIMENTAL*/
