/* hellovm.c - a kind of "hello world" for libjit
 *
 * Written by Norbert Bollow <nb@SoftwareEconomics.biz>
 * 
 * This program is a JIT for the "Hello VM" bytecode language, which
 * has just one (string) register, and three opcodes: A) load string
 * constant into register, B) output contents of register, C) exit program.
 *
 * Compile with: gcc hellovm.c -ljit -o hellovm
 *
 * A valid "Hello VM" bytecode language program, suitable as input file for
 * this JIT, can be generated by the following perl script
 *
 * #!/usr/bin/perl
 * open TEST, ">test";
 * $bytecode="AHello World!\n\x00BC";
 * print TEST "HelloVM\x00", pack('i',length($bytecode)), $bytecode;
 *
 * A slightly less trivial example can be generated e.g. with
 * $bytecode="AHello, \x00BBAWorld!\n\x00BC";
 *
 * The contents of this file are in the Public Domain.
 */

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <jit/jit.h>
char* readbytes(int, int);
static void hellovm_output(char *);
static void hellovm_exit();
static void out_of_memory();

int main(int argc, char *argv[])
{
	int fd;
	char *buf;
	int len;
	jit_context_t context;
	const jit_type_t hellovm_output_arg_type=jit_type_void_ptr;
	jit_type_t hellovm_signature_main;
	jit_type_t hellovm_signature_exit, hellovm_signature_output;
	jit_type_t hellovm_type_string;
	jit_value_t hellovm_reg;
	jit_value_t tmp;
	char *str;
	jit_function_t hellovm_main;
	int pos;
	int willexit=0;
	int retval;

	if(argc != 2)
	{
		printf("Usage: hellovm program\n");
		return 99;
	}

	if((fd = open(argv[1], O_RDONLY)) < 0)
	{
		perror(argv[1]);
		return 99;
	}

	/* read filetype identification string and length field */
	buf=readbytes(fd, 12);

	/* check filetype identification string */
	if(jit_strcmp("HelloVM", buf)!=0)
	{
		fprintf(stderr, "%s is not in HelloVM format.\n", argv[1]);
		return 99;
	}

	/* check length field and read bytecode data */
	len=*((int*) (buf+8));
	if(len<0)
	{
		fprintf(stderr, "%s: Invalid length field.\n", argv[1]);
		return 99;
	}
	free(buf);
	buf=readbytes(fd, len);
	close(fd);

	/* Now the fun begins :) */
	jit_init();
	context = jit_context_create();
	jit_context_build_start(context);
	/* Build signatures for output and exit functions */
	hellovm_signature_output = jit_type_create_signature
		(jit_abi_cdecl, jit_type_void, 
		 (jit_type_t*)&hellovm_output_arg_type, 1, 0);
	if (!hellovm_signature_output) out_of_memory();
	hellovm_signature_exit = jit_type_create_signature
		(jit_abi_cdecl, jit_type_void, NULL, 0, 0);
	if (!hellovm_signature_exit) out_of_memory();
	/* There is always a single function with signature: int main() */
	hellovm_signature_main = jit_type_create_signature
		(jit_abi_cdecl, jit_type_int, NULL, 0, 0);
	if (!hellovm_signature_main) out_of_memory();
        hellovm_main = jit_function_create(context, hellovm_signature_main);
	if (!hellovm_main) out_of_memory();
	/* The HelloVM has a single register holding a NUL-terminated string */
	hellovm_type_string = jit_type_create_pointer(jit_type_sys_char, 1);
	if (!hellovm_type_string) out_of_memory();
	hellovm_reg = jit_value_create(hellovm_main, hellovm_type_string);
	if (!hellovm_reg) out_of_memory();
	/* Initialize the string register with "" */
	tmp=jit_value_create_nint_constant
		(hellovm_main, hellovm_type_string, (int)"");
	if (!tmp) out_of_memory();
	jit_insn_store(hellovm_main, hellovm_reg, tmp);
	/* Now JIT the supplied bytecodes */
	pos=0;
	while(pos<len)
	{
		switch(buf[pos])
		{
			case 'A':
			{
				/* load string constant into hellovm_reg */
				pos++;
				str=jit_strndup(buf+pos, len-pos);
				if (!str && buf[pos] && len-pos)
					out_of_memory();
				tmp=jit_value_create_nint_constant
				      (hellovm_main, hellovm_type_string,
				       (int)str);
				if (!tmp) out_of_memory();
				jit_insn_store(hellovm_main, hellovm_reg, tmp);
				pos+=jit_strlen(str)+1;
			}
			break;
			case 'B':
			{
				/* output contents of hellovm_reg */
				pos++;
				jit_insn_call_native
					(hellovm_main, "hellovm_output",
					 (void *)hellovm_output, 
					 hellovm_signature_output,
					 &hellovm_reg, 1,
					 JIT_CALL_NOTHROW);
			}
			break;
			case 'C':
			{
				/* exit with exit value 0 */
				pos++;
				jit_insn_call_native
					(hellovm_main, "hellovm_exit",
					 (void *)hellovm_exit, 
					 hellovm_signature_exit,
					 NULL, 0,
					 JIT_CALL_NOTHROW|JIT_CALL_NORETURN);
				willexit=1;
			}
			break;
			default:
			{
				fprintf(stderr, "Error: Illegal opcode\n");
				return 99;
			}
		}
	}
	if(willexit)
	{
		if (!jit_function_compile(hellovm_main))
		{
			fprintf(stderr, "JIT compilation error\n");
			return 99;
		}
		jit_context_build_end(context);
		free(buf);
		if (!jit_function_apply(hellovm_main, NULL, &retval))
		{
			fprintf(stderr, "Exception during execution\n");
			return 99;
		}
		/* not reached with the current instruction set */
		return retval;
	}
	else
	{
		jit_function_abandon(hellovm_main);
		fprintf(stderr, "Error: program without exit opcode\n");
		return 99;
	}
}

static void hellovm_output(char *value)
{
	fputs(value, stdout);
}

static void hellovm_nulput(char *value)
{
	putchar(0);
}

static void hellovm_exit()
{
	exit(0);
}

static void out_of_memory()
{
	fprintf(stderr, "Out of memory.\n");
	exit(99);	
}

/* This function will not return unless the specified number of bytes
 * has been read successfully */
char* readbytes(int file, int bytestoread)
{
	char *buffer;
	int pos=0;
	int bytesread;

	if((buffer = jit_malloc(bytestoread)) == NULL)
	{
		perror("memory allocation error in readbytes()");
		exit(99);
	}

	while (pos<bytestoread)
	{
		bytesread=read(file, buffer+pos, bytestoread-pos);
		if(bytesread==-1)
		{
			perror("read error in readbytes()");
			exit(99);
		}
		if(bytesread==0)
		{
			fprintf(stderr, "unexpected eof in readbytes()\n");
			exit(99);
		}
		pos+=bytesread;
	}
	return buffer;
}
