/* ginsn.h - GAS instruction representation.
   Copyright (C) 2023 Free Software Foundation, Inc.

   This file is part of GAS, the GNU Assembler.

   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, 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 GAS; see the file COPYING.  If not, write to the Free
   Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
   02110-1301, USA.  */

#include "as.h"
#include "subsegs.h"
#include "ginsn.h"
#include "scfi.h"

#ifdef TARGET_USE_GINSN

static const char *const ginsn_type_names[] =
{
#define _GINSN_TYPE_ITEM(NAME, STR) STR,
  _GINSN_TYPES
#undef _GINSN_TYPE_ITEM
};

static ginsnS *
ginsn_alloc (void)
{
  ginsnS *ginsn = XCNEW (ginsnS);
  return ginsn;
}

static ginsnS *
ginsn_init (enum ginsn_type type, const symbolS *sym, bool real_p)
{
  ginsnS *ginsn = ginsn_alloc ();
  ginsn->type = type;
  ginsn->sym = sym;
  if (real_p)
    ginsn->flags |= GINSN_F_INSN_REAL;
  return ginsn;
}

static void
ginsn_cleanup (ginsnS **ginsnp)
{
  ginsnS *ginsn;

  if (!ginsnp || !*ginsnp)
    return;

  ginsn = *ginsnp;
  if (ginsn->scfi_ops)
    {
      scfi_ops_cleanup (ginsn->scfi_ops);
      ginsn->scfi_ops = NULL;
    }

  free (ginsn);
  *ginsnp = NULL;
}

static void
ginsn_set_src (struct ginsn_src *src, enum ginsn_src_type type, unsigned int reg,
	       offsetT immdisp)
{
  if (!src)
    return;

  src->type = type;
  /* Even when the use-case is SCFI, the value of reg may be > SCFI_MAX_REG_ID.
     E.g., in AMD64, push fs etc.  */
  src->reg = reg;
  src->immdisp = immdisp;
}

static void
ginsn_set_dst (struct ginsn_dst *dst, enum ginsn_dst_type type, unsigned int reg,
	       offsetT disp)
{
  if (!dst)
    return;

  dst->type = type;
  dst->reg = reg;

  if (type == GINSN_DST_INDIRECT)
    dst->disp = disp;
}

static void
ginsn_set_file_line (ginsnS *ginsn, const char *file, unsigned int line)
{
  if (!ginsn)
    return;

  ginsn->file = file;
  ginsn->line = line;
}

struct ginsn_src *
ginsn_get_src1 (ginsnS *ginsn)
{
  return &ginsn->src[0];
}

struct ginsn_src *
ginsn_get_src2 (ginsnS *ginsn)
{
  return &ginsn->src[1];
}

struct ginsn_dst *
ginsn_get_dst (ginsnS *ginsn)
{
  return &ginsn->dst;
}

unsigned int
ginsn_get_src_reg (struct ginsn_src *src)
{
  return src->reg;
}

enum ginsn_src_type
ginsn_get_src_type (struct ginsn_src *src)
{
  return src->type;
}

offsetT
ginsn_get_src_disp (struct ginsn_src *src)
{
  return src->immdisp;
}

offsetT
ginsn_get_src_imm (struct ginsn_src *src)
{
  return src->immdisp;
}

unsigned int
ginsn_get_dst_reg (struct ginsn_dst *dst)
{
  return dst->reg;
}

enum ginsn_dst_type
ginsn_get_dst_type (struct ginsn_dst *dst)
{
  return dst->type;
}

offsetT
ginsn_get_dst_disp (struct ginsn_dst *dst)
{
  return dst->disp;
}

void
label_ginsn_map_insert (const symbolS *label, ginsnS *ginsn)
{
  const char *name = S_GET_NAME (label);
  str_hash_insert (frchain_now->frch_ginsn_data->label_ginsn_map,
		   name, ginsn, 0 /* noreplace.  */);
}

ginsnS *
label_ginsn_map_find (const symbolS *label)
{
  const char *name = S_GET_NAME (label);
  ginsnS *ginsn
    = (ginsnS *) str_hash_find (frchain_now->frch_ginsn_data->label_ginsn_map,
				name);
  return ginsn;
}

ginsnS *
ginsn_new_phantom (const symbolS *sym)
{
  ginsnS *ginsn = ginsn_alloc ();
  ginsn->type = GINSN_TYPE_PHANTOM;
  ginsn->sym = sym;
  /* By default, GINSN_F_INSN_REAL is not set in ginsn->flags.  */
  return ginsn;
}

ginsnS *
ginsn_new_symbol (const symbolS *sym, bool func_begin_p)
{
  ginsnS *ginsn = ginsn_alloc ();
  ginsn->type = GINSN_TYPE_SYMBOL;
  ginsn->sym = sym;
  if (func_begin_p)
    ginsn->flags |= GINSN_F_FUNC_MARKER;
  return ginsn;
}

ginsnS *
ginsn_new_symbol_func_begin (const symbolS *sym)
{
  return ginsn_new_symbol (sym, true);
}

ginsnS *
ginsn_new_symbol_func_end (const symbolS *sym)
{
  return ginsn_new_symbol (sym, false);
}

