/* tc-kvx.c -- Assemble for the KVX ISA

   Copyright (C) 2009-2024 Free Software Foundation, Inc.
   Contributed by Kalray SA.

   This file is part of GAS.

   GAS 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 3 of the license, or
   (at your option) any later version.

   GAS 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; see the file COPYING3. If not,
   see <http://www.gnu.org/licenses/>.  */

#include "as.h"
#include "obstack.h"
#include "subsegs.h"
#include "tc-kvx.h"
#include "libiberty.h"

#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>

#ifdef OBJ_ELF
#include "elf/kvx.h"
#include "dwarf2dbg.h"
#include "dw2gencfi.h"
#endif

#define D(args...) do { if(debug) fprintf(args); } while(0)

static void supported_cores (char buf[], size_t buflen);

#define NELEMS(a) ((int) (sizeof (a)/sizeof ((a)[0])))

#define STREQ(x,y) !strcmp(((x) ? (x) : ""), ((y) ? (y) : ""))
#define STRNEQ(x,y,n) !strncmp(((x) ? (x) : ""), ((y) ? (y) : ""),(n))

/* The PARALLEL_BIT is set to 0 when an instruction is the last of a bundle. */
#define PARALLEL_BIT (1u << 31)

/*TB begin*/
int size_type_function = 1;
/*TB end */

struct kvx_as_env env = {
  .params = {
    .abi = ELF_KVX_ABI_UNDEF,
    .osabi = ELFOSABI_NONE,
    .core = -1,
    .core_set = 0,
    .abi_set = 0,
    .osabi_set = 0,
    .pic_flags = 0,
    .arch_size = 64
  },
  .opts = {
    .march = NULL,
    .check_resource_usage = 1,
    .generate_illegal_code = 0,
    .dump_table = 0,
    .dump_insn = 0,
    .diagnostics = 1,
    .more = 1,
    .allow_all_sfr = 0
  }
};

/* This string should contain position in string where error occured. */

/* Default kvx_registers array. */
const struct kvx_Register *kvx_registers = NULL;
/* Default kvx_modifiers array. */
const char ***kvx_modifiers = NULL;
/* Default kvx_regfiles array. */
const int *kvx_regfiles = NULL;
/* Default values used if no assume directive is given */
const struct kvx_core_info *kvx_core_info = NULL;

/***********************************************/
/*    Generic Globals for GAS                  */
/***********************************************/

const char comment_chars[]        = "#";
const char line_comment_chars[]   = "#";
const char line_separator_chars[] = ";";
const char EXP_CHARS[]            = "eE";
const char FLT_CHARS[]            = "dD";
const int md_short_jump_size      = 0;
const int md_long_jump_size       = 0;

/***********************************************/
/*           Local Types                       */
/***********************************************/

/* a fix up record                       */

struct kvx_fixup
{
  /* The expression used.  */
  expressionS exp;
  /* The place in the frag where this goes.  */
  int where;
  /* The relocation.  */
  bfd_reloc_code_real_type reloc;
};

/* a single assembled instruction record */
/* may include immediate extension words  */
struct kvxinsn
{
  /* written out?  */
  int written;
  /* Opcode table entry for this insn */
  const struct kvxopc *opdef;
  /* length of instruction in words (1 or 2) */
  int len;
  /* insn is extended */
  int immx0;
  /* insn has two immx */
  int immx1;
  /* order to stabilize sort */
  int order;
  /* instruction words */
  uint32_t words[KVXMAXBUNDLEWORDS];	
  /* the number of fixups [0,2] */
  int nfixups;
  /* the actual fixups */
  struct kvx_fixup fixup[2];
};

typedef void (*print_insn_t) (struct kvxopc * op);
static print_insn_t print_insn = NULL;

/* Set to TRUE when we assemble instructions.  */
static bool assembling_insn = false;

#define NOIMMX -1

/* Was KVXMAXBUNDLEISSUE, changed because of NOPs */
static struct kvxinsn insbuf[KVXMAXBUNDLEWORDS];
static int insncnt = 0;
static struct kvxinsn immxbuf[KVXMAXBUNDLEWORDS];
static int immxcnt = 0;

static void
incr_immxcnt (void)
{
  immxcnt++;
  if (immxcnt >= KVXMAXBUNDLEWORDS)
    as_bad ("Max immx number exceeded: %d", immxcnt);
}

static void set_byte_counter (asection * sec, int value);
static void
set_byte_counter (asection * sec, int value)
{
  sec->target_index = value;
}

static int get_byte_counter (asection * sec);
int
get_byte_counter (asection * sec)
{
  return sec->target_index;
}

const char *
kvx_target_format (void)
{
  return env.params.arch_size == 64 ? "elf64-kvx" : "elf32-kvx";
}

/****************************************************/
/*  ASSEMBLER Pseudo-ops.  Some of this just        */
/*  extends the default definitions                 */
/*  others are KVX specific                          */
/****************************************************/

static void kvx_check_resources (int);
static void kvx_proc (int start);
static void kvx_endp (int start);
static void kvx_type (int start);

const pseudo_typeS md_pseudo_table[] = {
  /* override default 2-bytes */
  { "word",             cons,                  4 },

  /* KVX specific */
  { "dword",            cons,                  8 },

  /* Override align directives to have a boundary as argument (and not the
     power of two as in p2align) */
  { "align",            s_align_bytes,         0 },

  { "checkresources",   kvx_check_resources,   1 },
  { "nocheckresources", kvx_check_resources,   0 },

  { "proc",             kvx_proc,              1 },
  { "endp",             kvx_endp,              0 },

  { "type",             kvx_type,              0 },

#ifdef OBJ_ELF
  { "file",             dwarf2_directive_file, 0 },
  { "loc",              dwarf2_directive_loc,  0 },
#endif
  { NULL,               0,                     0 }
};


static int inside_bundle = 0;

/* Stores the labels inside bundles (typically debug labels) that need
   to be postponed to the next bundle. */
struct label_fix
{
  struct label_fix *next;
  symbolS *sym;
} *label_fixes = 0;

/*****************************************************/
/*   OPTIONS PROCESSING                              */
/*****************************************************/

const char *md_shortopts = "hV";	/* catted to std short options */

/* added to std long options */

#define OPTION_HEXFILE               (OPTION_MD_BASE + 0)
#define OPTION_MARCH                 (OPTION_MD_BASE + 4)
#define OPTION_CHECK_RESOURCES       (OPTION_MD_BASE + 5)
#define OPTION_NO_CHECK_RESOURCES    (OPTION_MD_BASE + 6)
#define OPTION_GENERATE_ILLEGAL_CODE (OPTION_MD_BASE + 7)
#define OPTION_DUMP_TABLE            (OPTION_MD_BASE + 8)
#define OPTION_PIC                   (OPTION_MD_BASE + 9)
#define OPTION_BIGPIC                (OPTION_MD_BASE + 10)
#define OPTION_NOPIC                 (OPTION_MD_BASE + 12)
#define OPTION_32                    (OPTION_MD_BASE + 13)
#define OPTION_DUMPINSN              (OPTION_MD_BASE + 15)
#define OPTION_ALL_SFR               (OPTION_MD_BASE + 16)
#define OPTION_DIAGNOSTICS           (OPTION_MD_BASE + 17)
#define OPTION_NO_DIAGNOSTICS        (OPTION_MD_BASE + 18)
#define OPTION_MORE                  (OPTION_MD_BASE + 19)
#define OPTION_NO_MORE               (OPTION_MD_BASE + 20)

struct option md_longopts[] = {
  { "march",                 required_argument, NULL, OPTION_MARCH                 },
  { "check-resources",       no_argument,       NULL, OPTION_CHECK_RESOURCES       },
  { "no-check-resources",    no_argument,       NULL, OPTION_NO_CHECK_RESOURCES    },
  { "generate-illegal-code", no_argument,       NULL, OPTION_GENERATE_ILLEGAL_CODE },
  { "dump-table",            no_argument,       NULL, OPTION_DUMP_TABLE            },
  { "mpic",                  no_argument,       NULL, OPTION_PIC                   },
  { "mPIC",                  no_argument,       NULL, OPTION_BIGPIC                },
  { "mnopic",                no_argument,       NULL, OPTION_NOPIC                 },
  { "m32",                   no_argument,       NULL, OPTION_32                    },
  { "dump-insn",             no_argument,       NULL, OPTION_DUMPINSN              },
  { "all-sfr",               no_argument,       NULL, OPTION_ALL_SFR               },
  { "diagnostics",           no_argument,       NULL, OPTION_DIAGNOSTICS           },
  { "no-diagnostics",        no_argument,       NULL, OPTION_NO_DIAGNOSTICS        },
  { "more",                  no_argument,       NULL, OPTION_MORE                  },
  { "no-more",               no_argument,       NULL, OPTION_NO_MORE               },
  { NULL,                    no_argument,       NULL, 0                            }
};

size_t md_longopts_size = sizeof (md_longopts);

int
md_parse_option (int c, const char *arg ATTRIBUTE_UNUSED)
{
  int find_core = 0;

  switch (c)
    {
    case 'h':
      md_show_usage (stdout);
      exit (EXIT_SUCCESS);
      break;

      /* -V: SVR4 argument to print version ID.  */
    case 'V':
      print_version_id ();
      exit (EXIT_SUCCESS);
      break;
    case OPTION_MARCH:
      env.opts.march = strdup (arg);
      for (int i = 0; i < KVXNUMCORES && !find_core; ++i)
	    if (!strcasecmp (env.opts.march, kvx_core_info_table[i]->name))
	      {
		kvx_core_info = kvx_core_info_table[i];
		kvx_registers = kvx_registers_table[i];
		kvx_modifiers = kvx_modifiers_table[i];
		kvx_regfiles = kvx_regfiles_table[i];

		find_core = 1;
		break;
	      }
      if (!find_core)
	{
	  char buf[100];
	  supported_cores (buf, sizeof (buf));
	  as_fatal ("Specified arch not supported [%s]", buf);
	}
      break;
    case OPTION_CHECK_RESOURCES:
      env.opts.check_resource_usage = 1;
      break;
    case OPTION_NO_CHECK_RESOURCES:
      env.opts.check_resource_usage = 0;
      break;
    case OPTION_GENERATE_ILLEGAL_CODE:
      env.opts.generate_illegal_code = 1;
      break;
    case OPTION_DUMP_TABLE:
      env.opts.dump_table = 1;
      break;
    case OPTION_DUMPINSN:
      env.opts.dump_insn = 1;
      break;
    case OPTION_ALL_SFR:
      env.opts.allow_all_sfr = 1;
      break;
    case OPTION_DIAGNOSTICS:
      env.opts.diagnostics = 1;
      break;
    case OPTION_NO_DIAGNOSTICS:
      env.opts.diagnostics = 0;
      break;
    case OPTION_MORE:
      env.opts.more = 1;
      break;
    case OPTION_NO_MORE:
      env.opts.more = 0;
      break;
    case OPTION_PIC:
      /* fallthrough, for now the same on KVX */
    case OPTION_BIGPIC:
      env.params.pic_flags |= ELF_KVX_ABI_PIC_BIT;
      break;
    case OPTION_NOPIC:
      env.params.pic_flags &= ~(ELF_KVX_ABI_PIC_BIT);
      break;
    case OPTION_32:
      env.params.arch_size = 32;
      break;

    default:
      return 0;
    }
  return 1;
}

