/*
 * $Id: jexc_translate.c,v 1.3 2000/09/06 13:33:55 m-hirano Exp $
 * $RWC_Release: Omni-1.6 $
 * $RWC_Copyright:
 *  Omni Compiler Software Version 1.5-1.6
 *  Copyright (C) 2002 PC Cluster Consortium
 *  
 *  This software is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License version
 *  2.1 published by the Free Software Foundation.
 *  
 *  Omni Compiler Software Version 1.0-1.4
 *  Copyright (C) 1999, 2000, 2001.
 *   Tsukuba Research Center, Real World Computing Partnership, Japan.
 *  
 *  Please check the Copyright and License information in the files named
 *  COPYRIGHT and LICENSE under the top  directory of the Omni Compiler
 *  Software release kit.
 *  
 *  
 *  $
 */

#include "jexc.h"

class_file *class_files = NULL;
class_file *class_root,*class_tail;

int method_count = 0;
int class_count = 0;

int string_count = 0;
char *string_constants[MAX_STRING_CONSTANT];

void resolve_ref(class_file *cfp);
void link_class(class_file *cfp);
int find_method_disp(class_file *cfp,char *name,char *desc);
void output_init_class(class_file *cfp,FILE *fp);
void output_interface(FILE *fp,class_file *cfp,class_file *i_cfp);

void output_str_constant(char *s,FILE *fp);
void align_offset(DATA_TYPE t,int *offset);

void jexc_init()
{
    init_opcode_table();
    init_sys_class();
}

void input_class_file(char *file)
{
    class_file *cfp;

    if(file == NULL) return;	/* nothing */

    if(debug_flag) printf("reading class file '%s'...\n",file);

    cfp = read_class_file(file);
    
    if(dump_flag) dump_class_file(stdout,cfp);

    /* link class file */
    class_tail->link = cfp;
    class_tail = cfp;

    /* set class name as key */
    cfp->this_class_name = get_class_name(cfp,cfp->this_class);
}

void jexc_translate(char *outfile)
{
    FILE *fp;
    class_file *cfp;
    method_info *mp;
    int i;

    class_tail = class_root;
    
    /* pass1: resolve reference */
    for(cfp = class_files; cfp != NULL; cfp = cfp->link)
	if(!cfp->is_system) resolve_ref(cfp);

    /* stop */
    if(nerrors > 0) exit(1);

    /* pass2: link */
    for(cfp = class_files; cfp != NULL; cfp = cfp->link)
	if(!cfp->is_linked) link_class(cfp);

    /* pass 3: output code */
    fp = fopen(outfile,"w");
    if(fp == NULL){
	error("cannot open '%s'",outfile);
	exit(1);
    }
    fprintf(fp,"#include \"jexc_vm.h\"\n\n");

    fprintf(fp,"/* #class=%d, #method=%d */\n",class_count,method_count);
    fprintf(fp,"int n_class = %d;\n",class_count);

    /* output string string table */
    fprintf(fp,"/* string constant table */\n");
    fprintf(fp,"int n_string_constant = %d;\n",string_count);
    fprintf(fp,"char *string_constants[]={\n");
    for(i = 0; i < string_count; i++){
	output_str_constant(string_constants[i],fp);
	fprintf(fp,",/* %d */\n",i);
    }
    fprintf(fp,"};\n\n");

    fprintf(fp,"/* method function */\n");
    for(cfp = class_root; cfp != NULL; cfp = cfp->next)
	output_methods(cfp,fp);
    
    fprintf(fp,"\n\n/* initialize function */\n");
    fprintf(fp,"JEXC_BEGIN_INITIALIZE\n");
    for(cfp = class_root; cfp != NULL; cfp = cfp->next)
	output_init_class(cfp,fp);
    fprintf(fp,"JEXC_END_INITIALIZE\n");

    /* finally output main */
    if(main_class_name != NULL){
	cfp = find_class(intern(main_class_name));
	if(cfp == NULL) exit(1);

	/* find main routine */
	mp = find_method(cfp,intern("main"),
			 intern("([Ljava/lang/String;)V"));
	if(mp == NULL) exit(1);

	fprintf(fp,"JEXC_DEFINE_MAIN(%d,%d)/* main=%s */\n",
		cfp->id,mp->disp,main_class_name);
    }

    fprintf(fp,"/* EOF */\n");

    fclose(fp);
}