ginsnS *
ginsn_new_symbol_user_label (const symbolS *sym)
{
  ginsnS *ginsn = ginsn_new_symbol (sym, false);
  ginsn->flags |= GINSN_F_USER_LABEL;
  return ginsn;
}

ginsnS *
ginsn_new_add (const symbolS *sym, bool real_p,
	       enum ginsn_src_type src1_type, unsigned int src1_reg, offsetT src1_disp,
	       enum ginsn_src_type src2_type, unsigned int src2_reg, offsetT src2_disp,
	       enum ginsn_dst_type dst_type, unsigned int dst_reg, offsetT dst_disp)
{
  ginsnS *ginsn = ginsn_init (GINSN_TYPE_ADD, sym, real_p);
  /* src info.  */
  ginsn_set_src (&ginsn->src[0], src1_type, src1_reg, src1_disp);
  ginsn_set_src (&ginsn->src[1], src2_type, src2_reg, src2_disp);
  /* dst info.  */
  ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp);

  return ginsn;
}

ginsnS *
ginsn_new_and (const symbolS *sym, bool real_p,
	       enum ginsn_src_type src1_type, unsigned int src1_reg, offsetT src1_disp,
	       enum ginsn_src_type src2_type, unsigned int src2_reg, offsetT src2_disp,
	       enum ginsn_dst_type dst_type, unsigned int dst_reg, offsetT dst_disp)
{
  ginsnS *ginsn = ginsn_init (GINSN_TYPE_AND, sym, real_p);
  /* src info.  */
  ginsn_set_src (&ginsn->src[0], src1_type, src1_reg, src1_disp);
  ginsn_set_src (&ginsn->src[1], src2_type, src2_reg, src2_disp);
  /* dst info.  */
  ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp);

  return ginsn;
}

ginsnS *
ginsn_new_call (const symbolS *sym, bool real_p,
		enum ginsn_src_type src_type, unsigned int src_reg,
		const symbolS *src_text_sym)

{
  ginsnS *ginsn = ginsn_init (GINSN_TYPE_CALL, sym, real_p);
  /* src info.  */
  ginsn_set_src (&ginsn->src[0], src_type, src_reg, 0);

  if (src_type == GINSN_SRC_SYMBOL)
    ginsn->src[0].sym = src_text_sym;

  return ginsn;
}

ginsnS *
ginsn_new_jump (const symbolS *sym, bool real_p,
		enum ginsn_src_type src_type, unsigned int src_reg,
		const symbolS *src_ginsn_sym)
{
  ginsnS *ginsn = ginsn_init (GINSN_TYPE_JUMP, sym, real_p);
  /* src info.  */
  ginsn_set_src (&ginsn->src[0], src_type, src_reg, 0);

  if (src_type == GINSN_SRC_SYMBOL)
    ginsn->src[0].sym = src_ginsn_sym;

  return ginsn;
}

ginsnS *
ginsn_new_jump_cond (const symbolS *sym, bool real_p,
		     enum ginsn_src_type src_type, unsigned int src_reg,
		     const symbolS *src_ginsn_sym)
{
  ginsnS *ginsn = ginsn_init (GINSN_TYPE_JUMP_COND, sym, real_p);
  /* src info.  */
  ginsn_set_src (&ginsn->src[0], src_type, src_reg, 0);

  if (src_type == GINSN_SRC_SYMBOL)
    ginsn->src[0].sym = src_ginsn_sym;

  return ginsn;
}

ginsnS *
ginsn_new_mov (const symbolS *sym, bool real_p,
	       enum ginsn_src_type src_type, unsigned int src_reg, offsetT src_disp,
	       enum ginsn_dst_type dst_type, unsigned int dst_reg, offsetT dst_disp)
{
  ginsnS *ginsn = ginsn_init (GINSN_TYPE_MOV, sym, real_p);
  /* src info.  */
  ginsn_set_src (&ginsn->src[0], src_type, src_reg, src_disp);
  /* dst info.  */
  ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp);

  return ginsn;
}

ginsnS *
ginsn_new_store (const symbolS *sym, bool real_p,
		 enum ginsn_src_type src_type, unsigned int src_reg,
		 enum ginsn_dst_type dst_type, unsigned int dst_reg, offsetT dst_disp)
{
  ginsnS *ginsn = ginsn_init (GINSN_TYPE_STORE, sym, real_p);
  /* src info.  */
  ginsn_set_src (&ginsn->src[0], src_type, src_reg, 0);
  /* dst info.  */
  gas_assert (dst_type == GINSN_DST_INDIRECT);
  ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp);

  return ginsn;
}

ginsnS *
ginsn_new_load (const symbolS *sym, bool real_p,
		enum ginsn_src_type src_type, unsigned int src_reg, offsetT src_disp,
		enum ginsn_dst_type dst_type, unsigned int dst_reg)
{
  ginsnS *ginsn = ginsn_init (GINSN_TYPE_LOAD, sym, real_p);
  /* src info.  */
  gas_assert (src_type == GINSN_SRC_INDIRECT);
  ginsn_set_src (&ginsn->src[0], src_type, src_reg, src_disp);
  /* dst info.  */
  ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, 0);

  return ginsn;
}