void
md_show_usage (FILE * stream)
{
  char buf[100];
  supported_cores (buf, sizeof (buf));

  fprintf (stream, "\n"
"KVX specific options:\n\n"
"  --check-resources\t Perform minimal resource checking\n"
"  --march [%s]\t Select architecture\n"
"  -V \t\t\t Print assembler version number\n\n"
"  The options -M, --mri and -f are not supported in this assembler.\n", buf);
}

/**************************************************/
/*              UTILITIES                         */
/**************************************************/

/*
 * Read a value from to the object file
 */

static valueT md_chars_to_number (char *buf, int n);
valueT
md_chars_to_number (char *buf, int n)
{
  valueT val = 0;

  if (n > (int) sizeof (val) || n <= 0)
    abort ();

  while (n--)
    {
      val <<= 8;
      val |= (buf[n] & 0xff);
    }

  return val;
}

/* Returns the corresponding pseudo function matching SYM and to be
   used for data section */
static struct pseudo_func *
kvx_get_pseudo_func_data_scn (symbolS * sym)
{
  for (int i = 0; i < kvx_core_info->nb_pseudo_funcs; i++)
    if (sym == kvx_core_info->pseudo_funcs[i].sym
	&& kvx_core_info->pseudo_funcs[i].pseudo_relocs.single != BFD_RELOC_UNUSED)
	return &kvx_core_info->pseudo_funcs[i];
  return NULL;
}

/* Returns the corresponding pseudo function matching SYM and operand
   format OPND */
static struct pseudo_func *
kvx_get_pseudo_func2 (symbolS *sym, struct kvx_operand * opnd)
{
  for (int i = 0; i < kvx_core_info->nb_pseudo_funcs; i++)
    if (sym == kvx_core_info->pseudo_funcs[i].sym)
      for (int relidx = 0; relidx < opnd->reloc_nb; relidx++)
	if (opnd->relocs[relidx] == kvx_core_info->pseudo_funcs[i].pseudo_relocs.kreloc
	    && (env.params.arch_size == (int) kvx_core_info->pseudo_funcs[i].pseudo_relocs.avail_modes
	      || kvx_core_info->pseudo_funcs[i].pseudo_relocs.avail_modes == PSEUDO_ALL))
	  return &kvx_core_info->pseudo_funcs[i];

  return NULL;
}

static void
supported_cores (char buf[], size_t buflen)
{
  buf[0] = '\0';
  for (int i = 0; i < KVXNUMCORES; i++)
    {
      if (buf[0] == '\0')
	strcpy (buf, kvx_core_info_table[i]->name);
      else
	if ((strlen (buf) + 1 + strlen (kvx_core_info_table[i]->name) + 1) < buflen)
	  {
	    strcat (buf, "|");
	    strcat (buf, kvx_core_info_table[i]->name);
	  }
    }
}

/***************************************************/
/*   ASSEMBLE AN INSTRUCTION                       */
/***************************************************/

/*
 * Insert ARG into the operand described by OPDEF in instruction INSN
 * Returns 1 if the immediate extension (IMMX) has been
 * handled along with relocation, 0 if not.
 */
static int
insert_operand (struct kvxinsn *insn, struct kvx_operand *opdef,
		struct token_list *tok)
{
  uint64_t op = 0;
  struct kvx_bitfield *bfields = opdef->bfield;
  int bf_nb = opdef->bitfields;
  int immx_ready = 0;

