/*  This file is part of the program psim.

    Copyright (C) 1994-1997, Andrew Cagney <cagney@highland.com.au>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
    */


#include "misc.h"
#include "lf.h"
#include "table.h"
#include "filter.h"
#include "igen.h"

#include "ld-insn.h"
#include "ld-decode.h"

#include "gen.h"

#include "gen-semantics.h"
#include "gen-idecode.h"
#include "gen-icache.h"



static void
print_icache_function_header (lf *file,
			      const char *basename,
			      const char *format_name,
			      opcode_bits *expanded_bits,
			      int is_function_definition,
			      int nr_prefetched_words)
{
  lf_printf(file, "\n");
  lf_print__function_type_function (file, print_icache_function_type,
				    "EXTERN_ICACHE", " ");
  print_function_name (file,
		       basename, format_name, NULL,
		       expanded_bits,
		       function_name_prefix_icache);
  lf_printf (file, "\n(");
  print_icache_function_formal (file, nr_prefetched_words);
  lf_printf (file, ")");
  if (!is_function_definition)
    lf_printf (file, ";");
  lf_printf (file, "\n");
}


void
print_icache_declaration (lf *file,
			  insn_entry *insn,
			  opcode_bits *expanded_bits,
			  insn_opcodes *opcodes,
			  int nr_prefetched_words)
{
  print_icache_function_header (file,
				insn->name,
				insn->format_name,
				expanded_bits,
				0/* is not function definition */,
				nr_prefetched_words);
}