ginsnS *
ginsn_new_sub (const symbolS *sym, bool real_p,
	       enum ginsn_src_type src1_type, unsigned int src1_reg, offsetT src1_disp,
	       enum ginsn_src_type src2_type, unsigned int src2_reg, offsetT src2_disp,
	       enum ginsn_dst_type dst_type, unsigned int dst_reg, offsetT dst_disp)
{
  ginsnS *ginsn = ginsn_init (GINSN_TYPE_SUB, sym, real_p);
  /* src info.  */
  ginsn_set_src (&ginsn->src[0], src1_type, src1_reg, src1_disp);
  ginsn_set_src (&ginsn->src[1], src2_type, src2_reg, src2_disp);
  /* dst info.  */
  ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp);

  return ginsn;
}

/* PS: Note this API does not identify the displacement values of
   src1/src2/dst.  At this time, it is unnecessary for correctness to support
   the additional argument.  */

ginsnS *
ginsn_new_other (const symbolS *sym, bool real_p,
		 enum ginsn_src_type src1_type, unsigned int src1_val,
		 enum ginsn_src_type src2_type, unsigned int src2_val,
		 enum ginsn_dst_type dst_type, unsigned int dst_reg)
{
  ginsnS *ginsn = ginsn_init (GINSN_TYPE_OTHER, sym, real_p);
  /* src info.  */
  ginsn_set_src (&ginsn->src[0], src1_type, src1_val, src1_val);
  /* GINSN_SRC_INDIRECT src2_type is not expected.  */
  gas_assert (src2_type != GINSN_SRC_INDIRECT);
  ginsn_set_src (&ginsn->src[1], src2_type, src2_val, src2_val);
  /* dst info.  */
  ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, 0);

  return ginsn;
}

ginsnS *
ginsn_new_return (const symbolS *sym, bool real_p)
{
  ginsnS *ginsn = ginsn_init (GINSN_TYPE_RETURN, sym, real_p);
  return ginsn;
}

void
ginsn_set_where (ginsnS *ginsn)
{
  const char *file;
  unsigned int line;
  file = as_where (&line);
  ginsn_set_file_line (ginsn, file, line);
}

int
ginsn_link_next (ginsnS *ginsn, ginsnS *next)
{
  int ret = 0;

  /* Avoid data corruption by limiting the scope of the API.  */
  if (!ginsn || ginsn->next)
    return 1;

  ginsn->next = next;

  return ret;
}

bool
ginsn_track_reg_p (unsigned int dw2reg, enum ginsn_gen_mode gmode)
{
  bool track_p = false;

  if (gmode == GINSN_GEN_SCFI && dw2reg <= SCFI_MAX_REG_ID)
    {
      /* FIXME - rename this to tc_ ? */
      track_p |= SCFI_CALLEE_SAVED_REG_P (dw2reg);
      track_p |= (dw2reg == REG_FP);
      track_p |= (dw2reg == REG_SP);
    }

  return track_p;
}

static bool
ginsn_indirect_jump_p (ginsnS *ginsn)
{
  bool ret_p = false;
  if (!ginsn)
    return ret_p;

  ret_p = (ginsn->type == GINSN_TYPE_JUMP
	   && ginsn->src[0].type == GINSN_SRC_REG);
  return ret_p;
}

/* Return whether the GINSN is an unconditional jump to a label which is
   defined locally in the scope of the block of insns, which are currently
   being processed for GCFG creation.  */

static bool
ginsn_direct_local_jump_p (ginsnS *ginsn)
{
  bool local_p = false;
  const symbolS *taken_label;

  if (!ginsn)
    return local_p;

  if (ginsn->type == GINSN_TYPE_JUMP
      && ginsn->src[0].type == GINSN_SRC_SYMBOL)
    {
      taken_label = ginsn->src[0].sym;
      local_p = (label_ginsn_map_find (taken_label) != NULL);
    }
  return local_p;
}

static char *
ginsn_src_print (struct ginsn_src *src)
{
  int str_size = 0;
  const size_t len = GINSN_LISTING_OPND_LEN;
  char *src_str = XNEWVEC (char, len);

  memset (src_str, 0, len);

  switch (src->type)
    {
    case GINSN_SRC_REG:
      str_size = snprintf (src_str, len, "%%r%d", ginsn_get_src_reg (src));
      break;
    case GINSN_SRC_IMM:
      str_size = snprintf (src_str, len, "%lld",
			   (long long int) ginsn_get_src_imm (src));
      break;
    case GINSN_SRC_INDIRECT:
      str_size = snprintf (src_str, len, "[%%r%d+%lld]",
			   ginsn_get_src_reg (src),
			   (long long int) ginsn_get_src_disp (src));
      break;
    default:
      break;
    }

  gas_assert (str_size >= 0 && str_size < (int)len);

  return src_str;
}