  if (opdef->width == 0)
    return 0;

#define add_fixup(insn_, reloc_, exp_) \
  do { \
    (insn_)->fixup[(insn_)->nfixups].reloc = (reloc_); \
    (insn_)->fixup[(insn_)->nfixups].exp = (exp_);     \
    (insn_)->fixup[(insn_)->nfixups].where = 0;        \
    (insn_)->nfixups++;                                \
  } while (0)

#define add_immx(insn_, words_, reloc_, exp_, nfixups_, len_) \
  do { \
    immxbuf[immxcnt].words[0] = (words_);                \
    immxbuf[immxcnt].fixup[0].reloc = (reloc_);          \
    immxbuf[immxcnt].fixup[0].exp = (exp_);              \
    immxbuf[immxcnt].fixup[0].where = 0;                 \
    immxbuf[immxcnt].nfixups = (nfixups_);               \
    immxbuf[immxcnt].len = (len_);                       \
    /* decrement insn->len: immx part handled separately \
       from insn and must not be emited twice.  */       \
    (insn_)->len -= 1;                                   \
    incr_immxcnt ();                                     \
  } while (0)

#define chk_imm(core_, imm_) \
  (env.params.core == ELF_KVX_CORE_## core_ && opdef->type == (imm_))

  /* try to resolve the value */

  switch (tok->category)
    {
    case CAT_REGISTER:
      op = S_GET_VALUE (str_hash_find (env.reg_hash, tok->tok));
      op -= opdef->bias;
      op >>= opdef->shift;
      break;
    case CAT_MODIFIER:
      op = tok->val;
      op -= opdef->bias;
      op >>= opdef->shift;
      break;
    case CAT_IMMEDIATE:
      {
	char *ilp_save = input_line_pointer;
	input_line_pointer = tok->tok;
	expressionS exp = { 0 };
	expression (&exp);
	input_line_pointer = ilp_save;

	/* We are dealing with a pseudo-function.  */
	if (tok->tok[0] == '@')
	  {
	    if (insn->nfixups == 0)
	      {
		expressionS reloc_arg;
		reloc_arg = exp;
		reloc_arg.X_op = O_symbol;
		struct pseudo_func *pf =
		  kvx_get_pseudo_func2 (exp.X_op_symbol, opdef);
		/* S64 uses LO10/UP27/EX27 format (3 words), with one reloc in each words (3) */
		/* S43 uses LO10/EX6/UP27 format (2 words), with 2 relocs in main syllabes and 1 in extra word */
		/* S37 uses LO10/UP27 format (2 words), with one reloc in each word (2) */

		/* Beware that immxbuf must be filled in the same order as relocs should be emitted. */

		if (pf->pseudo_relocs.reloc_type == S64_LO10_UP27_EX27
		    || pf->pseudo_relocs.reloc_type == S43_LO10_UP27_EX6
		    || pf->pseudo_relocs.reloc_type == S37_LO10_UP27)
		  {
		    add_fixup (insn, pf->pseudo_relocs.reloc_lo10, reloc_arg);

		    insn->immx0 = immxcnt;
		    add_immx (insn, 0, pf->pseudo_relocs.reloc_up27,
			      reloc_arg, 1, 1);
		    immx_ready = 1;
		  }
		else if (pf->pseudo_relocs.reloc_type == S32_LO5_UP27)
		  {
		    add_fixup (insn, pf->pseudo_relocs.reloc_lo5, reloc_arg);

		    insn->immx0 = immxcnt;
		    add_immx (insn, 0, pf->pseudo_relocs.reloc_up27,
			      reloc_arg, 1, 1);
		    immx_ready = 1;
		  }
		else if (pf->pseudo_relocs.reloc_type == S16)
		  add_fixup (insn, pf->pseudo_relocs.single, reloc_arg);
		else
		  as_fatal ("Unexpected fixup");

		if (pf->pseudo_relocs.reloc_type == S64_LO10_UP27_EX27)
		  {
		    insn->immx1 = immxcnt;
		    add_immx (insn, 0, pf->pseudo_relocs.reloc_ex, reloc_arg,
			      1, 1);
		  }
		else if (pf->pseudo_relocs.reloc_type == S43_LO10_UP27_EX6)
		  add_fixup (insn, pf->pseudo_relocs.reloc_ex, reloc_arg);
	      }
	  }
	else
	  {
	    if (exp.X_op == O_constant)
	      {
		/* This is a immediate: either a regular immediate, or an
		   immediate that was saved in a variable through `.equ'.  */
		uint64_t sval = (int64_t) tok->val;
		op = opdef->flags & kvxSIGNED ? sval : tok->val;
		op >>= opdef->shift;
	      }
	    else if (exp.X_op == O_subtract)
	      as_fatal ("O_subtract not supported.");
	    else
	      {

		/* This is a symbol which needs a relocation.  */
		if (insn->nfixups == 0)
		  {
		    if (chk_imm (KV3_1, Immediate_kv3_v1_pcrel17)
			|| chk_imm (KV3_2, Immediate_kv3_v2_pcrel17)
			|| chk_imm (KV4_1, Immediate_kv4_v1_pcrel17))
		      add_fixup (insn, BFD_RELOC_KVX_PCREL17, exp);
		    else if (chk_imm (KV3_1, Immediate_kv3_v1_pcrel27)
			     || chk_imm (KV3_2, Immediate_kv3_v2_pcrel27)
			     || chk_imm (KV4_1, Immediate_kv4_v1_pcrel27))
		      add_fixup (insn, BFD_RELOC_KVX_PCREL27, exp);
		    else if (chk_imm (KV3_1, Immediate_kv3_v1_wrapped32)
			     || chk_imm (KV3_2, Immediate_kv3_v2_wrapped32)
			     || chk_imm (KV4_1, Immediate_kv4_v1_wrapped32))
		      {
			add_fixup (insn, BFD_RELOC_KVX_S32_LO5, exp);

			insn->immx0 = immxcnt;
			add_immx (insn, 0, BFD_RELOC_KVX_S32_UP27, exp, 1, 1);

			immx_ready = 1;
		      }
		    else if (chk_imm (KV3_1, Immediate_kv3_v1_signed10)
			     || chk_imm (KV3_2, Immediate_kv3_v2_signed10)
			     || chk_imm (KV4_1, Immediate_kv4_v1_signed10))
		      add_fixup (insn, BFD_RELOC_KVX_S37_LO10, exp);
		    else if (chk_imm (KV3_1, Immediate_kv3_v1_signed37)
			     || chk_imm (KV3_2, Immediate_kv3_v2_signed37)
			     || chk_imm (KV4_1, Immediate_kv4_v1_signed37))
		      {
			add_fixup (insn, BFD_RELOC_KVX_S37_LO10, exp);

			insn->immx0 = immxcnt;
			add_immx (insn, 0, BFD_RELOC_KVX_S37_UP27, exp, 1, 1);

			immx_ready = 1;
		      }
		    else if (chk_imm (KV3_1, Immediate_kv3_v1_signed43)
			     || chk_imm (KV3_2, Immediate_kv3_v2_signed43)
			     || chk_imm (KV4_1, Immediate_kv4_v1_signed43))
		      {
			add_fixup (insn, BFD_RELOC_KVX_S43_LO10, exp);
			add_fixup (insn, BFD_RELOC_KVX_S43_EX6, exp);

			insn->immx0 = immxcnt;
			add_immx (insn, insn->words[1],
				  BFD_RELOC_KVX_S43_UP27, exp, 1, 1);

			immx_ready = 1;
		      }
		    else if (chk_imm (KV3_1, Immediate_kv3_v1_wrapped64)
			     || chk_imm (KV3_2, Immediate_kv3_v2_wrapped64)
			     || chk_imm (KV4_1, Immediate_kv4_v1_wrapped64))
		      {
			add_fixup (insn, BFD_RELOC_KVX_S64_LO10, exp);

			insn->immx0 = immxcnt;
			add_immx (insn, insn->words[1],
				  BFD_RELOC_KVX_S64_UP27, exp, 1, 1);

			insn->immx1 = immxcnt;
			add_immx (insn, insn->words[2],
				  BFD_RELOC_KVX_S64_EX27, exp, 1, 1);

			immx_ready = 1;
		      }
		    else
		      as_fatal ("don't know how to generate a fixup record");
		    return immx_ready;
		  }
		else
		  as_fatal ("No room for fixup ");
	      }
	  }
      }
      break;
    default:
      break;
    }

  for (int bf_idx = 0; bf_idx < bf_nb; bf_idx++)
    {
      uint64_t value =
	((uint64_t) op >> bfields[bf_idx].from_offset);
      int j = 0;
      int to_offset = bfields[bf_idx].to_offset;
      value &= (1LL << bfields[bf_idx].size) - 1;
      j = to_offset / 32;
      to_offset = to_offset % 32;
      insn->words[j] |= (value << to_offset) & 0xffffffff;
    }

  return immx_ready;

#undef chk_imm
#undef add_immx
#undef add_fixup
}

/*
 * Given a set of operands and a matching instruction,
 * assemble it
 *
 */
static void
assemble_insn (const struct kvxopc * opcode, struct token_list *tok, struct kvxinsn *insn)
{
  unsigned immx_ready = 0;

  memset (insn, 0, sizeof (*insn));
  insn->opdef = opcode;
  for (int i = 0; i < opcode->wordcount; i++)
    {
      insn->words[i] = opcode->codewords[i].opcode;
      insn->len += 1;
    }

  insn->immx0 = NOIMMX;
  insn->immx1 = NOIMMX;

  struct token_list *tok_ = tok;
  struct kvx_operand **format = (struct kvx_operand **) opcode->format;

  while (tok_)
    {
      int ret = insert_operand (insn, *format, tok_);
      immx_ready |= ret;
      while ((tok_ = tok_->next) && tok_->category == CAT_SEPARATOR);
      format++;
    }

  // Handle immx if insert_operand did not already take care of that
  if (!immx_ready)
    {
      for (int i = 0; i < opcode->wordcount; i++)
	{
	  if (opcode->codewords[i].flags & kvxOPCODE_FLAG_IMMX0)
	    {
	      insn->immx0 = immxcnt;
	      immxbuf[immxcnt].words[0] = insn->words[i];
	      immxbuf[immxcnt].nfixups = 0;
	      immxbuf[immxcnt].len = 1;
	      insn->len -= 1;
	      incr_immxcnt ();
	    }
	  if (opcode->codewords[i].flags & kvxOPCODE_FLAG_IMMX1)
	    {
	      insn->immx1 = immxcnt;
	      immxbuf[immxcnt].words[0] = insn->words[i];
	      immxbuf[immxcnt].nfixups = 0;
	      immxbuf[immxcnt].len = 1;
	      insn->len -= 1;
	      incr_immxcnt ();
	    }
	}
    }
}

/* Emit an instruction from the instruction array into the object
 * file. INSN points to an element of the instruction array. STOPFLAG
 * is true if this is the last instruction in the bundle.
 *
 * Only handles main syllables of bundle. Immediate extensions are
 * handled by insert_operand.
 */
static void
emit_insn (struct kvxinsn * insn, int insn_pos, int stopflag)
{
  char *f;
  unsigned int image;

  /* if we are listing, attach frag to previous line.  */
  if (listing)
    listing_prev_line ();

  /* Update text size for lane parity checking.  */
  set_byte_counter (now_seg, (get_byte_counter (now_seg) + (insn->len * 4)));

  /* allocate space in the fragment.  */
  f = frag_more (insn->len * 4);

  /* spit out bits.  */
  for (int i = 0; i < insn->len; i++)
    {
      image = insn->words[i];

      /* Handle bundle parallel bit. */ ;
      if ((i == insn->len - 1) && stopflag)
	image &= ~PARALLEL_BIT;
      else
	image |= PARALLEL_BIT;

      /* Emit the instruction image. */
      md_number_to_chars (f + (i * 4), image, 4);
    }

  /* generate fixup records */

  for (int i = 0; i < insn->nfixups; i++)
    {
      int size, pcrel;
      reloc_howto_type *reloc_howto =
	bfd_reloc_type_lookup (stdoutput, insn->fixup[i].reloc);
      assert (reloc_howto);
      size = bfd_get_reloc_size (reloc_howto);
      pcrel = reloc_howto->pc_relative;

      /* In case the PCREL relocation is not for the first insn in the
         bundle, we have to offset it.  The pc used by the hardware
         references a bundle and not separate insn.
       */
      assert (!(insn_pos == -1 && pcrel));
      if (pcrel && insn_pos > 0)
	insn->fixup[i].exp.X_add_number += insn_pos * 4;

      fixS *fixup = fix_new_exp (frag_now,
				 f - frag_now->fr_literal +
				 insn->fixup[i].where,
				 size,
				 &(insn->fixup[i].exp),
				 pcrel,
				 insn->fixup[i].reloc);
      /*
       * Set this bit so that large value can still be
       * handled. Without it, assembler will fail in fixup_segment
       * when it checks there is enough bits to store the value. As we
       * usually split our reloc across different words, it may think
       * that 4 bytes are not enough for large value. This simply
       * skips the tests
       */
      fixup->fx_no_overflow = 1;
    }
}


/* Called for any expression that can not be recognized.  When the
 * function is called, `input_line_pointer' will point to the start of
 * the expression.  */
/* FIXME: Should be done by the parser */
void
md_operand (expressionS * e)
{
  /* enum pseudo_type pseudo_type; */
  /* char *name = NULL; */
  size_t len;
  int ch, i;

  switch (*input_line_pointer)
    {
    case '@':
      /* Find what relocation pseudo-function we're dealing with. */
      /* pseudo_type = 0; */
      ch = *++input_line_pointer;
      for (i = 0; i < kvx_core_info->nb_pseudo_funcs; ++i)
	if (kvx_core_info->pseudo_funcs[i].name && kvx_core_info->pseudo_funcs[i].name[0] == ch)
	  {
	    len = strlen (kvx_core_info->pseudo_funcs[i].name);
	    if (strncmp (kvx_core_info->pseudo_funcs[i].name + 1,
			 input_line_pointer + 1, len - 1) == 0
		&& !is_part_of_name (input_line_pointer[len]))
	      {
		input_line_pointer += len;
		break;
	      }
	  }
      SKIP_WHITESPACE ();
      if (*input_line_pointer != '(')
	{
	  as_bad ("Expected '('");
	  goto err;
	}
      /* Skip '('.  */
      ++input_line_pointer;
      if (!kvx_core_info->pseudo_funcs[i].pseudo_relocs.has_no_arg)
	expression (e);
      if (*input_line_pointer++ != ')')
	{
	  as_bad ("Missing ')'");
	  goto err;
	}
      if (!kvx_core_info->pseudo_funcs[i].pseudo_relocs.has_no_arg)
	{
	  if (e->X_op != O_symbol)
	    as_fatal ("Illegal combination of relocation functions");
	}
      /* Make sure gas doesn't get rid of local symbols that are used
         in relocs.  */
      e->X_op = O_pseudo_fixup;
      e->X_op_symbol = kvx_core_info->pseudo_funcs[i].sym;
      break;

    default:
      break;
    }
  return;

err:
  ignore_rest_of_line ();
}

/*
 * Return the Bundling type for an insn.
 */
static int
find_bundling (const struct kvxinsn * insn)
{
  return insn->opdef->bundling;
}

static int
find_reservation (const struct kvxinsn * insn)
{
  return insn->opdef->reservation;
}

static struct kvxopc *
assemble_tokens (struct token_list *tok_list)
{
  assert (tok_list != NULL);
  struct token_list *toks = tok_list;

  /* make sure there is room in instruction buffer */
  /* Was KVXMAXBUNDLEISSUE, changed because of NOPs */
  if (insncnt >= KVXMAXBUNDLEWORDS)
    as_fatal ("[assemble_tokens]: too many instructions in bundle.");

  /* TODO: Merge */
  struct kvxinsn *insn;
  insn = insbuf + insncnt;

  /* The formats table registers the modifier into the opcode, therefore we need
     to fuse both before looking up the opcodes hashtable.  */
  char *opcode = NULL;

  opcode = toks->tok;
  toks = toks->next;

  while (toks && toks->category == CAT_SEPARATOR)
    toks = toks->next;

  /* Find the format requested by the instruction.  */
  struct kvxopc *format_tbl = str_hash_find (env.opcode_hash, opcode);
  struct kvxopc *format = NULL;

  struct token_list *toks_ = toks;

  while (!format && format_tbl && STREQ (opcode, format_tbl->as_op))
  {
    for (int i = 0 ; toks_ && format_tbl->format[i]
	&& toks_->class_id == format_tbl->format[i]->type ;)
      {
	toks_ = toks_->next;
	while (toks_ && toks_->category == CAT_SEPARATOR)
	  toks_ = toks_->next;
	i += 1;
      }

    if (!toks_)
      format = format_tbl;
    else
      {
	toks_ = toks;
	format_tbl++;
      }
  }

  assert (format != NULL);

  assemble_insn (format, toks, insn);
  insncnt++;

  return NULL;
}

/*
 * Write in buf at most buf_size.
 * Returns the number of writen characters.
 */
static int ATTRIBUTE_UNUSED
insn_syntax (struct kvxopc * op, char *buf, int buf_size)
{
  int chars = snprintf (buf, buf_size, "%s ", op->as_op);
  const char *fmtp = op->fmtstring;
  char ch = 0;

  for (int i = 0; op->format[i]; i++)
    {
      int type = op->format[i]->type;
      const char *type_name = TOKEN_NAME (type);
      int offset = 0;

      for (int j = 0 ; type_name[j] ; ++j)
	if (type_name[j] == '_')
	  offset = j + 1;

      /* Print characters in the format string up to the following * % or nul. */
      while ((chars < buf_size) && (ch = *fmtp) && ch != '%')
	{
	  buf[chars++] = ch;
	  fmtp++;
	}

      /* Skip past %s */
      if (ch == '%')
	{
	  ch = *fmtp++;
	  fmtp++;
	}

      chars += snprintf (&buf[chars], buf_size - chars, "%s", type_name + offset);
    }

  /* Print trailing characters in the format string, if any */
  while ((chars < buf_size) && (ch = *fmtp))
    {
      buf[chars++] = ch;
      fmtp++;
    }

  if (chars < buf_size)
    buf[chars++] = '\0';
  else
    buf[buf_size - 1] = '\0';

  return chars;
}

#define ASM_CHARS_MAX (71)

static void
kvx_print_insn (struct kvxopc * op ATTRIBUTE_UNUSED)
{
  char asm_str[ASM_CHARS_MAX];
  int chars = insn_syntax (op, asm_str, ASM_CHARS_MAX);
  const char *insn_type = "UNKNOWN";
  const char *insn_mode = "";

  for (int i = chars - 1; i < ASM_CHARS_MAX - 1; i++)
    asm_str[i] = '-';

  /* This is a hack which works because the Bundling is the same for all cores
     for now.  */
  switch ((int) op->bundling)
    {
    case Bundling_kv3_v1_ALL:
      insn_type = "ALL  ";
      break;
    case Bundling_kv3_v1_BCU:
      insn_type = "BCU  ";
      break;
    case Bundling_kv3_v1_TCA:
      insn_type = "TCA  ";
      break;
    case Bundling_kv3_v1_FULL:
    case Bundling_kv3_v1_FULL_X:
    case Bundling_kv3_v1_FULL_Y:
      insn_type = "FULL ";
      break;
    case Bundling_kv3_v1_LITE:
    case Bundling_kv3_v1_LITE_X:
    case Bundling_kv3_v1_LITE_Y:
      insn_type = "LITE ";
      break;
    case Bundling_kv3_v1_TINY:
    case Bundling_kv3_v1_TINY_X:
    case Bundling_kv3_v1_TINY_Y:
      insn_type = "TINY ";
      break;
    case Bundling_kv3_v1_MAU:
    case Bundling_kv3_v1_MAU_X:
    case Bundling_kv3_v1_MAU_Y:
      insn_type = "MAU  ";
      break;
    case Bundling_kv3_v1_LSU:
    case Bundling_kv3_v1_LSU_X:
    case Bundling_kv3_v1_LSU_Y:
      insn_type = "LSU  ";
      break;
    case Bundling_kv3_v1_NOP:
      insn_type = "NOP  ";
      break;
    default:
      as_fatal ("Unhandled Bundling class %d", op->bundling);
    }

  if (op->codewords[0].flags & kvxOPCODE_FLAG_MODE64
      && op->codewords[0].flags & kvxOPCODE_FLAG_MODE32)
    insn_mode = "32 and 64";
  else if (op->codewords[0].flags & kvxOPCODE_FLAG_MODE64)
    insn_mode = "64";
  else if (op->codewords[0].flags & kvxOPCODE_FLAG_MODE32)
    insn_mode = "32";
  else
    as_fatal ("Unknown instruction mode.");

  printf ("%s | syllables: %d | type: %s | mode: %s bits\n", asm_str,
	  op->wordcount, insn_type, insn_mode);
}

/* Comparison function compatible with qsort.  This is used to sort the issues
   into the right order.  */
static int
kvxinsn_compare (const void *a, const void *b)
{
  struct kvxinsn *kvxinsn_a = *(struct kvxinsn **) a;
  struct kvxinsn *kvxinsn_b = *(struct kvxinsn **) b;
  int bundling_a = find_bundling (kvxinsn_a);
  int bundling_b = find_bundling (kvxinsn_b);
  int order_a = kvxinsn_a->order;
  int order_b = kvxinsn_b->order;
  if (bundling_a != bundling_b)
    return (bundling_b < bundling_a) - (bundling_a < bundling_b);
  return (order_b < order_a) - (order_a < order_b);
}

static void
kvx_reorder_bundle (struct kvxinsn *bundle_insn[], int bundle_insncnt)
{
  enum
  { EXU_BCU, EXU_TCA, EXU_ALU0, EXU_ALU1, EXU_MAU, EXU_LSU, EXU__ };
  struct kvxinsn *issued[EXU__];
  int tag, exu;

  memset (issued, 0, sizeof (issued));
  for (int i = 0; i < bundle_insncnt; i++)
    {
      struct kvxinsn *kvxinsn = bundle_insn[i];
      tag = -1, exu = -1;
      /* This is a hack. It works because all the Bundling are the same for all
         cores for now.  */
      switch ((int) find_bundling (kvxinsn))
	{
	case Bundling_kv3_v1_ALL:
	  if (bundle_insncnt > 1)
	    as_fatal ("Too many ops in a single op bundle");
	  issued[0] = kvxinsn;
	  break;
	case Bundling_kv3_v1_BCU:
	  if (!issued[EXU_BCU])
	    issued[EXU_BCU] = kvxinsn;
	  else
	    as_fatal ("More than one BCU instruction in bundle");
	  break;
	case Bundling_kv3_v1_TCA:
	  if (!issued[EXU_TCA])
	    issued[EXU_TCA] = kvxinsn;
	  else
	    as_fatal ("More than one TCA instruction in bundle");
	  break;
	case Bundling_kv3_v1_FULL:
	case Bundling_kv3_v1_FULL_X:
	case Bundling_kv3_v1_FULL_Y:
	  if (!issued[EXU_ALU0])
	    {
	      issued[EXU_ALU0] = kvxinsn;
	      tag = Modifier_kv3_v1_exunum_ALU0;
	      exu = EXU_ALU0;
	    }
	  else
	    as_fatal ("More than one ALU FULL instruction in bundle");
	  break;
	case Bundling_kv3_v1_LITE:
	case Bundling_kv3_v1_LITE_X:
	case Bundling_kv3_v1_LITE_Y:
	  if (!issued[EXU_ALU0])
	    {
	      issued[EXU_ALU0] = kvxinsn;
	      tag = Modifier_kv3_v1_exunum_ALU0;
	      exu = EXU_ALU0;
	    }
	  else if (!issued[EXU_ALU1])
	    {
	      issued[EXU_ALU1] = kvxinsn;
	      tag = Modifier_kv3_v1_exunum_ALU1;
	      exu = EXU_ALU1;
	    }
	  else
	    as_fatal ("Too many ALU FULL or LITE instructions in bundle");
	  break;
	case Bundling_kv3_v1_MAU:
	case Bundling_kv3_v1_MAU_X:
	case Bundling_kv3_v1_MAU_Y:
	  if (!issued[EXU_MAU])
	    {
	      issued[EXU_MAU] = kvxinsn;
	      tag = Modifier_kv3_v1_exunum_MAU;
	      exu = EXU_MAU;
	    }
	  else
	    as_fatal ("More than one MAU instruction in bundle");
	  break;
	case Bundling_kv3_v1_LSU:
	case Bundling_kv3_v1_LSU_X:
	case Bundling_kv3_v1_LSU_Y:
	  if (!issued[EXU_LSU])
	    {
	      issued[EXU_LSU] = kvxinsn;
	      tag = Modifier_kv3_v1_exunum_LSU;
	      exu = EXU_LSU;
	    }
	  else
	    as_fatal ("More than one LSU instruction in bundle");
	  break;
	case Bundling_kv3_v1_TINY:
	case Bundling_kv3_v1_TINY_X:
	case Bundling_kv3_v1_TINY_Y:
	case Bundling_kv3_v1_NOP:
	  if (!issued[EXU_ALU0])
	    {
	      issued[EXU_ALU0] = kvxinsn;
	      tag = Modifier_kv3_v1_exunum_ALU0;
	      exu = EXU_ALU0;
	    }
	  else if (!issued[EXU_ALU1])
	    {
	      issued[EXU_ALU1] = kvxinsn;
	      tag = Modifier_kv3_v1_exunum_ALU1;
	      exu = EXU_ALU1;
	    }
	  else if (!issued[EXU_MAU])
	    {
	      issued[EXU_MAU] = kvxinsn;
	      tag = Modifier_kv3_v1_exunum_MAU;
	      exu = EXU_MAU;
	    }
	  else if (!issued[EXU_LSU])
	    {
	      issued[EXU_LSU] = kvxinsn;
	      tag = Modifier_kv3_v1_exunum_LSU;
	      exu = EXU_LSU;
	    }
	  else
	    as_fatal ("Too many ALU instructions in bundle");
	  break;
	default:
	  as_fatal ("Unhandled Bundling class %d", find_bundling (kvxinsn));
	}
      if (tag >= 0)
	{
	  if (issued[exu]->immx0 != NOIMMX)
	    immxbuf[issued[exu]->immx0].words[0] |= (tag << 27);
	  if (issued[exu]->immx1 != NOIMMX)
	    immxbuf[issued[exu]->immx1].words[0] |= (tag << 27);
	}
    }

  int i;
  for (i = 0, exu = 0; exu < EXU__; exu++)
    {
      if (issued[exu])
	bundle_insn[i++] = issued[exu];
    }
  if (i != bundle_insncnt)
    as_fatal ("Mismatch between bundle and issued instructions");
}

static void
kvx_check_resource_usage (struct kvxinsn **bundle_insn, int bundle_insncnt)
{
  const int reservation_table_len =
    (kvx_core_info->reservation_table_lines * kvx_core_info->resource_max);
  const int *resources = kvx_core_info->resources;
  int *resources_used =
    malloc (reservation_table_len * sizeof (int));
  memset (resources_used, 0, reservation_table_len * sizeof (int));

  for (int i = 0; i < bundle_insncnt; i++)
  {
    int insn_reservation = find_reservation (bundle_insn[i]);
    int reservation = insn_reservation & 0xff;
    const int *reservation_table = kvx_core_info->reservation_table_table[reservation];
    for (int j = 0; j < reservation_table_len; j++)
      resources_used[j] += reservation_table[j];
  }

  for (int i = 0; i < kvx_core_info->reservation_table_lines; i++)
    {
      for (int j = 0; j < kvx_core_info->resource_max; j++)
	if (resources_used[(i * kvx_core_info->resource_max) + j] > resources[j])
	  {
	    int v = resources_used[(i * kvx_core_info->resource_max) + j];
	    free (resources_used);
	    as_fatal ("Resource %s over-used in bundle: %d used, %d available",
		kvx_core_info->resource_names[j], v, resources[j]);
	  }
  }
  free (resources_used);
}

/*
 * Called by core to assemble a single line
 */
void
md_assemble (char *line)
{
  char *line_cursor = line;

  if (get_byte_counter (now_seg) & 3)
    as_fatal ("code segment not word aligned in md_assemble");

  while (line_cursor && line_cursor[0] && (line_cursor[0] == ' '))
    line_cursor++;

  /* ;; was converted to "be" by line hook          */
  /* here we look for the bundle end                */
  /* and actually output any instructions in bundle */
  /* also we need to implement the stop bit         */
  /* check for bundle end */
  if (strncmp (line_cursor, "be", 2) == 0)
  {
    inside_bundle = 0;
    //int sec_align = bfd_get_section_alignment(stdoutput, now_seg);
    /* Was KVXMAXBUNDLEISSUE, changed because of NOPs */
    struct kvxinsn *bundle_insn[KVXMAXBUNDLEWORDS];
    int bundle_insncnt = 0;
    int syllables = 0;
    int entry;

#ifdef OBJ_ELF
    /* Emit Dwarf debug line information */
    dwarf2_emit_insn (0);
#endif
    for (int j = 0; j < insncnt; j++)
    {
      insbuf[j].order = j;
      bundle_insn[bundle_insncnt++] = &insbuf[j];
      syllables += insbuf[j].len;
    }

    if (syllables + immxcnt > KVXMAXBUNDLEWORDS)
      as_fatal ("Bundle has too many syllables : %d instead of %d",
	  syllables + immxcnt, KVXMAXBUNDLEWORDS);

    if (env.opts.check_resource_usage)
      kvx_check_resource_usage (bundle_insn, bundle_insncnt);

    /* Reorder and check the bundle.  */
    if (!env.opts.generate_illegal_code)
    {
      /* Sort the bundle_insn in order of bundling. */
      qsort (bundle_insn, bundle_insncnt, sizeof (struct kvxinsn *), kvxinsn_compare);

      kvx_reorder_bundle (bundle_insn, bundle_insncnt);
    }

    /* The ordering of the insns has been set correctly in bundle_insn. */
    for (int i = 0; i < bundle_insncnt; i++)
    {
      emit_insn (bundle_insn[i], i, (i == bundle_insncnt + immxcnt - 1));
      bundle_insn[i]->written = 1;
    }

    // Emit immx, ordering them by EXU tags, 0 to 3
    entry = 0;
    for (int tag = 0; tag < 4; tag++)
    {
      for (int j = 0; j < immxcnt; j++)
      {
#define kv3_exunum2_fld(x) (int)(((unsigned int)(x) >> 27) & 0x3)
	if (kv3_exunum2_fld (immxbuf[j].words[0]) == tag)
	{
	  assert (immxbuf[j].written == 0);
	  int insn_pos = bundle_insncnt + entry;
	  emit_insn (&(immxbuf[j]), insn_pos, entry == immxcnt - 1);
	  immxbuf[j].written = 1;
	  entry++;
	}
#undef kv3_exunum2_fld
      }
    }
    if (entry != immxcnt)
      as_fatal ("%d IMMX produced, only %d emitted.", immxcnt, entry);

    /* The debug label that appear in the middle of bundles
       had better appear to be attached to the next
       bundle. This is because usually these labels point to
       the first instruction where some condition is met. If
       the label isn't handled this way it will be attached to
       the current bundle which is wrong as the corresponding
       instruction wasn't executed yet. */
    while (label_fixes)
    {
      struct label_fix *fix = label_fixes;

      label_fixes = fix->next;
      symbol_set_value_now (fix->sym);
      free (fix);
    }

    insncnt = 0;
    immxcnt = 0;
    memset (immxbuf, 0, sizeof (immxbuf));

    return;
  }

    char *buf = NULL;
    sscanf (line_cursor, "%m[^\n]", &buf);
    struct token_s my_tok = { .insn = buf, .begin = 0, .end = 0, .class_id = -1 , .val = 0 };
    struct token_list *tok_lst = parse (my_tok);
    free (buf);

    if (!tok_lst)
      return;

    /* Skip opcode */
    line_cursor += strlen (tok_lst->tok);

  assembling_insn = true;

  inside_bundle = 1;
  assemble_tokens (tok_lst);
  free_token_list (tok_lst);
  assembling_insn = false;
}

static void
kvx_set_cpu (void)
{
  if (!kvx_core_info)
    kvx_core_info = &kvx_kv3_v1_core_info;

  if (!kvx_registers)
    kvx_registers = kvx_kv3_v1_registers;

  if (!kvx_regfiles)
    kvx_regfiles = kvx_kv3_v1_regfiles;

  if (!kvx_modifiers)
    kvx_modifiers = kvx_kv3_v1_modifiers;

  if (env.params.core == -1)
      env.params.core = kvx_core_info->elf_core;

  int kvx_bfd_mach;
  print_insn = kvx_print_insn;

  switch (kvx_core_info->elf_core)
    {
    case ELF_KVX_CORE_KV3_1:
      kvx_bfd_mach = env.params.arch_size == 32 ? bfd_mach_kv3_1 : bfd_mach_kv3_1_64;
      setup (ELF_KVX_CORE_KV3_1);
      break;
    case ELF_KVX_CORE_KV3_2:
      kvx_bfd_mach = env.params.arch_size == 32 ? bfd_mach_kv3_2 : bfd_mach_kv3_2_64;
      setup (ELF_KVX_CORE_KV3_2);
      break;
    case ELF_KVX_CORE_KV4_1:
      kvx_bfd_mach = env.params.arch_size == 32 ? bfd_mach_kv4_1 : bfd_mach_kv4_1_64;
      setup (ELF_KVX_CORE_KV4_1);
      break;
    default:
      as_fatal ("Unknown elf core: 0x%x", kvx_core_info->elf_core);
    }

  if (!bfd_set_arch_mach (stdoutput, TARGET_ARCH, kvx_bfd_mach))
    as_warn (_("could not set architecture and machine"));
}

static int
kvxop_compar (const void *a, const void *b)
{
  const struct kvxopc *opa = (const struct kvxopc *) a;
  const struct kvxopc *opb = (const struct kvxopc *) b;
  int res = strcmp (opa->as_op, opb->as_op);

  if (res)
    return res;
  else
    {
      for (int i = 0; opa->format[i] && opb->format[i]; ++i)
	if (opa->format[i]->width != opb->format[i]->width)
	  return opa->format[i]->width - opb->format[i]->width;
      return 0;
    }
}

/***************************************************/
/*    INITIALIZE ASSEMBLER                         */
/***************************************************/

static int
print_hash (void **slot, void *arg ATTRIBUTE_UNUSED)
{
  string_tuple_t *tuple = *((string_tuple_t **) slot);
  printf ("%s\n", tuple->key);
  return 1;
}

static void
declare_register (const char *name, int number)
{
  symbolS *regS = symbol_create (name, reg_section,
				 &zero_address_frag, number);

  if (str_hash_insert (env.reg_hash, S_GET_NAME (regS), regS, 0) != NULL)
    as_fatal (_("duplicate %s"), name);
}

void
md_begin ()
{
  kvx_set_cpu ();

  /*
   * Declare register names with symbols
   */

  env.reg_hash = str_htab_create ();

  for (int i = 0; i < kvx_regfiles[KVX_REGFILE_REGISTERS]; i++)
    declare_register (kvx_registers[i].name, kvx_registers[i].id);

  /* Sort optab, so that identical mnemonics appear consecutively */
  {
    int nel;
    for (nel = 0; !STREQ ("", kvx_core_info->optab[nel].as_op); nel++)
      ;
    qsort (kvx_core_info->optab, nel, sizeof (kvx_core_info->optab[0]),
	   kvxop_compar);
  }

  /* The '?' is an operand separator */
  lex_type['?'] = 0;

  /* Create the opcode hash table      */
  /* Each name should appear only once */

  env.opcode_hash = str_htab_create ();
  env.reloc_hash = str_htab_create ();

  {
    struct kvxopc *op;
    const char *name = 0;
    for (op = kvx_core_info->optab; !(STREQ ("", op->as_op)); op++)
      {
	/* enter in hash table if this is a new name */
	if (!(STREQ (name, op->as_op)))
	  {
	    name = op->as_op;
	    if (str_hash_insert (env.opcode_hash, name, op, 0))
	      as_fatal ("internal error: can't hash opcode `%s'", name);
	  }


	for (int i = 0 ; op->format[i] ; ++i)
	  {
	    const char *reloc_name = TOKEN_NAME (op->format[i]->type);
	    void *relocs = op->format[i]->relocs;
	    if (op->format[i]->relocs[0] != 0
		&& !str_hash_find (env.reloc_hash, reloc_name))
	      if (str_hash_insert (env.reloc_hash, reloc_name, relocs, 0))
		  as_fatal ("internal error: can't hash type `%s'", reloc_name);
	  }
      }
  }

  if (env.opts.dump_table)
    {
      htab_traverse (env.opcode_hash, print_hash, NULL);
      exit (0);
    }

  if (env.opts.dump_insn)
    {
      for (struct kvxopc *op = kvx_core_info->optab; !(STREQ ("", op->as_op)); op++)
	print_insn (op);
      exit (0);
    }

  /* Here we enforce the minimum section alignment.  Remember, in
   * the linker we can make the boudaries between the linked sections
   * on larger boundaries.  The text segment is aligned to long words
   * because of the odd/even constraint on immediate extensions
   */

  bfd_set_section_alignment (text_section, 3);	/* -- 8 bytes */
  bfd_set_section_alignment (data_section, 2);	/* -- 4 bytes */
  bfd_set_section_alignment (bss_section, 2);	/* -- 4 bytes */
  subseg_set (text_section, 0);

  symbolS *gotoff_sym   = symbol_create (".<gotoff>",   undefined_section, &zero_address_frag, 0);
  symbolS *got_sym      = symbol_create (".<got>",      undefined_section, &zero_address_frag, 0);
  symbolS *plt_sym      = symbol_create (".<plt>",      undefined_section, &zero_address_frag, 0);
  symbolS *tlsgd_sym    = symbol_create (".<tlsgd>",    undefined_section, &zero_address_frag, 0);
  symbolS *tlsie_sym    = symbol_create (".<tlsie>",    undefined_section, &zero_address_frag, 0);
  symbolS *tlsle_sym    = symbol_create (".<tlsle>",    undefined_section, &zero_address_frag, 0);
  symbolS *tlsld_sym    = symbol_create (".<tlsld>",    undefined_section, &zero_address_frag, 0);
  symbolS *dtpoff_sym   = symbol_create (".<dtpoff>",   undefined_section, &zero_address_frag, 0);
  symbolS *plt64_sym    = symbol_create (".<plt64>",    undefined_section, &zero_address_frag, 0);
  symbolS *gotaddr_sym  = symbol_create (".<gotaddr>",  undefined_section, &zero_address_frag, 0);
  symbolS *pcrel16_sym  = symbol_create (".<pcrel16>",  undefined_section, &zero_address_frag, 0);
  symbolS *pcrel_sym    = symbol_create (".<pcrel>",    undefined_section, &zero_address_frag, 0);
  symbolS *signed32_sym = symbol_create (".<signed32>", undefined_section, &zero_address_frag, 0);

  for (int i = 0; i < kvx_core_info->nb_pseudo_funcs; ++i)
    {
      symbolS *sym;
      if (!strcmp (kvx_core_info->pseudo_funcs[i].name, "gotoff"))
	sym = gotoff_sym;
      else if (!strcmp (kvx_core_info->pseudo_funcs[i].name, "got"))
	sym = got_sym;
      else if (!strcmp (kvx_core_info->pseudo_funcs[i].name, "plt"))
	sym = plt_sym;
      else if (!strcmp (kvx_core_info->pseudo_funcs[i].name, "tlsgd"))
	sym = tlsgd_sym;
      else if (!strcmp (kvx_core_info->pseudo_funcs[i].name, "tlsle"))
	sym = tlsle_sym;
      else if (!strcmp (kvx_core_info->pseudo_funcs[i].name, "tlsld"))
	sym = tlsld_sym;
      else if (!strcmp (kvx_core_info->pseudo_funcs[i].name, "dtpoff"))
	sym = dtpoff_sym;
      else if (!strcmp (kvx_core_info->pseudo_funcs[i].name, "tlsie"))
	sym = tlsie_sym;
      else if (!strcmp (kvx_core_info->pseudo_funcs[i].name, "plt64"))
	sym = plt64_sym;
      else if (!strcmp (kvx_core_info->pseudo_funcs[i].name, "pcrel16"))
	sym = pcrel16_sym;
      else if (!strcmp (kvx_core_info->pseudo_funcs[i].name, "pcrel"))
	sym = pcrel_sym;
      else if (!strcmp (kvx_core_info->pseudo_funcs[i].name, "gotaddr"))
	sym = gotaddr_sym;
      else if (!strcmp (kvx_core_info->pseudo_funcs[i].name, "signed32"))
	sym = signed32_sym;
      else
	as_fatal ("internal error: Unknown pseudo func `%s'",
	    kvx_core_info->pseudo_funcs[i].name);

      kvx_core_info->pseudo_funcs[i].sym = sym;
    }
}

/***************************************************/
/*          ASSEMBLER CLEANUP STUFF                */
/***************************************************/

/* Return non-zero if the indicated VALUE has overflowed the maximum
   range expressible by a signed number with the indicated number of
   BITS.

   This is from tc-aarch64.c
*/

static bfd_boolean
signed_overflow (offsetT value, unsigned bits)
{
  offsetT lim;
  if (bits >= sizeof (offsetT) * 8)
    return FALSE;
  lim = (offsetT) 1 << (bits - 1);
  return (value < -lim || value >= lim);
}

/***************************************************/
/*          ASSEMBLER FIXUP STUFF                  */
/***************************************************/

void
md_apply_fix (fixS * fixP, valueT * valueP, segT segmentP ATTRIBUTE_UNUSED)
{
  char *const fixpos = fixP->fx_frag->fr_literal + fixP->fx_where;
  valueT value = *valueP;
  valueT image;
  arelent *rel;

  rel = (arelent *) xmalloc (sizeof (arelent));

  rel->howto = bfd_reloc_type_lookup (stdoutput, fixP->fx_r_type);
  if (rel->howto == NULL)
    {
      as_fatal
	("[md_apply_fix] unsupported relocation type (can't find howto)");
    }

  /* Note whether this will delete the relocation.  */
  if (fixP->fx_addsy == NULL && fixP->fx_pcrel == 0)
    fixP->fx_done = 1;

  if (fixP->fx_size > 0)
    image = md_chars_to_number (fixpos, fixP->fx_size);
  else
    image = 0;
  if (fixP->fx_addsy != NULL)
    {
      switch (fixP->fx_r_type)
	{
	case BFD_RELOC_KVX_S37_TLS_LE_UP27:
	case BFD_RELOC_KVX_S37_TLS_LE_LO10:

	case BFD_RELOC_KVX_S43_TLS_LE_EX6:
	case BFD_RELOC_KVX_S43_TLS_LE_UP27:
	case BFD_RELOC_KVX_S43_TLS_LE_LO10:

	case BFD_RELOC_KVX_S37_TLS_GD_LO10:
	case BFD_RELOC_KVX_S37_TLS_GD_UP27:

	case BFD_RELOC_KVX_S43_TLS_GD_LO10:
	case BFD_RELOC_KVX_S43_TLS_GD_UP27:
	case BFD_RELOC_KVX_S43_TLS_GD_EX6:

	case BFD_RELOC_KVX_S37_TLS_IE_LO10:
	case BFD_RELOC_KVX_S37_TLS_IE_UP27:

	case BFD_RELOC_KVX_S43_TLS_IE_LO10:
	case BFD_RELOC_KVX_S43_TLS_IE_UP27:
	case BFD_RELOC_KVX_S43_TLS_IE_EX6:

	case BFD_RELOC_KVX_S37_TLS_LD_LO10:
	case BFD_RELOC_KVX_S37_TLS_LD_UP27:

	case BFD_RELOC_KVX_S43_TLS_LD_LO10:
	case BFD_RELOC_KVX_S43_TLS_LD_UP27:
	case BFD_RELOC_KVX_S43_TLS_LD_EX6:

	  S_SET_THREAD_LOCAL (fixP->fx_addsy);
	  break;
	default:
	  break;
	}
    }

  /* If relocation has been marked for deletion, apply remaining changes */
  if (fixP->fx_done)
    {
      switch (fixP->fx_r_type)
	{
	case BFD_RELOC_8:
	case BFD_RELOC_16:
	case BFD_RELOC_32:
	case BFD_RELOC_64:

	case BFD_RELOC_KVX_GLOB_DAT:
	case BFD_RELOC_KVX_32_GOT:
	case BFD_RELOC_KVX_64_GOT:
	case BFD_RELOC_KVX_64_GOTOFF:
	case BFD_RELOC_KVX_32_GOTOFF:
	  image = value;
	  md_number_to_chars (fixpos, image, fixP->fx_size);
	  break;

	case BFD_RELOC_KVX_PCREL17:
	  if (signed_overflow (value, 17 + 2))
	    as_bad_where (fixP->fx_file, fixP->fx_line,
			  _("branch out of range"));
	  goto pcrel_common;

	case BFD_RELOC_KVX_PCREL27:
	  if (signed_overflow (value, 27 + 2))
	    as_bad_where (fixP->fx_file, fixP->fx_line,
			  _("branch out of range"));
	  goto pcrel_common;

	case BFD_RELOC_KVX_S16_PCREL:
	  if (signed_overflow (value, 16))
	    as_bad_where (fixP->fx_file, fixP->fx_line,
			  _("signed16 PCREL value out of range"));
	  goto pcrel_common;

	case BFD_RELOC_KVX_S43_PCREL_LO10:
	case BFD_RELOC_KVX_S43_PCREL_UP27:
	case BFD_RELOC_KVX_S43_PCREL_EX6:
	  if (signed_overflow (value, 10 + 27 + 6))
	    as_bad_where (fixP->fx_file, fixP->fx_line,
			  _("signed43 PCREL value out of range"));
	  goto pcrel_common;

	case BFD_RELOC_KVX_S37_PCREL_LO10:
	case BFD_RELOC_KVX_S37_PCREL_UP27:
	  if (signed_overflow (value, 10 + 27))
	    as_bad_where (fixP->fx_file, fixP->fx_line,
			  _("signed37 PCREL value out of range"));
	  goto pcrel_common;

	case BFD_RELOC_KVX_S64_PCREL_LO10:
	case BFD_RELOC_KVX_S64_PCREL_UP27:
	case BFD_RELOC_KVX_S64_PCREL_EX27:

	pcrel_common:
	  if (fixP->fx_pcrel || fixP->fx_addsy)
	    return;
	  value =
	    (((value >> rel->howto->rightshift) << rel->howto->bitpos) & rel->
	     howto->dst_mask);
	  image = (image & ~(rel->howto->dst_mask)) | value;
	  md_number_to_chars (fixpos, image, fixP->fx_size);
	  break;

	case BFD_RELOC_KVX_S64_GOTADDR_LO10:
	case BFD_RELOC_KVX_S64_GOTADDR_UP27:
	case BFD_RELOC_KVX_S64_GOTADDR_EX27:

	case BFD_RELOC_KVX_S43_GOTADDR_LO10:
	case BFD_RELOC_KVX_S43_GOTADDR_UP27:
	case BFD_RELOC_KVX_S43_GOTADDR_EX6:

	case BFD_RELOC_KVX_S37_GOTADDR_LO10:
	case BFD_RELOC_KVX_S37_GOTADDR_UP27:
	  value = 0;
	  /* Fallthrough */

	case BFD_RELOC_KVX_S32_UP27:

	case BFD_RELOC_KVX_S37_UP27:

	case BFD_RELOC_KVX_S43_UP27:

	case BFD_RELOC_KVX_S64_UP27:
	case BFD_RELOC_KVX_S64_EX27:
	case BFD_RELOC_KVX_S64_LO10:

	case BFD_RELOC_KVX_S43_TLS_LE_UP27:
	case BFD_RELOC_KVX_S43_TLS_LE_EX6:

	case BFD_RELOC_KVX_S37_TLS_LE_UP27:

	case BFD_RELOC_KVX_S37_GOTOFF_UP27:

	case BFD_RELOC_KVX_S43_GOTOFF_UP27:
	case BFD_RELOC_KVX_S43_GOTOFF_EX6:

	case BFD_RELOC_KVX_S43_GOT_UP27:
	case BFD_RELOC_KVX_S43_GOT_EX6:

	case BFD_RELOC_KVX_S37_GOT_UP27:

	case BFD_RELOC_KVX_S32_LO5:
	case BFD_RELOC_KVX_S37_LO10:

	case BFD_RELOC_KVX_S43_LO10:
	case BFD_RELOC_KVX_S43_EX6:

	case BFD_RELOC_KVX_S43_TLS_LE_LO10:
	case BFD_RELOC_KVX_S37_TLS_LE_LO10:

	case BFD_RELOC_KVX_S37_GOTOFF_LO10:
	case BFD_RELOC_KVX_S43_GOTOFF_LO10:

	case BFD_RELOC_KVX_S43_GOT_LO10:
	case BFD_RELOC_KVX_S37_GOT_LO10:

	default:
	  as_fatal ("[md_apply_fix]:"
		    "unsupported relocation type (type not handled : %d)",
		    fixP->fx_r_type);
	}
    }
  xfree (rel);
}

/*
 * Warning: Can be called only in fixup_segment() after fx_addsy field
 * has been updated by calling symbol_get_value_expression(...->X_add_symbol)
 */
int
kvx_validate_sub_fix (fixS * fixP)
{
  segT add_symbol_segment, sub_symbol_segment;

  switch (fixP->fx_r_type)
    {
    case BFD_RELOC_8:
    case BFD_RELOC_16:
    case BFD_RELOC_32:
      if (fixP->fx_addsy != NULL)
	add_symbol_segment = S_GET_SEGMENT (fixP->fx_addsy);
      else
	return 0;
      if (fixP->fx_subsy != NULL)
	sub_symbol_segment = S_GET_SEGMENT (fixP->fx_subsy);
      else
	return 0;

      if ((strcmp (S_GET_NAME (fixP->fx_addsy),
		   S_GET_NAME (fixP->fx_subsy)) == 0) &&
	  (add_symbol_segment == sub_symbol_segment))
	return 1;
      break;
    default:
      break;
    }

  return 0;
}

/* This is called whenever some data item (not an instruction) needs a
 * fixup.  */
void
kvx_cons_fix_new (fragS * f, int where, int nbytes, expressionS * exp,
		  bfd_reloc_code_real_type code)
{
  if (exp->X_op == O_pseudo_fixup)
    {
      exp->X_op = O_symbol;
      struct pseudo_func *pf =
	kvx_get_pseudo_func_data_scn (exp->X_op_symbol);
      assert (pf != NULL);
      code = pf->pseudo_relocs.single;

      if (code == BFD_RELOC_UNUSED)
	as_fatal ("Unsupported relocation");
    }
  else
    {
      switch (nbytes)
	{
	case 1:
	  code = BFD_RELOC_8;
	  break;
	case 2:
	  code = BFD_RELOC_16;
	  break;
	case 4:
	  code = BFD_RELOC_32;
	  break;
	case 8:
	  code = BFD_RELOC_64;
	  break;
	default:
	  as_fatal ("unsupported BFD relocation size %u", nbytes);
	  break;
	}
    }
  fix_new_exp (f, where, nbytes, exp, 0, code);
}

/*
 * generate a relocation record
 */

arelent *
tc_gen_reloc (asection * sec ATTRIBUTE_UNUSED, fixS * fixp)
{
  arelent *reloc;
  bfd_reloc_code_real_type code;

  reloc = (arelent *) xmalloc (sizeof (arelent));

  reloc->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (asymbol *));
  *reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy);
  reloc->address = fixp->fx_frag->fr_address + fixp->fx_where;

  code = fixp->fx_r_type;
  if (code == BFD_RELOC_32 && fixp->fx_pcrel)
    code = BFD_RELOC_32_PCREL;
  reloc->howto = bfd_reloc_type_lookup (stdoutput, code);

  if (reloc->howto == NULL)
    {
      as_bad_where (fixp->fx_file, fixp->fx_line,
		    "cannot represent `%s' relocation in object file",
		    bfd_get_reloc_code_name (code));
      return NULL;
    }