static void
print_icache_extraction (lf *file,
			 const char *format_name,
			 cache_entry_type cache_type,
			 const char *entry_name,
			 const char *entry_type,
			 const char *entry_expression,
			 char *single_insn_field,
			 line_ref *line,
			 insn_field_entry *cur_field,
			 opcode_bits *expanded_bits,
			 icache_decl_type what_to_declare,
			 icache_body_type what_to_do)
{
  const char *expression;
  opcode_bits *bits;
  char *reason;
  ASSERT (format_name != NULL);
  ASSERT (entry_name != NULL);
  
  /* figure out exactly what should be going on here */
  switch (cache_type)
    {
    case scratch_value:
      if ((what_to_do & put_values_in_icache)
	  || what_to_do == do_not_use_icache)
	{
	  reason = "scratch";
	  what_to_do = do_not_use_icache;
	}
      else
	return;
      break;
    case compute_value:
      if ((what_to_do & get_values_from_icache)
	  || what_to_do == do_not_use_icache)
	{
	  reason = "compute";
	  what_to_do = do_not_use_icache;
	}
      else
	return;
      break;
    case cache_value:
      if ((what_to_declare != undef_variables)
	  || !(what_to_do & put_values_in_icache))
	{
	  reason = "cache";
	  what_to_declare = ((what_to_do & put_values_in_icache)
			     ? declare_variables
			     : what_to_declare);
	}
      else
	return;
      break;
    }
  
  /* For the type, default to a simple unsigned */
  if (entry_type == NULL || strlen (entry_type) == 0)
    entry_type = "unsigned";
  
  /* look through the set of expanded sub fields to see if this field
     has been given a constant value */
  for (bits = expanded_bits;
       bits != NULL;
       bits = bits->next)
    {
      if (bits->field == cur_field)
	break;
    }
  
  /* Define a storage area for the cache element */
  switch (what_to_declare)
    {
    case undef_variables:
      /* We've finished with the #define value - destory it */
      lf_indent_suppress (file);
      lf_printf (file, "#undef %s\n", entry_name);
      return;
    case define_variables:
      /* Using direct access for this entry, define it */
      lf_indent_suppress (file);
      lf_printf (file, "#define %s ", entry_name);
      break;
    case declare_variables:
      /* using variables to define the value */
      if (line != NULL)
	lf_print__line_ref (file, line);
      lf_printf (file, "%s const %s UNUSED = ", entry_type, entry_name);
      break;
    }
  
  
  /* define a value for that storage area as determined by what is in
     the cache */
  if (bits != NULL
      && single_insn_field != NULL
      && strcmp (entry_name, single_insn_field) == 0
      && strcmp (entry_name, cur_field->val_string) == 0
      && ((bits->opcode->is_boolean && bits->value == 0)
	  || (!bits->opcode->is_boolean)))
    {
      /* The cache rule is specifying what to do with a simple
         instruction field.
	 
	 Because of instruction expansion, the field is either a
	 constant value or equal to the specified constant (boolean
	 comparison). (The latter indicated by bits->value == 0).
	 
         The case of a field not being equal to the specified boolean
         value is handled later. */
      expression = "constant field";
      ASSERT (bits->field == cur_field);
      if (bits->opcode->is_boolean)
	{
	  ASSERT (bits->value == 0);
	  lf_printf (file, "%d", bits->opcode->boolean_constant);
	}
      else if (bits->opcode->last < bits->field->last)
	{
	  lf_printf (file, "%d",
		     bits->value << (bits->field->last - bits->opcode->last));
	}
      else
	{
	  lf_printf (file, "%d", bits->value);
	}
    }
  else if (bits != NULL
	   && single_insn_field != NULL
	   && strncmp (entry_name,
		       single_insn_field,
		       strlen (single_insn_field)) == 0
	   && strncmp (entry_name + strlen (single_insn_field),
		       "_is_",
		       strlen ("_is_")) == 0
	   && ((bits->opcode->is_boolean
		&& ((unsigned) atol (entry_name + strlen (single_insn_field) + strlen ("_is_"))
		    == bits->opcode->boolean_constant))
	       || (!bits->opcode->is_boolean)))
    {
      /* The cache rule defines an entry for the comparison between a
	 single instruction field and a constant.  The value of the
	 comparison in someway matches that of the opcode field that
	 was made constant through expansion. */
      expression = "constant compare";
      if (bits->opcode->is_boolean)
	{
	  lf_printf (file, "%d /* %s == %d */",
		     bits->value == 0,
		     single_insn_field,
		     bits->opcode->boolean_constant);
	}
      else if (bits->opcode->last < bits->field->last)
	{
	  lf_printf (file, "%d /* %s == %d */",
		     (atol (entry_name + strlen (single_insn_field) + strlen ("_is_"))
		      == (bits->value << (bits->field->last - bits->opcode->last))),
		     single_insn_field,
		     (bits->value << (bits->field->last - bits->opcode->last)));
	}
      else
	{
	  lf_printf (file, "%d /* %s == %d */",
		     (atol (entry_name + strlen (single_insn_field) + strlen ("_is_"))
		      == bits->value),
		     single_insn_field,
		     bits->value);
	}
    }
  else
    {
      /* put the field in the local variable, possibly also enter it
	 into the cache */
      expression = "extraction";
      /* handle the cache */
      if ((what_to_do & get_values_from_icache)
	  || (what_to_do & put_values_in_icache))
	{
	  lf_printf (file, "cache_entry->crack.%s.%s",
		     format_name,
		     entry_name);
	  if (what_to_do & put_values_in_icache) /* also put it in the cache? */
	    {
	      lf_printf (file, " = ");
	    }
	}
      if ((what_to_do & put_values_in_icache)
	  || what_to_do == do_not_use_icache)
	{
	  if (cur_field != NULL)
	    {
	      if (entry_expression != NULL && strlen (entry_expression) > 0)
		error (line, "Instruction field entry with nonempty expression\n");
	      if (cur_field->first == 0 && cur_field->last == options.insn_bit_size - 1)
		lf_printf (file, "(instruction_%d)",
			   cur_field->word_nr);
	      else if (cur_field->last == options.insn_bit_size - 1)
		lf_printf (file, "MASKED%d (instruction_%d, %d, %d)",
			   options.insn_bit_size,
			   cur_field->word_nr,
			   i2target (options.hi_bit_nr, cur_field->first),
			   i2target (options.hi_bit_nr, cur_field->last));
	      else
		lf_printf (file, "EXTRACTED%d (instruction_%d, %d, %d)",
			   options.insn_bit_size,
			   cur_field->word_nr,
			   i2target (options.hi_bit_nr, cur_field->first),
			   i2target (options.hi_bit_nr, cur_field->last));
	    }
	  else
	    {
	      lf_printf (file, "%s", entry_expression);
	    }
	}
    }
  
  switch (what_to_declare)
    {
    case define_variables:
    case undef_variables:
      break;
    case declare_variables:
      lf_printf (file, ";");
      break;
    }

  ASSERT (reason != NULL && expression != NULL);
  lf_printf (file, " /* %s - %s */\n", reason, expression);
}