static char*
ginsn_dst_print (struct ginsn_dst *dst)
{
  int str_size = 0;
  const size_t len = GINSN_LISTING_OPND_LEN;
  char *dst_str = XNEWVEC (char, len);

  memset (dst_str, 0, len);

  switch (dst->type)
    {
    case GINSN_DST_REG:
      str_size = snprintf (dst_str, len,
			   "%%r%d", ginsn_get_dst_reg (dst));
      break;
    case GINSN_DST_INDIRECT:
      str_size = snprintf (dst_str, len,
			   "[%%r%d+%lld]", ginsn_get_dst_reg (dst),
			   (long long int) ginsn_get_dst_disp (dst));
      break;
    default:
      /* Other dst types are unexpected.  */
      gas_assert (dst->type == GINSN_DST_UNKNOWN);
      break;
    }

  /* str_size will remain 0 when GINSN_DST_UNKNOWN.  */
  gas_assert (str_size >= 0 && str_size < (int)len);

  return dst_str;
}

static const char*
ginsn_type_func_marker_print (ginsnS *ginsn)
{
  int id = 0;
  static const char * const ginsn_sym_strs[] =
    { "", "FUNC_BEGIN", "FUNC_END" };

  if (GINSN_F_FUNC_BEGIN_P (ginsn))
    id = 1;
  else if (GINSN_F_FUNC_END_P (ginsn))
    id = 2;

  return ginsn_sym_strs[id];
}

static char*
ginsn_print (ginsnS *ginsn)
{
  struct ginsn_src *src;
  struct ginsn_dst *dst;
  int str_size = 0;
  size_t len = GINSN_LISTING_LEN;
  char *ginsn_str = XNEWVEC (char, len);

  memset (ginsn_str, 0, len);

  str_size = snprintf (ginsn_str, GINSN_LISTING_LEN, "ginsn: %s",
		       ginsn_type_names[ginsn->type]);
  gas_assert (str_size >= 0 && str_size < GINSN_LISTING_LEN);

  /* For some ginsn types, no further information is printed for now.  */
  if (ginsn->type == GINSN_TYPE_CALL
      || ginsn->type == GINSN_TYPE_RETURN)
    goto end;
  else if (ginsn->type == GINSN_TYPE_SYMBOL)
    {
      if (GINSN_F_USER_LABEL_P (ginsn))
	str_size += snprintf (ginsn_str + str_size,
			      GINSN_LISTING_LEN - str_size,
			      " %s", S_GET_NAME (ginsn->sym));
      else
	str_size += snprintf (ginsn_str + str_size,
			      GINSN_LISTING_LEN - str_size,
			      " %s", ginsn_type_func_marker_print (ginsn));
      goto end;
    }

  /* src 1.  */
  src = ginsn_get_src1 (ginsn);
  char *src_buf = ginsn_src_print (src);
  str_size += snprintf (ginsn_str + str_size, GINSN_LISTING_LEN - str_size,
			" %s", src_buf);
  free (src_buf);
  gas_assert (str_size >= 0 && str_size < GINSN_LISTING_LEN);

  /* src 2.  */
  src = ginsn_get_src2 (ginsn);
  src_buf = ginsn_src_print (src);
  if (strlen (src_buf))
    {
      str_size += snprintf (ginsn_str + str_size, GINSN_LISTING_LEN - str_size,
			    ", %s", src_buf);
    }
  free (src_buf);
  gas_assert (str_size >= 0 && str_size < GINSN_LISTING_LEN);

  /* dst.  */
  dst = ginsn_get_dst (ginsn);
  char *dst_buf = ginsn_dst_print (dst);
  if (strlen (dst_buf))
    {
      str_size += snprintf (ginsn_str + str_size, GINSN_LISTING_LEN - str_size,
			    ", %s", dst_buf);
    }
  free (dst_buf);

end:
  gas_assert (str_size >= 0 && str_size < GINSN_LISTING_LEN);
  return ginsn_str;
}

static void
gbb_cleanup (gbbS **bbp)
{
  gbbS *bb = NULL;

  if (!bbp && !*bbp)
    return;

  bb = *bbp;

  if (bb->entry_state)
    {
      free (bb->entry_state);
      bb->entry_state = NULL;
    }
  if (bb->exit_state)
    {
      free (bb->exit_state);
      bb->exit_state = NULL;
    }
  free (bb);
  *bbp = NULL;
}

/* Add an edge from the source bb FROM_BB to the sink bb TO_BB.  */

static void
bb_add_edge (gbbS* from_bb, gbbS *to_bb)
{
  gedgeS *tmpedge = NULL;
  gedgeS *gedge;
  bool exists = false;

  if (!from_bb || !to_bb)
    return;

  /* Create a new edge object.  */
  gedge = XCNEW (gedgeS);
  gedge->dst_bb = to_bb;
  gedge->next = NULL;
  gedge->visited = false;

  /* Add it in.  */
  if (from_bb->out_gedges == NULL)
    {
      from_bb->out_gedges = gedge;
      from_bb->num_out_gedges++;
    }
  else
    {
      /* Get the head of the list.  */
      tmpedge = from_bb->out_gedges;
      while (tmpedge)
	{
	  /* Do not add duplicate edges.  Duplicated edges will cause unwanted
	     failures in the forward and backward passes for SCFI.  */
	  if (tmpedge->dst_bb == to_bb)
	    {
	      exists = true;
	      break;
	    }
	  if (tmpedge->next)
	    tmpedge = tmpedge->next;
	  else
	    break;
	}

      if (!exists)
	{
	  tmpedge->next = gedge;
	  from_bb->num_out_gedges++;
	}
      else
	free (gedge);
    }
}