//  if (!fixp->fx_pcrel != !reloc->howto->pc_relative)
//    {
//      as_fatal ("internal error? cannot generate `%s' relocation",
//		bfd_get_reloc_code_name (code));
//    }
//  assert (!fixp->fx_pcrel == !reloc->howto->pc_relative);

  reloc->addend = fixp->fx_offset;

  /*
   * Ohhh, this is ugly.  The problem is that if this is a local global
   * symbol, the relocation will entirely be performed at link time, not
   * at assembly time.  bfd_perform_reloc doesn't know about this sort
   * of thing, and as a result we need to fake it out here.
   */

  /* GD I'm not sure what this is used for in the kvx case but it sure  */
  /* messes up the relocs when emit_all_relocs is used as they are not */
  /* resolved with respect to a global sysmbol (e.g. .text), and hence */
  /* they are ALWAYS resolved at link time                             */
  /* FIXME FIXME                                                       */

  /* clarkes: 030827:  This code (and the other half of the fix in write.c)
   * have caused problems with the PIC relocations.
   * The root problem is that bfd_install_relocation adds in to the reloc
   * addend the section offset of a symbol defined in the current object.
   * This causes problems on numerous other targets too, and there are
   * several different methods used to get around it:
   *   1.  In tc_gen_reloc, subtract off the value that bfd_install_relocation
   *       added.  That is what we do here, and it is also done the
   *       same way for alpha.
   *   2.  In md_apply_fix, subtract off the value that bfd_install_relocation
   *       will add.  This is done on SH (non-ELF) and sparc targets.
   *   3.  In the howto structure for the relocations, specify a
   *       special function that does not return bfd_reloc_continue.
   *       This causes bfd_install_relocaion to terminate before it
   *       adds in the symbol offset.  This is done on SH ELF targets.
   *       Note that on ST200 we specify bfd_elf_generic_reloc as
   *       the special function.  This will return bfd_reloc_continue
   *       only in some circumstances, but in particular if the reloc
   *       is marked as partial_inplace in the bfd howto structure, then
   *       bfd_elf_generic_reloc will return bfd_reloc_continue.
   *       Some ST200 relocations are marked as partial_inplace
   *       (this is an error in my opinion because ST200 always uses
   *       a separate addend), but some are not.  The PIC relocations
   *       are not marked as partial_inplace, so for them,
   *       bfd_elf_generic_reloc returns bfd_reloc_ok, and the addend
   *       is not modified by bfd_install_relocation.   The relocations
   *       R_KVX_16 and R_KVX_32 are marked partial_inplace, and so for
   *       these we need to correct the addend.
   * In the code below, the condition in the emit_all_relocs branch
   * (now moved to write.c) is the inverse of the condition that
   * bfd_elf_generic_reloc uses to short-circuit the code in
   * bfd_install_relocation that modifies the addend.  The condition
   * in the else branch match the condition used in the alpha version
   * of tc_gen_reloc (see tc-alpha.c).
   * I do not know why we need to use different conditions in these
   * two branches, it seems to me that the condition should be the same
   * whether or not emit_all_relocs is true.
   * I also do not understand why it was necessary to move the emit_all_relocs
   * condition to write.c.
   */

  if (S_IS_EXTERNAL (fixp->fx_addsy) &&
      !S_IS_COMMON (fixp->fx_addsy) && reloc->howto->partial_inplace)
    reloc->addend -= symbol_get_bfdsym (fixp->fx_addsy)->value;

  return reloc;
}