class_file *find_class(char *name)
{
    class_file *cfp;
    for(cfp = class_files; cfp != NULL; cfp = cfp->link)
	if(cfp->this_class_name == name) return cfp;
    error("undefined class '%s'",name);
    return NULL;
}

field_info *find_field(class_file *cfp,char *name, char *desc)
{
    int k;
    field_info *fp;

    for(k = 0; k < cfp->field_count; k++){
	fp = &cfp->fields[k];    
	if(strcmp(name,get_cp_str(cfp,fp->name_index)) == 0 &&
	   strcmp(desc,get_cp_str(cfp,fp->desc_index)) == 0)
	    return fp;
    }
    error("undefined field: %s.%s %s",
	    cfp->this_class_name,name,desc);
    return NULL;
}

method_info *find_method(class_file *cfp,char *name, char *desc)
{
    int k;
    method_info *mp;

    for(k = 0; k < cfp->method_count; k++){
	mp = &cfp->methods[k];    
	if(strcmp(name,get_cp_str(cfp,mp->name_index)) == 0 &&
	   strcmp(desc,get_cp_str(cfp,mp->desc_index)) == 0)
	    return mp;
    }
    error("undefined method: %s.%s %s",
	    cfp->this_class_name,name,desc);
    return NULL;
}

void resolve_ref(class_file *cfp)
{
    cp_info *cp;
    int k,index;
    class_file *cfq;
    
    /* resolve constant pool */
    for(k = 1; k < cfp->cp_count; k++){
	cp = &cfp->cp_info_table[k];
	switch(cp->tag){
	case CONSTANT_utf8:
	case CONSTANT_Integer:
	case CONSTANT_Float:
	    break;
	case CONSTANT_Long:
	case CONSTANT_Double:
	    k++;
	    break;
	case CONSTANT_Class:
	    cp->info.class.cfp = find_class(get_class_name(cfp,k));
	    break;
	case CONSTANT_String:
	    index = cp->info.string.string_index;
	    cp->info.string.str = get_cp_str(cfp,index);
	    if(string_count >= MAX_STRING_CONSTANT)
		fatal("too many string constant (>=%d)",MAX_STRING_CONSTANT);
	    string_constants[string_count] = cp->info.string.str;
	    cp->info.string.id = string_count++;
	    break;
	case CONSTANT_Fieldref:
	    cfq = find_class(get_field_class_name(cfp,k));
	    if(cfq == NULL) break;
	    cp->info.field.fp = 
		find_field(cfq, get_field_name(cfp,k),
			    get_field_desc(cfp,k));
	    break;
	case CONSTANT_Methodref:
	case CONSTANT_InterfaceMethodref:
	    cfq = find_class(get_method_class_name(cfp,k));
	    if(cfq == NULL) break;
	    cp->info.method.mp = 
		find_method(cfq, get_method_name(cfp,k),
			    get_method_desc(cfp,k));
	    break;
	case CONSTANT_NameAndType:
	    break;
	default:
	    fatal("resolve_ref: CONSTANT_???");
	}
    }

    /* link super */
    cfp->super = cfp->cp_info_table[cfp->super_class].info.class.cfp;
    if(IS_INTERFACE_CLASS(cfp)) cfp->is_interface = TRUE;
}