static void
cfg_add_bb (gcfgS *gcfg, gbbS *gbb)
{
  gbbS *last_bb = NULL;

  if (!gcfg->root_bb)
    gcfg->root_bb = gbb;
  else
    {
      last_bb = gcfg->root_bb;
      while (last_bb->next)
	last_bb = last_bb->next;

      last_bb->next = gbb;
    }
  gcfg->num_gbbs++;

  gbb->id = gcfg->num_gbbs;
}

static gbbS *
add_bb_at_ginsn (const symbolS *func, gcfgS *gcfg, ginsnS *ginsn, gbbS *prev_bb,
		 int *errp);

/* Return the already existing basic block (if present), which begins with
   GINSN, in the given GCFG.  Return NULL otherwise.  */

static gbbS *
find_bb (gcfgS *gcfg, ginsnS *ginsn)
{
  gbbS *found_bb = NULL;
  gbbS *gbb = NULL;

  if (!ginsn)
    return found_bb;

  if (ginsn->visited)
    {
      cfg_for_each_bb (gcfg, gbb)
	{
	  if (gbb->first_ginsn == ginsn)
	    {
	      found_bb = gbb;
	      break;
	    }
	}
      /* Must be found because ginsn is visited.  */
      gas_assert (found_bb);
    }

  return found_bb;
}

/* Get the basic block starting at GINSN in the GCFG.

   If not already present, the function will make one, while adding an edge
   from the PREV_BB to it.  */

static gbbS *
find_or_make_bb (const symbolS *func, gcfgS *gcfg, ginsnS *ginsn, gbbS *prev_bb,
		 int *errp)
{
  gbbS *found_bb = NULL;

  found_bb = find_bb (gcfg, ginsn);
  if (!found_bb)
    found_bb = add_bb_at_ginsn (func, gcfg, ginsn, prev_bb, errp);

  gas_assert (found_bb);
  gas_assert (found_bb->first_ginsn == ginsn);

  return found_bb;
}

/* Add basic block(s) for all reachable, unvisited ginsns, starting from GINSN,
   to the given GCFG.  Also add an edge from the PREV_BB to the root of the
   newly added basic block(s).

   This is a recursive function which returns the root of the added basic
   blocks.  */