/* Round up segment to appropriate boundary */

valueT
md_section_align (asection * seg ATTRIBUTE_UNUSED, valueT size)
{
#ifndef OBJ_ELF
  /* This is not right for ELF; a.out wants it, and COFF will force
   * the alignment anyways.  */
  int align = bfd_get_section_alignment (stdoutput, seg);
  valueT mask = ((valueT) 1 << align) - 1;
  return (size + mask) & ~mask;
#else
  return size;
#endif
}

int
md_estimate_size_before_relax (register fragS * fragP ATTRIBUTE_UNUSED,
			       segT segtype ATTRIBUTE_UNUSED)
{
  as_fatal ("estimate_size_before_relax called");
}

void
md_convert_frag (bfd * abfd ATTRIBUTE_UNUSED,
		 asection * sec ATTRIBUTE_UNUSED,
		 fragS * fragp ATTRIBUTE_UNUSED)
{
  as_fatal ("kvx convert_frag");
}

symbolS *
md_undefined_symbol (char *name ATTRIBUTE_UNUSED)
{
  return 0;
}

const char *
md_atof (int type ATTRIBUTE_UNUSED,
	 char *litp ATTRIBUTE_UNUSED, int *sizep ATTRIBUTE_UNUSED)
{
  return ieee_md_atof (type, litp, sizep, TARGET_BYTES_BIG_ENDIAN);
}