void
print_icache_body (lf *file,
		   insn_entry *instruction,
		   opcode_bits *expanded_bits,
		   cache_entry *cache_rules,
		   icache_decl_type what_to_declare,
		   icache_body_type what_to_do,
		   int nr_prefetched_words)
{
  /* extract instruction fields */
  lf_printf (file, "/* Extraction: %s\n", instruction->name);
  lf_printf (file, "     ");
  switch (what_to_declare)
    {
    case define_variables:
      lf_printf (file, "#define");
      break;
    case declare_variables:
      lf_printf (file, "declare");
      break;
    case undef_variables:
      lf_printf (file, "#undef");
      break;
    }
  lf_printf (file, " ");
  switch (what_to_do)
    {
    case get_values_from_icache:
      lf_printf (file, "get-values-from-icache");
      break;
    case put_values_in_icache:
      lf_printf (file, "put-values-in-icache");
      break;
    case both_values_and_icache:
      lf_printf (file, "get-values-from-icache|put-values-in-icache");
      break;
    case do_not_use_icache:
      lf_printf (file, "do-not-use-icache");
      break;
    }
  lf_printf (file, "\n     ");
  print_insn_words (file, instruction);
  lf_printf(file, " */\n");
  
  /* pass zero - fetch from memory any missing instructions.

     Some of the instructions will have already been fetched (in the
     instruction array), others will still need fetching. */
  switch (what_to_do)
    {
    case get_values_from_icache:
      break;
    case put_values_in_icache:
    case both_values_and_icache:
    case do_not_use_icache:
      {
	int word_nr;
	switch (what_to_declare)
	  {
	  case undef_variables:
	    break;
	  case define_variables:
	  case declare_variables:
	    for (word_nr = nr_prefetched_words;
		 word_nr < instruction->nr_words;
		 word_nr++)
	      {
		/* FIXME - should be using print_icache_extraction? */
		lf_printf (file, "%sinstruction_word instruction_%d = ",
			   options.prefix.global.name,
			   word_nr);
		lf_printf (file, "IMEM_IMMED (cia, %d)", word_nr);
		lf_printf (file, ";\n");
	      }
	  }
      }
    }

  /* if putting the instruction words in the cache, define references
     for them */
  if (options.gen.insn_in_icache) {
    /* FIXME: is the instruction_word type correct? */
    print_icache_extraction (file,
			     instruction->format_name,
			     cache_value,
			     "insn", /* name */
			     "instruction_word", /* type */
			     "instruction", /* expression */
			     NULL, /* origin */
			     NULL, /* line */
			     NULL, NULL,
			     what_to_declare,
			     what_to_do);
  }
  lf_printf(file, "\n");

  /* pass one - process instruction fields.

     If there is no cache rule, the default is to enter the field into
     the cache */
  {
    insn_word_entry *word;
    for (word = instruction->words;
	 word != NULL;
	 word = word->next)
      {
	insn_field_entry *cur_field;
	for (cur_field = word->first;
	     cur_field->first < options.insn_bit_size;
	     cur_field = cur_field->next)
	  {
	    if (cur_field->type == insn_field_string)
	      {
		cache_entry *cache_rule;
		cache_entry_type value_type = cache_value;
		line_ref *value_line = instruction->line;
		/* check the cache table to see if it contains a rule
		   overriding the default cache action for an
		   instruction field */
		for (cache_rule = cache_rules;
		     cache_rule != NULL;
		     cache_rule = cache_rule->next)
		  {
		    if (filter_is_subset (instruction->field_names,
					  cache_rule->original_fields)
			&& strcmp (cache_rule->name, cur_field->val_string) == 0)
		      {
			value_type = cache_rule->entry_type;
			value_line = cache_rule->line;
			if (value_type == compute_value)
			  {
			    options.warning (cache_rule->line,
					     "instruction field of type `compute' changed to `cache'\n");
			    cache_rule->entry_type = cache_value;
			  }
			break;
		      }
		  }
		/* Define an entry for the field within the
                   instruction */
		print_icache_extraction (file,
					 instruction->format_name,
					 value_type,
					 cur_field->val_string, /* name */
					 NULL, /* type */
					 NULL, /* expression */
					 cur_field->val_string, /* insn field */
					 value_line,
					 cur_field,
					 expanded_bits,
					 what_to_declare,
					 what_to_do);
	      }
	  }
      }
  }

  /* pass two - any cache fields not processed above */
  {
    cache_entry *cache_rule;
    for (cache_rule = cache_rules;
	 cache_rule != NULL;
	 cache_rule = cache_rule->next)
      {
	if (filter_is_subset (instruction->field_names,
			      cache_rule->original_fields)
	    && !filter_is_member (instruction->field_names,
				  cache_rule->name))
	  {
	    char *single_field = filter_next (cache_rule->original_fields, "");
	    if (filter_next (cache_rule->original_fields, single_field) != NULL)
	      single_field = NULL;
	    print_icache_extraction (file,
				     instruction->format_name,
				     cache_rule->entry_type,
				     cache_rule->name,
				     cache_rule->type,
				     cache_rule->expression,
				     single_field,
				     cache_rule->line,
				     NULL, /* cur_field */
				     expanded_bits,
				     what_to_declare,
				     what_to_do);
	  }
      }
  }

  lf_print__internal_ref (file);
}