static gbbS *
add_bb_at_ginsn (const symbolS *func, gcfgS *gcfg, ginsnS *ginsn, gbbS *prev_bb,
		 int *errp)
{
  gbbS *root_bb = NULL;
  gbbS *current_bb = NULL;
  ginsnS *target_ginsn = NULL;
  const symbolS *taken_label;

  /* Create a new bb.  N.B. The caller must ensure bb with this ginsn does not
     already exist.  */
  gas_assert (!find_bb (gcfg, ginsn));
  root_bb = XCNEW (gbbS);
  cfg_add_bb (gcfg, root_bb);
  root_bb->first_ginsn = ginsn;

  current_bb = root_bb;

  while (ginsn)
    {
      /* Skip these as they may be right after a GINSN_TYPE_RETURN.
	 For GINSN_TYPE_RETURN, we have already considered that as
	 end of bb, and a logical exit from function.  */
      if (GINSN_F_FUNC_END_P (ginsn))
	{
	  /* Dont mark them visited yet though, leaving the option of these
	     being visited via other control flows as applicable.  */
	  ginsn = ginsn->next;
	  continue;
	}

      if (ginsn->visited)
	{
	  /* If the ginsn has been visited earlier, the bb must exist by now
	     in the cfg.  */
	  prev_bb = current_bb;
	  current_bb = find_bb (gcfg, ginsn);
	  gas_assert (current_bb);
	  /* Add edge from the prev_bb.  */
	  if (prev_bb)
	    bb_add_edge (prev_bb, current_bb);
	  break;
	}
      else if (current_bb && current_bb->first_ginsn != ginsn
	       && GINSN_F_USER_LABEL_P (ginsn))
	{
	  /* Create new bb starting at ginsn for (user-defined) label.  This is
	     likely going to be a destination of a some control flow.  */
	  prev_bb = current_bb;
	  current_bb = find_or_make_bb (func, gcfg, ginsn, prev_bb, errp);
	  bb_add_edge (prev_bb, current_bb);
	  break;
	}

      if (current_bb == NULL)
	{
	  current_bb = XCNEW (gbbS);
	  cfg_add_bb (gcfg, current_bb);
	  current_bb->first_ginsn = ginsn;
	  /* Add edge for the Not Taken, or Fall-through path.  */
	  if (prev_bb)
	    bb_add_edge (prev_bb, current_bb);
	}

      ginsn->visited = true;
      current_bb->num_ginsns++;
      current_bb->last_ginsn = ginsn;

      /* Note that BB is _not_ split on ginsn of type GINSN_TYPE_CALL.  */
      if (ginsn->type == GINSN_TYPE_JUMP
	  || ginsn->type == GINSN_TYPE_JUMP_COND
	  || ginsn->type == GINSN_TYPE_RETURN)
	{
	  /* Indirect jumps must not be seen here.  The caller must have
	     already checked for that.  */
	  gas_assert (!ginsn_indirect_jump_p (ginsn));

	  /* Handle direct jumps.  For unconditional direct jumps, where the
	     target is not local to the function, treat them later as similar
	     to an exit from function (in the else block).  */
	  if (ginsn->type == GINSN_TYPE_JUMP_COND
	      || ginsn_direct_local_jump_p (ginsn))
	    {
	      gas_assert (ginsn->src[0].type == GINSN_SRC_SYMBOL);
	      taken_label = ginsn->src[0].sym;
	      gas_assert (taken_label);

	      /* Preserve the prev_bb to be the source bb as we are going to
		 follow the taken path of the conditional branch soon.  */
	      prev_bb = current_bb;

	      /* Follow the target on the taken path.  */
	      target_ginsn = label_ginsn_map_find (taken_label);
	      /* Add the bb for the target of the taken branch.  */
	      if (target_ginsn)
		{
		  current_bb = find_or_make_bb (func, gcfg, target_ginsn,
						prev_bb, errp);
		  gas_assert (prev_bb);
		  bb_add_edge (prev_bb, current_bb);
		  current_bb = NULL;
		}
	      else
		{
		  *errp = GCFG_JLABEL_NOT_PRESENT;
		  as_warn_where (ginsn->file, ginsn->line,
				 _("missing label '%s' in func '%s' may result in imprecise cfg"),
				 S_GET_NAME (taken_label), S_GET_NAME (func));
		}

	      if (ginsn->type == GINSN_TYPE_JUMP_COND)
		{
		  /* Add the bb for the fall through path.  */
		  current_bb = find_or_make_bb (func, gcfg, ginsn->next,
						prev_bb, errp);
		  gas_assert (prev_bb);
		  bb_add_edge (prev_bb, current_bb);
		  current_bb = NULL;
		}
	      else
		{
		  /* Unconditional jump.  Current BB has been processed.  */
		  current_bb = NULL;
		  /* We'll come back to the ginsns following these (local)
		     unconditional jmps from another path if they are indeed
		     reachable code.  */
		  break;
		}
	    }
	 else
	   {
	     gas_assert (ginsn->type == GINSN_TYPE_RETURN
			 || (ginsn->type == GINSN_TYPE_JUMP
			     && !ginsn_direct_local_jump_p (ginsn)));
	     /* Current BB has been processed.  */
	     current_bb = NULL;

	     /* We'll come back to the ginsns following GINSN_TYPE_RETURN or
		other (non-local) unconditional jmps from another path if they
		are indeed reachable code.  */
	     break;
	   }
	}

      ginsn = ginsn->next;
    }

  return root_bb;
}

static int
gbbs_compare (const void *v1, const void *v2)
{
  const gbbS *bb1 = *(const gbbS **) v1;
  const gbbS *bb2 = *(const gbbS **) v2;

  if (bb1->first_ginsn->id < bb2->first_ginsn->id)
    return -1;
  else if (bb1->first_ginsn->id > bb2->first_ginsn->id)
    return 1;
  else if (bb1->first_ginsn->id == bb2->first_ginsn->id)
    return 0;

  return 0;
}

/* Synthesize DWARF CFI and emit it.  */

static int
ginsn_pass_execute_scfi (const symbolS *func, gcfgS *gcfg, gbbS *root_bb)
{
  int err = scfi_synthesize_dw2cfi (func, gcfg, root_bb);
  if (!err)
    scfi_emit_dw2cfi (func);

  return err;
}

/* Traverse the list of ginsns for the function and warn if some
   ginsns are not visited.

   FIXME - this code assumes the caller has already performed a pass over
   ginsns such that the reachable ginsns are already marked.  Revisit this - we
   should ideally make this pass self-sufficient.  */

static int
ginsn_pass_warn_unreachable_code (const symbolS *func,
				  gcfgS *gcfg ATTRIBUTE_UNUSED,
				  ginsnS *root_ginsn)
{
  ginsnS *ginsn;
  bool unreach_p = false;

  if (!gcfg || !func || !root_ginsn)
    return 0;

  ginsn = root_ginsn;

  while (ginsn)
    {
      /* Some ginsns of type GINSN_TYPE_SYMBOL remain unvisited.  Some
	 may even be excluded from the CFG as they are not reachable, given
	 their function, e.g., user labels after return machine insn.  */
      if (!ginsn->visited
	  && !GINSN_F_FUNC_END_P (ginsn)
	  && !GINSN_F_USER_LABEL_P (ginsn))
	{
	  unreach_p = true;
	  break;
	}
      ginsn = ginsn->next;
    }

  if (unreach_p)
    as_warn_where (ginsn->file, ginsn->line,
		   _("GINSN: found unreachable code in func '%s'"),
		   S_GET_NAME (func));

  return unreach_p;
}