void link_class(class_file *cfp)
{
    class_file *cfq;
    field_info *fp;
    method_info *mp;
    int i_static_offset,n_static_offset;
    int i_var_offset,n_var_offset;
    int n_method;
    int k,d;
    DATA_TYPE t;

     if(cfp == NULL || cfp->is_linked) return;	/* already linked */
    link_class(cfp->super);

    /* link inteface */
    for(k = 0; k < cfp->interface_count; k++)
	link_class(get_class(cfp,cfp->interfaces[k]));

    class_tail->next = cfp;
    class_tail = cfp;
    cfp->is_linked = TRUE;
    cfp->id = class_count++;

    /* decide field offset */
    cfq = cfp->super;
    if(cfq == NULL){
	i_static_offset = 0;
	n_static_offset = 0;
	i_var_offset = 0;
	n_var_offset = 0;
    } else {
	i_static_offset = cfq->i_static;
	n_static_offset = cfq->n_static;
	i_var_offset = cfq->i_var;
	n_var_offset = cfq->n_var;
    }

    for(k = 0; k < cfp->field_count; k++){
	fp = &cfp->fields[k];    
	t = get_desc_data_type(get_cp_str(cfp,fp->desc_index));

	if(cfp->is_system){
	    if(fp->is_static){
		if(t == OBJECT){
		    fp->is_object = TRUE;
		    d = fp->disp+1;
		    if(d > n_static_offset) n_static_offset = d;
		} else {
		    fp->is_object = TRUE;
		    d = fp->disp+data_type_size(t);
		    if(d > i_static_offset) i_static_offset = d;
		}
	    } else {
		if(t == OBJECT){
		    fp->is_object = TRUE;
		    d = fp->disp+1;
		    if(d > n_var_offset) n_var_offset = d;
		} else {
		    fp->is_object = TRUE;
		    d = fp->disp+data_type_size(t);
		    if(d > i_var_offset) i_var_offset = d;
		}
	    }
	    continue;
	}

	if(IS_STATIC_FIELD(fp)){
	    fp->is_static = TRUE;
	    if(t == OBJECT){
		fp->disp = n_static_offset++;
		fp->is_object = TRUE;
	    } else {
		align_offset(t,&i_static_offset);
		fp->disp = i_static_offset;
		i_static_offset += data_type_size(t);
	    }
	} else {
	    if(t == OBJECT){
		fp->disp = n_var_offset++;
		fp->is_object = TRUE;
	    } else {
		align_offset(t,&i_var_offset);
		fp->disp = i_var_offset;
		i_var_offset += data_type_size(t);
	    }
	}
    }
    cfp->i_static = i_static_offset;
    cfp->n_static = n_static_offset;
    cfp->i_var = i_var_offset;
    cfp->n_var = n_var_offset;

    /* resolve method disp */
    if(cfq == NULL)
	n_method = 0;
    else 
	n_method = cfq->n_method;
    for(k = 0; k < cfp->method_count; k++){
	mp = &cfp->methods[k];    
	mp->id = method_count++;	/* put unique number */
	mp->disp = find_method_disp(cfq,get_cp_str(cfp,mp->name_index),
					get_cp_str(cfp,mp->desc_index));
	mp->n_args = get_desc_narg(get_cp_str(cfp,mp->desc_index));

	if(mp->disp < 0){ /* not found */
	    mp->disp = n_method++;
	}
    }
    cfp->n_method = n_method;
}

int find_method_disp(class_file *cfp,char *name,char *desc)
{
    method_info *mp;
    int k;

    while(cfp != NULL){
	for(k = 0; k < cfp->method_count; k++){
	    mp = &cfp->methods[k];    
	    if(strcmp(name,get_cp_str(cfp,mp->name_index)) == 0 &&
	       strcmp(desc,get_cp_str(cfp,mp->desc_index)) == 0)
		return mp->disp;
	}
	cfp = cfp->super;
    }
    return -1;
}