/*
 * calculate the base for a pcrel fixup
 * -- for relocation, we might need to add addend ?
 */

long
md_pcrel_from (fixS * fixP)
{
  return (fixP->fx_where + fixP->fx_frag->fr_address);
}

/************************************************************/
/*   Hooks into standard processing -- we hook into label   */
/*   handling code to detect double ':' and we hook before  */
/*   a line of code is processed to do some simple sed style */
/*   edits.                                                 */
/************************************************************/

static symbolS *last_proc_sym = NULL;
static int update_last_proc_sym = 0;

void
kvx_frob_label (symbolS *sym)
{
  if (update_last_proc_sym)
    {
      last_proc_sym = sym;
      update_last_proc_sym = 0;
    }

  if (inside_bundle)
    {
      struct label_fix *fix;
      fix = malloc (sizeof (*fix));
      fix->next = label_fixes;
      fix->sym = sym;
      label_fixes = fix;
    }

  dwarf2_emit_label (sym);
}

void
kvx_check_label (symbolS *sym)
{
  /* Labels followed by a second semi-colon are considered external symbols.  */
  if (*input_line_pointer == ':')
    {
      S_SET_EXTERNAL (sym);
      input_line_pointer++;
    }
}

/* Emit single bundle nop. This is needed by .nop asm directive
 * Have to manage end of bundle done usually by start_line_hook
 * using BE pseudo op
 */