void
gcfg_get_bbs_in_prog_order (gcfgS *gcfg, gbbS **prog_order_bbs)
{
  uint64_t i = 0;
  gbbS *gbb;

  if (!prog_order_bbs)
    return;

  cfg_for_each_bb (gcfg, gbb)
    {
      gas_assert (i < gcfg->num_gbbs);
      prog_order_bbs[i++] = gbb;
    }

  qsort (prog_order_bbs, gcfg->num_gbbs, sizeof (gbbS *), gbbs_compare);
}

/* Build the control flow graph for the ginsns of the function.

   It is important that the target adds an appropriate ginsn:
     - GINSN_TYPE_JUMP,
     - GINSN_TYPE_JUMP_COND,
     - GINSN_TYPE_CALL,
     - GINSN_TYPE_RET
  at the associated points in the function.  The correctness of the CFG
  depends on the accuracy of these 'change of flow instructions'.  */

gcfgS *
gcfg_build (const symbolS *func, int *errp)
{
  gcfgS *gcfg;
  ginsnS *first_ginsn;

  gcfg = XCNEW (gcfgS);
  first_ginsn = frchain_now->frch_ginsn_data->gins_rootP;
  add_bb_at_ginsn (func, gcfg, first_ginsn, NULL /* prev_bb.  */, errp);

  return gcfg;
}

void
gcfg_cleanup (gcfgS **gcfgp)
{
  gcfgS *cfg;
  gbbS *bb, *next_bb;
  gedgeS *edge, *next_edge;

  if (!gcfgp || !*gcfgp)
    return;

  cfg = *gcfgp;
  bb = gcfg_get_rootbb (cfg);

  while (bb)
    {
      next_bb = bb->next;

      /* Cleanup all the edges.  */
      edge = bb->out_gedges;
      while (edge)
	{
	  next_edge = edge->next;
	  free (edge);
	  edge = next_edge;
	}

      gbb_cleanup (&bb);
      bb = next_bb;
    }

  free (cfg);
  *gcfgp = NULL;
}

gbbS *
gcfg_get_rootbb (gcfgS *gcfg)
{
  gbbS *rootbb = NULL;

  if (!gcfg || !gcfg->num_gbbs)
    return NULL;

  rootbb = gcfg->root_bb;

  return rootbb;
}

void
gcfg_print (const gcfgS *gcfg, FILE *outfile)
{
  gbbS *gbb = NULL;
  gedgeS *gedge = NULL;
  uint64_t total_ginsns = 0;

  cfg_for_each_bb(gcfg, gbb)
    {
      fprintf (outfile, "BB [%" PRIu64 "] with num insns: %" PRIu64,
	       gbb->id, gbb->num_ginsns);
      fprintf (outfile, " [insns: %u to %u]\n",
	       gbb->first_ginsn->line, gbb->last_ginsn->line);
      total_ginsns += gbb->num_ginsns;
      bb_for_each_edge(gbb, gedge)
	fprintf (outfile, "  outgoing edge to %" PRIu64 "\n",
		 gedge->dst_bb->id);
    }
  fprintf (outfile, "\nTotal ginsns in all GBBs = %" PRIu64 "\n",
	   total_ginsns);
}

void
frch_ginsn_data_init (const symbolS *func, symbolS *start_addr,
		      enum ginsn_gen_mode gmode)
{
  /* FIXME - error out if prev object is not free'd ?  */
  frchain_now->frch_ginsn_data = XCNEW (struct frch_ginsn_data);

  frchain_now->frch_ginsn_data->mode = gmode;
  /* Annotate with the current function symbol.  */
  frchain_now->frch_ginsn_data->func = func;
  /* Create a new start address symbol now.  */
  frchain_now->frch_ginsn_data->start_addr = start_addr;
  /* Assume the set of ginsn are apt for CFG creation, by default.  */
  frchain_now->frch_ginsn_data->gcfg_apt_p = true;

  frchain_now->frch_ginsn_data->label_ginsn_map = str_htab_create ();
}

void
frch_ginsn_data_cleanup (void)
{
  ginsnS *ginsn = NULL;
  ginsnS *next_ginsn = NULL;

  ginsn = frchain_now->frch_ginsn_data->gins_rootP;
  while (ginsn)
    {
      next_ginsn = ginsn->next;
      ginsn_cleanup (&ginsn);
      ginsn = next_ginsn;
    }

  if (frchain_now->frch_ginsn_data->label_ginsn_map)
    htab_delete (frchain_now->frch_ginsn_data->label_ginsn_map);

  free (frchain_now->frch_ginsn_data);
  frchain_now->frch_ginsn_data = NULL;
}

/* Append GINSN to the list of ginsns for the current function being
   assembled.  */