void output_init_class(class_file *cfp,FILE *fp)
{
    int k,kk,i;
    method_info *mp;
    field_info *fpp;

    fprintf(fp,"CLASS_INIT_BEGIN(\"%s\",%d,%d,%d,%d,%d,%d,%d)\n",
	    cfp->this_class_name,
	    cfp->id,
	    (cfp->super == NULL) ? (-1): cfp->super->id,
	    cfp->i_static,cfp->n_static,
	    cfp->i_var,cfp->n_var,cfp->n_method);

    /* just dump field information */
    for(k = 0; k < cfp->field_count; k++){
	fpp = &cfp->fields[k];    
	fprintf(fp,"/* field: %s %s (is_static=%d,is_object=%d,disp=%d) */\n",
		get_cp_str(cfp,fpp->name_index),
		get_cp_str(cfp,fpp->desc_index),
		fpp->is_static,fpp->is_object,fpp->disp);
	/* output const val attribute */
	for(kk = 0; kk < fpp->attr_count; kk++){
	    if(fpp->is_static && fpp->attrs[kk].kind == ATTR_CONST_VAL){
		i = fpp->attrs[kk].index;
		switch(cfp->cp_info_table[i].tag){
		case CONSTANT_Integer:
		case CONSTANT_Float:
		    fprintf(fp,"CLASS_FIELD_INIT_%c(%d,0x%x)\n",
			    *get_cp_str(cfp,fpp->desc_index),
			    fpp->disp,
			    cfp->cp_info_table[i].info.i);
		    break;
		case CONSTANT_Long:
		case CONSTANT_Double:
		    fprintf(fp,"CLASS_FIELD_INIT_%c(%d,0x%x,0x%x)\n",
			    *get_cp_str(cfp,fpp->desc_index),
			    fpp->disp,
			    cfp->cp_info_table[i].info.L.low,
			    cfp->cp_info_table[i].info.L.high);
		    break;
		case CONSTANT_String:
		    fprintf(fp,"CLASS_FIELD_INIT_%c(%d,%d)\n",
			    *get_cp_str(cfp,fpp->desc_index),
			    fpp->disp,
			    cfp->cp_info_table[i].info.string.id);
		    break;
		default: break;
		}
	    }
	}
    }

    for(k = 0; k < cfp->method_count; k++){
	mp = &cfp->methods[k];
	if(mp->cname)
	    fprintf(fp,"CLASS_METHOD(%s,%d)",mp->cname,mp->disp);
	else
	    fprintf(fp,"CLASS_METHOD(method_%d,%d)",mp->id,mp->disp);
	
	fprintf(fp,"/* %s %s (disp=%d) */\n",
		get_cp_str(cfp,mp->name_index),
		get_cp_str(cfp,mp->desc_index),
		mp->disp);
    }

    /* output interface */
    if(!cfp->is_interface){
	for(k = 0; k < cfp->interface_count; k++)
	    output_interface(fp,cfp,get_class(cfp,cfp->interfaces[k]));
    }

    fprintf(fp,"CLASS_INIT_END\n");
}

void output_interface(FILE *fp,class_file *cfp,class_file *i_cfp)
{
    int k,disp;
    method_info *mp;

    if(!i_cfp->is_interface) fatal("not interface class in interface table");
    for(k = 0; k < i_cfp->method_count; k++){
	mp = &i_cfp->methods[k];
	if(mp->attr_count != 0) continue;
	disp = find_method_disp(cfp,get_cp_str(i_cfp,mp->name_index),
				get_cp_str(i_cfp,mp->desc_index));
	if(disp < 0) 
	    fatal("interface method <%s,%s> is not found",
		  get_cp_str(i_cfp,mp->name_index),
		  get_cp_str(i_cfp,mp->desc_index));
	fprintf(fp,"CLASS_INTERFACE(%d,%d)\n",mp->id,disp);
    }
    for(k = 0; k < i_cfp->interface_count; k++)
	output_interface(fp,cfp,get_class(i_cfp,i_cfp->interfaces[k]));
}


void align_offset(DATA_TYPE t,int *offset)
{
    int     align;
    align = data_type_align(t);
    if ( align != 0 ) *offset = ROUND(*offset, align);
}

void output_str_constant(char *s,FILE *fp)
{
    char c;
    fprintf(fp,"\"");
    while((c = *s++) != '\0'){
	if(c == '\"' || c == '\\' || (c < 0x20) || (c >= 0x7F))
	    fprintf(fp,"\\%03o",c);
	else
	    fprintf(fp,"%c",c);
    }
    fprintf(fp,"\"");
}