typedef struct _form_fields form_fields;
struct _form_fields {
  char *name;
  filter *fields;
  form_fields *next;
};

static form_fields *
insn_table_cache_fields (insn_table *isa)
{
  form_fields *forms = NULL;
  insn_entry *insn;
  for (insn = isa->insns;
       insn != NULL;
       insn = insn->next) {
    form_fields **form = &forms;
    while (1)
      {
	if (*form == NULL)
	  {
	    /* new format name, add it */
	    form_fields *new_form = ZALLOC (form_fields);
	    new_form->name = insn->format_name;
	    filter_add (&new_form->fields, insn->field_names);
	    *form = new_form;
	    break;
	  }
	else if (strcmp ((*form)->name, insn->format_name) == 0)
	  {
	    /* already present, add field names to the existing list */
	    filter_add (&(*form)->fields, insn->field_names);
	    break;
	  }
	form = &(*form)->next;
      }
  }
  return forms;
}



extern void
print_icache_struct (lf *file,
		     insn_table *isa,
		     cache_entry *cache_rules)
{
  /* Create a list of all the different instruction formats with their
     corresponding field names. */
  form_fields *formats = insn_table_cache_fields (isa);
  
  lf_printf (file, "\n");
  lf_printf (file, "#define WITH_%sIDECODE_CACHE_SIZE %d\n",
	     options.prefix.global.uname,
	     (options.gen.icache ? options.gen.icache_size : 0));
  lf_printf (file, "\n");
  
  /* create an instruction cache if being used */
  if (options.gen.icache) {
    lf_printf (file, "typedef struct _%sidecode_cache {\n",
	       options.prefix.global.name);
    lf_indent (file, +2);
    {
      form_fields *format;
      lf_printf (file, "unsigned_word address;\n");
      lf_printf (file, "void *semantic;\n");
      lf_printf (file, "union {\n");
      lf_indent (file, +2);
      for (format = formats;
	   format != NULL;
	   format = format->next)
	{
	  lf_printf (file, "struct {\n");
	  lf_indent (file, +2);
	  {
	    cache_entry *cache_rule;
	    char *field;
	    /* space for any instruction words */
	    if (options.gen.insn_in_icache)
	      lf_printf (file, "instruction_word insn[%d];\n", isa->max_nr_words);
	    /* define an entry for any applicable cache rules */
	    for (cache_rule = cache_rules;
		 cache_rule != NULL;
		 cache_rule = cache_rule->next)
	      {
		/* nb - sort of correct - should really check against
                   individual instructions */
		if (filter_is_subset (format->fields, cache_rule->original_fields))
		  {
		    char *memb;
		    lf_printf (file, "%s %s;",
			       (cache_rule->type == NULL
				? "unsigned" 
				: cache_rule->type),
			       cache_rule->name);
		    lf_printf (file, " /*");
		    for (memb = filter_next (cache_rule->original_fields, "");
			 memb != NULL;
			 memb = filter_next (cache_rule->original_fields, memb))
		      {
			lf_printf (file, " %s", memb);
		      }
		    lf_printf (file, " */\n");
		  }
	      }
	    /* define an entry for any fields not covered by a cache rule */
	    for (field = filter_next (format->fields, "");
		 field != NULL;
		 field = filter_next (format->fields, field))
	      {
		cache_entry *cache_rule;
		int found_rule = 0;
		for (cache_rule = cache_rules;
		     cache_rule != NULL;
		     cache_rule = cache_rule->next)
		  {
		    if (strcmp (cache_rule->name, field) == 0)
		      {
			found_rule = 1;
			break;
		      }
		  }
		if (!found_rule)
		  lf_printf (file, "unsigned %s; /* default */\n", field);
	      }
	  }
	  lf_indent (file, -2);
	  lf_printf (file, "} %s;\n", format->name);
	}
      lf_indent (file, -2);
      lf_printf (file, "} crack;\n");
    }
    lf_indent (file, -2);
    lf_printf (file, "} %sidecode_cache;\n", options.prefix.global.name);
  }
  else
    {
      /* alernativly, since no cache, emit a dummy definition for
	 idecode_cache so that code refering to the type can still compile */
      lf_printf(file, "typedef void %sidecode_cache;\n",
		options.prefix.global.name);
    }
  lf_printf (file, "\n");
}