int
frch_ginsn_data_append (ginsnS *ginsn)
{
  ginsnS *last = NULL;
  ginsnS *temp = NULL;
  uint64_t id = 0;

  if (!ginsn)
    return 1;

  if (frchain_now->frch_ginsn_data->gins_lastP)
    id = frchain_now->frch_ginsn_data->gins_lastP->id;

  /* Do the necessary preprocessing on the set of input GINSNs:
       - Update each ginsn with its ID.
     While you iterate, also keep gcfg_apt_p updated by checking whether any
     ginsn is inappropriate for GCFG creation.  */
  temp = ginsn;
  while (temp)
    {
      temp->id = ++id;

      if (ginsn_indirect_jump_p (temp))
	frchain_now->frch_ginsn_data->gcfg_apt_p = false;

      if (listing & LISTING_GINSN_SCFI)
	listing_newline (ginsn_print (temp));

      /* The input GINSN may be a linked list of multiple ginsns chained
	 together.  Find the last ginsn in the input chain of ginsns.  */
      last = temp;

      temp = temp->next;
    }

  /* Link in the ginsn to the tail.  */
  if (!frchain_now->frch_ginsn_data->gins_rootP)
    frchain_now->frch_ginsn_data->gins_rootP = ginsn;
  else
    ginsn_link_next (frchain_now->frch_ginsn_data->gins_lastP, ginsn);

  frchain_now->frch_ginsn_data->gins_lastP = last;

  return 0;
}

enum ginsn_gen_mode
frch_ginsn_gen_mode (void)
{
  enum ginsn_gen_mode gmode = GINSN_GEN_NONE;

  if (frchain_now->frch_ginsn_data)
    gmode = frchain_now->frch_ginsn_data->mode;

  return gmode;
}

int
ginsn_data_begin (const symbolS *func)
{
  ginsnS *ginsn;

  /* The previous block of asm must have been processed by now.  */
  if (frchain_now->frch_ginsn_data)
    as_bad (_("GINSN process for prev func not done"));

  /* FIXME - hard code the mode to GINSN_GEN_SCFI.
     This can be changed later when other passes on ginsns are formalised.  */
  frch_ginsn_data_init (func, symbol_temp_new_now (), GINSN_GEN_SCFI);

  /* Create and insert ginsn with function begin marker.  */
  ginsn = ginsn_new_symbol_func_begin (func);
  frch_ginsn_data_append (ginsn);

  return 0;
}

int
ginsn_data_end (const symbolS *label)
{
  ginsnS *ginsn;
  gbbS *root_bb;
  gcfgS *gcfg = NULL;
  const symbolS *func;
  int err = 0;

  if (!frchain_now->frch_ginsn_data)
    return err;

  /* Insert Function end marker.  */
  ginsn = ginsn_new_symbol_func_end (label);
  frch_ginsn_data_append (ginsn);

  func = frchain_now->frch_ginsn_data->func;

  /* Build the cfg of ginsn(s) of the function.  */
  if (!frchain_now->frch_ginsn_data->gcfg_apt_p)
    {
      as_bad (_("untraceable control flow for func '%s'"),
	      S_GET_NAME (func));
      goto end;
    }

  gcfg = gcfg_build (func, &err);

  root_bb = gcfg_get_rootbb (gcfg);
  if (!root_bb)
    {
      as_bad (_("Bad cfg of ginsn of func '%s'"), S_GET_NAME (func));
      goto end;
    }

  /* Execute the desired passes on ginsns.  */
  err = ginsn_pass_execute_scfi (func, gcfg, root_bb);
  if (err)
    goto end;

  /* Other passes, e.g., warn for unreachable code can be enabled too.  */
  ginsn = frchain_now->frch_ginsn_data->gins_rootP;
  err = ginsn_pass_warn_unreachable_code (func, gcfg, ginsn);

end:
  if (gcfg)
    gcfg_cleanup (&gcfg);
  frch_ginsn_data_cleanup ();

  return err;
}

/* Add GINSN_TYPE_SYMBOL type ginsn for user-defined labels.  These may be
   branch targets, and hence are necessary for control flow graph.  */

void
ginsn_frob_label (const symbolS *label)
{
  ginsnS *label_ginsn;
  const char *file;
  unsigned int line;

  if (frchain_now->frch_ginsn_data)
    {
      /* PS: Note how we keep the actual LABEL symbol as ginsn->sym.
	 Take care to avoid inadvertent updates or cleanups of symbols.  */
      label_ginsn = ginsn_new_symbol_user_label (label);
      /* Keep the location updated.  */
      file = as_where (&line);
      ginsn_set_file_line (label_ginsn, file, line);

      frch_ginsn_data_append (label_ginsn);

      label_ginsn_map_insert (label, label_ginsn);
    }
}

const symbolS *
ginsn_data_func_symbol (void)
{
  const symbolS *func = NULL;

  if (frchain_now->frch_ginsn_data)
    func = frchain_now->frch_ginsn_data->func;

  return func;
}

#else

int
ginsn_data_begin (const symbolS *func ATTRIBUTE_UNUSED)
{
  as_bad (_("ginsn unsupported for target"));
  return 1;
}

int
ginsn_data_end (const symbolS *label ATTRIBUTE_UNUSED)
{
  as_bad (_("ginsn unsupported for target"));
  return 1;
}

void
ginsn_frob_label (const symbolS *sym ATTRIBUTE_UNUSED)
{
  return;
}

const symbolS *
ginsn_data_func_symbol (void)
{
  return NULL;
}

#endif  /* TARGET_USE_GINSN.  */