void
kvx_emit_single_noop (void)
{
  char *nop;
  char *end_of_bundle;

  if (asprintf (&nop, "nop") < 0)
    as_fatal ("%s", xstrerror (errno));

  if (asprintf (&end_of_bundle, "be") < 0)
    as_fatal ("%s", xstrerror (errno));

  char *saved_ilp = input_line_pointer;
  md_assemble (nop);
  md_assemble (end_of_bundle);
  input_line_pointer = saved_ilp;
  free (nop);
  free (end_of_bundle);
}

/*  edit out some syntactic sugar that confuses GAS       */
/*  input_line_pointer is guaranteed to point to the      */
/*  the current line but may include text from following  */
/*  lines.  Thus, '\n' must be scanned for as well as '\0' */

void
kvx_md_start_line_hook (void)
{
  char *t;

  for (t = input_line_pointer; t && t[0] == ' '; t++);

  /* Detect illegal syntax patterns:
   * - two bundle ends on the same line: ;; ;;
   * - illegal token: ;;;
   */
  if (t && (t[0] == ';') && (t[1] == ';'))
    {
      char *tmp_t;
      bool newline_seen = false;

      if (t[2] == ';')
	as_fatal ("Syntax error: Illegal ;;; token");

      tmp_t = t + 2;

      while (tmp_t && tmp_t[0])
	{
	  while (tmp_t && tmp_t[0] &&
		 ((tmp_t[0] == ' ') || (tmp_t[0] == '\n')))
	    {
	      if (tmp_t[0] == '\n')
		newline_seen = true;
	      tmp_t++;
	    }
	  if (tmp_t[0] == ';' && tmp_t[1] == ';')
	    {
	      /* if there's no newline between the two bundle stops
	       * then raise a syntax error now, otherwise a strange error
	       * message from read.c will be raised: "junk at end of line..."
	       */
	      if (tmp_t[2] == ';')
		as_fatal ("Syntax error: Illegal ;;; token");

	      if (!newline_seen)
		  as_fatal ("Syntax error: More than one bundle stop on a line");
	      newline_seen = false;	/* reset */

	      /* this is an empty bundle, transform it into an
	       * empty statement */
	      tmp_t[0] = ';';
	      tmp_t[1] = ' ';

	      tmp_t += 2;
	    }
	  else
	    break;
	}
    }

  /* check for bundle end                             */
  /* we transform these into a special opcode BE      */
  /* because gas has ';' hardwired as a statement end */
  if (t && (t[0] == ';') && (t[1] == ';'))
    {
      t[0] = 'B';
      t[1] = 'E';
      return;
    }
}

static void
kvx_check_resources (int f)
{
  env.opts.check_resource_usage = f;
}

/** called before write_object_file */
void
kvx_end (void)
{
  int newflags;

  if (!env.params.core_set)
    env.params.core = kvx_core_info->elf_core;

  /* (pp) the flags must be set at once */
  newflags = env.params.core | env.params.abi | env.params.pic_flags;

  if (env.params.arch_size == 64)
    newflags |= ELF_KVX_ABI_64B_ADDR_BIT;

  bfd_set_private_flags (stdoutput, newflags);

  cleanup ();

  if (inside_bundle && insncnt != 0)
    as_bad ("unexpected end-of-file while processing a bundle."
	    "  Please check that ;; is on its own line.");
}

static void
kvx_type (int start ATTRIBUTE_UNUSED)
{
  char *name;
  char c;
  int type;
  char *typename = NULL;
  symbolS *sym;
  elf_symbol_type *elfsym;

  c = get_symbol_name (&name);
  sym = symbol_find_or_make (name);
  elfsym = (elf_symbol_type *) symbol_get_bfdsym (sym);
  *input_line_pointer = c;

  if (!*S_GET_NAME (sym))
    as_bad (_("Missing symbol name in directive"));

  SKIP_WHITESPACE ();
  if (*input_line_pointer == ',')
    ++input_line_pointer;


  SKIP_WHITESPACE ();
  if (*input_line_pointer == '#'
      || *input_line_pointer == '@'
      || *input_line_pointer == '"' || *input_line_pointer == '%')
    ++input_line_pointer;

  /* typename = input_line_pointer; */
  /* c = get_symbol_end(); */
  c = get_symbol_name (&typename);

  type = 0;
  if (strcmp (typename, "function") == 0
      || strcmp (typename, "STT_FUNC") == 0)
    type = BSF_FUNCTION;
  else if (strcmp (typename, "object") == 0
	   || strcmp (typename, "STT_OBJECT") == 0)
    type = BSF_OBJECT;
  else if (strcmp (typename, "tls_object") == 0
	   || strcmp (typename, "STT_TLS") == 0)
    type = BSF_OBJECT | BSF_THREAD_LOCAL;
  else if (strcmp (typename, "common") == 0
	   || strcmp (typename, "STT_COMMON") == 0)
    type = BSF_ELF_COMMON;
  else if (strcmp (typename, "gnu_unique_object") == 0
	   || strcmp (typename, "STB_GNU_UNIQUE") == 0)
    {
      elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_unique;
      type = BSF_OBJECT | BSF_GNU_UNIQUE;
    }
  else if (strcmp (typename, "notype") == 0
	   || strcmp (typename, "STT_NOTYPE") == 0)
    ;
#ifdef md_elf_symbol_type
  else if ((type = md_elf_symbol_type (typename, sym, elfsym)) != -1)
    ;
#endif
  else
    as_bad (_("unrecognized symbol type \"%s\""), typename);

  *input_line_pointer = c;

  if (*input_line_pointer == '"')
    ++input_line_pointer;

  elfsym->symbol.flags |= type;
  symbol_get_bfdsym (sym)->flags |= type;

  demand_empty_rest_of_line ();
}