static void
print_icache_function (lf *file,
		       insn_entry *instruction,
		       opcode_bits *expanded_bits,
		       insn_opcodes *opcodes,
		       cache_entry *cache_rules,
		       int nr_prefetched_words)
{
  int indent;

  /* generate code to enter decoded instruction into the icache */
  lf_printf(file, "\n");
  lf_print__function_type_function (file, print_icache_function_type,
				    "EXTERN_ICACHE", "\n");
  indent = print_function_name (file,
				instruction->name,
				instruction->format_name,
				NULL,
				expanded_bits,
				function_name_prefix_icache);
  indent += lf_printf (file, " ");
  lf_indent (file, +indent);
  lf_printf (file, "(");
  print_icache_function_formal (file, nr_prefetched_words);
  lf_printf (file, ")\n");
  lf_indent (file, -indent);
  
  /* function header */
  lf_printf (file, "{\n");
  lf_indent (file, +2);
  
  print_my_defines (file,
		    instruction->name,
		    instruction->format_name,
		    expanded_bits);
  print_itrace (file, instruction, 1/*putting-value-in-cache*/);
  
  print_idecode_validate (file, instruction, opcodes);
  
  lf_printf (file, "\n");
  lf_printf (file, "{\n");
  lf_indent (file, +2);
  if (options.gen.semantic_icache)
    lf_printf (file, "unsigned_word nia;\n");
  print_icache_body (file,
		     instruction,
		     expanded_bits,
		     cache_rules,
		     (options.gen.direct_access
		      ? define_variables
		      : declare_variables),
		     (options.gen.semantic_icache
		      ? both_values_and_icache
		      : put_values_in_icache),
		     nr_prefetched_words);
  
  lf_printf (file, "\n");
  lf_printf (file, "cache_entry->address = cia;\n");
  lf_printf (file, "cache_entry->semantic = ");
  print_function_name (file,
		       instruction->name,
		       instruction->format_name,
		       NULL,
		       expanded_bits,
		       function_name_prefix_semantics);
  lf_printf (file, ";\n");
  lf_printf (file, "\n");

  if (options.gen.semantic_icache) {
    lf_printf (file, "/* semantic routine */\n");
    print_semantic_body (file,
			 instruction,
			 expanded_bits,
			 opcodes);
    lf_printf (file, "return nia;\n");
  }
  
  if (!options.gen.semantic_icache)
    {
      lf_printf (file, "/* return the function proper */\n");
      lf_printf (file, "return ");
      print_function_name (file,
			   instruction->name,
			   instruction->format_name,
			   NULL,
			   expanded_bits,
			   function_name_prefix_semantics);
      lf_printf (file, ";\n");
    }
  
  if (options.gen.direct_access)
    {
      print_icache_body (file,
			 instruction,
			 expanded_bits,
			 cache_rules,
			 undef_variables,
			 (options.gen.semantic_icache
			  ? both_values_and_icache
			  : put_values_in_icache),
			 nr_prefetched_words);
    }
  
  lf_indent (file, -2);
  lf_printf (file, "}\n");
  lf_indent (file, -2);
  lf_printf (file, "}\n");
}