#define ENDPROCEXTENSION	"$endproc"
#define MINUSEXPR		".-"

static int proc_endp_status = 0;

static void
kvx_endp (int start ATTRIBUTE_UNUSED)
{
  char c;
  char *name;

  if (inside_bundle)
    as_warn (".endp directive inside a bundle.");
  /* function name is optionnal and is ignored */
  /* there may be several names separated by commas... */
  while (1)
    {
      SKIP_WHITESPACE ();
      c = get_symbol_name (&name);
      (void) restore_line_pointer (c);
      SKIP_WHITESPACE ();
      if (*input_line_pointer != ',')
	break;
      ++input_line_pointer;
    }
  demand_empty_rest_of_line ();

  if (!proc_endp_status)
    {
      as_warn (".endp directive doesn't follow .proc -- ignoring ");
      return;
    }

  proc_endp_status = 0;

  /* TB begin : add BSF_FUNCTION attribute to last_proc_sym symbol */
  if (size_type_function)
    {
      if (!last_proc_sym)
	{
	  as_bad ("Cannot set function attributes (bad symbol)");
	  return;
	}

      /*    last_proc_sym->symbol.flags |= BSF_FUNCTION; */
      symbol_get_bfdsym (last_proc_sym)->flags |= BSF_FUNCTION;
      /* Add .size funcname,.-funcname in order to add size
       * attribute to the current function */
      {
	const int newdirective_sz =
	  strlen (S_GET_NAME (last_proc_sym)) + strlen (MINUSEXPR) + 1;
	char *newdirective = malloc (newdirective_sz);
	char *savep = input_line_pointer;
	expressionS exp;

	memset (newdirective, 0, newdirective_sz);

	/* BUILD :".-funcname" expression */
	strcat (newdirective, MINUSEXPR);
	strcat (newdirective, S_GET_NAME (last_proc_sym));
	input_line_pointer = newdirective;
	expression (&exp);

	if (exp.X_op == O_constant)
	  {
	    S_SET_SIZE (last_proc_sym, exp.X_add_number);
	    if (symbol_get_obj (last_proc_sym)->size)
	      {
		xfree (symbol_get_obj (last_proc_sym)->size);
		symbol_get_obj (last_proc_sym)->size = NULL;
	      }
	  }
	else
	  {
	    symbol_get_obj (last_proc_sym)->size =
	      (expressionS *) xmalloc (sizeof (expressionS));
	    *symbol_get_obj (last_proc_sym)->size = exp;
	  }

	/* just restore the real input pointer */
	input_line_pointer = savep;
	free (newdirective);
      }
    }
  /* TB end */

  last_proc_sym = NULL;
}

static void
kvx_proc (int start ATTRIBUTE_UNUSED)
{
  char c;
  char *name;
  /* there may be several names separated by commas... */
  while (1)
    {
      SKIP_WHITESPACE ();
      c = get_symbol_name (&name);
      (void) restore_line_pointer (c);

      SKIP_WHITESPACE ();
      if (*input_line_pointer != ',')
	break;
      ++input_line_pointer;
    }
  demand_empty_rest_of_line ();

  if (proc_endp_status)
    {
      as_warn (".proc follows .proc -- ignoring");
      return;
    }

  proc_endp_status = 1;

  /* this code emit a global symbol to mark the end of each function    */
  /* the symbol emitted has a name formed by the original function name */
  /* concatenated with $endproc so if _foo is a function name the symbol */
  /* marking the end of it is _foo$endproc                              */
  /* It is also required for generation of .size directive in kvx_endp() */

  if (size_type_function)
    update_last_proc_sym = 1;
}

int
kvx_force_reloc (fixS * fixP)
{
  symbolS *sym;
  asection *symsec;

  if (generic_force_reloc (fixP))
    return 1;

  switch (fixP->fx_r_type)
    {
    case BFD_RELOC_KVX_32_GOTOFF:
    case BFD_RELOC_KVX_S37_GOTOFF_UP27:
    case BFD_RELOC_KVX_S37_GOTOFF_LO10:

    case BFD_RELOC_KVX_64_GOTOFF:
    case BFD_RELOC_KVX_S43_GOTOFF_UP27:
    case BFD_RELOC_KVX_S43_GOTOFF_LO10:
    case BFD_RELOC_KVX_S43_GOTOFF_EX6:

    case BFD_RELOC_KVX_32_GOT:
    case BFD_RELOC_KVX_64_GOT:
    case BFD_RELOC_KVX_S37_GOT_UP27:
    case BFD_RELOC_KVX_S37_GOT_LO10:

    case BFD_RELOC_KVX_GLOB_DAT:
      return 1;
    default:
      return 0;
    }

  sym = fixP->fx_addsy;
  if (sym)
    {
      symsec = S_GET_SEGMENT (sym);
      /* if (bfd_is_abs_section (symsec)) return 0; */
      if (!SEG_NORMAL (symsec))
	return 0;
    }
  return 1;
}

int
kvx_force_reloc_sub_same (fixS * fixP, segT sec)
{
  symbolS *sym;
  asection *symsec;
  const char *sec_name = NULL;

  if (generic_force_reloc (fixP))
    return 1;

  switch (fixP->fx_r_type)
    {
    case BFD_RELOC_KVX_32_GOTOFF:
    case BFD_RELOC_KVX_S37_GOTOFF_UP27:
    case BFD_RELOC_KVX_S37_GOTOFF_LO10:

    case BFD_RELOC_KVX_64_GOTOFF:
    case BFD_RELOC_KVX_S43_GOTOFF_UP27:
    case BFD_RELOC_KVX_S43_GOTOFF_LO10:
    case BFD_RELOC_KVX_S43_GOTOFF_EX6:

    case BFD_RELOC_KVX_32_GOT:
    case BFD_RELOC_KVX_64_GOT:
    case BFD_RELOC_KVX_S37_GOT_UP27:
    case BFD_RELOC_KVX_S37_GOT_LO10:

    case BFD_RELOC_KVX_S37_LO10:
    case BFD_RELOC_KVX_S37_UP27:

    case BFD_RELOC_KVX_GLOB_DAT:
      return 1;

    default:
      return 0;
    }

  sym = fixP->fx_addsy;
  if (sym)
    {
      symsec = S_GET_SEGMENT (sym);
      /* if (bfd_is_abs_section (symsec)) return 0; */
      if (!SEG_NORMAL (symsec))
	return 0;

      /*
       * for .debug_arrange, .debug_frame, .eh_frame sections, containing
       * expressions of the form "sym2 - sym1 + addend", solve them even when
       * --emit-all-relocs is set. Otherwise, a relocation on two symbols
       * is necessary and fails at elf level. Binopt should not be impacted by
       * the resolution of this relocatable expression on symbols inside a
       * function.
       */
      sec_name = segment_name (sec);
      if ((strcmp (sec_name, ".eh_frame") == 0) ||
	  (strcmp (sec_name, ".except_table") == 0) ||
	  (strncmp (sec_name, ".debug_", sizeof (".debug_")) == 0))
	return 0;
    }
  return 1;
}

/* Implement HANDLE_ALIGN.  */

static void
kvx_make_nops (char *buf, bfd_vma bytes)
{
  bfd_vma i = 0;
  unsigned int j;

  static unsigned int nop_single = 0;

  if (!nop_single)
    {
      const struct kvxopc *opcode =
	(struct kvxopc *) str_hash_find (env.opcode_hash, "nop");

      if (opcode == NULL)
	as_fatal
	  ("internal error: could not find opcode for 'nop' during padding");

      nop_single = opcode->codewords[0].opcode;
    }

  /* KVX instructions are always 4-bytes aligned. If we are at a position */
  /* that is not 4 bytes aligned, it means this is not part of an instruction, */
  /* so it is safe to use a zero byte for padding. */

  for (j = bytes % 4; j > 0; j--)
    buf[i++] = 0;

  for (j = 0; j < (bytes - i); j += 4)
    {
      unsigned nop = nop_single;

      // nop has bundle end only if #4 nop or last padding nop.
      // Sets the parallel bit when neither conditions are matched.
      // 4*4 = biggest nop bundle we can get
      // 12 = offset when writting the last nop possible in a 4 nops bundle
      // bytes-i-4 = offset for the last 4-words in the padding
      if (j % (4 * 4) != 12 && j != (bytes - i - 4))
	nop |= PARALLEL_BIT;

      memcpy (buf + i + j, &nop, sizeof (nop));
    }
}

/* Pads code section with bundle of nops when possible, 0 if not. */
void
kvx_handle_align (fragS *fragP)
{
  switch (fragP->fr_type)
    {
    case rs_align_code:
      {
	bfd_signed_vma bytes = (fragP->fr_next->fr_address
				- fragP->fr_address - fragP->fr_fix);
	char *p = fragP->fr_literal + fragP->fr_fix;

	if (bytes <= 0)
	  break;

	/* Insert zeros or nops to get 4 byte alignment.  */
	kvx_make_nops (p, bytes);
	fragP->fr_fix += bytes;
      }
      break;

    default:
      break;
    }
}
/*
 * This is just used for debugging
 */

ATTRIBUTE_UNUSED
static void
print_operand (expressionS * e, FILE * out)
{
  if (e)
    {
      switch (e->X_op)
	{
	case O_register:
	  fprintf (out, "%s", kvx_registers[e->X_add_number].name);
	  break;

	case O_constant:
	  if (e->X_add_symbol)
	    {
	      if (e->X_add_number)
		fprintf (out, "(%s + %d)", S_GET_NAME (e->X_add_symbol),
			 (int) e->X_add_number);
	      else
		fprintf (out, "%s", S_GET_NAME (e->X_add_symbol));
	    }
	  else
	    fprintf (out, "%d", (int) e->X_add_number);
	  break;

	case O_symbol:
	  if (e->X_add_symbol)
	    {
	      if (e->X_add_number)
		fprintf (out, "(%s + %d)", S_GET_NAME (e->X_add_symbol),
			 (int) e->X_add_number);
	      else
		fprintf (out, "%s", S_GET_NAME (e->X_add_symbol));
	    }
	  else
	    fprintf (out, "%d", (int) e->X_add_number);
	  break;

	default:
	  fprintf (out, "o,ptype-%d", e->X_op);
	}
    }
}

void
kvx_cfi_frame_initial_instructions (void)
{
  cfi_add_CFA_def_cfa (KVX_SP_REGNO, 0);
}

int
kvx_regname_to_dw2regnum (const char *regname)
{
  unsigned int regnum = -1;
  const char *p;
  char *q;

  if (regname[0] == 'r')
    {
      p = regname + 1;
      regnum = strtoul (p, &q, 10);
      if (p == q || *q || regnum >= 64)
	return -1;
    }
  return regnum;
}