void
print_icache_definition (lf *file,
			 insn_entry *insn,
			 opcode_bits *expanded_bits,
			 insn_opcodes *opcodes,
			 cache_entry *cache_rules,
			 int nr_prefetched_words)
{
  print_icache_function (file,
			 insn,
			 expanded_bits,
			 opcodes,
			 cache_rules,
			 nr_prefetched_words);
}



void
print_icache_internal_function_declaration (lf *file,
					    function_entry *function,
					    void *data)
{
  ASSERT (options.gen.icache);
  if (function->is_internal)
    {
      lf_printf (file, "\n");
      lf_print__function_type_function (file, print_icache_function_type,
					"INLINE_ICACHE", "\n");
      print_function_name (file,
			   function->name,
			   NULL,
			   NULL,
			   NULL,
			   function_name_prefix_icache);
      lf_printf (file, "\n(");
      print_icache_function_formal (file, 0);
      lf_printf (file, ");\n");
    }
}


void
print_icache_internal_function_definition (lf *file,
					   function_entry *function,
					   void *data)
{
  ASSERT (options.gen.icache);
  if (function->is_internal)
    {
      lf_printf (file, "\n");
      lf_print__function_type_function (file, print_icache_function_type,
					"INLINE_ICACHE", "\n");
      print_function_name (file,
			   function->name,
			   NULL,
			   NULL,
			   NULL,
			   function_name_prefix_icache);
      lf_printf (file, "\n(");
      print_icache_function_formal (file, 0);
      lf_printf (file, ")\n");
      lf_printf (file, "{\n");
      lf_indent (file, +2);
      lf_printf (file, "/* semantic routine */\n");
      if (options.gen.semantic_icache)
	{
	  lf_print__line_ref (file, function->code->line);
	  table_print_code (file, function->code);
	  lf_printf (file, "error (\"Internal function must longjump\\n\");\n");
	  lf_printf (file, "return 0;\n");
	}
      else
	{
	  lf_printf (file, "return ");
	  print_function_name (file,
			       function->name,
			       NULL,
			       NULL,
			       NULL,
			       function_name_prefix_semantics);
	  lf_printf (file, ";\n");
	}
      
      lf_print__internal_ref (file);
      lf_indent (file, -2);
      lf_printf (file, "}\n");
    }
}
