[V2,07/10] gas: synthesize CFI for hand-written asm

Message ID 20231030165137.2570939-8-indu.bhagat@oracle.com
State New
Headers
Series Synthesize CFI for hand-written asm |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_binutils_check--master-arm success Testing passed

Commit Message

Indu Bhagat Oct. 30, 2023, 4:51 p.m. UTC
  [Changes from the RFC patch set]
  - Fixed few more unhandled cases in the x86 creation of ginsn.
  - Added skeleton functionality to print ginsn in the listing output of
    GAS.  The sub-option chosen is "i":
    $ as --help
    -a[sub-option...]    turn on listings
                         Sub-options [default hls]:
                         ...
                         d      omit debugging directives
                         ...
                         i      include ginsn and synthesized CFI info
    Documentation for ginsn is still pending.
  - Other bugfixes in the SCFI machinery.
[End of changes]

This patch adds support in GAS to create generic GAS instructions
(a.k.a., the ginsn) for the x86 backend (AMD64 ABI only at this time).
Using this ginsn infrastructure, GAS can then synthesize CFI for
hand-written asm for x86_64.

A ginsn is a target-independent representation of the machine
instructions.  One machine instruction may need one or more ginsn.

This patch also adds skeleton support for printing ginsn in the listing
output for debugging purposes.

Since the current use-case of ginsn is to synthesize CFI, the x86 target
generates ginsns necessary for the following machine instructions only:

 - All change of flow instructions, including all conditional and
   unconditional branches, call and return from functions.
 - All register saves and unsaves to the stack.
 - All instructions affecting the two registers that could potentially
   be used as the base register for CFA tracking.  For SCFI, the base
   register for CFA tracking is limited to REG_SP and REG_FP only for
   now.

The representation of ginsn is kept simple:

- GAS instruction has GINSN_NUM_SRC_OPNDS (defined to be 2 at this time)
  number of source operands and one destination operand at this time.
- GAS instruction uses DWARF register numbers in its representation and
  does not track register size.
- GAS instructions carry location information (file name and line
  number).
- GAS instructions are ID's with a natural number in order of their
  addtion to the list.  This can be used as a proxy for the static
  program order of the corresponding machine instructions.

Note that, GAS instruction (ginsn) format does not support
GINSN_TYPE_PUSH and GINSN_TYPE_POP.  Some architectures, like aarch64,
do not have push and pop instructions, but rather STP/LDP/STR/LDR etc.
instructions.  Further these instructions have a variety of addressing
modes, like offset, pre-indexing and post-indexing etc.  Among other
things, one of differences in these addressing modes is _when_ the addr
register is updated with the result of the address calculation: before
or after the memory operation.  To best support such needs, the generic
instructions like GINSN_TYPE_LOAD, GINSN_TYPE_STORE together with
GINSN_TYPE_ADD, and GINSN_TYPE_SUB may be used.  For load and store to
stack, the addressing mode with src / dst type of
GINSN_SRC_TYPE_INDIRECT / GINSN_DST_TYPE_INDIRECT respectively and the
register of REG_SP is expected.

The functionality provided in ginsn.c and scfi.c is compiled in when a
target defines TARGET_USE_SCFI and TARGET_USE_GINSN.  This can be
revisited later when there are other use-cases of creating ginsn's in
GAS, apart from the current use-case of synthesizing CFI for
hand-written asm.

Support is added only for AMD64 ABI at this time.  If the user
specifies, --scfi --32, GAS issues an error:

  "Fatal error: Synthesizing CFI is not supported for this ABI"

For synthesizing (DWARF) CFI, the SCFI machinery requires the programmer
to adhere to some pre-requisites for their asm:
   - Hand-written asm block must begin with a .type   foo, @function
It is highly recommended to, additionally, also ensure that:
   - Hand-written asm block ends with a .size foo, .-foo

The SCFI machinery encodes some rules which align with the standard
calling convention specified by the ABI.  Apart from the rules, the SCFI
machinery employs some heuristics.  For example:
   - The base register for CFA tracking may be either REG_SP or REG_FP.
   - If the base register for CFA tracking is REG_SP, the precise amount of
     stack usage (and hence, the value of REG_SP) must be known at all times.
   - If using dynamic stack allocation, the function must switch to
     FP-based CFA.  This means using instructions like the following (in
     AMD64) in prologue:
        pushq   %rbp
        movq    %rsp, %rbp
     and analogous instructions in epilogue.
   - Save and Restore of callee-saved registers must be symmetrical.
     However, the SCFI machinery at this time only warns if any such asymmetry
     is seen.

These heuristics / rules are architecture-independent and are meant to
employed for all architectures/ABIs using SCFI in the future.

gas/
	* Makefile.am: Add new files.
	* Makefile.in: Regenerated.
	* as.c (defined): Handle documentation and listing option for
	ginsns and SCFI.
	* config/obj-elf.c (obj_elf_size): Invoke ginsn_data_end.
	(obj_elf_type): Invoke ginsn_data_begin.
	* config/tc-i386.c (ginsn_new): New functionality to generate
	ginsns.
	(x86_scfi_callee_saved_p): New function.
	(ginsn_dw2_regnum): Likewise.
	(ginsn_set_where): Likewise.
	(x86_ginsn_alu): Likewise.
	(x86_ginsn_move): Likewise.
	(x86_ginsn_lea): Likewise.
	(x86_ginsn_jump): Likewise.
	(x86_ginsn_jump_cond): Likewise.
	(md_assemble): Invoke ginsn_new.
	(s_insn): Likewise.
	(i386_target_format): Add hard error for usage of --scfi with non AMD64 ABIs.
	* config/tc-i386.h (TARGET_USE_GINSN): New definition.
	(TARGET_USE_SCFI): Likewise.
	(SCFI_NUM_REGS): Likewise.
	(REG_FP): Likewise.
	(REG_SP): Likewise.
	(SCFI_INIT_CFA_OFFSET): Likewise.
	(SCFI_CALLEE_SAVED_REG_P): Likewise.
	(x86_scfi_callee_saved_p): Likewise.
	* gas/listing.h (LISTING_GINSN_SCFI): New define for ginsn and
	SCFI.
	* gas/read.c (read_a_source_file): Close SCFI processing at end
	of file read.
	* gas/scfidw2gen.c (scfi_process_cfi_label): Add implementation.
	(scfi_process_cfi_signal_frame): Likewise.
	* subsegs.h (struct frch_ginsn_data): New forward declaration.
	(struct frchain): New member for ginsn data.
	* gas/subsegs.c (subseg_set_rest): Initialize the new member.
	* symbols.c (colon): Invoke ginsn_frob_label to convey
	user-defined labels to ginsn infrastructure.
	* ginsn.c: New file.
	* ginsn.h: New file.
	* scfi.c: New file.
	* scfi.h: New file.
---
 gas/Makefile.am      |    4 +
 gas/Makefile.in      |   19 +-
 gas/as.c             |    5 +
 gas/config/obj-elf.c |   16 +
 gas/config/tc-i386.c |  735 ++++++++++++++++++++++++-
 gas/config/tc-i386.h |   21 +
 gas/ginsn.c          | 1225 ++++++++++++++++++++++++++++++++++++++++++
 gas/ginsn.h          |  392 ++++++++++++++
 gas/listing.h        |    1 +
 gas/read.c           |    4 +
 gas/scfi.c           | 1222 +++++++++++++++++++++++++++++++++++++++++
 gas/scfi.h           |   38 ++
 gas/scfidw2gen.c     |   28 +-
 gas/subsegs.c        |    1 +
 gas/subsegs.h        |    2 +
 gas/symbols.c        |    3 +
 16 files changed, 3705 insertions(+), 11 deletions(-)
 create mode 100644 gas/ginsn.c
 create mode 100644 gas/ginsn.h
 create mode 100644 gas/scfi.c
 create mode 100644 gas/scfi.h
  

Comments

Jan Beulich Oct. 31, 2023, 2:10 p.m. UTC | #1
On 30.10.2023 17:51, Indu Bhagat wrote:
> gas/
> 	* Makefile.am: Add new files.
> 	* Makefile.in: Regenerated.
> 	* as.c (defined): Handle documentation and listing option for
> 	ginsns and SCFI.
> 	* config/obj-elf.c (obj_elf_size): Invoke ginsn_data_end.
> 	(obj_elf_type): Invoke ginsn_data_begin.
> 	* config/tc-i386.c (ginsn_new): New functionality to generate
> 	ginsns.
> 	(x86_scfi_callee_saved_p): New function.
> 	(ginsn_dw2_regnum): Likewise.
> 	(ginsn_set_where): Likewise.
> 	(x86_ginsn_alu): Likewise.
> 	(x86_ginsn_move): Likewise.
> 	(x86_ginsn_lea): Likewise.
> 	(x86_ginsn_jump): Likewise.
> 	(x86_ginsn_jump_cond): Likewise.
> 	(md_assemble): Invoke ginsn_new.
> 	(s_insn): Likewise.
> 	(i386_target_format): Add hard error for usage of --scfi with non AMD64 ABIs.
> 	* config/tc-i386.h (TARGET_USE_GINSN): New definition.
> 	(TARGET_USE_SCFI): Likewise.
> 	(SCFI_NUM_REGS): Likewise.
> 	(REG_FP): Likewise.
> 	(REG_SP): Likewise.
> 	(SCFI_INIT_CFA_OFFSET): Likewise.
> 	(SCFI_CALLEE_SAVED_REG_P): Likewise.
> 	(x86_scfi_callee_saved_p): Likewise.

For this arch-specific code there's a fundamental question of maintenance
cost here: It doesn't look very reasonable to me to demand of people adding
support for new ISA extensions to also take into consideration all of this
new machinery. Yet if any such addition affects SCFI, things will go out-
of-sync without updating this code as well. It may not be very often that
such updating is necessary, but right now there's APX work in progress
which I expect will need dealing with here as well.

> --- a/gas/as.c
> +++ b/gas/as.c
> @@ -45,6 +45,7 @@
>  #include "codeview.h"
>  #include "bfdver.h"
>  #include "write.h"
> +#include "ginsn.h"
>  
>  #ifdef HAVE_ITBL_CPU
>  #include "itbl-ops.h"
> @@ -245,6 +246,7 @@ Options:\n\
>                        	  d      omit debugging directives\n\
>                        	  g      include general info\n\
>                        	  h      include high-level source\n\
> +			  i      include ginsn and synthesized CFI info\n\
>                        	  l      include assembly\n\
>                        	  m      include macro expansions\n\
>                        	  n      omit forms processing\n\

Please can you make indentation match neighboring code?

> --- a/gas/config/obj-elf.c
> +++ b/gas/config/obj-elf.c
> @@ -24,6 +24,7 @@
>  #include "subsegs.h"
>  #include "obstack.h"
>  #include "dwarf2dbg.h"
> +#include "ginsn.h"
>  
>  #ifndef ECOFF_DEBUGGING
>  #define ECOFF_DEBUGGING 0
> @@ -2302,6 +2303,13 @@ obj_elf_size (int ignore ATTRIBUTE_UNUSED)
>        symbol_get_obj (sym)->size = XNEW (expressionS);
>        *symbol_get_obj (sym)->size = exp;
>      }
> +
> +  /* If the symbol in the directive matches the current function being
> +     processed, indicate end of the current stream of ginsns.  */
> +  if (flag_synth_cfi
> +      && S_IS_FUNCTION (sym) && sym == ginsn_data_func_symbol ())
> +    ginsn_data_end (symbol_temp_new_now ());
> +
>    demand_empty_rest_of_line ();
>  }

Besides the restrictions mentioned, presence of this just in obj-elf.c
means non-ELF targets also won't be able to use this. You'll therefore
want to adjust the config/tc-i386.h such that e.g. COFF targets don't
needlessly have a large set of dead code compiled in.

> --- a/gas/config/tc-i386.c
> +++ b/gas/config/tc-i386.c
> @@ -30,6 +30,7 @@
>  #include "subsegs.h"
>  #include "dwarf2dbg.h"
>  #include "dw2gencfi.h"
> +#include "scfi.h"
>  #include "gen-sframe.h"
>  #include "sframe.h"
>  #include "elf/x86-64.h"
> @@ -193,8 +194,11 @@ static unsigned int x86_isa_1_used;
>  static unsigned int x86_feature_2_used;
>  /* Generate x86 used ISA and feature properties.  */
>  static unsigned int x86_used_note = DEFAULT_X86_USED_NOTE;
> +
>  #endif

Nit: Stray change?

> +static ginsnS *ginsn_new (symbolS *sym, enum ginsn_gen_mode gmode);

I don't see the need for this: The function definition lives ahead of any
caller afaics.

> @@ -5116,6 +5120,716 @@ static INLINE bool may_need_pass2 (const insn_template *t)
>  	       && t->base_opcode == 0x63);
>  }
>  
> +bool
> +x86_scfi_callee_saved_p (uint32_t dw2reg_num)
> +{
> +  if (dw2reg_num == 3 /* rbx.  */
> +      || dw2reg_num == REG_FP /* rbp.  */
> +      || dw2reg_num == REG_SP /* rsp.  */
> +      || (dw2reg_num >= 12 && dw2reg_num <= 15) /* r12 - r15.  */)
> +    return true;

Non-GPRs aren't of interest here?

What about the Windows ABI, which is also used in the EFI world?

> +  return false;
> +}
> +
> +static uint32_t
> +ginsn_dw2_regnum (const reg_entry *ireg)
> +{
> +  /* PS: Note the data type here as int32_t, because of Dw2Inval (-1).  */
> +  int32_t dwarf_reg = Dw2Inval;
> +  const reg_entry *temp;
> +
> +  if (ireg->dw2_regnum[0] == Dw2Inval && ireg->dw2_regnum[1] == Dw2Inval)
> +    return dwarf_reg;
> +
> +  dwarf_reg = ireg->dw2_regnum[flag_code >> 1];

flag_code == CODE_64BIT would likely be more robust.

Also are all calls here synchronous, i.e. happening right when a
particular insn is processed? Otherwise what about flag_code changing
in between?

Plus in how far does it make sense to handle other than CODE_64BIT when
you tie yourself to the SysV 64-bit ABI?

> +  if (dwarf_reg == Dw2Inval)
> +    {
> +      temp = ireg + 16;
> +      dwarf_reg = ginsn_dw2_regnum (temp);
> +    }

What is this about? This clearly needs a comment, and it may also need
a comment in opcodes/i386-reg.tbl to clarify that certain ordering
requirements have to be retained. It also wants to be clarified that
the recursion here won't be indefinite.

> +  if (dwarf_reg == Dw2Inval)
> +    gas_assert (1); /* Needs to be addressed.  */

This is dead code (you appear to mean 0 instead of 1). Yet even then
still gas_assert (dwarf_reg != Dw2Inval) please.

> +  return (uint32_t) dwarf_reg;
> +}
> +
> +static void
> +ginsn_set_where (ginsnS* ginsn)

Nit: Misplaced *.

> +{
> +  const char *file;
> +  unsigned int line;
> +  file = as_where (&line);
> +  ginsn_set_file_line (ginsn, file, line);
> +}

This function, also from its name, isn't x86-specific, is it? As such
it wants to live in ginsn.[ch].

> +static ginsnS *
> +x86_ginsn_alu (i386_insn insn, symbolS *insn_end_sym)
> +{
> +  offsetT src_imm;
> +  uint32_t dw2_regnum;
> +  enum ginsn_src_type src_type;
> +  enum ginsn_dst_type dst_type;
> +  ginsnS *ginsn = NULL;
> +
> +  /* FIXME - create ginsn for REG_SP target only ? */
> +  /* Map for insn.tm.extension_opcode
> +     000 ADD    100 AND
> +     001 OR     101 SUB
> +     010 ADC    110 XOR
> +     011 SBB    111 CMP  */
> +
> +  /* add/sub imm, %reg.
> +     and imm, %reg only at this time for SCFI. */
> +  if (!(insn.tm.extension_opcode == 0
> +	|| insn.tm.extension_opcode == 4
> +	|| insn.tm.extension_opcode == 5))
> +    return ginsn;

Why is AND permitted, but OR/XOR aren't?

Also this is about ALU insns with immediate operands only, yet that
fact isn't reflected in the function name.

> +  /* TBD_GINSN_REPRESENTATION_LIMIT: There is no representation for when a
> +     symbol is used as an operand, like so:
> +	  addq    $simd_cmp_op+8, %rdx
> +     Skip generating any ginsn for this.  */
> +  if (insn.imm_operands == 1
> +      && insn.op[0].imms->X_op == O_symbol)
> +    return ginsn;
> +
> +  gas_assert (insn.imm_operands == 1
> +	      && insn.op[0].imms->X_op == O_constant);

Perhaps less fragile if you use != O_constant in the preceding if()?
The remaining half could the move ahead of that if(), allowing half
of its condition to also be dropped.

> +  src_imm = insn.op[0].imms->X_add_number;
> +  /* The second operand may be a register or indirect access.  */
> +  if (insn.mem_operands == 1 && insn.base_reg)
> +    {
> +      dw2_regnum = ginsn_dw2_regnum (insn.base_reg);
> +      src_type = GINSN_SRC_INDIRECT;
> +      dst_type = GINSN_DST_INDIRECT;

The possibly in use index register isn't of interest in this case?
Nor the displacement in the memory operand, ...

> +    }
> +  else if (insn.mem_operands == 1 && insn.index_reg)
> +    {
> +      dw2_regnum = ginsn_dw2_regnum (insn.index_reg);
> +      src_type = GINSN_SRC_INDIRECT;
> +      dst_type = GINSN_DST_INDIRECT;

... similarly applicable here? Nor a segment override?

> +    }
> +  else
> +    {
> +      gas_assert (insn.reg_operands == 1);

Afaict this will trigger when the memory operand has neither base
nor index.

> +      dw2_regnum = ginsn_dw2_regnum (insn.op[1].regs);
> +      src_type = GINSN_SRC_REG;
> +      dst_type = GINSN_DST_REG;
> +    }
> +
> +  /* For ginsn, keep the imm as second src operand.  */
> +  if (insn.tm.extension_opcode == 5)
> +    ginsn = ginsn_new_sub (insn_end_sym, true,
> +			   src_type, dw2_regnum, 0,
> +			   GINSN_SRC_IMM, 0, src_imm,
> +			   dst_type, dw2_regnum, 0);
> +  else if (insn.tm.extension_opcode == 4)
> +    ginsn = ginsn_new_and (insn_end_sym, true,
> +			   src_type, dw2_regnum, 0,
> +			   GINSN_SRC_IMM, 0, src_imm,
> +			   dst_type, dw2_regnum, 0);
> +  else if (insn.tm.extension_opcode == 0)
> +    ginsn = ginsn_new_add (insn_end_sym, true,
> +			   src_type, dw2_regnum, 0,
> +			   GINSN_SRC_IMM, 0, src_imm,
> +			   dst_type, dw2_regnum, 0);

I think this would benefit from setting a function pointer near the
top of the function. Else can this please at least be put in switch()
form?

> +  ginsn_set_where (ginsn);
> +
> +  return ginsn;
> +}
> +
> +static ginsnS *
> +x86_ginsn_move (i386_insn insn, symbolS *insn_end_sym)
> +{
> +  ginsnS *ginsn;
> +  uint16_t opcode;
> +  uint32_t dst_reg;
> +  uint32_t src_reg;
> +  offsetT dst_disp;
> +  offsetT src_disp;
> +  const reg_entry *dst = NULL;
> +  const reg_entry *src = NULL;
> +  enum ginsn_dst_type dst_type;
> +  enum ginsn_src_type src_type;
> +
> +  opcode = insn.tm.base_opcode;
> +  src_type = GINSN_SRC_REG;
> +  src_disp = dst_disp = 0;
> +  dst_type = GINSN_DST_REG;

Please can such be initializers of their variables?

> +  if (opcode == 0x8b)
> +    {
> +      /* mov  disp(%reg), %reg.  */
> +      if (insn.mem_operands && insn.base_reg)
> +	{
> +	  src = insn.base_reg;
> +	  if (insn.disp_operands == 1)
> +	    src_disp = insn.op[0].disps->X_add_number;
> +	  src_type = GINSN_SRC_INDIRECT;
> +	}
> +      else
> +	src = insn.op[0].regs;

What about disp(%reg,%reg) or disp(,%reg) kinds of memory operands?

> +      dst = insn.op[1].regs;
> +    }
> +  else if (opcode == 0x89 || opcode == 0x88)

How come 0x88 is handled here, but 0x8a isn't handled above?

> +    {
> +      /* mov %reg, disp(%reg).  */
> +      src = insn.op[0].regs;
> +      if (insn.mem_operands && insn.base_reg)
> +	{
> +	  dst = insn.base_reg;
> +	  if (insn.disp_operands == 1)
> +	    dst_disp = insn.op[1].disps->X_add_number;
> +	  dst_type = GINSN_DST_INDIRECT;
> +	}
> +      else
> +	dst = insn.op[1].regs;
> +    }
> +
> +  src_reg = ginsn_dw2_regnum (src);
> +  dst_reg = ginsn_dw2_regnum (dst);
> +
> +  ginsn = ginsn_new_mov (insn_end_sym, true,
> +			 src_type, src_reg, src_disp,
> +			 dst_type, dst_reg, dst_disp);
> +  ginsn_set_where (ginsn);
> +
> +  return ginsn;
> +}
> +
> +static ginsnS *
> +x86_ginsn_lea (i386_insn insn, symbolS *insn_end_sym)
> +{
> +  offsetT src_disp = 0;
> +  ginsnS *ginsn = NULL;
> +  uint32_t base_reg;
> +  uint32_t index_reg;
> +  offsetT index_scale;
> +  uint32_t dst_reg;
> +
> +  if (!insn.index_reg && !insn.base_reg)
> +    {
> +      /* lea symbol, %rN.  */
> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
> +      /* FIXME - Skip encoding information about the symbol.
> +	 This is TBD_GINSN_INFO_LOSS, but it is fine if the mode is
> +	 GINSN_GEN_SCFI.  */
> +      ginsn = ginsn_new_mov (insn_end_sym, false,
> +			     GINSN_SRC_IMM, 0xf /* arbitrary const.  */, 0,
> +			     GINSN_DST_REG, dst_reg, 0);
> +    }
> +  else if (insn.base_reg && !insn.index_reg)
> +    {
> +      /* lea    -0x2(%base),%dst.  */
> +      base_reg = ginsn_dw2_regnum (insn.base_reg);
> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
> +
> +      if (insn.disp_operands)
> +	src_disp = insn.op[0].disps->X_add_number;

What if the displacement expression is other than O_constant?

> +      if (src_disp)
> +	/* Generate an ADD ginsn.  */
> +	ginsn = ginsn_new_add (insn_end_sym, true,
> +			       GINSN_SRC_REG, base_reg, 0,
> +			       GINSN_SRC_IMM, 0, src_disp,
> +			       GINSN_DST_REG, dst_reg, 0);
> +      else
> +	  /* Generate a MOV ginsn.  */
> +	  ginsn = ginsn_new_mov (insn_end_sym, true,
> +				 GINSN_SRC_REG, base_reg, 0,
> +				 GINSN_DST_REG, dst_reg, 0);
> +    }
> +  else if (!insn.base_reg && insn.index_reg)
> +    {
> +      /* lea (,%index,imm), %dst.  */
> +      /* FIXME - Skip encoding an explicit multiply operation, instead use
> +	 GINSN_TYPE_OTHER.  This is TBD_GINSN_INFO_LOSS, but it is fine if
> +	 the mode is GINSN_GEN_SCFI.  */
> +      index_scale = insn.log2_scale_factor;
> +      index_reg = ginsn_dw2_regnum (insn.index_reg);
> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
> +      ginsn = ginsn_new_other (insn_end_sym, true,
> +			       GINSN_SRC_REG, index_reg,
> +			       GINSN_SRC_IMM, index_scale,
> +			       GINSN_DST_REG, dst_reg);
> +    }
> +  else
> +    {
> +      /* lea disp(%base,%index,imm) %dst.  */
> +      /* FIXME - Skip encoding information about the disp and imm for index
> +	 reg.  This is TBD_GINSN_INFO_LOSS, but it is fine if the mode is
> +	 GINSN_GEN_SCFI.  */
> +      base_reg = ginsn_dw2_regnum (insn.base_reg);
> +      index_reg = ginsn_dw2_regnum (insn.index_reg);
> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
> +      /* Generate an ADD ginsn.  */
> +      ginsn = ginsn_new_add (insn_end_sym, true,
> +			     GINSN_SRC_REG, base_reg, 0,
> +			     GINSN_SRC_REG, index_reg, 0,
> +			     GINSN_DST_REG, dst_reg, 0);

The comment mentions the displacement, but it's not passed on? In the earlier
"else if" block you also pay attention to the scla factor, but here you don't?
That said, I think I'm confused anyway about the FIXME comments.

> +    }
> +
> +  ginsn_set_where (ginsn);
> +
> +  return ginsn;
> +}
> +
> +static ginsnS *
> +x86_ginsn_jump (i386_insn insn, symbolS *insn_end_sym)
> +{
> +  ginsnS *ginsn = NULL;
> +  symbolS *src_symbol;
> +
> +  gas_assert (insn.disp_operands == 1);
> +
> +  if (insn.op[0].disps->X_op == O_symbol)
> +    {
> +      src_symbol = insn.op[0].disps->X_add_symbol;
> +      /* The jump target is expected to be a symbol with 0 addend.
> +	 Assert for now to see if this assumption is true.  */
> +      gas_assert (insn.op[0].disps->X_add_number == 0);

If you assert this means elsewhere this is being checked and a call
here avoided in case the value is non-zero. I can't spot such a
check, though.

> +      ginsn = ginsn_new_jump (insn_end_sym, true,
> +			      GINSN_SRC_SYMBOL, 0, src_symbol);
> +
> +      ginsn_set_where (ginsn);
> +    }
> +
> +  return ginsn;
> +}

Further, what about XABORT transferring control to what XBEGIN has
supplied? (XBEGIN can, in a sense, also be considered a [conditional]
branch.)

> +static ginsnS *
> +x86_ginsn_jump_cond (i386_insn insn, symbolS *insn_end_sym)
> +{
> +  ginsnS *ginsn = NULL;
> +  symbolS *src_symbol;
> +
> +  /* TBD_GINSN_GEN_NOT_SCFI: Ignore move to or from xmm reg for mode.  */
> +  if (i.tm.opcode_space == SPACE_0F)
> +    return ginsn;

What is the comment about? And what about SPACE_0F-encoded conditional
jumps (Jcc <disp32>)? And where are LOOP and J{E,R}CXZ handled?

> +  gas_assert (insn.disp_operands == 1);
> +
> +  if (insn.op[0].disps->X_op == O_symbol)
> +    {
> +      src_symbol = insn.op[0].disps->X_add_symbol;
> +      /* The jump target is expected to be a symbol with 0 addend.
> +	 Assert for now to see if this assumption is true.  */
> +      gas_assert (insn.op[0].disps->X_add_number == 0);
> +      ginsn = ginsn_new_jump_cond (insn_end_sym, true,
> +				   GINSN_SRC_SYMBOL, 0, src_symbol);
> +      ginsn_set_where (ginsn);
> +    }
> +  else
> +    /* Catch them for now so we know what we are dealing with.  */
> +    gas_assert (0);

I'm going from the assumption that this (and alike) will be addressed
before this series is committed?

> +  return ginsn;
> +}
> +
> +/* Generate one or more GAS instructions for the current machine dependent
> +   instruction.
> +
> +   Returns the head of linked list of ginsn(s) added, if success;
> +   Returns NULL if failure.  */
> +
> +static ginsnS *
> +ginsn_new (symbolS *insn_end_sym, enum ginsn_gen_mode gmode)

x86_ginsn_new()?

> +{
> +  uint16_t opcode;
> +  uint32_t dw2_regnum;
> +  uint32_t src2_dw2_regnum;
> +  int32_t gdisp = 0;
> +  ginsnS *ginsn = NULL;
> +  ginsnS *ginsn_next = NULL;
> +  ginsnS *ginsn_last = NULL;
> +
> +  /* FIXME - Need a way to check whether the decoding is sane.  The specific
> +     checks around i.tm.opcode_space were added as issues were seen.  Likely
> +     insufficient.  */

Furthermore opcode encoding space (SPACE_...) need to be taken into
account in all cases.

> +  /* Currently supports generation of selected ginsns, sufficient for
> +     the use-case of SCFI only.  To remove this condition will require
> +     work on this target-specific process of creation of ginsns.  Some
> +     of such places are tagged with TBD_GINSN_GEN_NOT_SCFI to serve as
> +     examples.  */
> +  if (gmode != GINSN_GEN_SCFI)
> +    return ginsn;
> +
> +  opcode = i.tm.base_opcode;
> +
> +  switch (opcode)
> +    {
> +    case 0x1:
> +      /* add reg, reg.  */
> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);

You don't care about opcode 0 (byte operation). Then what about 16-bit
operand size? Or, since we're talking of a 64-bit-ABI-only feature,
even 32-bit operand size?

Also what about opcode 0x3?

> +      if (i.reg_operands == 2)
> +	{
> +	  src2_dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
> +	  ginsn = ginsn_new_add (insn_end_sym, true,
> +				 GINSN_SRC_REG, dw2_regnum, 0,
> +				 GINSN_SRC_REG, src2_dw2_regnum, 0,
> +				 GINSN_DST_REG, src2_dw2_regnum, 0);
> +	  ginsn_set_where (ginsn);
> +	}
> +      else if (i.mem_operands && i.base_reg)
> +	{
> +	  src2_dw2_regnum = ginsn_dw2_regnum (i.base_reg);
> +	  if (i.disp_operands == 1)
> +	    gdisp = i.op[1].disps->X_add_number;
> +
> +	  ginsn = ginsn_new_add (insn_end_sym, true,
> +				 GINSN_SRC_REG, dw2_regnum, 0,
> +				 GINSN_SRC_INDIRECT, src2_dw2_regnum, gdisp,
> +				 GINSN_DST_INDIRECT, src2_dw2_regnum, gdisp);
> +	  ginsn_set_where (ginsn);
> +	}
> +      else
> +	/* Catch them for now so we know what we are dealing with.  */
> +	gas_assert (0);
> +
> +      break;
> +    case 0x29:
> +      /* If opcode_space == SPACE_0F, this is a movaps insn.  Skip it
> +	 for GINSN_GEN_SCFI.  */
> +      if (i.tm.opcode_space == SPACE_0F)
> +	break;

Extending on the earlier related comment: Why would you exclude just SPACE_0F?

> +      /* sub reg, reg.  */
> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
> +
> +      if (i.reg_operands == 2)
> +	{
> +	  src2_dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
> +	  ginsn = ginsn_new_sub (insn_end_sym, true,
> +				 GINSN_SRC_REG, dw2_regnum, 0,
> +				 GINSN_SRC_REG, src2_dw2_regnum, 0,
> +				 GINSN_DST_REG, src2_dw2_regnum, 0);
> +	  ginsn_set_where (ginsn);
> +	}
> +      else if (i.mem_operands && i.base_reg)
> +	{
> +	  src2_dw2_regnum = ginsn_dw2_regnum (i.base_reg);
> +	  if (i.disp_operands == 1)
> +	    gdisp = i.op[1].disps->X_add_number;
> +
> +	  ginsn = ginsn_new_sub (insn_end_sym, true,
> +				 GINSN_SRC_REG, dw2_regnum, 0,
> +				 GINSN_SRC_INDIRECT, src2_dw2_regnum, gdisp,
> +				 GINSN_DST_INDIRECT, src2_dw2_regnum, gdisp);
> +	  ginsn_set_where (ginsn);
> +	}
> +      else
> +	/* Catch them for now so we know what we are dealing with.  */
> +	gas_assert (0);
> +
> +      break;
> +    case 0xa0:
> +    case 0xa8:

Since individual case blocks are non-trivial, can there please be blank
lines between any two non-fall-through case blocks?

> +      /* If opcode_space != SPACE_0F, this is a test insn.  Skip it
> +	 for GINSN_GEN_SCFI.  */
> +      if (i.tm.opcode_space != SPACE_0F)
> +	break;

While here you properly use !=, now the comment is wrong.

> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
> +      /* push fs / push gs.  */
> +      ginsn = ginsn_new_sub (insn_end_sym, false,
> +			     GINSN_SRC_REG, REG_SP, 0,
> +			     GINSN_SRC_IMM, 0, 8,

Note how the 8 here assumes flag_code == CODE_64BIT. (You further assume
no operand size override here.)

> +			     GINSN_DST_REG, REG_SP, 0);
> +      ginsn_set_where (ginsn);
> +
> +      ginsn_next = ginsn_new_store (insn_end_sym, false,
> +				    GINSN_SRC_REG, dw2_regnum,
> +				    GINSN_DST_INDIRECT, REG_SP, 0);
> +      ginsn_set_where (ginsn_next);
> +
> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
> +      break;
> +    case 0xa1:
> +    case 0xa9:
> +      /* If opcode_space != SPACE_0F, this is test insn.  Skip it
> +	 for GINSN_GEN_SCFI.  */
> +      if (i.tm.opcode_space != SPACE_0F)
> +	break;
> +
> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
> +      /* pop fs / pop gs.  */
> +      ginsn = ginsn_new_load (insn_end_sym, false,
> +			      GINSN_SRC_INDIRECT, REG_SP, 0,
> +			      GINSN_DST_REG, dw2_regnum);
> +      ginsn_set_where (ginsn);
> +
> +      ginsn_next = ginsn_new_add (insn_end_sym, false,
> +				  GINSN_SRC_REG, REG_SP, 0,
> +				  GINSN_SRC_IMM, 0, 8,
> +				  GINSN_DST_REG, REG_SP, 0);
> +      ginsn_set_where (ginsn_next);
> +
> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
> +      break;
> +    case 0x50 ... 0x57:
> +      /* push reg.  */
> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
> +      ginsn = ginsn_new_sub (insn_end_sym, false,
> +			     GINSN_SRC_REG, REG_SP, 0,
> +			     GINSN_SRC_IMM, 0, 8,
> +			     GINSN_DST_REG, REG_SP, 0);
> +      ginsn_set_where (ginsn);
> +
> +      ginsn_next = ginsn_new_store (insn_end_sym, false,
> +				    GINSN_SRC_REG, dw2_regnum,
> +				    GINSN_DST_INDIRECT, REG_SP, 0);
> +      ginsn_set_where (ginsn_next);
> +
> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
> +      break;
> +    case 0x58 ... 0x5f:
> +      if (i.tm.opcode_space != SPACE_BASE)
> +	break;
> +      /* pop reg.  */
> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
> +      ginsn = ginsn_new_load (insn_end_sym, false,
> +			      GINSN_SRC_INDIRECT, REG_SP, 0,
> +			      GINSN_DST_REG, dw2_regnum);
> +      ginsn_set_where (ginsn);
> +
> +      ginsn_next = ginsn_new_add (insn_end_sym, false,
> +				  GINSN_SRC_REG, REG_SP, 0,
> +				  GINSN_SRC_IMM, 0, 8,
> +				  GINSN_DST_REG, REG_SP, 0);
> +      ginsn_set_where (ginsn_next);
> +
> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
> +      break;
> +    case 0x68:
> +    case 0x6a:
> +      /* push imm. */
> +      /* Skip getting the value of imm from machine instruction
> +	 because for ginsn generation this is not important.  */

In (e.g.) kernel code model it is possible to do

	push	<address>
	pop	%rbp

(could even be %rsp). How is such a frame (or stack) pointer change
not relevant?

> +      ginsn = ginsn_new_sub (insn_end_sym, false,
> +			     GINSN_SRC_REG, REG_SP, 0,
> +			     GINSN_SRC_IMM, 0, 8,
> +			     GINSN_DST_REG, REG_SP, 0);
> +      ginsn_set_where (ginsn);
> +
> +      ginsn_next = ginsn_new_store (insn_end_sym, false,
> +				    GINSN_SRC_IMM, 0,
> +				    GINSN_DST_INDIRECT, REG_SP, 0);
> +      ginsn_set_where (ginsn_next);
> +
> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
> +      break;
> +    case 0x70 ... 0x7f:
> +      ginsn = x86_ginsn_jump_cond (i, insn_end_sym);
> +      break;
> +    case 0x81:
> +    case 0x83:
> +      ginsn = x86_ginsn_alu (i, insn_end_sym);
> +      break;
> +    case 0x8b:
> +      /* Move r/m64 to r64.  */
> +    case 0x88:
> +    case 0x89:
> +      /* mov reg, reg/mem.  */
> +      ginsn = x86_ginsn_move (i, insn_end_sym);
> +      break;
> +    case 0x8d:
> +      /* lea disp(%src), %dst */
> +      ginsn = x86_ginsn_lea (i, insn_end_sym);
> +      break;
> +    case 0x8f:
> +      /* pop to mem.  */
> +      gas_assert (i.base_reg);

This opcode can in principle also have a register operand. We
_currently_ have no way for a user to request to use this
alternative encoding, but that's still a latent issue. In any
event, like elsewhere, all memory operand forms are possible
here, not just (%reg).

> +      dw2_regnum = ginsn_dw2_regnum (i.base_reg);
> +      ginsn = ginsn_new_load (insn_end_sym, false,
> +			      GINSN_SRC_INDIRECT, REG_SP, 0,
> +			      GINSN_DST_INDIRECT, dw2_regnum);

When both operands are "indirect", what's the difference between
move, load, and store? IOW if the above is permitted, can't all
three be folded into a single ginsn kind?

> +      ginsn_set_where (ginsn);
> +
> +      ginsn_next = ginsn_new_add (insn_end_sym, false,
> +				  GINSN_SRC_REG, REG_SP, 0,
> +				  GINSN_SRC_IMM, 0, 8,
> +				  GINSN_DST_REG, REG_SP, 0);
> +      ginsn_set_where (ginsn_next);
> +
> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
> +      break;
> +    case 0x9c:
> +      /* pushf / pushfd / pushfq.
> +	 Tracking EFLAGS register by number is not necessary.  */
> +      ginsn = ginsn_new_sub (insn_end_sym, false,
> +			     GINSN_SRC_REG, REG_SP, 0,
> +			     GINSN_SRC_IMM, 0, 8,

Why does the comment mention PUSHFD when you assume PUSHFQ here?

> +			     GINSN_DST_REG, REG_SP, 0);
> +      ginsn_set_where (ginsn);
> +
> +      ginsn_next = ginsn_new_store (insn_end_sym, false,
> +				    GINSN_SRC_IMM, 0,
> +				    GINSN_DST_INDIRECT, REG_SP, 0);
> +      ginsn_set_where (ginsn_next);
> +
> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
> +
> +      break;

Where's POPFD?

> +    case 0xff:
> +      /* push from mem.  */
> +      if (i.tm.extension_opcode == 6)
> +	{
> +	  ginsn = ginsn_new_sub (insn_end_sym, false,
> +				 GINSN_SRC_REG, REG_SP, 0,
> +				 GINSN_SRC_IMM, 0, 8,
> +				 GINSN_DST_REG, REG_SP, 0);
> +	  ginsn_set_where (ginsn);
> +
> +	  /* These instructions have no imm, only indirect access.  */
> +	  gas_assert (i.base_reg);
> +	  dw2_regnum = ginsn_dw2_regnum (i.base_reg);
> +	  ginsn_next = ginsn_new_store (insn_end_sym, false,
> +					GINSN_SRC_INDIRECT, dw2_regnum,
> +					GINSN_DST_INDIRECT, REG_SP, 0);
> +	  ginsn_set_where (ginsn_next);
> +
> +	  gas_assert (!ginsn_link_next (ginsn, ginsn_next));
> +	}
> +      else if (i.tm.extension_opcode == 4)
> +	{
> +	  /* jmp r/m.  E.g., notrack jmp *%rax.  */
> +	  if (i.reg_operands)
> +	    {
> +	      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
> +	      ginsn = ginsn_new_jump (insn_end_sym, true,
> +				      GINSN_SRC_REG, dw2_regnum, NULL);
> +	      ginsn_set_where (ginsn);
> +	    }
> +	  else if (i.mem_operands && i.index_reg)
> +	    {
> +	      /* jmp    *0x0(,%rax,8).  */
> +	      dw2_regnum = ginsn_dw2_regnum (i.index_reg);
> +	      ginsn = ginsn_new_jump (insn_end_sym, true,
> +				      GINSN_SRC_REG, dw2_regnum, NULL);
> +	      ginsn_set_where (ginsn);
> +	    }
> +	  else if (i.mem_operands && i.base_reg)
> +	    {
> +	      dw2_regnum = ginsn_dw2_regnum (i.base_reg);
> +	      ginsn = ginsn_new_jump (insn_end_sym, true,
> +				      GINSN_SRC_REG, dw2_regnum, NULL);
> +	      ginsn_set_where (ginsn);
> +	    }
> +	  else
> +	    /* Catch them for now so we know what we are dealing with.  */
> +	    gas_assert (0);
> +	}
> +      else if (i.tm.extension_opcode == 2)
> +	{
> +	  /* 0xFF /2 (call).  */
> +	  if (i.reg_operands)
> +	    {
> +	      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
> +	      ginsn = ginsn_new_call (insn_end_sym, true,
> +				      GINSN_SRC_REG, dw2_regnum, NULL);
> +	      ginsn_set_where (ginsn);
> +	    }
> +	  else if (i.mem_operands && i.base_reg)
> +	    {
> +	      dw2_regnum = ginsn_dw2_regnum (i.base_reg);
> +	      ginsn = ginsn_new_call (insn_end_sym, true,
> +				      GINSN_SRC_REG, dw2_regnum, NULL);
> +	      ginsn_set_where (ginsn);
> +	    }
> +	  else
> +	    /* Catch them for now so we know what we are dealing with.  */
> +	    gas_assert (0);
> +	}
> +      else
> +	/* Catch them for now so we know what we are dealing with.  */
> +	gas_assert (0);
> +      break;
> +    case 0xc2:
> +    case 0xc3:
> +      /* Near ret.  */
> +      ginsn = ginsn_new_return (insn_end_sym, true);
> +      ginsn_set_where (ginsn);
> +      break;

Where did the immediate operand of 0xC2 go? And what about far return
(and more generally far branches)?

> +    case 0xc9:
> +      /* The 'leave' instruction copies the contents of the RBP register
> +	 into the RSP register to release all stack space allocated to the
> +	 procedure.  */
> +      ginsn = ginsn_new_mov (insn_end_sym, false,
> +			     GINSN_SRC_REG, REG_FP, 0,
> +			     GINSN_DST_REG, REG_SP, 0);
> +      ginsn_set_where (ginsn);
> +
> +      /* Then it restores the old value of the RBP register from the stack.  */
> +      ginsn_next = ginsn_new_load (insn_end_sym, false,
> +				   GINSN_SRC_INDIRECT, REG_SP, 0,
> +				   GINSN_DST_REG, REG_FP);
> +      ginsn_set_where (ginsn_next);
> +
> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
> +      ginsn_last = ginsn_new_add (insn_end_sym, false,
> +				  GINSN_SRC_REG, REG_SP, 0,
> +				  GINSN_SRC_IMM, 0, 8,
> +				  GINSN_DST_REG, REG_SP, 0);
> +      ginsn_set_where (ginsn_next);
> +
> +      gas_assert (!ginsn_link_next (ginsn_next, ginsn_last));
> +      break;

You handle LEAVE, but not ENTER?

> +    case 0xe8:
> +      /* PS: SCFI machinery does not care about which func is being
> +	 called.  OK to skip that info.  */
> +      ginsn = ginsn_new_call (insn_end_sym, true,
> +			      GINSN_SRC_SYMBOL, 0, NULL);
> +      ginsn_set_where (ginsn);
> +      break;
> +    case 0xe9:
> +    case 0xeb:
> +      /* If opcode_space == SPACE_0F, this is a psubw por insn.  Skip it
> +	 for GINSN_GEN_SCFI.  */
> +      if (i.tm.opcode_space == SPACE_0F)
> +	break;
> +
> +      /* Unconditional jmp.  */
> +      ginsn = x86_ginsn_jump (i, insn_end_sym);
> +      ginsn_set_where (ginsn);
> +      break;
> +      /* Fall Through.  */
> +    default:

There's noo fall-through here.

> +      /* TBD_GINSN_GEN_NOT_SCFI: Keep a warning, for now, to find out about
> +	 possibly missed instructions affecting REG_SP or REG_FP.  These
> +	 checks may not be completely exhaustive as they do not involve
> +	 index / base reg.  */

Cases with early "break" should probably come here as well.

> +      if (i.op[0].regs)
> +	{
> +	  dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);

This isn't valid without first having checked that operand 0 is a
register operand: "regs" is a member of a union, and you may not
pass expressionS * into this function.

> +	  if (dw2_regnum == REG_SP || dw2_regnum == REG_FP)
> +	    as_warn_where (last_insn.file, last_insn.line,
> +			   _("SCFI: unhandled op 0x%x may cause incorrect CFI"),
> +			   i.tm.base_opcode);
> +	}
> +      if (i.op[1].regs)
> +	{
> +	  dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
> +	  if (dw2_regnum == REG_SP || dw2_regnum == REG_FP)
> +	    as_warn_where (last_insn.file, last_insn.line,
> +			   _("SCFI: unhandled op 0x%x may cause incorrect CFI"),
> +			   i.tm.base_opcode);
> +	}

What about insns with 3 GPR operands?

Also, why do you use as_warn_where() here? You're still in the
context of synchonously processing the current input line.

> +      /* Keep an eye on other instructions affecting control flow.  */
> +      gas_assert (!i.tm.opcode_modifier.jump);

Again this cannot remain like this, even if here there's no FIXME
remark.

> +      /* TBD_GINSN_GEN_NOT_SCFI: Skip all other opcodes uninteresting for
> +	 GINSN_GEN_SCFI mode.  */

"uninteresting" is an interesting term when dealing with hand-written
assembly. How do you know what an assembly writer is going to do?
While the patch description mentions a few constraints, having come
here I'm under the impression that there are quite a few more of them.
Maybe I simply didn't spot where all caveats are fully spelled out?

> @@ -5128,6 +5842,7 @@ md_assemble (char *line)
>    const char *end, *pass1_mnem = NULL;
>    enum i386_error pass1_err = 0;
>    const insn_template *t;
> +  ginsnS *ginsn;
>  
>    /* Initialize globals.  */
>    current_templates = NULL;
> @@ -5659,6 +6374,13 @@ md_assemble (char *line)
>    /* We are ready to output the insn.  */
>    output_insn ();
>  
> +  /* At this time, SCFI is enabled only for AMD64 ABI.  */
> +  if (flag_synth_cfi && x86_elf_abi == X86_64_ABI)
> +    {
> +      ginsn = ginsn_new (symbol_temp_new_now (), frch_ginsn_gen_mode ());
> +      frch_ginsn_data_append (ginsn);
> +    }

Why would this not work for the x32 ABI?

> @@ -10904,6 +11626,7 @@ s_insn (int dummy ATTRIBUTE_UNUSED)
>    valueT val;
>    bool vex = false, xop = false, evex = false;
>    static const templates tt = { &i.tm, &i.tm + 1 };
> +  ginsnS *ginsn;
>  
>    init_globals ();
>  
> @@ -11658,7 +12381,14 @@ s_insn (int dummy ATTRIBUTE_UNUSED)
>  
>    output_insn ();
>  
> - done:
> +  /* At this time, SCFI is enabled only for AMD64 ABI.  */
> +  if (flag_synth_cfi && x86_elf_abi == X86_64_ABI)
> +    {
> +      ginsn = ginsn_new (symbol_temp_new_now (), frch_ginsn_gen_mode ());
> +      frch_ginsn_data_append (ginsn);
> +    }
> +
> +done:

Please can you leave label indentation alone?

> @@ -15293,6 +16023,9 @@ i386_target_format (void)
>    else
>      as_fatal (_("unknown architecture"));
>  
> +  if (flag_synth_cfi && x86_elf_abi != X86_64_ABI)
> +    as_fatal (_("Synthesizing CFI is not supported for this ABI"));

With this, what use are the x86_elf_abi checks in md_assemble() and
s_insn()?

> --- a/gas/config/tc-i386.h
> +++ b/gas/config/tc-i386.h
> @@ -373,6 +373,27 @@ extern int i386_elf_section_type (const char *, size_t);
>  extern void i386_solaris_fix_up_eh_frame (segT);
>  #endif
>  
> +#define TARGET_USE_GINSN 1
> +/* Allow GAS to synthesize DWARF CFI for hand-written asm.
> +   PS: TARGET_USE_CFIPOP is a pre-condition.  */
> +#define TARGET_USE_SCFI 1
> +/* Identify the maximum DWARF register number of all the registers being
> +   tracked for SCFI.  This is the last DWARF register number of the set
> +   of SP, BP, and all callee-saved registers.  For AMD64, this means
> +   R15 (15).  Use SCFI_CALLEE_SAVED_REG_P to identify which registers
> +   are callee-saved from this set.  */
> +#define SCFI_NUM_REGS 15

Is this "number of" (and then having a value of 16 to permit covering
register 15), or is this "maximum register number" (and then needing a
different name)?

> --- /dev/null
> +++ b/gas/ginsn.c

I'll see about replying on the non-x86 parts of this separately.

Jan
  
Indu Bhagat Nov. 2, 2023, 8:15 a.m. UTC | #2
Hi Jan,

Thanks for reviewing.

On 10/31/23 07:10, Jan Beulich wrote:
> On 30.10.2023 17:51, Indu Bhagat wrote:
>> gas/
>> 	* Makefile.am: Add new files.
>> 	* Makefile.in: Regenerated.
>> 	* as.c (defined): Handle documentation and listing option for
>> 	ginsns and SCFI.
>> 	* config/obj-elf.c (obj_elf_size): Invoke ginsn_data_end.
>> 	(obj_elf_type): Invoke ginsn_data_begin.
>> 	* config/tc-i386.c (ginsn_new): New functionality to generate
>> 	ginsns.
>> 	(x86_scfi_callee_saved_p): New function.
>> 	(ginsn_dw2_regnum): Likewise.
>> 	(ginsn_set_where): Likewise.
>> 	(x86_ginsn_alu): Likewise.
>> 	(x86_ginsn_move): Likewise.
>> 	(x86_ginsn_lea): Likewise.
>> 	(x86_ginsn_jump): Likewise.
>> 	(x86_ginsn_jump_cond): Likewise.
>> 	(md_assemble): Invoke ginsn_new.
>> 	(s_insn): Likewise.
>> 	(i386_target_format): Add hard error for usage of --scfi with non AMD64 ABIs.
>> 	* config/tc-i386.h (TARGET_USE_GINSN): New definition.
>> 	(TARGET_USE_SCFI): Likewise.
>> 	(SCFI_NUM_REGS): Likewise.
>> 	(REG_FP): Likewise.
>> 	(REG_SP): Likewise.
>> 	(SCFI_INIT_CFA_OFFSET): Likewise.
>> 	(SCFI_CALLEE_SAVED_REG_P): Likewise.
>> 	(x86_scfi_callee_saved_p): Likewise.
> 
> For this arch-specific code there's a fundamental question of maintenance
> cost here: It doesn't look very reasonable to me to demand of people adding
> support for new ISA extensions to also take into consideration all of this
> new machinery. Yet if any such addition affects SCFI, things will go out-
> of-sync without updating this code as well. It may not be very often that
> such updating is necessary, but right now there's APX work in progress
> which I expect will need dealing with here as well.
> 

I understand your concerns.  FWIW, for SCFI, we will need to translate 
only a subset of instructions into ginsns (change of flow insns, 
save/restore and arith on REG_SP/REG_FP).  For APX, I see that there is 
are new instructions that fall in this set unfortunately.  Hopefully the 
set remains small. But in general, for future additions (and for APX 
currently), there will be time to act as SCFI is for hand-written asm 
for which synthesizing CFI is desired, not for all code that GAS 
necessarily has to deal with.

At the moment, our target is to see if the kernel can use SCFI for a 
subset of their hand-written asm for x86_64 and aarch64.

>> --- a/gas/as.c
>> +++ b/gas/as.c
>> @@ -45,6 +45,7 @@
>>   #include "codeview.h"
>>   #include "bfdver.h"
>>   #include "write.h"
>> +#include "ginsn.h"
>>   
>>   #ifdef HAVE_ITBL_CPU
>>   #include "itbl-ops.h"
>> @@ -245,6 +246,7 @@ Options:\n\
>>                         	  d      omit debugging directives\n\
>>                         	  g      include general info\n\
>>                         	  h      include high-level source\n\
>> +			  i      include ginsn and synthesized CFI info\n\
>>                         	  l      include assembly\n\
>>                         	  m      include macro expansions\n\
>>                         	  n      omit forms processing\n\
> 
> Please can you make indentation match neighboring code?
> 

OK.

>> --- a/gas/config/obj-elf.c
>> +++ b/gas/config/obj-elf.c
>> @@ -24,6 +24,7 @@
>>   #include "subsegs.h"
>>   #include "obstack.h"
>>   #include "dwarf2dbg.h"
>> +#include "ginsn.h"
>>   
>>   #ifndef ECOFF_DEBUGGING
>>   #define ECOFF_DEBUGGING 0
>> @@ -2302,6 +2303,13 @@ obj_elf_size (int ignore ATTRIBUTE_UNUSED)
>>         symbol_get_obj (sym)->size = XNEW (expressionS);
>>         *symbol_get_obj (sym)->size = exp;
>>       }
>> +
>> +  /* If the symbol in the directive matches the current function being
>> +     processed, indicate end of the current stream of ginsns.  */
>> +  if (flag_synth_cfi
>> +      && S_IS_FUNCTION (sym) && sym == ginsn_data_func_symbol ())
>> +    ginsn_data_end (symbol_temp_new_now ());
>> +
>>     demand_empty_rest_of_line ();
>>   }
> 
> Besides the restrictions mentioned, presence of this just in obj-elf.c
> means non-ELF targets also won't be able to use this. You'll therefore
> want to adjust the config/tc-i386.h such that e.g. COFF targets don't
> needlessly have a large set of dead code compiled in.
> 

OK.

>> --- a/gas/config/tc-i386.c
>> +++ b/gas/config/tc-i386.c
>> @@ -30,6 +30,7 @@
>>   #include "subsegs.h"
>>   #include "dwarf2dbg.h"
>>   #include "dw2gencfi.h"
>> +#include "scfi.h"
>>   #include "gen-sframe.h"
>>   #include "sframe.h"
>>   #include "elf/x86-64.h"
>> @@ -193,8 +194,11 @@ static unsigned int x86_isa_1_used;
>>   static unsigned int x86_feature_2_used;
>>   /* Generate x86 used ISA and feature properties.  */
>>   static unsigned int x86_used_note = DEFAULT_X86_USED_NOTE;
>> +
>>   #endif
> 
> Nit: Stray change?
> 
>> +static ginsnS *ginsn_new (symbolS *sym, enum ginsn_gen_mode gmode);
> 
> I don't see the need for this: The function definition lives ahead of any
> caller afaics.
> 

OK to both.

>> @@ -5116,6 +5120,716 @@ static INLINE bool may_need_pass2 (const insn_template *t)
>>   	       && t->base_opcode == 0x63);
>>   }
>>   
>> +bool
>> +x86_scfi_callee_saved_p (uint32_t dw2reg_num)
>> +{
>> +  if (dw2reg_num == 3 /* rbx.  */
>> +      || dw2reg_num == REG_FP /* rbp.  */
>> +      || dw2reg_num == REG_SP /* rsp.  */
>> +      || (dw2reg_num >= 12 && dw2reg_num <= 15) /* r12 - r15.  */)
>> +    return true;
> 
> Non-GPRs aren't of interest here?
> 
> What about the Windows ABI, which is also used in the EFI world?
> 

Windows ABI can be worked out later, if there is interest.  The current 
set of abstractions used in SCFI machinery are being worked out while 
ensuring that they work for at least two ABIs at the minimum - AMD64 and 
AAPCS64. So adding support for another ABI, hopefully gets some help.

>> +  return false;
>> +}
>> +
>> +static uint32_t
>> +ginsn_dw2_regnum (const reg_entry *ireg)
>> +{
>> +  /* PS: Note the data type here as int32_t, because of Dw2Inval (-1).  */
>> +  int32_t dwarf_reg = Dw2Inval;
>> +  const reg_entry *temp;
>> +
>> +  if (ireg->dw2_regnum[0] == Dw2Inval && ireg->dw2_regnum[1] == Dw2Inval)
>> +    return dwarf_reg;
>> +
>> +  dwarf_reg = ireg->dw2_regnum[flag_code >> 1];
> 
> flag_code == CODE_64BIT would likely be more robust.
> 
> Also are all calls here synchronous, i.e. happening right when a
> particular insn is processed? Otherwise what about flag_code changing
> in between?
> 

Looks like I havent factored that in. I think we will need to error out 
for now if flag_code changes in between.  I will need to take a look at 
the instructions and ABI differences to see what changes are necessary 
to support this.

> Plus in how far does it make sense to handle other than CODE_64BIT when
> you tie yourself to the SysV 64-bit ABI?
> 
>> +  if (dwarf_reg == Dw2Inval)
>> +    {
>> +      temp = ireg + 16;
>> +      dwarf_reg = ginsn_dw2_regnum (temp);
>> +    }
> 
> What is this about? This clearly needs a comment, and it may also need
> a comment in opcodes/i386-reg.tbl to clarify that certain ordering
> requirements have to be retained. It also wants to be clarified that
> the recursion here won't be indefinite.
> 

This code is indeed relying on the ordering of the the entries in 
i386_regtab [].  Its not a good way to achieve what it is doing, but I 
found no other way at that time. I will add code comments.

>> +  if (dwarf_reg == Dw2Inval)
>> +    gas_assert (1); /* Needs to be addressed.  */
> 
> This is dead code (you appear to mean 0 instead of 1). Yet even then
> still gas_assert (dwarf_reg != Dw2Inval) please.
> 

Will fix.

>> +  return (uint32_t) dwarf_reg;
>> +}
>> +
>> +static void
>> +ginsn_set_where (ginsnS* ginsn)
> 
> Nit: Misplaced *.
> 

OK.

>> +{
>> +  const char *file;
>> +  unsigned int line;
>> +  file = as_where (&line);
>> +  ginsn_set_file_line (ginsn, file, line);
>> +}
> 
> This function, also from its name, isn't x86-specific, is it? As such
> it wants to live in ginsn.[ch].
> 

OK.

>> +static ginsnS *
>> +x86_ginsn_alu (i386_insn insn, symbolS *insn_end_sym)
>> +{
>> +  offsetT src_imm;
>> +  uint32_t dw2_regnum;
>> +  enum ginsn_src_type src_type;
>> +  enum ginsn_dst_type dst_type;
>> +  ginsnS *ginsn = NULL;
>> +
>> +  /* FIXME - create ginsn for REG_SP target only ? */
>> +  /* Map for insn.tm.extension_opcode
>> +     000 ADD    100 AND
>> +     001 OR     101 SUB
>> +     010 ADC    110 XOR
>> +     011 SBB    111 CMP  */
>> +
>> +  /* add/sub imm, %reg.
>> +     and imm, %reg only at this time for SCFI. */
>> +  if (!(insn.tm.extension_opcode == 0
>> +	|| insn.tm.extension_opcode == 4
>> +	|| insn.tm.extension_opcode == 5))
>> +    return ginsn;
> 
> Why is AND permitted, but OR/XOR aren't?
> 
> Also this is about ALU insns with immediate operands only, yet that
> fact isn't reflected in the function name.
> 

OR/XOR should be handled. I will fix this.

This function is handling opcodes with imm only, true.  ADD/SUB with reg 
are handled elsewhere (with opcode == 0x1 and opcode == 0x29).

>> +  /* TBD_GINSN_REPRESENTATION_LIMIT: There is no representation for when a
>> +     symbol is used as an operand, like so:
>> +	  addq    $simd_cmp_op+8, %rdx
>> +     Skip generating any ginsn for this.  */
>> +  if (insn.imm_operands == 1
>> +      && insn.op[0].imms->X_op == O_symbol)
>> +    return ginsn;
>> +
>> +  gas_assert (insn.imm_operands == 1
>> +	      && insn.op[0].imms->X_op == O_constant);
> 
> Perhaps less fragile if you use != O_constant in the preceding if()?
> The remaining half could the move ahead of that if(), allowing half
> of its condition to also be dropped.
> 
>> +  src_imm = insn.op[0].imms->X_add_number;
>> +  /* The second operand may be a register or indirect access.  */
>> +  if (insn.mem_operands == 1 && insn.base_reg)
>> +    {
>> +      dw2_regnum = ginsn_dw2_regnum (insn.base_reg);
>> +      src_type = GINSN_SRC_INDIRECT;
>> +      dst_type = GINSN_DST_INDIRECT;
> 
> The possibly in use index register isn't of interest in this case?
> Nor the displacement in the memory operand, ...
> 

Not of interest, No.  When src_type and dst_type is INDIRECT.

>> +    }
>> +  else if (insn.mem_operands == 1 && insn.index_reg)
>> +    {
>> +      dw2_regnum = ginsn_dw2_regnum (insn.index_reg);
>> +      src_type = GINSN_SRC_INDIRECT;
>> +      dst_type = GINSN_DST_INDIRECT;
> 
> ... similarly applicable here? Nor a segment override?

Segment override probably is not of interest for SCFI.

> 
>> +    }
>> +  else
>> +    {
>> +      gas_assert (insn.reg_operands == 1);
> 
> Afaict this will trigger when the memory operand has neither base
> nor index.
> 

memory operand with neither base nor index ?  An asm instruction example 
will be helpful.

>> +      dw2_regnum = ginsn_dw2_regnum (insn.op[1].regs);
>> +      src_type = GINSN_SRC_REG;
>> +      dst_type = GINSN_DST_REG;
>> +    }
>> +
>> +  /* For ginsn, keep the imm as second src operand.  */
>> +  if (insn.tm.extension_opcode == 5)
>> +    ginsn = ginsn_new_sub (insn_end_sym, true,
>> +			   src_type, dw2_regnum, 0,
>> +			   GINSN_SRC_IMM, 0, src_imm,
>> +			   dst_type, dw2_regnum, 0);
>> +  else if (insn.tm.extension_opcode == 4)
>> +    ginsn = ginsn_new_and (insn_end_sym, true,
>> +			   src_type, dw2_regnum, 0,
>> +			   GINSN_SRC_IMM, 0, src_imm,
>> +			   dst_type, dw2_regnum, 0);
>> +  else if (insn.tm.extension_opcode == 0)
>> +    ginsn = ginsn_new_add (insn_end_sym, true,
>> +			   src_type, dw2_regnum, 0,
>> +			   GINSN_SRC_IMM, 0, src_imm,
>> +			   dst_type, dw2_regnum, 0);
> 
> I think this would benefit from setting a function pointer near the
> top of the function. Else can this please at least be put in switch()
> form?
> 

In this case, function pointer will also work.

>> +  ginsn_set_where (ginsn);
>> +
>> +  return ginsn;
>> +}
>> +
>> +static ginsnS *
>> +x86_ginsn_move (i386_insn insn, symbolS *insn_end_sym)
>> +{
>> +  ginsnS *ginsn;
>> +  uint16_t opcode;
>> +  uint32_t dst_reg;
>> +  uint32_t src_reg;
>> +  offsetT dst_disp;
>> +  offsetT src_disp;
>> +  const reg_entry *dst = NULL;
>> +  const reg_entry *src = NULL;
>> +  enum ginsn_dst_type dst_type;
>> +  enum ginsn_src_type src_type;
>> +
>> +  opcode = insn.tm.base_opcode;
>> +  src_type = GINSN_SRC_REG;
>> +  src_disp = dst_disp = 0;
>> +  dst_type = GINSN_DST_REG;
> 
> Please can such be initializers of their variables?
> 

OK.

>> +  if (opcode == 0x8b)
>> +    {
>> +      /* mov  disp(%reg), %reg.  */
>> +      if (insn.mem_operands && insn.base_reg)
>> +	{
>> +	  src = insn.base_reg;
>> +	  if (insn.disp_operands == 1)
>> +	    src_disp = insn.op[0].disps->X_add_number;
>> +	  src_type = GINSN_SRC_INDIRECT;
>> +	}
>> +      else
>> +	src = insn.op[0].regs;
> 
> What about disp(%reg,%reg) or disp(,%reg) kinds of memory operands?
> 

I will take a look.

>> +      dst = insn.op[1].regs;
>> +    }
>> +  else if (opcode == 0x89 || opcode == 0x88)
> 
> How come 0x88 is handled here, but 0x8a isn't handled above?
> 

It should have been. I will take a look.

>> +    {
>> +      /* mov %reg, disp(%reg).  */
>> +      src = insn.op[0].regs;
>> +      if (insn.mem_operands && insn.base_reg)
>> +	{
>> +	  dst = insn.base_reg;
>> +	  if (insn.disp_operands == 1)
>> +	    dst_disp = insn.op[1].disps->X_add_number;
>> +	  dst_type = GINSN_DST_INDIRECT;
>> +	}
>> +      else
>> +	dst = insn.op[1].regs;
>> +    }
>> +
>> +  src_reg = ginsn_dw2_regnum (src);
>> +  dst_reg = ginsn_dw2_regnum (dst);
>> +
>> +  ginsn = ginsn_new_mov (insn_end_sym, true,
>> +			 src_type, src_reg, src_disp,
>> +			 dst_type, dst_reg, dst_disp);
>> +  ginsn_set_where (ginsn);
>> +
>> +  return ginsn;
>> +}
>> +
>> +static ginsnS *
>> +x86_ginsn_lea (i386_insn insn, symbolS *insn_end_sym)
>> +{
>> +  offsetT src_disp = 0;
>> +  ginsnS *ginsn = NULL;
>> +  uint32_t base_reg;
>> +  uint32_t index_reg;
>> +  offsetT index_scale;
>> +  uint32_t dst_reg;
>> +
>> +  if (!insn.index_reg && !insn.base_reg)
>> +    {
>> +      /* lea symbol, %rN.  */
>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>> +      /* FIXME - Skip encoding information about the symbol.
>> +	 This is TBD_GINSN_INFO_LOSS, but it is fine if the mode is
>> +	 GINSN_GEN_SCFI.  */
>> +      ginsn = ginsn_new_mov (insn_end_sym, false,
>> +			     GINSN_SRC_IMM, 0xf /* arbitrary const.  */, 0,
>> +			     GINSN_DST_REG, dst_reg, 0);
>> +    }
>> +  else if (insn.base_reg && !insn.index_reg)
>> +    {
>> +      /* lea    -0x2(%base),%dst.  */
>> +      base_reg = ginsn_dw2_regnum (insn.base_reg);
>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>> +
>> +      if (insn.disp_operands)
>> +	src_disp = insn.op[0].disps->X_add_number;
> 
> What if the displacement expression is other than O_constant?
> 

For SCFI, a "complex" lea instruction will imply some untraceable change 
to the destination reg.  For SCFI, the extent of untraceable change is 
not of interest, hence, in general, some information loss in ginsn is 
tolerable.

I will have to take a look at such instructions to see what the options 
are.  If you have a example handy, it will be helpful.

>> +      if (src_disp)
>> +	/* Generate an ADD ginsn.  */
>> +	ginsn = ginsn_new_add (insn_end_sym, true,
>> +			       GINSN_SRC_REG, base_reg, 0,
>> +			       GINSN_SRC_IMM, 0, src_disp,
>> +			       GINSN_DST_REG, dst_reg, 0);
>> +      else
>> +	  /* Generate a MOV ginsn.  */
>> +	  ginsn = ginsn_new_mov (insn_end_sym, true,
>> +				 GINSN_SRC_REG, base_reg, 0,
>> +				 GINSN_DST_REG, dst_reg, 0);
>> +    }
>> +  else if (!insn.base_reg && insn.index_reg)
>> +    {
>> +      /* lea (,%index,imm), %dst.  */
>> +      /* FIXME - Skip encoding an explicit multiply operation, instead use
>> +	 GINSN_TYPE_OTHER.  This is TBD_GINSN_INFO_LOSS, but it is fine if
>> +	 the mode is GINSN_GEN_SCFI.  */
>> +      index_scale = insn.log2_scale_factor;
>> +      index_reg = ginsn_dw2_regnum (insn.index_reg);
>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>> +      ginsn = ginsn_new_other (insn_end_sym, true,
>> +			       GINSN_SRC_REG, index_reg,
>> +			       GINSN_SRC_IMM, index_scale,
>> +			       GINSN_DST_REG, dst_reg);
>> +    }
>> +  else
>> +    {
>> +      /* lea disp(%base,%index,imm) %dst.  */
>> +      /* FIXME - Skip encoding information about the disp and imm for index
>> +	 reg.  This is TBD_GINSN_INFO_LOSS, but it is fine if the mode is
>> +	 GINSN_GEN_SCFI.  */
>> +      base_reg = ginsn_dw2_regnum (insn.base_reg);
>> +      index_reg = ginsn_dw2_regnum (insn.index_reg);
>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>> +      /* Generate an ADD ginsn.  */
>> +      ginsn = ginsn_new_add (insn_end_sym, true,
>> +			     GINSN_SRC_REG, base_reg, 0,
>> +			     GINSN_SRC_REG, index_reg, 0,
>> +			     GINSN_DST_REG, dst_reg, 0);
> 
> The comment mentions the displacement, but it's not passed on? In the earlier
> "else if" block you also pay attention to the scla factor, but here you don't?
> That said, I think I'm confused anyway about the FIXME comments.
> 

Ah, This is what I was hinting at in the previous comment.

So whats going on here is the following -

First, An "add reg1, reg2, reg3" (reg3 being the destination) ginsn is 
only interesting for SCFI if the reg3 is either REG_FP or REG_SP. We 
still generate them all nevertheless for now.

Second, We say that "add reg1, reg2, reg3" makes reg3 "untraceable". 
This is based on the observation that static stack usage in a function 
will use imm based arithmetic insns. So, if reg3 is REG_SP, REG_FP, 
these instructions make REG_SP / REG_FP "untraceable".

Note that, We are not allowing all registers for CFA tracking. (Now 
_this_ is a constraint, yes. And which is why we dont support DRAP usage 
currently. But its a specific pattern that we can accommodate in future.)

Hence, tracking more information for such (untraceable) instructions is 
unnecessary for SCFI purposes.  If and when GAS uses ginsn (in the i386 
backend) for other purposes, such information loss will need to be 
revisited.

>> +    }
>> +
>> +  ginsn_set_where (ginsn);
>> +
>> +  return ginsn;
>> +}
>> +
>> +static ginsnS *
>> +x86_ginsn_jump (i386_insn insn, symbolS *insn_end_sym)
>> +{
>> +  ginsnS *ginsn = NULL;
>> +  symbolS *src_symbol;
>> +
>> +  gas_assert (insn.disp_operands == 1);
>> +
>> +  if (insn.op[0].disps->X_op == O_symbol)
>> +    {
>> +      src_symbol = insn.op[0].disps->X_add_symbol;
>> +      /* The jump target is expected to be a symbol with 0 addend.
>> +	 Assert for now to see if this assumption is true.  */
>> +      gas_assert (insn.op[0].disps->X_add_number == 0);
> 
> If you assert this means elsewhere this is being checked and a call
> here avoided in case the value is non-zero. I can't spot such a
> check, though.
> 

No, this assert was for me to take action if this assumption is not 
true.  Just to make sure I see what needs to be done if there are other 
encodings to take care of.  Have not hit this yet in my tests.

>> +      ginsn = ginsn_new_jump (insn_end_sym, true,
>> +			      GINSN_SRC_SYMBOL, 0, src_symbol);
>> +
>> +      ginsn_set_where (ginsn);
>> +    }
>> +
>> +  return ginsn;
>> +}
> 
> Further, what about XABORT transferring control to what XBEGIN has
> supplied? (XBEGIN can, in a sense, also be considered a [conditional]
> branch.)
> 

My plan after writing this x86 -> ginsn translator was:
There are and will remain many machine instructions that may need to be 
supported depending on hand-written assmebly programmers use.  This is 
the reason I had left some asserts towards the end in the ginsn_new () 
API: if any instructions which affect the control flow, or where the 
destination is REG_FP / REG_SP, or push* / pop*  are not handled, we 
will have an assert fail and GAS will need to handle it.

>> +static ginsnS *
>> +x86_ginsn_jump_cond (i386_insn insn, symbolS *insn_end_sym)
>> +{
>> +  ginsnS *ginsn = NULL;
>> +  symbolS *src_symbol;
>> +
>> +  /* TBD_GINSN_GEN_NOT_SCFI: Ignore move to or from xmm reg for mode.  */
>> +  if (i.tm.opcode_space == SPACE_0F)
>> +    return ginsn;
> 
> What is the comment about? And what about SPACE_0F-encoded conditional
> jumps (Jcc <disp32>)? And where are LOOP and J{E,R}CXZ handled?
> 

These need to be handled.  I will take a look.

>> +  gas_assert (insn.disp_operands == 1);
>> +
>> +  if (insn.op[0].disps->X_op == O_symbol)
>> +    {
>> +      src_symbol = insn.op[0].disps->X_add_symbol;
>> +      /* The jump target is expected to be a symbol with 0 addend.
>> +	 Assert for now to see if this assumption is true.  */
>> +      gas_assert (insn.op[0].disps->X_add_number == 0);
>> +      ginsn = ginsn_new_jump_cond (insn_end_sym, true,
>> +				   GINSN_SRC_SYMBOL, 0, src_symbol);
>> +      ginsn_set_where (ginsn);
>> +    }
>> +  else
>> +    /* Catch them for now so we know what we are dealing with.  */
>> +    gas_assert (0);
> 
> I'm going from the assumption that this (and alike) will be addressed
> before this series is committed?
> 

I will work on the ones you have raised above in the review. My plan was 
to exercise this code in different ways after commmitting and address 
the failures as I run into them.

>> +  return ginsn;
>> +}
>> +
>> +/* Generate one or more GAS instructions for the current machine dependent
>> +   instruction.
>> +
>> +   Returns the head of linked list of ginsn(s) added, if success;
>> +   Returns NULL if failure.  */
>> +
>> +static ginsnS *
>> +ginsn_new (symbolS *insn_end_sym, enum ginsn_gen_mode gmode)
> 
> x86_ginsn_new()?
> 

Yes, this looks better.

>> +{
>> +  uint16_t opcode;
>> +  uint32_t dw2_regnum;
>> +  uint32_t src2_dw2_regnum;
>> +  int32_t gdisp = 0;
>> +  ginsnS *ginsn = NULL;
>> +  ginsnS *ginsn_next = NULL;
>> +  ginsnS *ginsn_last = NULL;
>> +
>> +  /* FIXME - Need a way to check whether the decoding is sane.  The specific
>> +     checks around i.tm.opcode_space were added as issues were seen.  Likely
>> +     insufficient.  */
> 
> Furthermore opcode encoding space (SPACE_...) need to be taken into
> account in all cases.
> 
>> +  /* Currently supports generation of selected ginsns, sufficient for
>> +     the use-case of SCFI only.  To remove this condition will require
>> +     work on this target-specific process of creation of ginsns.  Some
>> +     of such places are tagged with TBD_GINSN_GEN_NOT_SCFI to serve as
>> +     examples.  */
>> +  if (gmode != GINSN_GEN_SCFI)
>> +    return ginsn;
>> +
>> +  opcode = i.tm.base_opcode;
>> +
>> +  switch (opcode)
>> +    {
>> +    case 0x1:
>> +      /* add reg, reg.  */
>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
> 
> You don't care about opcode 0 (byte operation). Then what about 16-bit
> operand size? Or, since we're talking of a 64-bit-ABI-only feature,
> even 32-bit operand size?
> 

Operand size in some cases does not affect SCFI. So we dont keep 
information about operand sizes.  The case that I should handle operand 
size is when they are pushed to / popped from stack.

> Also what about opcode 0x3?
> 

LSL instruction ? It doesnt look like it can affect DWARF CFI of a 
function. Please correct me if this is wrong.

>> +      if (i.reg_operands == 2)
>> +	{
>> +	  src2_dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
>> +	  ginsn = ginsn_new_add (insn_end_sym, true,
>> +				 GINSN_SRC_REG, dw2_regnum, 0,
>> +				 GINSN_SRC_REG, src2_dw2_regnum, 0,
>> +				 GINSN_DST_REG, src2_dw2_regnum, 0);
>> +	  ginsn_set_where (ginsn);
>> +	}
>> +      else if (i.mem_operands && i.base_reg)
>> +	{
>> +	  src2_dw2_regnum = ginsn_dw2_regnum (i.base_reg);
>> +	  if (i.disp_operands == 1)
>> +	    gdisp = i.op[1].disps->X_add_number;
>> +
>> +	  ginsn = ginsn_new_add (insn_end_sym, true,
>> +				 GINSN_SRC_REG, dw2_regnum, 0,
>> +				 GINSN_SRC_INDIRECT, src2_dw2_regnum, gdisp,
>> +				 GINSN_DST_INDIRECT, src2_dw2_regnum, gdisp);
>> +	  ginsn_set_where (ginsn);
>> +	}
>> +      else
>> +	/* Catch them for now so we know what we are dealing with.  */
>> +	gas_assert (0);
>> +
>> +      break;
>> +    case 0x29:
>> +      /* If opcode_space == SPACE_0F, this is a movaps insn.  Skip it
>> +	 for GINSN_GEN_SCFI.  */
>> +      if (i.tm.opcode_space == SPACE_0F)
>> +	break;
> 
> Extending on the earlier related comment: Why would you exclude just SPACE_0F?
> 

Perhaps here I should check that space == SPACE_BASE and only allow 
that? And also for others.

>> +      /* sub reg, reg.  */
>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>> +
>> +      if (i.reg_operands == 2)
>> +	{
>> +	  src2_dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
>> +	  ginsn = ginsn_new_sub (insn_end_sym, true,
>> +				 GINSN_SRC_REG, dw2_regnum, 0,
>> +				 GINSN_SRC_REG, src2_dw2_regnum, 0,
>> +				 GINSN_DST_REG, src2_dw2_regnum, 0);
>> +	  ginsn_set_where (ginsn);
>> +	}
>> +      else if (i.mem_operands && i.base_reg)
>> +	{
>> +	  src2_dw2_regnum = ginsn_dw2_regnum (i.base_reg);
>> +	  if (i.disp_operands == 1)
>> +	    gdisp = i.op[1].disps->X_add_number;
>> +
>> +	  ginsn = ginsn_new_sub (insn_end_sym, true,
>> +				 GINSN_SRC_REG, dw2_regnum, 0,
>> +				 GINSN_SRC_INDIRECT, src2_dw2_regnum, gdisp,
>> +				 GINSN_DST_INDIRECT, src2_dw2_regnum, gdisp);
>> +	  ginsn_set_where (ginsn);
>> +	}
>> +      else
>> +	/* Catch them for now so we know what we are dealing with.  */
>> +	gas_assert (0);
>> +
>> +      break;
>> +    case 0xa0:
>> +    case 0xa8:
> 
> Since individual case blocks are non-trivial, can there please be blank
> lines between any two non-fall-through case blocks?
> 

OK.

>> +      /* If opcode_space != SPACE_0F, this is a test insn.  Skip it
>> +	 for GINSN_GEN_SCFI.  */
>> +      if (i.tm.opcode_space != SPACE_0F)
>> +	break;
> 
> While here you properly use !=, now the comment is wrong.
> 
>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>> +      /* push fs / push gs.  */
>> +      ginsn = ginsn_new_sub (insn_end_sym, false,
>> +			     GINSN_SRC_REG, REG_SP, 0,
>> +			     GINSN_SRC_IMM, 0, 8,
> 
> Note how the 8 here assumes flag_code == CODE_64BIT. (You further assume
> no operand size override here.)
> 

Hmm. Yes, I do assume no operand size override here. I will need to 
understand how to calculate the operand size here. Looks like I need to 
check for instruction prefixes 66H or REX.W.  Is there an API in the 
backend that be used for this ?

>> +			     GINSN_DST_REG, REG_SP, 0);
>> +      ginsn_set_where (ginsn);
>> +
>> +      ginsn_next = ginsn_new_store (insn_end_sym, false,
>> +				    GINSN_SRC_REG, dw2_regnum,
>> +				    GINSN_DST_INDIRECT, REG_SP, 0);
>> +      ginsn_set_where (ginsn_next);
>> +
>> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
>> +      break;
>> +    case 0xa1:
>> +    case 0xa9:
>> +      /* If opcode_space != SPACE_0F, this is test insn.  Skip it
>> +	 for GINSN_GEN_SCFI.  */
>> +      if (i.tm.opcode_space != SPACE_0F)
>> +	break;
>> +
>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>> +      /* pop fs / pop gs.  */
>> +      ginsn = ginsn_new_load (insn_end_sym, false,
>> +			      GINSN_SRC_INDIRECT, REG_SP, 0,
>> +			      GINSN_DST_REG, dw2_regnum);
>> +      ginsn_set_where (ginsn);
>> +
>> +      ginsn_next = ginsn_new_add (insn_end_sym, false,
>> +				  GINSN_SRC_REG, REG_SP, 0,
>> +				  GINSN_SRC_IMM, 0, 8,
>> +				  GINSN_DST_REG, REG_SP, 0);
>> +      ginsn_set_where (ginsn_next);
>> +
>> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
>> +      break;
>> +    case 0x50 ... 0x57:
>> +      /* push reg.  */
>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>> +      ginsn = ginsn_new_sub (insn_end_sym, false,
>> +			     GINSN_SRC_REG, REG_SP, 0,
>> +			     GINSN_SRC_IMM, 0, 8,
>> +			     GINSN_DST_REG, REG_SP, 0);
>> +      ginsn_set_where (ginsn);
>> +
>> +      ginsn_next = ginsn_new_store (insn_end_sym, false,
>> +				    GINSN_SRC_REG, dw2_regnum,
>> +				    GINSN_DST_INDIRECT, REG_SP, 0);
>> +      ginsn_set_where (ginsn_next);
>> +
>> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
>> +      break;
>> +    case 0x58 ... 0x5f:
>> +      if (i.tm.opcode_space != SPACE_BASE)
>> +	break;
>> +      /* pop reg.  */
>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>> +      ginsn = ginsn_new_load (insn_end_sym, false,
>> +			      GINSN_SRC_INDIRECT, REG_SP, 0,
>> +			      GINSN_DST_REG, dw2_regnum);
>> +      ginsn_set_where (ginsn);
>> +
>> +      ginsn_next = ginsn_new_add (insn_end_sym, false,
>> +				  GINSN_SRC_REG, REG_SP, 0,
>> +				  GINSN_SRC_IMM, 0, 8,
>> +				  GINSN_DST_REG, REG_SP, 0);
>> +      ginsn_set_where (ginsn_next);
>> +
>> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
>> +      break;
>> +    case 0x68:
>> +    case 0x6a:
>> +      /* push imm. */
>> +      /* Skip getting the value of imm from machine instruction
>> +	 because for ginsn generation this is not important.  */
> 
> In (e.g.) kernel code model it is possible to do
> 
> 	push	<address>
> 	pop	%rbp
> 
> (could even be %rsp). How is such a frame (or stack) pointer change
> not relevant?
> 

For SCFI, we are tracking _where_ the saves and restore are done. The 
values are not important.

In the above example, push <address> must tell the SCFI machinery that 
stack pointer has been updated.  What value was pushed to stack is not 
important.  Now rbp is a callee-saved register, so one must have saved 
to stack before reovering it (speaking of ABI/calling convention 
compliant asm).  So in the sequence above, the SCFI machinery must 
complain that this looks fishy.

>> +      ginsn = ginsn_new_sub (insn_end_sym, false,
>> +			     GINSN_SRC_REG, REG_SP, 0,
>> +			     GINSN_SRC_IMM, 0, 8,
>> +			     GINSN_DST_REG, REG_SP, 0);
>> +      ginsn_set_where (ginsn);
>> +
>> +      ginsn_next = ginsn_new_store (insn_end_sym, false,
>> +				    GINSN_SRC_IMM, 0,
>> +				    GINSN_DST_INDIRECT, REG_SP, 0);
>> +      ginsn_set_where (ginsn_next);
>> +
>> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
>> +      break;
>> +    case 0x70 ... 0x7f:
>> +      ginsn = x86_ginsn_jump_cond (i, insn_end_sym);
>> +      break;
>> +    case 0x81:
>> +    case 0x83:
>> +      ginsn = x86_ginsn_alu (i, insn_end_sym);
>> +      break;
>> +    case 0x8b:
>> +      /* Move r/m64 to r64.  */
>> +    case 0x88:
>> +    case 0x89:
>> +      /* mov reg, reg/mem.  */
>> +      ginsn = x86_ginsn_move (i, insn_end_sym);
>> +      break;
>> +    case 0x8d:
>> +      /* lea disp(%src), %dst */
>> +      ginsn = x86_ginsn_lea (i, insn_end_sym);
>> +      break;
>> +    case 0x8f:
>> +      /* pop to mem.  */
>> +      gas_assert (i.base_reg);
> 
> This opcode can in principle also have a register operand. We
> _currently_ have no way for a user to request to use this
> alternative encoding, but that's still a latent issue. In any
> event, like elsewhere, all memory operand forms are possible
> here, not just (%reg).
> 
>> +      dw2_regnum = ginsn_dw2_regnum (i.base_reg);
>> +      ginsn = ginsn_new_load (insn_end_sym, false,
>> +			      GINSN_SRC_INDIRECT, REG_SP, 0,
>> +			      GINSN_DST_INDIRECT, dw2_regnum);
> 
> When both operands are "indirect", what's the difference between
> move, load, and store? IOW if the above is permitted, can't all
> three be folded into a single ginsn kind?
> 

In theory, any of mov/load/store ginsn type may be used if both operands 
are indirect mem access.

>> +      ginsn_set_where (ginsn);
>> +
>> +      ginsn_next = ginsn_new_add (insn_end_sym, false,
>> +				  GINSN_SRC_REG, REG_SP, 0,
>> +				  GINSN_SRC_IMM, 0, 8,
>> +				  GINSN_DST_REG, REG_SP, 0);
>> +      ginsn_set_where (ginsn_next);
>> +
>> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
>> +      break;
>> +    case 0x9c:
>> +      /* pushf / pushfd / pushfq.
>> +	 Tracking EFLAGS register by number is not necessary.  */
>> +      ginsn = ginsn_new_sub (insn_end_sym, false,
>> +			     GINSN_SRC_REG, REG_SP, 0,
>> +			     GINSN_SRC_IMM, 0, 8,
> 
> Why does the comment mention PUSHFD when you assume PUSHFQ here?
> 

Pushfd should not be stated here. I see pushfd is not valid for 64-bit 
op mode. Will fix it.

>> +			     GINSN_DST_REG, REG_SP, 0);
>> +      ginsn_set_where (ginsn);
>> +
>> +      ginsn_next = ginsn_new_store (insn_end_sym, false,
>> +				    GINSN_SRC_IMM, 0,
>> +				    GINSN_DST_INDIRECT, REG_SP, 0);
>> +      ginsn_set_where (ginsn_next);
>> +
>> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
>> +
>> +      break;
> 
> Where's POPFD?
> 

Missed it! popf / popfq opcode == 0x9d will be handled.

>> +    case 0xff:
>> +      /* push from mem.  */
>> +      if (i.tm.extension_opcode == 6)
>> +	{
>> +	  ginsn = ginsn_new_sub (insn_end_sym, false,
>> +				 GINSN_SRC_REG, REG_SP, 0,
>> +				 GINSN_SRC_IMM, 0, 8,
>> +				 GINSN_DST_REG, REG_SP, 0);
>> +	  ginsn_set_where (ginsn);
>> +
>> +	  /* These instructions have no imm, only indirect access.  */
>> +	  gas_assert (i.base_reg);
>> +	  dw2_regnum = ginsn_dw2_regnum (i.base_reg);
>> +	  ginsn_next = ginsn_new_store (insn_end_sym, false,
>> +					GINSN_SRC_INDIRECT, dw2_regnum,
>> +					GINSN_DST_INDIRECT, REG_SP, 0);
>> +	  ginsn_set_where (ginsn_next);
>> +
>> +	  gas_assert (!ginsn_link_next (ginsn, ginsn_next));
>> +	}
>> +      else if (i.tm.extension_opcode == 4)
>> +	{
>> +	  /* jmp r/m.  E.g., notrack jmp *%rax.  */
>> +	  if (i.reg_operands)
>> +	    {
>> +	      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>> +	      ginsn = ginsn_new_jump (insn_end_sym, true,
>> +				      GINSN_SRC_REG, dw2_regnum, NULL);
>> +	      ginsn_set_where (ginsn);
>> +	    }
>> +	  else if (i.mem_operands && i.index_reg)
>> +	    {
>> +	      /* jmp    *0x0(,%rax,8).  */
>> +	      dw2_regnum = ginsn_dw2_regnum (i.index_reg);
>> +	      ginsn = ginsn_new_jump (insn_end_sym, true,
>> +				      GINSN_SRC_REG, dw2_regnum, NULL);
>> +	      ginsn_set_where (ginsn);
>> +	    }
>> +	  else if (i.mem_operands && i.base_reg)
>> +	    {
>> +	      dw2_regnum = ginsn_dw2_regnum (i.base_reg);
>> +	      ginsn = ginsn_new_jump (insn_end_sym, true,
>> +				      GINSN_SRC_REG, dw2_regnum, NULL);
>> +	      ginsn_set_where (ginsn);
>> +	    }
>> +	  else
>> +	    /* Catch them for now so we know what we are dealing with.  */
>> +	    gas_assert (0);
>> +	}
>> +      else if (i.tm.extension_opcode == 2)
>> +	{
>> +	  /* 0xFF /2 (call).  */
>> +	  if (i.reg_operands)
>> +	    {
>> +	      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>> +	      ginsn = ginsn_new_call (insn_end_sym, true,
>> +				      GINSN_SRC_REG, dw2_regnum, NULL);
>> +	      ginsn_set_where (ginsn);
>> +	    }
>> +	  else if (i.mem_operands && i.base_reg)
>> +	    {
>> +	      dw2_regnum = ginsn_dw2_regnum (i.base_reg);
>> +	      ginsn = ginsn_new_call (insn_end_sym, true,
>> +				      GINSN_SRC_REG, dw2_regnum, NULL);
>> +	      ginsn_set_where (ginsn);
>> +	    }
>> +	  else
>> +	    /* Catch them for now so we know what we are dealing with.  */
>> +	    gas_assert (0);
>> +	}
>> +      else
>> +	/* Catch them for now so we know what we are dealing with.  */
>> +	gas_assert (0);
>> +      break;
>> +    case 0xc2:
>> +    case 0xc3:
>> +      /* Near ret.  */
>> +      ginsn = ginsn_new_return (insn_end_sym, true);
>> +      ginsn_set_where (ginsn);
>> +      break;
> 
> Where did the immediate operand of 0xC2 go? And what about far return
> (and more generally far branches)?
> 

A return ginsn for SCFI can afford this information loss.

>> +    case 0xc9:
>> +      /* The 'leave' instruction copies the contents of the RBP register
>> +	 into the RSP register to release all stack space allocated to the
>> +	 procedure.  */
>> +      ginsn = ginsn_new_mov (insn_end_sym, false,
>> +			     GINSN_SRC_REG, REG_FP, 0,
>> +			     GINSN_DST_REG, REG_SP, 0);
>> +      ginsn_set_where (ginsn);
>> +
>> +      /* Then it restores the old value of the RBP register from the stack.  */
>> +      ginsn_next = ginsn_new_load (insn_end_sym, false,
>> +				   GINSN_SRC_INDIRECT, REG_SP, 0,
>> +				   GINSN_DST_REG, REG_FP);
>> +      ginsn_set_where (ginsn_next);
>> +
>> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
>> +      ginsn_last = ginsn_new_add (insn_end_sym, false,
>> +				  GINSN_SRC_REG, REG_SP, 0,
>> +				  GINSN_SRC_IMM, 0, 8,
>> +				  GINSN_DST_REG, REG_SP, 0);
>> +      ginsn_set_where (ginsn_next);
>> +
>> +      gas_assert (!ginsn_link_next (ginsn_next, ginsn_last));
>> +      break;
> 
> You handle LEAVE, but not ENTER?
> 

I should have. From the three variants ([Enter imm16,0], [ENTER imm16,1] 
and [ENTER imm16, imm8], the last one is not super clear currently. Is 
it possible for you to pass down some understanding to me on that ?

>> +    case 0xe8:
>> +      /* PS: SCFI machinery does not care about which func is being
>> +	 called.  OK to skip that info.  */
>> +      ginsn = ginsn_new_call (insn_end_sym, true,
>> +			      GINSN_SRC_SYMBOL, 0, NULL);
>> +      ginsn_set_where (ginsn);
>> +      break;
>> +    case 0xe9:
>> +    case 0xeb:
>> +      /* If opcode_space == SPACE_0F, this is a psubw por insn.  Skip it
>> +	 for GINSN_GEN_SCFI.  */
>> +      if (i.tm.opcode_space == SPACE_0F)
>> +	break;
>> +
>> +      /* Unconditional jmp.  */
>> +      ginsn = x86_ginsn_jump (i, insn_end_sym);
>> +      ginsn_set_where (ginsn);
>> +      break;
>> +      /* Fall Through.  */
>> +    default:
> 
> There's noo fall-through here.
> 
>> +      /* TBD_GINSN_GEN_NOT_SCFI: Keep a warning, for now, to find out about
>> +	 possibly missed instructions affecting REG_SP or REG_FP.  These
>> +	 checks may not be completely exhaustive as they do not involve
>> +	 index / base reg.  */
> 
> Cases with early "break" should probably come here as well.
> 
OK.

>> +      if (i.op[0].regs)
>> +	{
>> +	  dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
> 
> This isn't valid without first having checked that operand 0 is a
> register operand: "regs" is a member of a union, and you may not
> pass expressionS * into this function.
> 
>> +	  if (dw2_regnum == REG_SP || dw2_regnum == REG_FP)
>> +	    as_warn_where (last_insn.file, last_insn.line,
>> +			   _("SCFI: unhandled op 0x%x may cause incorrect CFI"),
>> +			   i.tm.base_opcode);
>> +	}
>> +      if (i.op[1].regs)
>> +	{
>> +	  dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
>> +	  if (dw2_regnum == REG_SP || dw2_regnum == REG_FP)
>> +	    as_warn_where (last_insn.file, last_insn.line,
>> +			   _("SCFI: unhandled op 0x%x may cause incorrect CFI"),
>> +			   i.tm.base_opcode);
>> +	}
> 
> What about insns with 3 GPR operands?
> 
> Also, why do you use as_warn_where() here? You're still in the
> context of synchonously processing the current input line.
> 
>> +      /* Keep an eye on other instructions affecting control flow.  */
>> +      gas_assert (!i.tm.opcode_modifier.jump);
> 
> Again this cannot remain like this, even if here there's no FIXME
> remark.
> 

My thinking was to exercise the code more soon and fix the cases as I 
run into them. And do this in iterations after the patches go in.

>> +      /* TBD_GINSN_GEN_NOT_SCFI: Skip all other opcodes uninteresting for
>> +	 GINSN_GEN_SCFI mode.  */
> 
> "uninteresting" is an interesting term when dealing with hand-written
> assembly. How do you know what an assembly writer is going to do?
> While the patch description mentions a few constraints, having come
> here I'm under the impression that there are quite a few more of them.
> Maybe I simply didn't spot where all caveats are fully spelled out?
> 

I have tried to clear up some things in my comments above. Perhaps 
scfi.c will make things clearer.

>> @@ -5128,6 +5842,7 @@ md_assemble (char *line)
>>     const char *end, *pass1_mnem = NULL;
>>     enum i386_error pass1_err = 0;
>>     const insn_template *t;
>> +  ginsnS *ginsn;
>>   
>>     /* Initialize globals.  */
>>     current_templates = NULL;
>> @@ -5659,6 +6374,13 @@ md_assemble (char *line)
>>     /* We are ready to output the insn.  */
>>     output_insn ();
>>   
>> +  /* At this time, SCFI is enabled only for AMD64 ABI.  */
>> +  if (flag_synth_cfi && x86_elf_abi == X86_64_ABI)
>> +    {
>> +      ginsn = ginsn_new (symbol_temp_new_now (), frch_ginsn_gen_mode ());
>> +      frch_ginsn_data_append (ginsn);
>> +    }
> 
> Why would this not work for the x32 ABI?
> 

As per my understanding, all of this should work for X86_64_X32_ABI. Its 
just that I would like to double-check and run tests.

>> @@ -10904,6 +11626,7 @@ s_insn (int dummy ATTRIBUTE_UNUSED)
>>     valueT val;
>>     bool vex = false, xop = false, evex = false;
>>     static const templates tt = { &i.tm, &i.tm + 1 };
>> +  ginsnS *ginsn;
>>   
>>     init_globals ();
>>   
>> @@ -11658,7 +12381,14 @@ s_insn (int dummy ATTRIBUTE_UNUSED)
>>   
>>     output_insn ();
>>   
>> - done:
>> +  /* At this time, SCFI is enabled only for AMD64 ABI.  */
>> +  if (flag_synth_cfi && x86_elf_abi == X86_64_ABI)
>> +    {
>> +      ginsn = ginsn_new (symbol_temp_new_now (), frch_ginsn_gen_mode ());
>> +      frch_ginsn_data_append (ginsn);
>> +    }
>> +
>> +done:
> 
> Please can you leave label indentation alone?
> 

Oops, will fix.

>> @@ -15293,6 +16023,9 @@ i386_target_format (void)
>>     else
>>       as_fatal (_("unknown architecture"));
>>   
>> +  if (flag_synth_cfi && x86_elf_abi != X86_64_ABI)
>> +    as_fatal (_("Synthesizing CFI is not supported for this ABI"));
> 
> With this, what use are the x86_elf_abi checks in md_assemble() and
> s_insn()?
> 

Yes, there is redundancy; I could remove from md_assemble () and s_insn ().

>> --- a/gas/config/tc-i386.h
>> +++ b/gas/config/tc-i386.h
>> @@ -373,6 +373,27 @@ extern int i386_elf_section_type (const char *, size_t);
>>   extern void i386_solaris_fix_up_eh_frame (segT);
>>   #endif
>>   
>> +#define TARGET_USE_GINSN 1
>> +/* Allow GAS to synthesize DWARF CFI for hand-written asm.
>> +   PS: TARGET_USE_CFIPOP is a pre-condition.  */
>> +#define TARGET_USE_SCFI 1
>> +/* Identify the maximum DWARF register number of all the registers being
>> +   tracked for SCFI.  This is the last DWARF register number of the set
>> +   of SP, BP, and all callee-saved registers.  For AMD64, this means
>> +   R15 (15).  Use SCFI_CALLEE_SAVED_REG_P to identify which registers
>> +   are callee-saved from this set.  */
>> +#define SCFI_NUM_REGS 15
> 
> Is this "number of" (and then having a value of 16 to permit covering
> register 15), or is this "maximum register number" (and then needing a
> different name)?
> 

This is "maximum register number". I will rename this to SCFI_MAX_REG_ID.

>> --- /dev/null
>> +++ b/gas/ginsn.c
> 
> I'll see about replying on the non-x86 parts of this separately.
> 

Thanks again for reviewing.  The reviews have been very helpful.
  
Jan Beulich Nov. 2, 2023, 11:39 a.m. UTC | #3
On 02.11.2023 09:15, Indu Bhagat wrote:
> On 10/31/23 07:10, Jan Beulich wrote:
>> On 30.10.2023 17:51, Indu Bhagat wrote:
>>> gas/
>>> 	* Makefile.am: Add new files.
>>> 	* Makefile.in: Regenerated.
>>> 	* as.c (defined): Handle documentation and listing option for
>>> 	ginsns and SCFI.
>>> 	* config/obj-elf.c (obj_elf_size): Invoke ginsn_data_end.
>>> 	(obj_elf_type): Invoke ginsn_data_begin.
>>> 	* config/tc-i386.c (ginsn_new): New functionality to generate
>>> 	ginsns.
>>> 	(x86_scfi_callee_saved_p): New function.
>>> 	(ginsn_dw2_regnum): Likewise.
>>> 	(ginsn_set_where): Likewise.
>>> 	(x86_ginsn_alu): Likewise.
>>> 	(x86_ginsn_move): Likewise.
>>> 	(x86_ginsn_lea): Likewise.
>>> 	(x86_ginsn_jump): Likewise.
>>> 	(x86_ginsn_jump_cond): Likewise.
>>> 	(md_assemble): Invoke ginsn_new.
>>> 	(s_insn): Likewise.
>>> 	(i386_target_format): Add hard error for usage of --scfi with non AMD64 ABIs.
>>> 	* config/tc-i386.h (TARGET_USE_GINSN): New definition.
>>> 	(TARGET_USE_SCFI): Likewise.
>>> 	(SCFI_NUM_REGS): Likewise.
>>> 	(REG_FP): Likewise.
>>> 	(REG_SP): Likewise.
>>> 	(SCFI_INIT_CFA_OFFSET): Likewise.
>>> 	(SCFI_CALLEE_SAVED_REG_P): Likewise.
>>> 	(x86_scfi_callee_saved_p): Likewise.
>>
>> For this arch-specific code there's a fundamental question of maintenance
>> cost here: It doesn't look very reasonable to me to demand of people adding
>> support for new ISA extensions to also take into consideration all of this
>> new machinery. Yet if any such addition affects SCFI, things will go out-
>> of-sync without updating this code as well. It may not be very often that
>> such updating is necessary, but right now there's APX work in progress
>> which I expect will need dealing with here as well.
>>
> 
> I understand your concerns.  FWIW, for SCFI, we will need to translate 
> only a subset of instructions into ginsns (change of flow insns, 
> save/restore and arith on REG_SP/REG_FP).

Considering what you say further down regarding untraceability, I'm afraid
you will need to care about all insns which have SP/FP as their destination.

>  For APX, I see that there is 
> are new instructions that fall in this set unfortunately.  Hopefully the 
> set remains small. But in general, for future additions (and for APX 
> currently), there will be time to act as SCFI is for hand-written asm 
> for which synthesizing CFI is desired, not for all code that GAS 
> necessarily has to deal with.

I understand that, but if a new ISA extension is supported in a new gas
version, I'd expect it to be legitimate for people to expect that SCFI is
then also supported for them right away. Else detecting what may or may
not be used with particular versions of gas is going to become a nightmare.

>>> +static ginsnS *
>>> +x86_ginsn_alu (i386_insn insn, symbolS *insn_end_sym)
>>> +{
>>> +  offsetT src_imm;
>>> +  uint32_t dw2_regnum;
>>> +  enum ginsn_src_type src_type;
>>> +  enum ginsn_dst_type dst_type;
>>> +  ginsnS *ginsn = NULL;
>>> +
>>> +  /* FIXME - create ginsn for REG_SP target only ? */
>>> +  /* Map for insn.tm.extension_opcode
>>> +     000 ADD    100 AND
>>> +     001 OR     101 SUB
>>> +     010 ADC    110 XOR
>>> +     011 SBB    111 CMP  */
>>> +
>>> +  /* add/sub imm, %reg.
>>> +     and imm, %reg only at this time for SCFI. */
>>> +  if (!(insn.tm.extension_opcode == 0
>>> +	|| insn.tm.extension_opcode == 4
>>> +	|| insn.tm.extension_opcode == 5))
>>> +    return ginsn;
>>
>> Why is AND permitted, but OR/XOR aren't?
>>
>> Also this is about ALU insns with immediate operands only, yet that
>> fact isn't reflected in the function name.
>>
> 
> OR/XOR should be handled. I will fix this.
> 
> This function is handling opcodes with imm only, true.  ADD/SUB with reg 
> are handled elsewhere (with opcode == 0x1 and opcode == 0x29).

I saw that, but this doesn't eliminate my remark regarding the name of
this function. Also the two other opcodes you mention aren't quite
enough.

>>> +  /* TBD_GINSN_REPRESENTATION_LIMIT: There is no representation for when a
>>> +     symbol is used as an operand, like so:
>>> +	  addq    $simd_cmp_op+8, %rdx
>>> +     Skip generating any ginsn for this.  */
>>> +  if (insn.imm_operands == 1
>>> +      && insn.op[0].imms->X_op == O_symbol)
>>> +    return ginsn;
>>> +
>>> +  gas_assert (insn.imm_operands == 1
>>> +	      && insn.op[0].imms->X_op == O_constant);
>>
>> Perhaps less fragile if you use != O_constant in the preceding if()?
>> The remaining half could the move ahead of that if(), allowing half
>> of its condition to also be dropped.
>>
>>> +  src_imm = insn.op[0].imms->X_add_number;
>>> +  /* The second operand may be a register or indirect access.  */
>>> +  if (insn.mem_operands == 1 && insn.base_reg)
>>> +    {
>>> +      dw2_regnum = ginsn_dw2_regnum (insn.base_reg);
>>> +      src_type = GINSN_SRC_INDIRECT;
>>> +      dst_type = GINSN_DST_INDIRECT;
>>
>> The possibly in use index register isn't of interest in this case?
>> Nor the displacement in the memory operand, ...
>>
> 
> Not of interest, No.  When src_type and dst_type is INDIRECT.

It's not clear to me what you mean with your reply. If the addressing
form used isn't relevant, why do you handle base-only and ...

>>> +    }
>>> +  else if (insn.mem_operands == 1 && insn.index_reg)
>>> +    {
>>> +      dw2_regnum = ginsn_dw2_regnum (insn.index_reg);
>>> +      src_type = GINSN_SRC_INDIRECT;
>>> +      dst_type = GINSN_DST_INDIRECT;
>>
>> ... similarly applicable here?

... index-only separately in the first place?

>> Nor a segment override?
> 
> Segment override probably is not of interest for SCFI.
> 
>>
>>> +    }
>>> +  else
>>> +    {
>>> +      gas_assert (insn.reg_operands == 1);
>>
>> Afaict this will trigger when the memory operand has neither base
>> nor index.
>>
> 
> memory operand with neither base nor index ?  An asm instruction example 
> will be helpful.

	add	$1, symbol
	add	%eax, symbol

Maybe you really don't need to consider those (if only GPR destinations
are of interest), but then a comment should be saying so and you'd
probably want to bail from the function early on in such cases.

>>> +static ginsnS *
>>> +x86_ginsn_lea (i386_insn insn, symbolS *insn_end_sym)
>>> +{
>>> +  offsetT src_disp = 0;
>>> +  ginsnS *ginsn = NULL;
>>> +  uint32_t base_reg;
>>> +  uint32_t index_reg;
>>> +  offsetT index_scale;
>>> +  uint32_t dst_reg;
>>> +
>>> +  if (!insn.index_reg && !insn.base_reg)
>>> +    {
>>> +      /* lea symbol, %rN.  */
>>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>>> +      /* FIXME - Skip encoding information about the symbol.
>>> +	 This is TBD_GINSN_INFO_LOSS, but it is fine if the mode is
>>> +	 GINSN_GEN_SCFI.  */
>>> +      ginsn = ginsn_new_mov (insn_end_sym, false,
>>> +			     GINSN_SRC_IMM, 0xf /* arbitrary const.  */, 0,
>>> +			     GINSN_DST_REG, dst_reg, 0);
>>> +    }
>>> +  else if (insn.base_reg && !insn.index_reg)
>>> +    {
>>> +      /* lea    -0x2(%base),%dst.  */
>>> +      base_reg = ginsn_dw2_regnum (insn.base_reg);
>>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>>> +
>>> +      if (insn.disp_operands)
>>> +	src_disp = insn.op[0].disps->X_add_number;
>>
>> What if the displacement expression is other than O_constant?
>>
> 
> For SCFI, a "complex" lea instruction will imply some untraceable change 
> to the destination reg.  For SCFI, the extent of untraceable change is 
> not of interest, hence, in general, some information loss in ginsn is 
> tolerable.

As a fundamental request: For anything that's of no interest, can you
please try to cover this with as little (and thus clear) code as possible?
No taking apart of sub-cases when those are indifferent in the end anyway.

> I will have to take a look at such instructions to see what the options 
> are.  If you have a example handy, it will be helpful.

	lea	symbol(%rax), %rbp

>>> +      if (src_disp)
>>> +	/* Generate an ADD ginsn.  */
>>> +	ginsn = ginsn_new_add (insn_end_sym, true,
>>> +			       GINSN_SRC_REG, base_reg, 0,
>>> +			       GINSN_SRC_IMM, 0, src_disp,
>>> +			       GINSN_DST_REG, dst_reg, 0);
>>> +      else
>>> +	  /* Generate a MOV ginsn.  */
>>> +	  ginsn = ginsn_new_mov (insn_end_sym, true,
>>> +				 GINSN_SRC_REG, base_reg, 0,
>>> +				 GINSN_DST_REG, dst_reg, 0);
>>> +    }
>>> +  else if (!insn.base_reg && insn.index_reg)
>>> +    {
>>> +      /* lea (,%index,imm), %dst.  */
>>> +      /* FIXME - Skip encoding an explicit multiply operation, instead use
>>> +	 GINSN_TYPE_OTHER.  This is TBD_GINSN_INFO_LOSS, but it is fine if
>>> +	 the mode is GINSN_GEN_SCFI.  */
>>> +      index_scale = insn.log2_scale_factor;
>>> +      index_reg = ginsn_dw2_regnum (insn.index_reg);
>>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>>> +      ginsn = ginsn_new_other (insn_end_sym, true,
>>> +			       GINSN_SRC_REG, index_reg,
>>> +			       GINSN_SRC_IMM, index_scale,
>>> +			       GINSN_DST_REG, dst_reg);
>>> +    }
>>> +  else
>>> +    {
>>> +      /* lea disp(%base,%index,imm) %dst.  */
>>> +      /* FIXME - Skip encoding information about the disp and imm for index
>>> +	 reg.  This is TBD_GINSN_INFO_LOSS, but it is fine if the mode is
>>> +	 GINSN_GEN_SCFI.  */
>>> +      base_reg = ginsn_dw2_regnum (insn.base_reg);
>>> +      index_reg = ginsn_dw2_regnum (insn.index_reg);
>>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>>> +      /* Generate an ADD ginsn.  */
>>> +      ginsn = ginsn_new_add (insn_end_sym, true,
>>> +			     GINSN_SRC_REG, base_reg, 0,
>>> +			     GINSN_SRC_REG, index_reg, 0,
>>> +			     GINSN_DST_REG, dst_reg, 0);
>>
>> The comment mentions the displacement, but it's not passed on? In the earlier
>> "else if" block you also pay attention to the scla factor, but here you don't?
>> That said, I think I'm confused anyway about the FIXME comments.
>>
> 
> Ah, This is what I was hinting at in the previous comment.
> 
> So whats going on here is the following -
> 
> First, An "add reg1, reg2, reg3" (reg3 being the destination) ginsn is 
> only interesting for SCFI if the reg3 is either REG_FP or REG_SP. We 
> still generate them all nevertheless for now.
> 
> Second, We say that "add reg1, reg2, reg3" makes reg3 "untraceable". 
> This is based on the observation that static stack usage in a function 
> will use imm based arithmetic insns.

That is, you only intend to support that way of allocating stack space
(i.e. one of the apparently many constraints to using this machinery).

> So, if reg3 is REG_SP, REG_FP, 
> these instructions make REG_SP / REG_FP "untraceable".
> 
> Note that, We are not allowing all registers for CFA tracking. (Now 
> _this_ is a constraint, yes. And which is why we dont support DRAP usage 
> currently. But its a specific pattern that we can accommodate in future.)
> 
> Hence, tracking more information for such (untraceable) instructions is 
> unnecessary for SCFI purposes.  If and when GAS uses ginsn (in the i386 
> backend) for other purposes, such information loss will need to be 
> revisited.

I guess there wants to be a central comment (either in common code, or
once per arch) which other comments then can refer to. To the reader
of such code it needs to be clear what bits of information are relevant
and which ones can be ignored (for now).

>>> +      ginsn = ginsn_new_jump (insn_end_sym, true,
>>> +			      GINSN_SRC_SYMBOL, 0, src_symbol);
>>> +
>>> +      ginsn_set_where (ginsn);
>>> +    }
>>> +
>>> +  return ginsn;
>>> +}
>>
>> Further, what about XABORT transferring control to what XBEGIN has
>> supplied? (XBEGIN can, in a sense, also be considered a [conditional]
>> branch.)
>>
> 
> My plan after writing this x86 -> ginsn translator was:
> There are and will remain many machine instructions that may need to be 
> supported depending on hand-written assmebly programmers use.  This is 
> the reason I had left some asserts towards the end in the ginsn_new () 
> API: if any instructions which affect the control flow, or where the 
> destination is REG_FP / REG_SP, or push* / pop*  are not handled, we 
> will have an assert fail and GAS will need to handle it.

Elsewhere you mention you want to use this for some of the kernel's
assembly files. If this feature went in with incomplete coverage, the
kernel build system detecting its availability will likely be misled.

Furthermore I'm of the opinion that at the time this series goes in,
assertions should remain only to check that state is as expected. They
should not be used to check that user input is unexpected. Such may
only be communicated back by ordinary diagnostics; the assembler should
not crash (and even deliberately) on certain input. Besides such being
a bug, it also gets in the way of fuzzing.

>>> +static ginsnS *
>>> +x86_ginsn_jump_cond (i386_insn insn, symbolS *insn_end_sym)
>>> +{
>>> +  ginsnS *ginsn = NULL;
>>> +  symbolS *src_symbol;
>>> +
>>> +  /* TBD_GINSN_GEN_NOT_SCFI: Ignore move to or from xmm reg for mode.  */
>>> +  if (i.tm.opcode_space == SPACE_0F)
>>> +    return ginsn;
>>
>> What is the comment about? And what about SPACE_0F-encoded conditional
>> jumps (Jcc <disp32>)? And where are LOOP and J{E,R}CXZ handled?
>>
> 
> These need to be handled.  I will take a look.

Just to mention it: The Jcc <disp32> forms should appear only during
relaxation, while - aiui - all your code runs earlier than that. I was
misled by you (elsewhere) handling both 0xE9 and 0xEB as JMP opcodes,
despite the <disp32> form there - iirc - also only appearing during
relaxation.

>>> +  gas_assert (insn.disp_operands == 1);
>>> +
>>> +  if (insn.op[0].disps->X_op == O_symbol)
>>> +    {
>>> +      src_symbol = insn.op[0].disps->X_add_symbol;
>>> +      /* The jump target is expected to be a symbol with 0 addend.
>>> +	 Assert for now to see if this assumption is true.  */
>>> +      gas_assert (insn.op[0].disps->X_add_number == 0);
>>> +      ginsn = ginsn_new_jump_cond (insn_end_sym, true,
>>> +				   GINSN_SRC_SYMBOL, 0, src_symbol);
>>> +      ginsn_set_where (ginsn);
>>> +    }
>>> +  else
>>> +    /* Catch them for now so we know what we are dealing with.  */
>>> +    gas_assert (0);
>>
>> I'm going from the assumption that this (and alike) will be addressed
>> before this series is committed?
> 
> I will work on the ones you have raised above in the review. My plan was 
> to exercise this code in different ways after commmitting and address 
> the failures as I run into them.

Well, as said, I think full insn coverage is a prereq to committing, or
else there needs to be a way to easily detect an assumed fully functional
implementation in a later gas version, no matter that a partially
functional one may have been present in a number of earlier versions
already. But of course Nick in particular may have a different view on
this, and his view would trump mine.

>>> +{
>>> +  uint16_t opcode;
>>> +  uint32_t dw2_regnum;
>>> +  uint32_t src2_dw2_regnum;
>>> +  int32_t gdisp = 0;
>>> +  ginsnS *ginsn = NULL;
>>> +  ginsnS *ginsn_next = NULL;
>>> +  ginsnS *ginsn_last = NULL;
>>> +
>>> +  /* FIXME - Need a way to check whether the decoding is sane.  The specific
>>> +     checks around i.tm.opcode_space were added as issues were seen.  Likely
>>> +     insufficient.  */
>>
>> Furthermore opcode encoding space (SPACE_...) need to be taken into
>> account in all cases.
>>
>>> +  /* Currently supports generation of selected ginsns, sufficient for
>>> +     the use-case of SCFI only.  To remove this condition will require
>>> +     work on this target-specific process of creation of ginsns.  Some
>>> +     of such places are tagged with TBD_GINSN_GEN_NOT_SCFI to serve as
>>> +     examples.  */
>>> +  if (gmode != GINSN_GEN_SCFI)
>>> +    return ginsn;
>>> +
>>> +  opcode = i.tm.base_opcode;
>>> +
>>> +  switch (opcode)
>>> +    {
>>> +    case 0x1:
>>> +      /* add reg, reg.  */
>>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>>
>> You don't care about opcode 0 (byte operation). Then what about 16-bit
>> operand size? Or, since we're talking of a 64-bit-ABI-only feature,
>> even 32-bit operand size?
>>
> 
> Operand size in some cases does not affect SCFI. So we dont keep 
> information about operand sizes.  The case that I should handle operand 
> size is when they are pushed to / popped from stack.

So you care about recognizing when %rbp is overwritten by an insn. And
you also care about the same for %ebp and %bp. In that sense operand
size may indeed be unnecessary to determine. Except that then you also
want to deal with byte operations, i.e. %bpl being the destination.

>> Also what about opcode 0x3?
> 
> LSL instruction ? It doesnt look like it can affect DWARF CFI of a 
> function. Please correct me if this is wrong.

LSL is 0f03, i.e. opcode 0x3 in SPACE_0F. What my comment was about is
ADD with inversed operands (ModRM.rm soure, ModRM.reg destination). All
four of these flavors of ADD are covered by a single template, using the
D and W attributes to establish opcode bits 0 and 1 based on actual
operands (and possibly pseudo-prefixes).

>>> +      if (i.reg_operands == 2)
>>> +	{
>>> +	  src2_dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
>>> +	  ginsn = ginsn_new_add (insn_end_sym, true,
>>> +				 GINSN_SRC_REG, dw2_regnum, 0,
>>> +				 GINSN_SRC_REG, src2_dw2_regnum, 0,
>>> +				 GINSN_DST_REG, src2_dw2_regnum, 0);
>>> +	  ginsn_set_where (ginsn);
>>> +	}
>>> +      else if (i.mem_operands && i.base_reg)
>>> +	{
>>> +	  src2_dw2_regnum = ginsn_dw2_regnum (i.base_reg);
>>> +	  if (i.disp_operands == 1)
>>> +	    gdisp = i.op[1].disps->X_add_number;
>>> +
>>> +	  ginsn = ginsn_new_add (insn_end_sym, true,
>>> +				 GINSN_SRC_REG, dw2_regnum, 0,
>>> +				 GINSN_SRC_INDIRECT, src2_dw2_regnum, gdisp,
>>> +				 GINSN_DST_INDIRECT, src2_dw2_regnum, gdisp);
>>> +	  ginsn_set_where (ginsn);
>>> +	}
>>> +      else
>>> +	/* Catch them for now so we know what we are dealing with.  */
>>> +	gas_assert (0);
>>> +
>>> +      break;
>>> +    case 0x29:
>>> +      /* If opcode_space == SPACE_0F, this is a movaps insn.  Skip it
>>> +	 for GINSN_GEN_SCFI.  */
>>> +      if (i.tm.opcode_space == SPACE_0F)
>>> +	break;
>>
>> Extending on the earlier related comment: Why would you exclude just SPACE_0F?
>>
> 
> Perhaps here I should check that space == SPACE_BASE and only allow 
> that? And also for others.

Indeed.

>>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>>> +      /* push fs / push gs.  */
>>> +      ginsn = ginsn_new_sub (insn_end_sym, false,
>>> +			     GINSN_SRC_REG, REG_SP, 0,
>>> +			     GINSN_SRC_IMM, 0, 8,
>>
>> Note how the 8 here assumes flag_code == CODE_64BIT. (You further assume
>> no operand size override here.)
>>
> 
> Hmm. Yes, I do assume no operand size override here. I will need to 
> understand how to calculate the operand size here. Looks like I need to 
> check for instruction prefixes 66H or REX.W.  Is there an API in the 
> backend that be used for this ?

i.prefixes[] and (depending on the phase of assembly) i.rex should be
telling you.

>>> +      dw2_regnum = ginsn_dw2_regnum (i.base_reg);
>>> +      ginsn = ginsn_new_load (insn_end_sym, false,
>>> +			      GINSN_SRC_INDIRECT, REG_SP, 0,
>>> +			      GINSN_DST_INDIRECT, dw2_regnum);
>>
>> When both operands are "indirect", what's the difference between
>> move, load, and store? IOW if the above is permitted, can't all
>> three be folded into a single ginsn kind?
>>
> 
> In theory, any of mov/load/store ginsn type may be used if both operands 
> are indirect mem access.

Which suggests that things can be simplified then.

>>> +    case 0xc2:
>>> +    case 0xc3:
>>> +      /* Near ret.  */
>>> +      ginsn = ginsn_new_return (insn_end_sym, true);
>>> +      ginsn_set_where (ginsn);
>>> +      break;
>>
>> Where did the immediate operand of 0xC2 go? And what about far return
>> (and more generally far branches)?
>>
> 
> A return ginsn for SCFI can afford this information loss.

Hmm, right, I guess by how much the stack pointer is incremented at the
return point isn't of any interest anymore. Unless of course you intended
to support co-routines.

>>> +    case 0xc9:
>>> +      /* The 'leave' instruction copies the contents of the RBP register
>>> +	 into the RSP register to release all stack space allocated to the
>>> +	 procedure.  */
>>> +      ginsn = ginsn_new_mov (insn_end_sym, false,
>>> +			     GINSN_SRC_REG, REG_FP, 0,
>>> +			     GINSN_DST_REG, REG_SP, 0);
>>> +      ginsn_set_where (ginsn);
>>> +
>>> +      /* Then it restores the old value of the RBP register from the stack.  */
>>> +      ginsn_next = ginsn_new_load (insn_end_sym, false,
>>> +				   GINSN_SRC_INDIRECT, REG_SP, 0,
>>> +				   GINSN_DST_REG, REG_FP);
>>> +      ginsn_set_where (ginsn_next);
>>> +
>>> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
>>> +      ginsn_last = ginsn_new_add (insn_end_sym, false,
>>> +				  GINSN_SRC_REG, REG_SP, 0,
>>> +				  GINSN_SRC_IMM, 0, 8,
>>> +				  GINSN_DST_REG, REG_SP, 0);
>>> +      ginsn_set_where (ginsn_next);
>>> +
>>> +      gas_assert (!ginsn_link_next (ginsn_next, ginsn_last));
>>> +      break;
>>
>> You handle LEAVE, but not ENTER?
>>
> 
> I should have. From the three variants ([Enter imm16,0], [ENTER imm16,1] 
> and [ENTER imm16, imm8], the last one is not super clear currently. Is 
> it possible for you to pass down some understanding to me on that ?

I can't say more than what the CPU doc says: The second operand tells
how many earlier frame pointers are copied to the local frame. Perhaps
you want to simply treat non-zero values as untraceable SP/FP?

Jan
  
Jan Beulich Nov. 2, 2023, 3:53 p.m. UTC | #4
On 30.10.2023 17:51, Indu Bhagat wrote:
> --- /dev/null
> +++ b/gas/ginsn.c
> @@ -0,0 +1,1225 @@
> +/* 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
> +
> +const char *const ginsn_type_names[] =

This looks like it wants to be static.

> +{
> +#define _GINSN_TYPE_ITEM(NAME, STR) STR,
> +  _GINSN_TYPES
> +#undef _GINSN_TYPE_ITEM
> +};
> +
> +const char *const ginsn_src_type_names[] =

This and ...

> +{
> +#define _GINSN_SRC_TYPE_ITEM(NAME, STR) STR,
> +  _GINSN_SRC_TYPES
> +#undef _GINSN_SRC_TYPE_ITEM
> +};
> +const char *const ginsn_dst_type_names[] =

... this also look so, but then they're also unused inside this CU,
so when making them static the compiler would complain. If they're
needed by a later patch, perhaps they should be moved there? And
if entirely unused, perhaps they should both be deleted (or at least
commented / #ifdef-ed out)?

With the many symbols further down it's also hard to spot whether any
of them should perhaps also be static.

> +{
> +#define _GINSN_DST_TYPE_ITEM(NAME, STR) STR,
> +  _GINSN_DST_TYPES
> +#undef _GINSN_DST_TYPE_ITEM
> +};
> +
> +static
> +ginsnS *ginsn_alloc (void)
> +{
> +  ginsnS *ginsn = XCNEW (ginsnS);
> +  return ginsn;
> +}
> +
> +static ginsnS*
> +ginsn_init (enum ginsn_type type, symbolS *sym, bool real_p)
> +{
> +  ginsnS *ginsn = ginsn_alloc ();
> +  ginsn->type = type;
> +  ginsn->sym = sym;

Is a symbol hanging off of a ginsn ever intended to be altered? If
not, field and function argument would want to be pointer-to-const.

> +  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);
> +  ginsn = NULL;

This doesn't do anything useful. Did you perhaps mean to set *ginsnp to NULL,
and then also ahead of calling free()?

> +}
> +
> +static void
> +ginsn_set_src (struct ginsn_src *src, enum ginsn_src_type type, uint32_t reg,
> +	       int32_t immdisp)

I find the use of fixed-width types suspicious here: For reg, likely it wants
to be unsigned int. For immdisp it's less clear: Immediates can be wider than
32 bits, and they may also be either signed ot unsigned. From an abstract
perspective, assuming the immediate value actually is used for anything, I'd
expect offsetT to be used, following struct expressionS' X_add_number.

> +{
> +  if (!src)
> +    return;
> +
> +  src->type = type;
> +  /* Even when the use-case is SCFI, the value of reg may be > SCFI_NUM_REGS.
> +     E.g., in AMD64, push fs etc.  */
> +  src->reg = reg;
> +
> +  if (type == GINSN_SRC_IMM || type == GINSN_SRC_INDIRECT)
> +    src->immdisp = immdisp;

What's the point of the conditional around the assignment?

> +ginsnS *
> +ginsn_new_add (symbolS *sym, bool real_p,
> +	       enum ginsn_src_type src1_type, uint32_t src1_val, int32_t src1_disp,
> +	       enum ginsn_src_type src2_type, uint32_t src2_val, int32_t src2_disp,
> +	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp)
> +{
> +  ginsnS *ginsn = ginsn_init (GINSN_TYPE_ADD, sym, real_p);
> +  /* src info.  */
> +  ginsn_set_src (&ginsn->src[0], src1_type, src1_val, src1_disp);
> +  ginsn_set_src (&ginsn->src[1], src2_type, src2_val, src2_disp);
> +  /* dst info.  */
> +  ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp);
> +
> +  return ginsn;
> +}
> +
> +ginsnS *
> +ginsn_new_and (symbolS *sym, bool real_p,
> +	       enum ginsn_src_type src1_type, uint32_t src1_val, int32_t src1_disp,
> +	       enum ginsn_src_type src2_type, uint32_t src2_val, int32_t src2_disp,
> +	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp)
> +{
> +  ginsnS *ginsn = ginsn_init (GINSN_TYPE_AND, sym, real_p);
> +  /* src info.  */
> +  ginsn_set_src (&ginsn->src[0], src1_type, src1_val, src1_disp);
> +  ginsn_set_src (&ginsn->src[1], src2_type, src2_val, src2_disp);
> +  ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp);
> +
> +  return ginsn;
> +}

The comments in the two functions don't look overly useful to me. But if
you have them, please have them be consistent across similar functions.

> +ginsnS *
> +ginsn_new_mov (symbolS *sym, bool real_p,
> +	       enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
> +	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t 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;
> +}

As indicated before, if both src and dst can be indirect here, ...

> +ginsnS *
> +ginsn_new_store (symbolS *sym, bool real_p,
> +		 enum ginsn_src_type src_type, uint32_t src_reg,
> +		 enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t 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 (symbolS *sym, bool real_p,
> +		enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
> +		enum ginsn_dst_type dst_type, uint32_t 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;
> +}

... I can't see what these are needed for.

> +ginsnS *
> +ginsn_new_sub (symbolS *sym, bool real_p,
> +	       enum ginsn_src_type src1_type, uint32_t src1_val, int32_t src1_disp,
> +	       enum ginsn_src_type src2_type, uint32_t src2_val, int32_t src2_disp,
> +	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp)
> +{
> +  ginsnS *ginsn = ginsn_init (GINSN_TYPE_SUB, sym, real_p);
> +  /* src info.  */
> +  ginsn_set_src (&ginsn->src[0], src1_type, src1_val, src1_disp);
> +  ginsn_set_src (&ginsn->src[1], src2_type, src2_val, 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.  */

And why do the displacements need tracking for add, and, and sub?

> +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);

On x86 an indirect jump can also have a memory source operand (i.e. what
you call "indirect").

> +static bool
> +ginsn_direct_local_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_SYMBOL
> +	    && S_IS_LOCAL (ginsn->src[0].sym));

This looks fragile: You may not yet have seen the .globl or .weak directive.

> +static char*
> +ginsn_src_print (struct ginsn_src *src)
> +{
> +  size_t len = 39;
> +  char *src_str = XNEWVEC (char, len);
> +
> +  memset (src_str, 0, len);
> +
> +  if (src->type == GINSN_SRC_REG)
> +    {
> +      char *buf = XNEWVEC (char, 32);
> +      sprintf (buf, "%%r%d, ", ginsn_get_src_reg (src));
> +      strcat (src_str, buf);
> +    }
> +  else if (src->type == GINSN_SRC_IMM)
> +    {
> +      char *buf = XNEWVEC (char, 32);
> +      sprintf (buf, "%d, ", ginsn_get_src_imm (src));

Here and below, if you stick to int32_t as the type, this wants to use
PRId32.

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

static and with a 2nd const?

> +static char*
> +ginsn_print (ginsnS *ginsn)
> +{
> +  struct ginsn_src *src;
> +  struct ginsn_dst *dst;
> +  size_t len = GINSN_LISTING_LEN;
> +  char *ginsn_str = XNEWVEC (char, len);
> +
> +  memset (ginsn_str, 0, len);
> +
> +  strcpy (ginsn_str, "ginsn: ");
> +
> +  strcat (ginsn_str, ginsn_type_names[ginsn->type]);
> +  strcat (ginsn_str, " ");
> +
> +  /* For some ginsn types, no further information is printed for now.  */
> +  if (ginsn->type == GINSN_TYPE_CALL
> +      || ginsn->type == GINSN_TYPE_RETURN
> +      || ginsn->type == GINSN_TYPE_OTHER)
> +    goto end;
> +  else if (ginsn->type == GINSN_TYPE_SYMBOL)
> +    {
> +      if (GINSN_F_USER_LABEL_P (ginsn))
> +	strncat (ginsn_str, S_GET_NAME (ginsn->sym), len - 10);
> +      else
> +	strcat (ginsn_str, ginsn_type_sym_begin_end_print (ginsn));
> +      goto end;
> +    }
> +
> +  /* src 1.  */
> +  src = ginsn_get_src1 (ginsn);
> +  strcat (ginsn_str, ginsn_src_print (src));
> +
> +  /* src 2.  */
> +  src = ginsn_get_src2 (ginsn);
> +  strcat (ginsn_str, ginsn_src_print (src));
> +
> +  /* dst.  */
> +  dst = ginsn_get_dst (ginsn);
> +  strcat (ginsn_str, ginsn_dst_print (dst));
> +
> +end:
> +  gas_assert (strlen (ginsn_str) < GINSN_LISTING_LEN);

This check comes too late - if it triggers, you've corrupted memory
already. I wonder anyway why you construct all this "by hand", rather
than using snprintf().

Jan
  
Indu Bhagat Nov. 4, 2023, 7:29 a.m. UTC | #5
On 11/2/23 08:53, Jan Beulich wrote:
> On 30.10.2023 17:51, Indu Bhagat wrote:
>> --- /dev/null
>> +++ b/gas/ginsn.c
>> @@ -0,0 +1,1225 @@
>> +/* 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
>> +
>> +const char *const ginsn_type_names[] =
> 
> This looks like it wants to be static.
> 

OK.

>> +{
>> +#define _GINSN_TYPE_ITEM(NAME, STR) STR,
>> +  _GINSN_TYPES
>> +#undef _GINSN_TYPE_ITEM
>> +};
>> +
>> +const char *const ginsn_src_type_names[] =
> 
> This and ...
> 
>> +{
>> +#define _GINSN_SRC_TYPE_ITEM(NAME, STR) STR,
>> +  _GINSN_SRC_TYPES
>> +#undef _GINSN_SRC_TYPE_ITEM
>> +};
>> +const char *const ginsn_dst_type_names[] =
> 
> ... this also look so, but then they're also unused inside this CU,
> so when making them static the compiler would complain. If they're
> needed by a later patch, perhaps they should be moved there? And
> if entirely unused, perhaps they should both be deleted (or at least
> commented / #ifdef-ed out)?
> 

Remains of some previous implementation that I discarded. I will remove 
these.

> With the many symbols further down it's also hard to spot whether any
> of them should perhaps also be static.
> 
>> +{
>> +#define _GINSN_DST_TYPE_ITEM(NAME, STR) STR,
>> +  _GINSN_DST_TYPES
>> +#undef _GINSN_DST_TYPE_ITEM
>> +};
>> +
>> +static
>> +ginsnS *ginsn_alloc (void)
>> +{
>> +  ginsnS *ginsn = XCNEW (ginsnS);
>> +  return ginsn;
>> +}
>> +
>> +static ginsnS*
>> +ginsn_init (enum ginsn_type type, symbolS *sym, bool real_p)
>> +{
>> +  ginsnS *ginsn = ginsn_alloc ();
>> +  ginsn->type = type;
>> +  ginsn->sym = sym;
> 
> Is a symbol hanging off of a ginsn ever intended to be altered? If
> not, field and function argument would want to be pointer-to-const.
> 

No. This symbol is not intended to be altered.

However, using const symbolS* will cause complaints of "passing argument 
1 of ‘S_GET_NAME’ discards ‘const’ qualifier" etc.  Calling most of the 
S_XXX (symbolS *sym) will need some type casting etc, but that will 
somewhat defeat the purpose?

>> +  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);
>> +  ginsn = NULL;
> 
> This doesn't do anything useful. Did you perhaps mean to set *ginsnp to NULL,
> and then also ahead of calling free()?
> 

Thanks. Yes I meant to do *ginsnp = NULL;

>> +}
>> +
>> +static void
>> +ginsn_set_src (struct ginsn_src *src, enum ginsn_src_type type, uint32_t reg,
>> +	       int32_t immdisp)
> 
> I find the use of fixed-width types suspicious here: For reg, likely it wants
> to be unsigned int. For immdisp it's less clear: Immediates can be wider than
> 32 bits, and they may also be either signed ot unsigned. From an abstract
> perspective, assuming the immediate value actually is used for anything, I'd
> expect offsetT to be used, following struct expressionS' X_add_number.
> 

Thanks, I will consider trying offsetT for immediate. It is a more 
appropriate data type than int32_t.  For reg, why is uint32_t less 
appropriate than unsigned int ?

>> +{
>> +  if (!src)
>> +    return;
>> +
>> +  src->type = type;
>> +  /* Even when the use-case is SCFI, the value of reg may be > SCFI_NUM_REGS.
>> +     E.g., in AMD64, push fs etc.  */
>> +  src->reg = reg;
>> +
>> +  if (type == GINSN_SRC_IMM || type == GINSN_SRC_INDIRECT)
>> +    src->immdisp = immdisp;
> 
> What's the point of the conditional around the assignment?
> 

Stale conditional. I earlier had some data structures with union etc. 
which I have since gotten rid of.

>> +ginsnS *
>> +ginsn_new_add (symbolS *sym, bool real_p,
>> +	       enum ginsn_src_type src1_type, uint32_t src1_val, int32_t src1_disp,
>> +	       enum ginsn_src_type src2_type, uint32_t src2_val, int32_t src2_disp,
>> +	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp)
>> +{
>> +  ginsnS *ginsn = ginsn_init (GINSN_TYPE_ADD, sym, real_p);
>> +  /* src info.  */
>> +  ginsn_set_src (&ginsn->src[0], src1_type, src1_val, src1_disp);
>> +  ginsn_set_src (&ginsn->src[1], src2_type, src2_val, src2_disp);
>> +  /* dst info.  */
>> +  ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp);
>> +
>> +  return ginsn;
>> +}
>> +
>> +ginsnS *
>> +ginsn_new_and (symbolS *sym, bool real_p,
>> +	       enum ginsn_src_type src1_type, uint32_t src1_val, int32_t src1_disp,
>> +	       enum ginsn_src_type src2_type, uint32_t src2_val, int32_t src2_disp,
>> +	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp)
>> +{
>> +  ginsnS *ginsn = ginsn_init (GINSN_TYPE_AND, sym, real_p);
>> +  /* src info.  */
>> +  ginsn_set_src (&ginsn->src[0], src1_type, src1_val, src1_disp);
>> +  ginsn_set_src (&ginsn->src[1], src2_type, src2_val, src2_disp);
>> +  ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp);
>> +
>> +  return ginsn;
>> +}
> 
> The comments in the two functions don't look overly useful to me. But if
> you have them, please have them be consistent across similar functions.
> 

OK.

>> +ginsnS *
>> +ginsn_new_mov (symbolS *sym, bool real_p,
>> +	       enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
>> +	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t 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;
>> +}
> 
> As indicated before, if both src and dst can be indirect here, ...
> 
>> +ginsnS *
>> +ginsn_new_store (symbolS *sym, bool real_p,
>> +		 enum ginsn_src_type src_type, uint32_t src_reg,
>> +		 enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t 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 (symbolS *sym, bool real_p,
>> +		enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
>> +		enum ginsn_dst_type dst_type, uint32_t 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;
>> +}
> 
> ... I can't see what these are needed for.
> 

For x86, they may not seem necessary. But for other architectures, or 
say for future uses-cases, we may need them. I think it is more 
meaningful (and readable) to see a LOAD/STORE/MOV ginsn for a machine 
instruction of the same type.  For other RISC-like ISAs, it is clearer 
to have separate MOV/LOAD/STORE instructions.

ginsn is meant to provide an infrastructure for other uses cases that 
may crop up later.

>> +ginsnS *
>> +ginsn_new_sub (symbolS *sym, bool real_p,
>> +	       enum ginsn_src_type src1_type, uint32_t src1_val, int32_t src1_disp,
>> +	       enum ginsn_src_type src2_type, uint32_t src2_val, int32_t src2_disp,
>> +	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp)
>> +{
>> +  ginsnS *ginsn = ginsn_init (GINSN_TYPE_SUB, sym, real_p);
>> +  /* src info.  */
>> +  ginsn_set_src (&ginsn->src[0], src1_type, src1_val, src1_disp);
>> +  ginsn_set_src (&ginsn->src[1], src2_type, src2_val, 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.  */
> 
> And why do the displacements need tracking for add, and, and sub?
> 

Hmm, I think its just a bad choice of arg name.  The value passed in 
src1_disp / src2_disp is actually the immediates in case add/sub/and. I 
will update it.

>> +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);
> 
> On x86 an indirect jump can also have a memory source operand (i.e. what
> you call "indirect").
> 

IIRC, This is another case of acceptable loss of information in ginsns. 
I will double check this too when I get to fixing up the tc-i386.c to 
address those review comments for the ginsn creation process.

>> +static bool
>> +ginsn_direct_local_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_SYMBOL
>> +	    && S_IS_LOCAL (ginsn->src[0].sym));
> 
> This looks fragile: You may not yet have seen the .globl or .weak directive.
> 

Hmm. I think the usage of S_IS_LOCAL is incorrect here. Further, I 
already have a defined label -> ginsn map per function block, and the 
checks with S_IS_LOCAL is not necessary really.  Will take a second look.

>> +static char*
>> +ginsn_src_print (struct ginsn_src *src)
>> +{
>> +  size_t len = 39;
>> +  char *src_str = XNEWVEC (char, len);
>> +
>> +  memset (src_str, 0, len);
>> +
>> +  if (src->type == GINSN_SRC_REG)
>> +    {
>> +      char *buf = XNEWVEC (char, 32);
>> +      sprintf (buf, "%%r%d, ", ginsn_get_src_reg (src));
>> +      strcat (src_str, buf);
>> +    }
>> +  else if (src->type == GINSN_SRC_IMM)
>> +    {
>> +      char *buf = XNEWVEC (char, 32);
>> +      sprintf (buf, "%d, ", ginsn_get_src_imm (src));
> 
> Here and below, if you stick to int32_t as the type, this wants to use
> PRId32.
> 

OK.

>> +static const char*
>> +ginsn_type_sym_begin_end_print (ginsnS *ginsn)
>> +{
>> +  int id = 0;
>> +  const char *ginsn_sym_strs[]
>> +    = { "", "FUNC_BEGIN", "FUNC_END" };
> 
> static and with a 2nd const?
> 

OK.

>> +static char*
>> +ginsn_print (ginsnS *ginsn)
>> +{
>> +  struct ginsn_src *src;
>> +  struct ginsn_dst *dst;
>> +  size_t len = GINSN_LISTING_LEN;
>> +  char *ginsn_str = XNEWVEC (char, len);
>> +
>> +  memset (ginsn_str, 0, len);
>> +
>> +  strcpy (ginsn_str, "ginsn: ");
>> +
>> +  strcat (ginsn_str, ginsn_type_names[ginsn->type]);
>> +  strcat (ginsn_str, " ");
>> +
>> +  /* For some ginsn types, no further information is printed for now.  */
>> +  if (ginsn->type == GINSN_TYPE_CALL
>> +      || ginsn->type == GINSN_TYPE_RETURN
>> +      || ginsn->type == GINSN_TYPE_OTHER)
>> +    goto end;
>> +  else if (ginsn->type == GINSN_TYPE_SYMBOL)
>> +    {
>> +      if (GINSN_F_USER_LABEL_P (ginsn))
>> +	strncat (ginsn_str, S_GET_NAME (ginsn->sym), len - 10);
>> +      else
>> +	strcat (ginsn_str, ginsn_type_sym_begin_end_print (ginsn));
>> +      goto end;
>> +    }
>> +
>> +  /* src 1.  */
>> +  src = ginsn_get_src1 (ginsn);
>> +  strcat (ginsn_str, ginsn_src_print (src));
>> +
>> +  /* src 2.  */
>> +  src = ginsn_get_src2 (ginsn);
>> +  strcat (ginsn_str, ginsn_src_print (src));
>> +
>> +  /* dst.  */
>> +  dst = ginsn_get_dst (ginsn);
>> +  strcat (ginsn_str, ginsn_dst_print (dst));
>> +
>> +end:
>> +  gas_assert (strlen (ginsn_str) < GINSN_LISTING_LEN);
> 
> This check comes too late - if it triggers, you've corrupted memory
> already. I wonder anyway why you construct all this "by hand", rather
> than using snprintf().
> 

I will update this to use snprintf instead.

Thanks for the review.
Indu
  
Jan Beulich Nov. 6, 2023, 11:03 a.m. UTC | #6
On 04.11.2023 08:29, Indu Bhagat wrote:
> On 11/2/23 08:53, Jan Beulich wrote:
>> On 30.10.2023 17:51, Indu Bhagat wrote:
>>> +static ginsnS*
>>> +ginsn_init (enum ginsn_type type, symbolS *sym, bool real_p)
>>> +{
>>> +  ginsnS *ginsn = ginsn_alloc ();
>>> +  ginsn->type = type;
>>> +  ginsn->sym = sym;
>>
>> Is a symbol hanging off of a ginsn ever intended to be altered? If
>> not, field and function argument would want to be pointer-to-const.
>>
> 
> No. This symbol is not intended to be altered.
> 
> However, using const symbolS* will cause complaints of "passing argument 
> 1 of ‘S_GET_NAME’ discards ‘const’ qualifier" etc.  Calling most of the 
> S_XXX (symbolS *sym) will need some type casting etc, but that will 
> somewhat defeat the purpose?

Well, yes, of course you shouldn't be casting away const-ness. I've made
a patch to adjust S_GET_NAME(), which I'll post after having run a full
set of tests on it.

>>> +static void
>>> +ginsn_set_src (struct ginsn_src *src, enum ginsn_src_type type, uint32_t reg,
>>> +	       int32_t immdisp)
>>
>> I find the use of fixed-width types suspicious here: For reg, likely it wants
>> to be unsigned int. For immdisp it's less clear: Immediates can be wider than
>> 32 bits, and they may also be either signed ot unsigned. From an abstract
>> perspective, assuming the immediate value actually is used for anything, I'd
>> expect offsetT to be used, following struct expressionS' X_add_number.
>>
> 
> Thanks, I will consider trying offsetT for immediate. It is a more 
> appropriate data type than int32_t.  For reg, why is uint32_t less 
> appropriate than unsigned int ?

Fixed-width types come with a price: In principle they may not even be
available, and they may also not be the most efficient types to deal with
for an architecture. Therefore my rule of thumb is that they're best
used only for "describing" interfaces. As long as (here) register numbers
will fit in an unsigned int, using that basic type looks more appropriate
to me.

>>> +ginsnS *
>>> +ginsn_new_mov (symbolS *sym, bool real_p,
>>> +	       enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
>>> +	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t 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;
>>> +}
>>
>> As indicated before, if both src and dst can be indirect here, ...
>>
>>> +ginsnS *
>>> +ginsn_new_store (symbolS *sym, bool real_p,
>>> +		 enum ginsn_src_type src_type, uint32_t src_reg,
>>> +		 enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t 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 (symbolS *sym, bool real_p,
>>> +		enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
>>> +		enum ginsn_dst_type dst_type, uint32_t 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;
>>> +}
>>
>> ... I can't see what these are needed for.
>>
> 
> For x86, they may not seem necessary. But for other architectures, or 
> say for future uses-cases, we may need them. I think it is more 
> meaningful (and readable) to see a LOAD/STORE/MOV ginsn for a machine 
> instruction of the same type.  For other RISC-like ISAs, it is clearer 
> to have separate MOV/LOAD/STORE instructions.
> 
> ginsn is meant to provide an infrastructure for other uses cases that 
> may crop up later.

But then I consider it as odd that you munge loads/stores on x86 into
ginsn_new_mov(), by using "indirect" operands. Imo it would be better
to be consistent here, one way or the other.

Jan
  
Indu Bhagat Dec. 10, 2023, 10:22 a.m. UTC | #7
Hi Jan,

Apologies for the delay (was traveling). I finally got around to work on 
these review items (Will post a V3 soon).

On 11/2/23 04:39, Jan Beulich wrote:
> On 02.11.2023 09:15, Indu Bhagat wrote:
>> On 10/31/23 07:10, Jan Beulich wrote:
>>> On 30.10.2023 17:51, Indu Bhagat wrote:
>>>> gas/
>>>> 	* Makefile.am: Add new files.
>>>> 	* Makefile.in: Regenerated.
>>>> 	* as.c (defined): Handle documentation and listing option for
>>>> 	ginsns and SCFI.
>>>> 	* config/obj-elf.c (obj_elf_size): Invoke ginsn_data_end.
>>>> 	(obj_elf_type): Invoke ginsn_data_begin.
>>>> 	* config/tc-i386.c (ginsn_new): New functionality to generate
>>>> 	ginsns.
>>>> 	(x86_scfi_callee_saved_p): New function.
>>>> 	(ginsn_dw2_regnum): Likewise.
>>>> 	(ginsn_set_where): Likewise.
>>>> 	(x86_ginsn_alu): Likewise.
>>>> 	(x86_ginsn_move): Likewise.
>>>> 	(x86_ginsn_lea): Likewise.
>>>> 	(x86_ginsn_jump): Likewise.
>>>> 	(x86_ginsn_jump_cond): Likewise.
>>>> 	(md_assemble): Invoke ginsn_new.
>>>> 	(s_insn): Likewise.
>>>> 	(i386_target_format): Add hard error for usage of --scfi with non AMD64 ABIs.
>>>> 	* config/tc-i386.h (TARGET_USE_GINSN): New definition.
>>>> 	(TARGET_USE_SCFI): Likewise.
>>>> 	(SCFI_NUM_REGS): Likewise.
>>>> 	(REG_FP): Likewise.
>>>> 	(REG_SP): Likewise.
>>>> 	(SCFI_INIT_CFA_OFFSET): Likewise.
>>>> 	(SCFI_CALLEE_SAVED_REG_P): Likewise.
>>>> 	(x86_scfi_callee_saved_p): Likewise.
>>>
>>> For this arch-specific code there's a fundamental question of maintenance
>>> cost here: It doesn't look very reasonable to me to demand of people adding
>>> support for new ISA extensions to also take into consideration all of this
>>> new machinery. Yet if any such addition affects SCFI, things will go out-
>>> of-sync without updating this code as well. It may not be very often that
>>> such updating is necessary, but right now there's APX work in progress
>>> which I expect will need dealing with here as well.
>>>
>>
>> I understand your concerns.  FWIW, for SCFI, we will need to translate
>> only a subset of instructions into ginsns (change of flow insns,
>> save/restore and arith on REG_SP/REG_FP).
> 
> Considering what you say further down regarding untraceability, I'm afraid
> you will need to care about all insns which have SP/FP as their destination.
> 

Yeah, All insns which have SP/FP as their destination must be seen by 
the SCFI machinery. If any instruction which has SP/FP as destination is 
missed, and the user has specified a --scfi command line argument, my 
thinking is that GAS should spit out an error or a diagnostic.

That said, the current implementation handles the most commonly used 
instructions to manage stack pointer for dynamic and static stack 
allocation. For unhandled instructions which end up _possibly_ affecting 
REG_FP/REG_SP (I say _possibly_ because at this time I check both 
i.op[0].regs and i.op[1].regs for REG_SP/REG_FP), there is a warning:
"Warning: SCFI: unhandled op 0x63 may cause incorrect CFI"

I will continue to work through these identified opcodes and reduce 
these warnings further.

>>   For APX, I see that there is
>> are new instructions that fall in this set unfortunately.  Hopefully the
>> set remains small. But in general, for future additions (and for APX
>> currently), there will be time to act as SCFI is for hand-written asm
>> for which synthesizing CFI is desired, not for all code that GAS
>> necessarily has to deal with.
> 
> I understand that, but if a new ISA extension is supported in a new gas
> version, I'd expect it to be legitimate for people to expect that SCFI is
> then also supported for them right away. Else detecting what may or may
> not be used with particular versions of gas is going to become a nightmare.
> 
>>>> +static ginsnS *
>>>> +x86_ginsn_alu (i386_insn insn, symbolS *insn_end_sym)
>>>> +{
>>>> +  offsetT src_imm;
>>>> +  uint32_t dw2_regnum;
>>>> +  enum ginsn_src_type src_type;
>>>> +  enum ginsn_dst_type dst_type;
>>>> +  ginsnS *ginsn = NULL;
>>>> +
>>>> +  /* FIXME - create ginsn for REG_SP target only ? */
>>>> +  /* Map for insn.tm.extension_opcode
>>>> +     000 ADD    100 AND
>>>> +     001 OR     101 SUB
>>>> +     010 ADC    110 XOR
>>>> +     011 SBB    111 CMP  */
>>>> +
>>>> +  /* add/sub imm, %reg.
>>>> +     and imm, %reg only at this time for SCFI. */
>>>> +  if (!(insn.tm.extension_opcode == 0
>>>> +	|| insn.tm.extension_opcode == 4
>>>> +	|| insn.tm.extension_opcode == 5))
>>>> +    return ginsn;
>>>
>>> Why is AND permitted, but OR/XOR aren't?
>>>
>>> Also this is about ALU insns with immediate operands only, yet that
>>> fact isn't reflected in the function name.
>>>
>>
>> OR/XOR should be handled. I will fix this.
>>

Spoke too soon. I remembered later why OR/XOR is not handled but AND is 
(I have now added a code comment around this).

An OR/XOR on REG_SP/REG_FP as destination makes the destination 
untraceable.  So these operations are skipped here.  At a later point 
(towards the end of x86_ginsn_new): the ginsn generation machinery will 
complain about these OR/XOR as "unhandled opcode for SCFI" and bail out 
if destination was REG_SP/REG_FP.

On that note, AND operation also makes REG_SP/REG_FP untraceable, but 
they are being generated currently.  This is because supporting DRAP 
pattern is something we plan to look into. Currently these are 
generated, but the SCFI machinery later bails out with a message "Error: 
SCFI: unsupported stack manipulation pattern"

>> This function is handling opcodes with imm only, true.  ADD/SUB with reg
>> are handled elsewhere (with opcode == 0x1 and opcode == 0x29).
> 
> I saw that, but this doesn't eliminate my remark regarding the name of
> this function. Also the two other opcodes you mention aren't quite
> enough.
> 

ACK. I added handling for 0x3 and 0x2b.  I updated the name of the 
function to reflect that this is handling opcodes with imm only: 
x86_ginsn_alu_imm.

>>>> +  /* TBD_GINSN_REPRESENTATION_LIMIT: There is no representation for when a
>>>> +     symbol is used as an operand, like so:
>>>> +	  addq    $simd_cmp_op+8, %rdx
>>>> +     Skip generating any ginsn for this.  */
>>>> +  if (insn.imm_operands == 1
>>>> +      && insn.op[0].imms->X_op == O_symbol)
>>>> +    return ginsn;
>>>> +
>>>> +  gas_assert (insn.imm_operands == 1
>>>> +	      && insn.op[0].imms->X_op == O_constant);
>>>
>>> Perhaps less fragile if you use != O_constant in the preceding if()?
>>> The remaining half could the move ahead of that if(), allowing half
>>> of its condition to also be dropped.
>>>
>>>> +  src_imm = insn.op[0].imms->X_add_number;
>>>> +  /* The second operand may be a register or indirect access.  */
>>>> +  if (insn.mem_operands == 1 && insn.base_reg)
>>>> +    {
>>>> +      dw2_regnum = ginsn_dw2_regnum (insn.base_reg);
>>>> +      src_type = GINSN_SRC_INDIRECT;
>>>> +      dst_type = GINSN_DST_INDIRECT;
>>>
>>> The possibly in use index register isn't of interest in this case?
>>> Nor the displacement in the memory operand, ...
>>>
>>
>> Not of interest, No.  When src_type and dst_type is INDIRECT.
> 
> It's not clear to me what you mean with your reply. If the addressing
> form used isn't relevant, why do you handle base-only and ...
> 
>>>> +    }
>>>> +  else if (insn.mem_operands == 1 && insn.index_reg)
>>>> +    {
>>>> +      dw2_regnum = ginsn_dw2_regnum (insn.index_reg);
>>>> +      src_type = GINSN_SRC_INDIRECT;
>>>> +      dst_type = GINSN_DST_INDIRECT;
>>>
>>> ... similarly applicable here?
> 
> ... index-only separately in the first place?
> 

This specific case does fall in the category of redundant ginsn for SCFI 
purposes. These INDIRECT addressing mode arith instructions can neither 
be the save/restore ops, nor can be used to alter REG_SP/REG_FP.

I have removed the handling of indirect access cases from 
x86_ginsn_alu_imm and tagged with TBD_GINSN_GEN_NOT_SCFI.

>>> Nor a segment override?
>>
>> Segment override probably is not of interest for SCFI.
>>
>>>
>>>> +    }
>>>> +  else
>>>> +    {
>>>> +      gas_assert (insn.reg_operands == 1);
>>>
>>> Afaict this will trigger when the memory operand has neither base
>>> nor index.
>>>
>>
>> memory operand with neither base nor index ?  An asm instruction example
>> will be helpful.
> 
> 	add	$1, symbol
> 	add	%eax, symbol
> 
> Maybe you really don't need to consider those (if only GPR destinations
> are of interest), but then a comment should be saying so and you'd
> probably want to bail from the function early on in such cases.
> 

Right, I dont need them for SCFI. Now we will exit early in such cases 
from relevant functions.

>>>> +static ginsnS *
>>>> +x86_ginsn_lea (i386_insn insn, symbolS *insn_end_sym)
>>>> +{
>>>> +  offsetT src_disp = 0;
>>>> +  ginsnS *ginsn = NULL;
>>>> +  uint32_t base_reg;
>>>> +  uint32_t index_reg;
>>>> +  offsetT index_scale;
>>>> +  uint32_t dst_reg;
>>>> +
>>>> +  if (!insn.index_reg && !insn.base_reg)
>>>> +    {
>>>> +      /* lea symbol, %rN.  */
>>>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>>>> +      /* FIXME - Skip encoding information about the symbol.
>>>> +	 This is TBD_GINSN_INFO_LOSS, but it is fine if the mode is
>>>> +	 GINSN_GEN_SCFI.  */
>>>> +      ginsn = ginsn_new_mov (insn_end_sym, false,
>>>> +			     GINSN_SRC_IMM, 0xf /* arbitrary const.  */, 0,
>>>> +			     GINSN_DST_REG, dst_reg, 0);
>>>> +    }
>>>> +  else if (insn.base_reg && !insn.index_reg)
>>>> +    {
>>>> +      /* lea    -0x2(%base),%dst.  */
>>>> +      base_reg = ginsn_dw2_regnum (insn.base_reg);
>>>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>>>> +
>>>> +      if (insn.disp_operands)
>>>> +	src_disp = insn.op[0].disps->X_add_number;
>>>
>>> What if the displacement expression is other than O_constant?
>>>
>>
>> For SCFI, a "complex" lea instruction will imply some untraceable change
>> to the destination reg.  For SCFI, the extent of untraceable change is
>> not of interest, hence, in general, some information loss in ginsn is
>> tolerable.
> 
> As a fundamental request: For anything that's of no interest, can you
> please try to cover this with as little (and thus clear) code as possible?
> No taking apart of sub-cases when those are indifferent in the end anyway.
> 

In general, this is something I have battled with on and off. In the end 
I chose the principle "Encode as precise ginsn as possible given its 
current representation".  I have tried to follow this mostly 
(x86_ginsn_lea is the known outlier). For all cases known to be 
imprecise, it is my intention to have them marked with 
TBD_GINSN_INFO_LOSS etc. And to not have too many of those..

If I leave out more sub-cases (elsewhere) because they are indifferent 
in the end for SCFI at this time, two problems:
   - More of the currently generated ginsn will look imprecise.
   - More code will need to be revisited when there is other ginsn 
generation modes to be supported.

>> I will have to take a look at such instructions to see what the options
>> are.  If you have a example handy, it will be helpful.
> 
> 	lea	symbol(%rax), %rbp
> 

Thanks. I have handled this one now.

>>>> +      if (src_disp)
>>>> +	/* Generate an ADD ginsn.  */
>>>> +	ginsn = ginsn_new_add (insn_end_sym, true,
>>>> +			       GINSN_SRC_REG, base_reg, 0,
>>>> +			       GINSN_SRC_IMM, 0, src_disp,
>>>> +			       GINSN_DST_REG, dst_reg, 0);
>>>> +      else
>>>> +	  /* Generate a MOV ginsn.  */
>>>> +	  ginsn = ginsn_new_mov (insn_end_sym, true,
>>>> +				 GINSN_SRC_REG, base_reg, 0,
>>>> +				 GINSN_DST_REG, dst_reg, 0);
>>>> +    }
>>>> +  else if (!insn.base_reg && insn.index_reg)
>>>> +    {
>>>> +      /* lea (,%index,imm), %dst.  */
>>>> +      /* FIXME - Skip encoding an explicit multiply operation, instead use
>>>> +	 GINSN_TYPE_OTHER.  This is TBD_GINSN_INFO_LOSS, but it is fine if
>>>> +	 the mode is GINSN_GEN_SCFI.  */
>>>> +      index_scale = insn.log2_scale_factor;
>>>> +      index_reg = ginsn_dw2_regnum (insn.index_reg);
>>>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>>>> +      ginsn = ginsn_new_other (insn_end_sym, true,
>>>> +			       GINSN_SRC_REG, index_reg,
>>>> +			       GINSN_SRC_IMM, index_scale,
>>>> +			       GINSN_DST_REG, dst_reg);
>>>> +    }
>>>> +  else
>>>> +    {
>>>> +      /* lea disp(%base,%index,imm) %dst.  */
>>>> +      /* FIXME - Skip encoding information about the disp and imm for index
>>>> +	 reg.  This is TBD_GINSN_INFO_LOSS, but it is fine if the mode is
>>>> +	 GINSN_GEN_SCFI.  */
>>>> +      base_reg = ginsn_dw2_regnum (insn.base_reg);
>>>> +      index_reg = ginsn_dw2_regnum (insn.index_reg);
>>>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>>>> +      /* Generate an ADD ginsn.  */
>>>> +      ginsn = ginsn_new_add (insn_end_sym, true,
>>>> +			     GINSN_SRC_REG, base_reg, 0,
>>>> +			     GINSN_SRC_REG, index_reg, 0,
>>>> +			     GINSN_DST_REG, dst_reg, 0);
>>>
>>> The comment mentions the displacement, but it's not passed on? In the earlier
>>> "else if" block you also pay attention to the scla factor, but here you don't?
>>> That said, I think I'm confused anyway about the FIXME comments.
>>>
>>
>> Ah, This is what I was hinting at in the previous comment.
>>
>> So whats going on here is the following -
>>
>> First, An "add reg1, reg2, reg3" (reg3 being the destination) ginsn is
>> only interesting for SCFI if the reg3 is either REG_FP or REG_SP. We
>> still generate them all nevertheless for now.
>>
>> Second, We say that "add reg1, reg2, reg3" makes reg3 "untraceable".
>> This is based on the observation that static stack usage in a function
>> will use imm based arithmetic insns.
> 
> That is, you only intend to support that way of allocating stack space
> (i.e. one of the apparently many constraints to using this machinery).
> 
>> So, if reg3 is REG_SP, REG_FP,
>> these instructions make REG_SP / REG_FP "untraceable".
>>
>> Note that, We are not allowing all registers for CFA tracking. (Now
>> _this_ is a constraint, yes. And which is why we dont support DRAP usage
>> currently. But its a specific pattern that we can accommodate in future.)
>>
>> Hence, tracking more information for such (untraceable) instructions is
>> unnecessary for SCFI purposes.  If and when GAS uses ginsn (in the i386
>> backend) for other purposes, such information loss will need to be
>> revisited.
> 
> I guess there wants to be a central comment (either in common code, or
> once per arch) which other comments then can refer to. To the reader
> of such code it needs to be clear what bits of information are relevant
> and which ones can be ignored (for now).
> 

OK. I will add some comments.

>>>> +      ginsn = ginsn_new_jump (insn_end_sym, true,
>>>> +			      GINSN_SRC_SYMBOL, 0, src_symbol);
>>>> +
>>>> +      ginsn_set_where (ginsn);
>>>> +    }
>>>> +
>>>> +  return ginsn;
>>>> +}
>>>
>>> Further, what about XABORT transferring control to what XBEGIN has
>>> supplied? (XBEGIN can, in a sense, also be considered a [conditional]
>>> branch.)
>>>
>>
>> My plan after writing this x86 -> ginsn translator was:
>> There are and will remain many machine instructions that may need to be
>> supported depending on hand-written assmebly programmers use.  This is
>> the reason I had left some asserts towards the end in the ginsn_new ()
>> API: if any instructions which affect the control flow, or where the
>> destination is REG_FP / REG_SP, or push* / pop*  are not handled, we
>> will have an assert fail and GAS will need to handle it.
> 
> Elsewhere you mention you want to use this for some of the kernel's
> assembly files. If this feature went in with incomplete coverage, the
> kernel build system detecting its availability will likely be misled.
> 
> Furthermore I'm of the opinion that at the time this series goes in,
> assertions should remain only to check that state is as expected. They
> should not be used to check that user input is unexpected. Such may
> only be communicated back by ordinary diagnostics; the assembler should
> not crash (and even deliberately) on certain input. Besides such being
> a bug, it also gets in the way of fuzzing.
> 

I hope V3 has improved in this regard as compared to V2. Some asserts 
still remain, I will continue to work my way through them.

>>>> +static ginsnS *
>>>> +x86_ginsn_jump_cond (i386_insn insn, symbolS *insn_end_sym)
>>>> +{
>>>> +  ginsnS *ginsn = NULL;
>>>> +  symbolS *src_symbol;
>>>> +
>>>> +  /* TBD_GINSN_GEN_NOT_SCFI: Ignore move to or from xmm reg for mode.  */
>>>> +  if (i.tm.opcode_space == SPACE_0F)
>>>> +    return ginsn;
>>>
>>> What is the comment about? And what about SPACE_0F-encoded conditional
>>> jumps (Jcc <disp32>)? And where are LOOP and J{E,R}CXZ handled?
>>>
>>
>> These need to be handled.  I will take a look.
> 
> Just to mention it: The Jcc <disp32> forms should appear only during
> relaxation, while - aiui - all your code runs earlier than that. I was
> misled by you (elsewhere) handling both 0xE9 and 0xEB as JMP opcodes,
> despite the <disp32> form there - iirc - also only appearing during
> relaxation.
> 

I added LOOP and J{E,R}CXZ insns.

Regarding JCC <disp32> et all appearing after relaxation: right. E9 
handling was an oversight. I have removed it.

>>>> +  gas_assert (insn.disp_operands == 1);
>>>> +
>>>> +  if (insn.op[0].disps->X_op == O_symbol)
>>>> +    {
>>>> +      src_symbol = insn.op[0].disps->X_add_symbol;
>>>> +      /* The jump target is expected to be a symbol with 0 addend.
>>>> +	 Assert for now to see if this assumption is true.  */
>>>> +      gas_assert (insn.op[0].disps->X_add_number == 0);
>>>> +      ginsn = ginsn_new_jump_cond (insn_end_sym, true,
>>>> +				   GINSN_SRC_SYMBOL, 0, src_symbol);
>>>> +      ginsn_set_where (ginsn);
>>>> +    }
>>>> +  else
>>>> +    /* Catch them for now so we know what we are dealing with.  */
>>>> +    gas_assert (0);
>>>
>>> I'm going from the assumption that this (and alike) will be addressed
>>> before this series is committed?
>>
>> I will work on the ones you have raised above in the review. My plan was
>> to exercise this code in different ways after commmitting and address
>> the failures as I run into them.
> 
> Well, as said, I think full insn coverage is a prereq to committing, or
> else there needs to be a way to easily detect an assumed fully functional
> implementation in a later gas version, no matter that a partially
> functional one may have been present in a number of earlier versions
> already. But of course Nick in particular may have a different view on
> this, and his view would trump mine.
> 
>>>> +{
>>>> +  uint16_t opcode;
>>>> +  uint32_t dw2_regnum;
>>>> +  uint32_t src2_dw2_regnum;
>>>> +  int32_t gdisp = 0;
>>>> +  ginsnS *ginsn = NULL;
>>>> +  ginsnS *ginsn_next = NULL;
>>>> +  ginsnS *ginsn_last = NULL;
>>>> +
>>>> +  /* FIXME - Need a way to check whether the decoding is sane.  The specific
>>>> +     checks around i.tm.opcode_space were added as issues were seen.  Likely
>>>> +     insufficient.  */
>>>
>>> Furthermore opcode encoding space (SPACE_...) need to be taken into
>>> account in all cases.
>>>
>>>> +  /* Currently supports generation of selected ginsns, sufficient for
>>>> +     the use-case of SCFI only.  To remove this condition will require
>>>> +     work on this target-specific process of creation of ginsns.  Some
>>>> +     of such places are tagged with TBD_GINSN_GEN_NOT_SCFI to serve as
>>>> +     examples.  */
>>>> +  if (gmode != GINSN_GEN_SCFI)
>>>> +    return ginsn;
>>>> +
>>>> +  opcode = i.tm.base_opcode;
>>>> +
>>>> +  switch (opcode)
>>>> +    {
>>>> +    case 0x1:
>>>> +      /* add reg, reg.  */
>>>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>>>
>>> You don't care about opcode 0 (byte operation). Then what about 16-bit
>>> operand size? Or, since we're talking of a 64-bit-ABI-only feature,
>>> even 32-bit operand size?
>>>
>>
>> Operand size in some cases does not affect SCFI. So we dont keep
>> information about operand sizes.  The case that I should handle operand
>> size is when they are pushed to / popped from stack.
> 
> So you care about recognizing when %rbp is overwritten by an insn. And
> you also care about the same for %ebp and %bp. In that sense operand
> size may indeed be unnecessary to determine. Except that then you also
> want to deal with byte operations, i.e. %bpl being the destination.
> 

I have adjusted the ginsn_dw2_regnum to handle correctly 8-bit register 
usages so that any writes to REG_SP/REG_FP are detected.  I also added a 
new test "ginsn-dw2-regnum" to exercise this API.

>>> Also what about opcode 0x3?
>>
>> LSL instruction ? It doesnt look like it can affect DWARF CFI of a
>> function. Please correct me if this is wrong.
> 
> LSL is 0f03, i.e. opcode 0x3 in SPACE_0F. What my comment was about is
> ADD with inversed operands (ModRM.rm soure, ModRM.reg destination). All
> four of these flavors of ADD are covered by a single template, using the
> D and W attributes to establish opcode bits 0 and 1 based on actual
> operands (and possibly pseudo-prefixes).
> 

I added handling for 0x3 ADD.

Can you elaborate on the "using the D and W attributes to establish 
opcode bits 0 and 1 based on actual operands (and possibly 
pseudo-prefixes)." a bit ?  Sounds like there is a way to identify the 
destination operand without switch casing on the opcode...Is there? Not 
just for handling of opcode 0x1 and 0x3 with a single template, I have a 
requirement elsewhere to know the dest opnd given an instruction without 
switch casing on the opcode value explicitly.

>>>> +      if (i.reg_operands == 2)
>>>> +	{
>>>> +	  src2_dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
>>>> +	  ginsn = ginsn_new_add (insn_end_sym, true,
>>>> +				 GINSN_SRC_REG, dw2_regnum, 0,
>>>> +				 GINSN_SRC_REG, src2_dw2_regnum, 0,
>>>> +				 GINSN_DST_REG, src2_dw2_regnum, 0);
>>>> +	  ginsn_set_where (ginsn);
>>>> +	}
>>>> +      else if (i.mem_operands && i.base_reg)
>>>> +	{
>>>> +	  src2_dw2_regnum = ginsn_dw2_regnum (i.base_reg);
>>>> +	  if (i.disp_operands == 1)
>>>> +	    gdisp = i.op[1].disps->X_add_number;
>>>> +
>>>> +	  ginsn = ginsn_new_add (insn_end_sym, true,
>>>> +				 GINSN_SRC_REG, dw2_regnum, 0,
>>>> +				 GINSN_SRC_INDIRECT, src2_dw2_regnum, gdisp,
>>>> +				 GINSN_DST_INDIRECT, src2_dw2_regnum, gdisp);
>>>> +	  ginsn_set_where (ginsn);
>>>> +	}
>>>> +      else
>>>> +	/* Catch them for now so we know what we are dealing with.  */
>>>> +	gas_assert (0);
>>>> +
>>>> +      break;
>>>> +    case 0x29:
>>>> +      /* If opcode_space == SPACE_0F, this is a movaps insn.  Skip it
>>>> +	 for GINSN_GEN_SCFI.  */
>>>> +      if (i.tm.opcode_space == SPACE_0F)
>>>> +	break;
>>>
>>> Extending on the earlier related comment: Why would you exclude just SPACE_0F?
>>>
>>
>> Perhaps here I should check that space == SPACE_BASE and only allow
>> that? And also for others.
> 
> Indeed.
> 
>>>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>>>> +      /* push fs / push gs.  */
>>>> +      ginsn = ginsn_new_sub (insn_end_sym, false,
>>>> +			     GINSN_SRC_REG, REG_SP, 0,
>>>> +			     GINSN_SRC_IMM, 0, 8,
>>>
>>> Note how the 8 here assumes flag_code == CODE_64BIT. (You further assume
>>> no operand size override here.)
>>>
>>
>> Hmm. Yes, I do assume no operand size override here. I will need to
>> understand how to calculate the operand size here. Looks like I need to
>> check for instruction prefixes 66H or REX.W.  Is there an API in the
>> backend that be used for this ?
> 
> i.prefixes[] and (depending on the phase of assembly) i.rex should be
> telling you.
> 

I have added checks for prefix 66H to detect 16-bit ops now. IIUC, I 
dont need to detect REX.W prefix specifically.

>>>> +      dw2_regnum = ginsn_dw2_regnum (i.base_reg);
>>>> +      ginsn = ginsn_new_load (insn_end_sym, false,
>>>> +			      GINSN_SRC_INDIRECT, REG_SP, 0,
>>>> +			      GINSN_DST_INDIRECT, dw2_regnum);
>>>
>>> When both operands are "indirect", what's the difference between
>>> move, load, and store? IOW if the above is permitted, can't all
>>> three be folded into a single ginsn kind?
>>>
>>
>> In theory, any of mov/load/store ginsn type may be used if both operands
>> are indirect mem access.
> 
> Which suggests that things can be simplified then.
> 

I prefer to keep mov/load/store as I find ginsn more readable then. Also 
other architectures do use the load/store ginsn; In aarch64, a 
load/store machine instruction translates to a load/store opcode in ginsn.

>>>> +    case 0xc2:
>>>> +    case 0xc3:
>>>> +      /* Near ret.  */
>>>> +      ginsn = ginsn_new_return (insn_end_sym, true);
>>>> +      ginsn_set_where (ginsn);
>>>> +      break;
>>>
>>> Where did the immediate operand of 0xC2 go? And what about far return
>>> (and more generally far branches)?
>>>
>>
>> A return ginsn for SCFI can afford this information loss.
> 
> Hmm, right, I guess by how much the stack pointer is incremented at the
> return point isn't of any interest anymore. Unless of course you intended
> to support co-routines.
> 
>>>> +    case 0xc9:
>>>> +      /* The 'leave' instruction copies the contents of the RBP register
>>>> +	 into the RSP register to release all stack space allocated to the
>>>> +	 procedure.  */
>>>> +      ginsn = ginsn_new_mov (insn_end_sym, false,
>>>> +			     GINSN_SRC_REG, REG_FP, 0,
>>>> +			     GINSN_DST_REG, REG_SP, 0);
>>>> +      ginsn_set_where (ginsn);
>>>> +
>>>> +      /* Then it restores the old value of the RBP register from the stack.  */
>>>> +      ginsn_next = ginsn_new_load (insn_end_sym, false,
>>>> +				   GINSN_SRC_INDIRECT, REG_SP, 0,
>>>> +				   GINSN_DST_REG, REG_FP);
>>>> +      ginsn_set_where (ginsn_next);
>>>> +
>>>> +      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
>>>> +      ginsn_last = ginsn_new_add (insn_end_sym, false,
>>>> +				  GINSN_SRC_REG, REG_SP, 0,
>>>> +				  GINSN_SRC_IMM, 0, 8,
>>>> +				  GINSN_DST_REG, REG_SP, 0);
>>>> +      ginsn_set_where (ginsn_next);
>>>> +
>>>> +      gas_assert (!ginsn_link_next (ginsn_next, ginsn_last));
>>>> +      break;
>>>
>>> You handle LEAVE, but not ENTER?
>>>
>>
>> I should have. From the three variants ([Enter imm16,0], [ENTER imm16,1]
>> and [ENTER imm16, imm8], the last one is not super clear currently. Is
>> it possible for you to pass down some understanding to me on that ?
> 
> I can't say more than what the CPU doc says: The second operand tells
> how many earlier frame pointers are copied to the local frame. Perhaps
> you want to simply treat non-zero values as untraceable SP/FP?
> 

I have handled ENTER with zero in operand values.  Other values are 
treated as untraceable SP/FP.

Thanks for reviewing
  
Indu Bhagat Dec. 10, 2023, 10:23 a.m. UTC | #8
On 11/6/23 03:03, Jan Beulich wrote:
> On 04.11.2023 08:29, Indu Bhagat wrote:
>> On 11/2/23 08:53, Jan Beulich wrote:
>>> On 30.10.2023 17:51, Indu Bhagat wrote:
>>>> +static ginsnS*
>>>> +ginsn_init (enum ginsn_type type, symbolS *sym, bool real_p)
>>>> +{
>>>> +  ginsnS *ginsn = ginsn_alloc ();
>>>> +  ginsn->type = type;
>>>> +  ginsn->sym = sym;
>>>
>>> Is a symbol hanging off of a ginsn ever intended to be altered? If
>>> not, field and function argument would want to be pointer-to-const.
>>>
>>
>> No. This symbol is not intended to be altered.
>>
>> However, using const symbolS* will cause complaints of "passing argument
>> 1 of ‘S_GET_NAME’ discards ‘const’ qualifier" etc.  Calling most of the
>> S_XXX (symbolS *sym) will need some type casting etc, but that will
>> somewhat defeat the purpose?
> 
> Well, yes, of course you shouldn't be casting away const-ness. I've made
> a patch to adjust S_GET_NAME(), which I'll post after having run a full
> set of tests on it.
> 

Thanks. I had rebased the series after your commit.  I had initially 
thought of this to be a big patch,  but clearly I was overthinking.

>>>> +static void
>>>> +ginsn_set_src (struct ginsn_src *src, enum ginsn_src_type type, uint32_t reg,
>>>> +	       int32_t immdisp)
>>>
>>> I find the use of fixed-width types suspicious here: For reg, likely it wants
>>> to be unsigned int. For immdisp it's less clear: Immediates can be wider than
>>> 32 bits, and they may also be either signed ot unsigned. From an abstract
>>> perspective, assuming the immediate value actually is used for anything, I'd
>>> expect offsetT to be used, following struct expressionS' X_add_number.
>>>
>>
>> Thanks, I will consider trying offsetT for immediate. It is a more
>> appropriate data type than int32_t.  For reg, why is uint32_t less
>> appropriate than unsigned int ?
> 
> Fixed-width types come with a price: In principle they may not even be
> available, and they may also not be the most efficient types to deal with
> for an architecture. Therefore my rule of thumb is that they're best
> used only for "describing" interfaces. As long as (here) register numbers
> will fit in an unsigned int, using that basic type looks more appropriate
> to me.
> 

Changed the type to unsigned int for reg. I see that the components 
surrounding the ginsn/scfi like dw2gencfi uses unsigned int for register 
numbers.  So it makes sense to stay uniform.

>>>> +ginsnS *
>>>> +ginsn_new_mov (symbolS *sym, bool real_p,
>>>> +	       enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
>>>> +	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t 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;
>>>> +}
>>>
>>> As indicated before, if both src and dst can be indirect here, ...
>>>
>>>> +ginsnS *
>>>> +ginsn_new_store (symbolS *sym, bool real_p,
>>>> +		 enum ginsn_src_type src_type, uint32_t src_reg,
>>>> +		 enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t 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 (symbolS *sym, bool real_p,
>>>> +		enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
>>>> +		enum ginsn_dst_type dst_type, uint32_t 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;
>>>> +}
>>>
>>> ... I can't see what these are needed for.
>>>
>>
>> For x86, they may not seem necessary. But for other architectures, or
>> say for future uses-cases, we may need them. I think it is more
>> meaningful (and readable) to see a LOAD/STORE/MOV ginsn for a machine
>> instruction of the same type.  For other RISC-like ISAs, it is clearer
>> to have separate MOV/LOAD/STORE instructions.
>>
>> ginsn is meant to provide an infrastructure for other uses cases that
>> may crop up later.
> 
> But then I consider it as odd that you munge loads/stores on x86 into
> ginsn_new_mov(), by using "indirect" operands. Imo it would be better
> to be consistent here, one way or the other.
> 

What I have not defined (deliberately) is any "temporary registers" in 
ginsn.  This means a CISC instruction cannot be split into multiple 
gisnns with data dependencies.  So to keep things simple, if machine 
instruction is a MOV, we pick MOV ginsn (with indirect access if this 
was a load/store).  If the machine instruction is an ADD, we pick ADD 
ginsn (with indirect access if applicable).

If I understand you correct, you suggest that we pick load/store ops 
like so:
   mov  disp(%reg), %reg --> load disp(%reg), %reg
   mov  %reg, disp(%reg) --> store %reg, disp(%reg)

Then what about arith ops with indirect access ? In theory those are 
load + arith + store. In other words, the above deliberation of picking 
load/store for mov seems inconsistent then.

For SCFI in aarch64 (WIP), load / store ginsns are used actively.
  
Jan Beulich Dec. 11, 2023, 7:59 a.m. UTC | #9
On 10.12.2023 11:22, Indu Bhagat wrote:
> On 11/2/23 04:39, Jan Beulich wrote:
>> On 02.11.2023 09:15, Indu Bhagat wrote:
>>> On 10/31/23 07:10, Jan Beulich wrote:
>>>> On 30.10.2023 17:51, Indu Bhagat wrote:
>>>>> gas/
>>>>> 	* Makefile.am: Add new files.
>>>>> 	* Makefile.in: Regenerated.
>>>>> 	* as.c (defined): Handle documentation and listing option for
>>>>> 	ginsns and SCFI.
>>>>> 	* config/obj-elf.c (obj_elf_size): Invoke ginsn_data_end.
>>>>> 	(obj_elf_type): Invoke ginsn_data_begin.
>>>>> 	* config/tc-i386.c (ginsn_new): New functionality to generate
>>>>> 	ginsns.
>>>>> 	(x86_scfi_callee_saved_p): New function.
>>>>> 	(ginsn_dw2_regnum): Likewise.
>>>>> 	(ginsn_set_where): Likewise.
>>>>> 	(x86_ginsn_alu): Likewise.
>>>>> 	(x86_ginsn_move): Likewise.
>>>>> 	(x86_ginsn_lea): Likewise.
>>>>> 	(x86_ginsn_jump): Likewise.
>>>>> 	(x86_ginsn_jump_cond): Likewise.
>>>>> 	(md_assemble): Invoke ginsn_new.
>>>>> 	(s_insn): Likewise.
>>>>> 	(i386_target_format): Add hard error for usage of --scfi with non AMD64 ABIs.
>>>>> 	* config/tc-i386.h (TARGET_USE_GINSN): New definition.
>>>>> 	(TARGET_USE_SCFI): Likewise.
>>>>> 	(SCFI_NUM_REGS): Likewise.
>>>>> 	(REG_FP): Likewise.
>>>>> 	(REG_SP): Likewise.
>>>>> 	(SCFI_INIT_CFA_OFFSET): Likewise.
>>>>> 	(SCFI_CALLEE_SAVED_REG_P): Likewise.
>>>>> 	(x86_scfi_callee_saved_p): Likewise.
>>>>
>>>> For this arch-specific code there's a fundamental question of maintenance
>>>> cost here: It doesn't look very reasonable to me to demand of people adding
>>>> support for new ISA extensions to also take into consideration all of this
>>>> new machinery. Yet if any such addition affects SCFI, things will go out-
>>>> of-sync without updating this code as well. It may not be very often that
>>>> such updating is necessary, but right now there's APX work in progress
>>>> which I expect will need dealing with here as well.
>>>>
>>>
>>> I understand your concerns.  FWIW, for SCFI, we will need to translate
>>> only a subset of instructions into ginsns (change of flow insns,
>>> save/restore and arith on REG_SP/REG_FP).
>>
>> Considering what you say further down regarding untraceability, I'm afraid
>> you will need to care about all insns which have SP/FP as their destination.
>>
> 
> Yeah, All insns which have SP/FP as their destination must be seen by 
> the SCFI machinery. If any instruction which has SP/FP as destination is 
> missed, and the user has specified a --scfi command line argument, my 
> thinking is that GAS should spit out an error or a diagnostic.
> 
> That said, the current implementation handles the most commonly used 
> instructions to manage stack pointer for dynamic and static stack 
> allocation. For unhandled instructions which end up _possibly_ affecting 
> REG_FP/REG_SP (I say _possibly_ because at this time I check both 
> i.op[0].regs and i.op[1].regs for REG_SP/REG_FP), there is a warning:
> "Warning: SCFI: unhandled op 0x63 may cause incorrect CFI"

Yet neither i.op[0] nor i.op[1] may represent the destination register of
an insn, when there are more than 2 operands.

>>>>> +static ginsnS *
>>>>> +x86_ginsn_alu (i386_insn insn, symbolS *insn_end_sym)
>>>>> +{
>>>>> +  offsetT src_imm;
>>>>> +  uint32_t dw2_regnum;
>>>>> +  enum ginsn_src_type src_type;
>>>>> +  enum ginsn_dst_type dst_type;
>>>>> +  ginsnS *ginsn = NULL;
>>>>> +
>>>>> +  /* FIXME - create ginsn for REG_SP target only ? */
>>>>> +  /* Map for insn.tm.extension_opcode
>>>>> +     000 ADD    100 AND
>>>>> +     001 OR     101 SUB
>>>>> +     010 ADC    110 XOR
>>>>> +     011 SBB    111 CMP  */
>>>>> +
>>>>> +  /* add/sub imm, %reg.
>>>>> +     and imm, %reg only at this time for SCFI. */
>>>>> +  if (!(insn.tm.extension_opcode == 0
>>>>> +	|| insn.tm.extension_opcode == 4
>>>>> +	|| insn.tm.extension_opcode == 5))
>>>>> +    return ginsn;
>>>>
>>>> Why is AND permitted, but OR/XOR aren't?
>>>>
>>>> Also this is about ALU insns with immediate operands only, yet that
>>>> fact isn't reflected in the function name.
>>>>
>>>
>>> OR/XOR should be handled. I will fix this.
>>>
> 
> Spoke too soon. I remembered later why OR/XOR is not handled but AND is 
> (I have now added a code comment around this).
> 
> An OR/XOR on REG_SP/REG_FP as destination makes the destination 
> untraceable.  So these operations are skipped here.  At a later point 
> (towards the end of x86_ginsn_new): the ginsn generation machinery will 
> complain about these OR/XOR as "unhandled opcode for SCFI" and bail out 
> if destination was REG_SP/REG_FP.
> 
> On that note, AND operation also makes REG_SP/REG_FP untraceable, but 
> they are being generated currently.  This is because supporting DRAP 
> pattern is something we plan to look into. Currently these are 
> generated, but the SCFI machinery later bails out with a message "Error: 
> SCFI: unsupported stack manipulation pattern"

But why would you need special handling just to mark a register untraceable?
Generic code merely looking at the destination register ought to be enough?

>>>>> +static ginsnS *
>>>>> +x86_ginsn_lea (i386_insn insn, symbolS *insn_end_sym)
>>>>> +{
>>>>> +  offsetT src_disp = 0;
>>>>> +  ginsnS *ginsn = NULL;
>>>>> +  uint32_t base_reg;
>>>>> +  uint32_t index_reg;
>>>>> +  offsetT index_scale;
>>>>> +  uint32_t dst_reg;
>>>>> +
>>>>> +  if (!insn.index_reg && !insn.base_reg)
>>>>> +    {
>>>>> +      /* lea symbol, %rN.  */
>>>>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>>>>> +      /* FIXME - Skip encoding information about the symbol.
>>>>> +	 This is TBD_GINSN_INFO_LOSS, but it is fine if the mode is
>>>>> +	 GINSN_GEN_SCFI.  */
>>>>> +      ginsn = ginsn_new_mov (insn_end_sym, false,
>>>>> +			     GINSN_SRC_IMM, 0xf /* arbitrary const.  */, 0,
>>>>> +			     GINSN_DST_REG, dst_reg, 0);
>>>>> +    }
>>>>> +  else if (insn.base_reg && !insn.index_reg)
>>>>> +    {
>>>>> +      /* lea    -0x2(%base),%dst.  */
>>>>> +      base_reg = ginsn_dw2_regnum (insn.base_reg);
>>>>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>>>>> +
>>>>> +      if (insn.disp_operands)
>>>>> +	src_disp = insn.op[0].disps->X_add_number;
>>>>
>>>> What if the displacement expression is other than O_constant?
>>>>
>>>
>>> For SCFI, a "complex" lea instruction will imply some untraceable change
>>> to the destination reg.  For SCFI, the extent of untraceable change is
>>> not of interest, hence, in general, some information loss in ginsn is
>>> tolerable.
>>
>> As a fundamental request: For anything that's of no interest, can you
>> please try to cover this with as little (and thus clear) code as possible?
>> No taking apart of sub-cases when those are indifferent in the end anyway.
>>
> 
> In general, this is something I have battled with on and off. In the end 
> I chose the principle "Encode as precise ginsn as possible given its 
> current representation".  I have tried to follow this mostly 
> (x86_ginsn_lea is the known outlier). For all cases known to be 
> imprecise, it is my intention to have them marked with 
> TBD_GINSN_INFO_LOSS etc. And to not have too many of those..
> 
> If I leave out more sub-cases (elsewhere) because they are indifferent 
> in the end for SCFI at this time, two problems:
>    - More of the currently generated ginsn will look imprecise.
>    - More code will need to be revisited when there is other ginsn 
> generation modes to be supported.

I can see your view. Still there's the other view of code not really
being needed right now potentially also net being in the exact shape it
may be needed down the road, at which point it would similarly need
touching again.

>>>>> +{
>>>>> +  uint16_t opcode;
>>>>> +  uint32_t dw2_regnum;
>>>>> +  uint32_t src2_dw2_regnum;
>>>>> +  int32_t gdisp = 0;
>>>>> +  ginsnS *ginsn = NULL;
>>>>> +  ginsnS *ginsn_next = NULL;
>>>>> +  ginsnS *ginsn_last = NULL;
>>>>> +
>>>>> +  /* FIXME - Need a way to check whether the decoding is sane.  The specific
>>>>> +     checks around i.tm.opcode_space were added as issues were seen.  Likely
>>>>> +     insufficient.  */
>>>>
>>>> Furthermore opcode encoding space (SPACE_...) need to be taken into
>>>> account in all cases.
>>>>
>>>>> +  /* Currently supports generation of selected ginsns, sufficient for
>>>>> +     the use-case of SCFI only.  To remove this condition will require
>>>>> +     work on this target-specific process of creation of ginsns.  Some
>>>>> +     of such places are tagged with TBD_GINSN_GEN_NOT_SCFI to serve as
>>>>> +     examples.  */
>>>>> +  if (gmode != GINSN_GEN_SCFI)
>>>>> +    return ginsn;
>>>>> +
>>>>> +  opcode = i.tm.base_opcode;
>>>>> +
>>>>> +  switch (opcode)
>>>>> +    {
>>>>> +    case 0x1:
>>>>> +      /* add reg, reg.  */
>>>>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>>>>
>>>> You don't care about opcode 0 (byte operation). Then what about 16-bit
>>>> operand size? Or, since we're talking of a 64-bit-ABI-only feature,
>>>> even 32-bit operand size?
>>>>
>>>
>>> Operand size in some cases does not affect SCFI. So we dont keep
>>> information about operand sizes.  The case that I should handle operand
>>> size is when they are pushed to / popped from stack.
>>
>> So you care about recognizing when %rbp is overwritten by an insn. And
>> you also care about the same for %ebp and %bp. In that sense operand
>> size may indeed be unnecessary to determine. Except that then you also
>> want to deal with byte operations, i.e. %bpl being the destination.
>>
> 
> I have adjusted the ginsn_dw2_regnum to handle correctly 8-bit register 
> usages so that any writes to REG_SP/REG_FP are detected.  I also added a 
> new test "ginsn-dw2-regnum" to exercise this API.
> 
>>>> Also what about opcode 0x3?
>>>
>>> LSL instruction ? It doesnt look like it can affect DWARF CFI of a
>>> function. Please correct me if this is wrong.
>>
>> LSL is 0f03, i.e. opcode 0x3 in SPACE_0F. What my comment was about is
>> ADD with inversed operands (ModRM.rm soure, ModRM.reg destination). All
>> four of these flavors of ADD are covered by a single template, using the
>> D and W attributes to establish opcode bits 0 and 1 based on actual
>> operands (and possibly pseudo-prefixes).
>>
> 
> I added handling for 0x3 ADD.
> 
> Can you elaborate on the "using the D and W attributes to establish 
> opcode bits 0 and 1 based on actual operands (and possibly 
> pseudo-prefixes)." a bit ?

Well, reg <-> reg/mem ADDs (taking the specific example) come in two
forms: ModR/M.reg reprsenting the source and the same representing the
destination. They're represented by a single opcode template, with D
indicating that both directions can be used.

Similarly there are multiple operand sizes supported by several such
templates, with W indicating that both byte nad word/dword/qword
operation is possible to encode by OR-ing in a single opcode bit.

For your purposes, W is likely of no direct interest. D may be, yet
then I take it that you work on actual operands, not on what templates
permit. In actual operands, destination (due to following to AT&T
syntax layout in the internal representation) is last at all stages of
assembly (for Intel syntax input after swapping operands, of course).

>>>>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>>>>> +      /* push fs / push gs.  */
>>>>> +      ginsn = ginsn_new_sub (insn_end_sym, false,
>>>>> +			     GINSN_SRC_REG, REG_SP, 0,
>>>>> +			     GINSN_SRC_IMM, 0, 8,
>>>>
>>>> Note how the 8 here assumes flag_code == CODE_64BIT. (You further assume
>>>> no operand size override here.)
>>>>
>>>
>>> Hmm. Yes, I do assume no operand size override here. I will need to
>>> understand how to calculate the operand size here. Looks like I need to
>>> check for instruction prefixes 66H or REX.W.  Is there an API in the
>>> backend that be used for this ?
>>
>> i.prefixes[] and (depending on the phase of assembly) i.rex should be
>> telling you.
>>
> 
> I have added checks for prefix 66H to detect 16-bit ops now. IIUC, I 
> dont need to detect REX.W prefix specifically.

I'm not sure. REX.W clear likely means the destination becomes untraceable?
Whereas REX.W set means to full register is updated (upper half not zeroed)
and hence it may remain traceable?

Jan
  
Jan Beulich Dec. 11, 2023, 8:02 a.m. UTC | #10
On 10.12.2023 11:23, Indu Bhagat wrote:
> On 11/6/23 03:03, Jan Beulich wrote:
>> On 04.11.2023 08:29, Indu Bhagat wrote:
>>> On 11/2/23 08:53, Jan Beulich wrote:
>>>> On 30.10.2023 17:51, Indu Bhagat wrote:
>>>>> +ginsnS *
>>>>> +ginsn_new_mov (symbolS *sym, bool real_p,
>>>>> +	       enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
>>>>> +	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t 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;
>>>>> +}
>>>>
>>>> As indicated before, if both src and dst can be indirect here, ...
>>>>
>>>>> +ginsnS *
>>>>> +ginsn_new_store (symbolS *sym, bool real_p,
>>>>> +		 enum ginsn_src_type src_type, uint32_t src_reg,
>>>>> +		 enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t 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 (symbolS *sym, bool real_p,
>>>>> +		enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
>>>>> +		enum ginsn_dst_type dst_type, uint32_t 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;
>>>>> +}
>>>>
>>>> ... I can't see what these are needed for.
>>>>
>>>
>>> For x86, they may not seem necessary. But for other architectures, or
>>> say for future uses-cases, we may need them. I think it is more
>>> meaningful (and readable) to see a LOAD/STORE/MOV ginsn for a machine
>>> instruction of the same type.  For other RISC-like ISAs, it is clearer
>>> to have separate MOV/LOAD/STORE instructions.
>>>
>>> ginsn is meant to provide an infrastructure for other uses cases that
>>> may crop up later.
>>
>> But then I consider it as odd that you munge loads/stores on x86 into
>> ginsn_new_mov(), by using "indirect" operands. Imo it would be better
>> to be consistent here, one way or the other.
>>
> 
> What I have not defined (deliberately) is any "temporary registers" in 
> ginsn.  This means a CISC instruction cannot be split into multiple 
> gisnns with data dependencies.  So to keep things simple, if machine 
> instruction is a MOV, we pick MOV ginsn (with indirect access if this 
> was a load/store).  If the machine instruction is an ADD, we pick ADD 
> ginsn (with indirect access if applicable).
> 
> If I understand you correct, you suggest that we pick load/store ops 
> like so:
>    mov  disp(%reg), %reg --> load disp(%reg), %reg
>    mov  %reg, disp(%reg) --> store %reg, disp(%reg)
> 
> Then what about arith ops with indirect access ? In theory those are 
> load + arith + store. In other words, the above deliberation of picking 
> load/store for mov seems inconsistent then.

If you don't decompose read-modify-write (memory destination) operations,
then some kind of inconsistency is always going to be there.

Jan
  
Indu Bhagat Dec. 19, 2023, 7:07 a.m. UTC | #11
On 12/10/23 23:59, Jan Beulich wrote:
> On 10.12.2023 11:22, Indu Bhagat wrote:
>> On 11/2/23 04:39, Jan Beulich wrote:
>>> On 02.11.2023 09:15, Indu Bhagat wrote:
>>>> On 10/31/23 07:10, Jan Beulich wrote:
>>>>> On 30.10.2023 17:51, Indu Bhagat wrote:
>>>>>> gas/
>>>>>> 	* Makefile.am: Add new files.
>>>>>> 	* Makefile.in: Regenerated.
>>>>>> 	* as.c (defined): Handle documentation and listing option for
>>>>>> 	ginsns and SCFI.
>>>>>> 	* config/obj-elf.c (obj_elf_size): Invoke ginsn_data_end.
>>>>>> 	(obj_elf_type): Invoke ginsn_data_begin.
>>>>>> 	* config/tc-i386.c (ginsn_new): New functionality to generate
>>>>>> 	ginsns.
>>>>>> 	(x86_scfi_callee_saved_p): New function.
>>>>>> 	(ginsn_dw2_regnum): Likewise.
>>>>>> 	(ginsn_set_where): Likewise.
>>>>>> 	(x86_ginsn_alu): Likewise.
>>>>>> 	(x86_ginsn_move): Likewise.
>>>>>> 	(x86_ginsn_lea): Likewise.
>>>>>> 	(x86_ginsn_jump): Likewise.
>>>>>> 	(x86_ginsn_jump_cond): Likewise.
>>>>>> 	(md_assemble): Invoke ginsn_new.
>>>>>> 	(s_insn): Likewise.
>>>>>> 	(i386_target_format): Add hard error for usage of --scfi with non AMD64 ABIs.
>>>>>> 	* config/tc-i386.h (TARGET_USE_GINSN): New definition.
>>>>>> 	(TARGET_USE_SCFI): Likewise.
>>>>>> 	(SCFI_NUM_REGS): Likewise.
>>>>>> 	(REG_FP): Likewise.
>>>>>> 	(REG_SP): Likewise.
>>>>>> 	(SCFI_INIT_CFA_OFFSET): Likewise.
>>>>>> 	(SCFI_CALLEE_SAVED_REG_P): Likewise.
>>>>>> 	(x86_scfi_callee_saved_p): Likewise.
>>>>>
>>>>> For this arch-specific code there's a fundamental question of maintenance
>>>>> cost here: It doesn't look very reasonable to me to demand of people adding
>>>>> support for new ISA extensions to also take into consideration all of this
>>>>> new machinery. Yet if any such addition affects SCFI, things will go out-
>>>>> of-sync without updating this code as well. It may not be very often that
>>>>> such updating is necessary, but right now there's APX work in progress
>>>>> which I expect will need dealing with here as well.
>>>>>
>>>>
>>>> I understand your concerns.  FWIW, for SCFI, we will need to translate
>>>> only a subset of instructions into ginsns (change of flow insns,
>>>> save/restore and arith on REG_SP/REG_FP).
>>>
>>> Considering what you say further down regarding untraceability, I'm afraid
>>> you will need to care about all insns which have SP/FP as their destination.
>>>
>>
>> Yeah, All insns which have SP/FP as their destination must be seen by
>> the SCFI machinery. If any instruction which has SP/FP as destination is
>> missed, and the user has specified a --scfi command line argument, my
>> thinking is that GAS should spit out an error or a diagnostic.
>>
>> That said, the current implementation handles the most commonly used
>> instructions to manage stack pointer for dynamic and static stack
>> allocation. For unhandled instructions which end up _possibly_ affecting
>> REG_FP/REG_SP (I say _possibly_ because at this time I check both
>> i.op[0].regs and i.op[1].regs for REG_SP/REG_FP), there is a warning:
>> "Warning: SCFI: unhandled op 0x63 may cause incorrect CFI"
> 
> Yet neither i.op[0] nor i.op[1] may represent the destination register of
> an insn, when there are more than 2 operands.
> 

I have now handled this case properly I think (will include this in V4). 
Thanks for your comment below.

>>>>>> +static ginsnS *
>>>>>> +x86_ginsn_alu (i386_insn insn, symbolS *insn_end_sym)
>>>>>> +{
>>>>>> +  offsetT src_imm;
>>>>>> +  uint32_t dw2_regnum;
>>>>>> +  enum ginsn_src_type src_type;
>>>>>> +  enum ginsn_dst_type dst_type;
>>>>>> +  ginsnS *ginsn = NULL;
>>>>>> +
>>>>>> +  /* FIXME - create ginsn for REG_SP target only ? */
>>>>>> +  /* Map for insn.tm.extension_opcode
>>>>>> +     000 ADD    100 AND
>>>>>> +     001 OR     101 SUB
>>>>>> +     010 ADC    110 XOR
>>>>>> +     011 SBB    111 CMP  */
>>>>>> +
>>>>>> +  /* add/sub imm, %reg.
>>>>>> +     and imm, %reg only at this time for SCFI. */
>>>>>> +  if (!(insn.tm.extension_opcode == 0
>>>>>> +	|| insn.tm.extension_opcode == 4
>>>>>> +	|| insn.tm.extension_opcode == 5))
>>>>>> +    return ginsn;
>>>>>
>>>>> Why is AND permitted, but OR/XOR aren't?
>>>>>
>>>>> Also this is about ALU insns with immediate operands only, yet that
>>>>> fact isn't reflected in the function name.
>>>>>
>>>>
>>>> OR/XOR should be handled. I will fix this.
>>>>
>>
>> Spoke too soon. I remembered later why OR/XOR is not handled but AND is
>> (I have now added a code comment around this).
>>
>> An OR/XOR on REG_SP/REG_FP as destination makes the destination
>> untraceable.  So these operations are skipped here.  At a later point
>> (towards the end of x86_ginsn_new): the ginsn generation machinery will
>> complain about these OR/XOR as "unhandled opcode for SCFI" and bail out
>> if destination was REG_SP/REG_FP.
>>
>> On that note, AND operation also makes REG_SP/REG_FP untraceable, but
>> they are being generated currently.  This is because supporting DRAP
>> pattern is something we plan to look into. Currently these are
>> generated, but the SCFI machinery later bails out with a message "Error:
>> SCFI: unsupported stack manipulation pattern"
> 
> But why would you need special handling just to mark a register untraceable?
> Generic code merely looking at the destination register ought to be enough?
> 

Yes, now a ginsn on type GINSN_TYPE_OTHER with destination REG_SP/REG_FP 
as applicable is generated.  The SCFI machinery then bails out if this 
ginsn makes REG_FP untraceable, when REG_FP based CFA tracking is 
active.  If CFA tracking is _not_ REG_FP based, it is fine to manipulate 
REG_FP in a untraceable way.

>>>>>> +static ginsnS *
>>>>>> +x86_ginsn_lea (i386_insn insn, symbolS *insn_end_sym)
>>>>>> +{
>>>>>> +  offsetT src_disp = 0;
>>>>>> +  ginsnS *ginsn = NULL;
>>>>>> +  uint32_t base_reg;
>>>>>> +  uint32_t index_reg;
>>>>>> +  offsetT index_scale;
>>>>>> +  uint32_t dst_reg;
>>>>>> +
>>>>>> +  if (!insn.index_reg && !insn.base_reg)
>>>>>> +    {
>>>>>> +      /* lea symbol, %rN.  */
>>>>>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>>>>>> +      /* FIXME - Skip encoding information about the symbol.
>>>>>> +	 This is TBD_GINSN_INFO_LOSS, but it is fine if the mode is
>>>>>> +	 GINSN_GEN_SCFI.  */
>>>>>> +      ginsn = ginsn_new_mov (insn_end_sym, false,
>>>>>> +			     GINSN_SRC_IMM, 0xf /* arbitrary const.  */, 0,
>>>>>> +			     GINSN_DST_REG, dst_reg, 0);
>>>>>> +    }
>>>>>> +  else if (insn.base_reg && !insn.index_reg)
>>>>>> +    {
>>>>>> +      /* lea    -0x2(%base),%dst.  */
>>>>>> +      base_reg = ginsn_dw2_regnum (insn.base_reg);
>>>>>> +      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
>>>>>> +
>>>>>> +      if (insn.disp_operands)
>>>>>> +	src_disp = insn.op[0].disps->X_add_number;
>>>>>
>>>>> What if the displacement expression is other than O_constant?
>>>>>
>>>>
>>>> For SCFI, a "complex" lea instruction will imply some untraceable change
>>>> to the destination reg.  For SCFI, the extent of untraceable change is
>>>> not of interest, hence, in general, some information loss in ginsn is
>>>> tolerable.
>>>
>>> As a fundamental request: For anything that's of no interest, can you
>>> please try to cover this with as little (and thus clear) code as possible?
>>> No taking apart of sub-cases when those are indifferent in the end anyway.
>>>
>>
>> In general, this is something I have battled with on and off. In the end
>> I chose the principle "Encode as precise ginsn as possible given its
>> current representation".  I have tried to follow this mostly
>> (x86_ginsn_lea is the known outlier). For all cases known to be
>> imprecise, it is my intention to have them marked with
>> TBD_GINSN_INFO_LOSS etc. And to not have too many of those..
>>
>> If I leave out more sub-cases (elsewhere) because they are indifferent
>> in the end for SCFI at this time, two problems:
>>     - More of the currently generated ginsn will look imprecise.
>>     - More code will need to be revisited when there is other ginsn
>> generation modes to be supported.
> 
> I can see your view. Still there's the other view of code not really
> being needed right now potentially also net being in the exact shape it
> may be needed down the road, at which point it would similarly need
> touching again.
> 
>>>>>> +{
>>>>>> +  uint16_t opcode;
>>>>>> +  uint32_t dw2_regnum;
>>>>>> +  uint32_t src2_dw2_regnum;
>>>>>> +  int32_t gdisp = 0;
>>>>>> +  ginsnS *ginsn = NULL;
>>>>>> +  ginsnS *ginsn_next = NULL;
>>>>>> +  ginsnS *ginsn_last = NULL;
>>>>>> +
>>>>>> +  /* FIXME - Need a way to check whether the decoding is sane.  The specific
>>>>>> +     checks around i.tm.opcode_space were added as issues were seen.  Likely
>>>>>> +     insufficient.  */
>>>>>
>>>>> Furthermore opcode encoding space (SPACE_...) need to be taken into
>>>>> account in all cases.
>>>>>
>>>>>> +  /* Currently supports generation of selected ginsns, sufficient for
>>>>>> +     the use-case of SCFI only.  To remove this condition will require
>>>>>> +     work on this target-specific process of creation of ginsns.  Some
>>>>>> +     of such places are tagged with TBD_GINSN_GEN_NOT_SCFI to serve as
>>>>>> +     examples.  */
>>>>>> +  if (gmode != GINSN_GEN_SCFI)
>>>>>> +    return ginsn;
>>>>>> +
>>>>>> +  opcode = i.tm.base_opcode;
>>>>>> +
>>>>>> +  switch (opcode)
>>>>>> +    {
>>>>>> +    case 0x1:
>>>>>> +      /* add reg, reg.  */
>>>>>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>>>>>
>>>>> You don't care about opcode 0 (byte operation). Then what about 16-bit
>>>>> operand size? Or, since we're talking of a 64-bit-ABI-only feature,
>>>>> even 32-bit operand size?
>>>>>
>>>>
>>>> Operand size in some cases does not affect SCFI. So we dont keep
>>>> information about operand sizes.  The case that I should handle operand
>>>> size is when they are pushed to / popped from stack.
>>>
>>> So you care about recognizing when %rbp is overwritten by an insn. And
>>> you also care about the same for %ebp and %bp. In that sense operand
>>> size may indeed be unnecessary to determine. Except that then you also
>>> want to deal with byte operations, i.e. %bpl being the destination.
>>>
>>
>> I have adjusted the ginsn_dw2_regnum to handle correctly 8-bit register
>> usages so that any writes to REG_SP/REG_FP are detected.  I also added a
>> new test "ginsn-dw2-regnum" to exercise this API.
>>
>>>>> Also what about opcode 0x3?
>>>>
>>>> LSL instruction ? It doesnt look like it can affect DWARF CFI of a
>>>> function. Please correct me if this is wrong.
>>>
>>> LSL is 0f03, i.e. opcode 0x3 in SPACE_0F. What my comment was about is
>>> ADD with inversed operands (ModRM.rm soure, ModRM.reg destination). All
>>> four of these flavors of ADD are covered by a single template, using the
>>> D and W attributes to establish opcode bits 0 and 1 based on actual
>>> operands (and possibly pseudo-prefixes).
>>>
>>
>> I added handling for 0x3 ADD.
>>
>> Can you elaborate on the "using the D and W attributes to establish
>> opcode bits 0 and 1 based on actual operands (and possibly
>> pseudo-prefixes)." a bit ?
> 
> Well, reg <-> reg/mem ADDs (taking the specific example) come in two
> forms: ModR/M.reg reprsenting the source and the same representing the
> destination. They're represented by a single opcode template, with D
> indicating that both directions can be used.
> 
> Similarly there are multiple operand sizes supported by several such
> templates, with W indicating that both byte nad word/dword/qword
> operation is possible to encode by OR-ing in a single opcode bit.
> 
> For your purposes, W is likely of no direct interest. D may be, yet
> then I take it that you work on actual operands, not on what templates
> permit. In actual operands, destination (due to following to AT&T
> syntax layout in the internal representation) is last at all stages of
> assembly (for Intel syntax input after swapping operands, of course).
> 

Thanks.  We will now take the last reg operand, i.op[i.operands - 
1].regs (after checks on i.operands and i.reg_operands) as destination 
and if the register is REG_SP/REG_FP, we generate GINSN_TYPE_OTHER with 
the appropriate destination register.

I will include this change in a V4 posting.

>>>>>> +      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
>>>>>> +      /* push fs / push gs.  */
>>>>>> +      ginsn = ginsn_new_sub (insn_end_sym, false,
>>>>>> +			     GINSN_SRC_REG, REG_SP, 0,
>>>>>> +			     GINSN_SRC_IMM, 0, 8,
>>>>>
>>>>> Note how the 8 here assumes flag_code == CODE_64BIT. (You further assume
>>>>> no operand size override here.)
>>>>>
>>>>
>>>> Hmm. Yes, I do assume no operand size override here. I will need to
>>>> understand how to calculate the operand size here. Looks like I need to
>>>> check for instruction prefixes 66H or REX.W.  Is there an API in the
>>>> backend that be used for this ?
>>>
>>> i.prefixes[] and (depending on the phase of assembly) i.rex should be
>>> telling you.
>>>
>>
>> I have added checks for prefix 66H to detect 16-bit ops now. IIUC, I
>> dont need to detect REX.W prefix specifically.
> 
> I'm not sure. REX.W clear likely means the destination becomes untraceable?
> Whereas REX.W set means to full register is updated (upper half not zeroed)
> and hence it may remain traceable?

I see that Push, Pop instructions do not need the REX prefix. Hence, 
checking for 66H to detect 16-bit operand size suffices.  Default 
operand size is 64-bit in 64-bit mode.
  

Patch

diff --git a/gas/Makefile.am b/gas/Makefile.am
index e174305ca62..b477d74cb53 100644
--- a/gas/Makefile.am
+++ b/gas/Makefile.am
@@ -82,6 +82,7 @@  GAS_CFILES = \
 	flonum-mult.c \
 	frags.c \
 	gen-sframe.c \
+	ginsn.c \
 	hash.c \
 	input-file.c \
 	input-scrub.c \
@@ -94,6 +95,7 @@  GAS_CFILES = \
 	remap.c \
 	sb.c \
 	scfidw2gen.c \
+	scfi.c \
 	sframe-opt.c \
 	stabs.c \
 	subsegs.c \
@@ -119,6 +121,7 @@  HFILES = \
 	flonum.h \
 	frags.h \
 	gen-sframe.h \
+	ginsn.h \
 	hash.h \
 	input-file.h \
 	itbl-lex.h \
@@ -130,6 +133,7 @@  HFILES = \
 	read.h \
 	sb.h \
 	scfidw2gen.h \
+	scfi.h \
 	subsegs.h \
 	symbols.h \
 	tc.h \
diff --git a/gas/Makefile.in b/gas/Makefile.in
index 87428bc46b8..99edb365a00 100644
--- a/gas/Makefile.in
+++ b/gas/Makefile.in
@@ -167,12 +167,13 @@  am__objects_1 = app.$(OBJEXT) as.$(OBJEXT) atof-generic.$(OBJEXT) \
 	ecoff.$(OBJEXT) ehopt.$(OBJEXT) expr.$(OBJEXT) \
 	flonum-copy.$(OBJEXT) flonum-konst.$(OBJEXT) \
 	flonum-mult.$(OBJEXT) frags.$(OBJEXT) gen-sframe.$(OBJEXT) \
-	hash.$(OBJEXT) input-file.$(OBJEXT) input-scrub.$(OBJEXT) \
-	listing.$(OBJEXT) literal.$(OBJEXT) macro.$(OBJEXT) \
-	messages.$(OBJEXT) output-file.$(OBJEXT) read.$(OBJEXT) \
-	remap.$(OBJEXT) sb.$(OBJEXT) scfidw2gen.$(OBJEXT) \
-	sframe-opt.$(OBJEXT) stabs.$(OBJEXT) subsegs.$(OBJEXT) \
-	symbols.$(OBJEXT) write.$(OBJEXT)
+	ginsn.$(OBJEXT) hash.$(OBJEXT) input-file.$(OBJEXT) \
+	input-scrub.$(OBJEXT) listing.$(OBJEXT) literal.$(OBJEXT) \
+	macro.$(OBJEXT) messages.$(OBJEXT) output-file.$(OBJEXT) \
+	read.$(OBJEXT) remap.$(OBJEXT) sb.$(OBJEXT) \
+	scfidw2gen.$(OBJEXT) scfi.$(OBJEXT) sframe-opt.$(OBJEXT) \
+	stabs.$(OBJEXT) subsegs.$(OBJEXT) symbols.$(OBJEXT) \
+	write.$(OBJEXT)
 am_as_new_OBJECTS = $(am__objects_1)
 am__dirstamp = $(am__leading_dot)dirstamp
 as_new_OBJECTS = $(am_as_new_OBJECTS)
@@ -570,6 +571,7 @@  GAS_CFILES = \
 	flonum-mult.c \
 	frags.c \
 	gen-sframe.c \
+	ginsn.c \
 	hash.c \
 	input-file.c \
 	input-scrub.c \
@@ -582,6 +584,7 @@  GAS_CFILES = \
 	remap.c \
 	sb.c \
 	scfidw2gen.c \
+	scfi.c \
 	sframe-opt.c \
 	stabs.c \
 	subsegs.c \
@@ -606,6 +609,7 @@  HFILES = \
 	flonum.h \
 	frags.h \
 	gen-sframe.h \
+	ginsn.h \
 	hash.h \
 	input-file.h \
 	itbl-lex.h \
@@ -617,6 +621,7 @@  HFILES = \
 	read.h \
 	sb.h \
 	scfidw2gen.h \
+	scfi.h \
 	subsegs.h \
 	symbols.h \
 	tc.h \
@@ -1325,6 +1330,7 @@  distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/flonum-mult.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/frags.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gen-sframe.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ginsn.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hash.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/input-file.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/input-scrub.Po@am__quote@
@@ -1339,6 +1345,7 @@  distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/read.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/remap.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sb.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/scfi.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/scfidw2gen.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sframe-opt.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stabs.Po@am__quote@
diff --git a/gas/as.c b/gas/as.c
index 97b0ed38fb6..9f962fd5816 100644
--- a/gas/as.c
+++ b/gas/as.c
@@ -45,6 +45,7 @@ 
 #include "codeview.h"
 #include "bfdver.h"
 #include "write.h"
+#include "ginsn.h"
 
 #ifdef HAVE_ITBL_CPU
 #include "itbl-ops.h"
@@ -245,6 +246,7 @@  Options:\n\
                       	  d      omit debugging directives\n\
                       	  g      include general info\n\
                       	  h      include high-level source\n\
+			  i      include ginsn and synthesized CFI info\n\
                       	  l      include assembly\n\
                       	  m      include macro expansions\n\
                       	  n      omit forms processing\n\
@@ -1089,6 +1091,9 @@  This program has absolutely no warranty.\n"));
 		    case 'h':
 		      listing |= LISTING_HLL;
 		      break;
+		    case 'i':
+		      listing |= LISTING_GINSN_SCFI;
+		      break;
 		    case 'l':
 		      listing |= LISTING_LISTING;
 		      break;
diff --git a/gas/config/obj-elf.c b/gas/config/obj-elf.c
index eaf2d6a2f29..4da007cbb38 100644
--- a/gas/config/obj-elf.c
+++ b/gas/config/obj-elf.c
@@ -24,6 +24,7 @@ 
 #include "subsegs.h"
 #include "obstack.h"
 #include "dwarf2dbg.h"
+#include "ginsn.h"
 
 #ifndef ECOFF_DEBUGGING
 #define ECOFF_DEBUGGING 0
@@ -2302,6 +2303,13 @@  obj_elf_size (int ignore ATTRIBUTE_UNUSED)
       symbol_get_obj (sym)->size = XNEW (expressionS);
       *symbol_get_obj (sym)->size = exp;
     }
+
+  /* If the symbol in the directive matches the current function being
+     processed, indicate end of the current stream of ginsns.  */
+  if (flag_synth_cfi
+      && S_IS_FUNCTION (sym) && sym == ginsn_data_func_symbol ())
+    ginsn_data_end (symbol_temp_new_now ());
+
   demand_empty_rest_of_line ();
 }
 
@@ -2490,6 +2498,14 @@  obj_elf_type (int ignore ATTRIBUTE_UNUSED)
 	elfsym->symbol.flags &= ~mask;
     }
 
+  if (S_IS_FUNCTION (sym) && flag_synth_cfi)
+    {
+      /* Wrap up processing the previous block of ginsns first.  */
+      if (frchain_now->frch_ginsn_data)
+	ginsn_data_end (symbol_temp_new_now ());
+      ginsn_data_begin (sym);
+    }
+
   demand_empty_rest_of_line ();
 }
 
diff --git a/gas/config/tc-i386.c b/gas/config/tc-i386.c
index 57ae6c522a7..0276aa9f48b 100644
--- a/gas/config/tc-i386.c
+++ b/gas/config/tc-i386.c
@@ -30,6 +30,7 @@ 
 #include "subsegs.h"
 #include "dwarf2dbg.h"
 #include "dw2gencfi.h"
+#include "scfi.h"
 #include "gen-sframe.h"
 #include "sframe.h"
 #include "elf/x86-64.h"
@@ -193,8 +194,11 @@  static unsigned int x86_isa_1_used;
 static unsigned int x86_feature_2_used;
 /* Generate x86 used ISA and feature properties.  */
 static unsigned int x86_used_note = DEFAULT_X86_USED_NOTE;
+
 #endif
 
+static ginsnS *ginsn_new (symbolS *sym, enum ginsn_gen_mode gmode);
+
 static const char *default_arch = DEFAULT_ARCH;
 
 /* parse_register() returns this when a register alias cannot be used.  */
@@ -5116,6 +5120,716 @@  static INLINE bool may_need_pass2 (const insn_template *t)
 	       && t->base_opcode == 0x63);
 }
 
+bool
+x86_scfi_callee_saved_p (uint32_t dw2reg_num)
+{
+  if (dw2reg_num == 3 /* rbx.  */
+      || dw2reg_num == REG_FP /* rbp.  */
+      || dw2reg_num == REG_SP /* rsp.  */
+      || (dw2reg_num >= 12 && dw2reg_num <= 15) /* r12 - r15.  */)
+    return true;
+
+  return false;
+}
+
+static uint32_t
+ginsn_dw2_regnum (const reg_entry *ireg)
+{
+  /* PS: Note the data type here as int32_t, because of Dw2Inval (-1).  */
+  int32_t dwarf_reg = Dw2Inval;
+  const reg_entry *temp;
+
+  if (ireg->dw2_regnum[0] == Dw2Inval && ireg->dw2_regnum[1] == Dw2Inval)
+    return dwarf_reg;
+
+  dwarf_reg = ireg->dw2_regnum[flag_code >> 1];
+  if (dwarf_reg == Dw2Inval)
+    {
+      temp = ireg + 16;
+      dwarf_reg = ginsn_dw2_regnum (temp);
+    }
+
+  if (dwarf_reg == Dw2Inval)
+    gas_assert (1); /* Needs to be addressed.  */
+
+  return (uint32_t) dwarf_reg;
+}
+
+static void
+ginsn_set_where (ginsnS* ginsn)
+{
+  const char *file;
+  unsigned int line;
+  file = as_where (&line);
+  ginsn_set_file_line (ginsn, file, line);
+}
+
+static ginsnS *
+x86_ginsn_alu (i386_insn insn, symbolS *insn_end_sym)
+{
+  offsetT src_imm;
+  uint32_t dw2_regnum;
+  enum ginsn_src_type src_type;
+  enum ginsn_dst_type dst_type;
+  ginsnS *ginsn = NULL;
+
+  /* FIXME - create ginsn for REG_SP target only ? */
+  /* Map for insn.tm.extension_opcode
+     000 ADD    100 AND
+     001 OR     101 SUB
+     010 ADC    110 XOR
+     011 SBB    111 CMP  */
+
+  /* add/sub imm, %reg.
+     and imm, %reg only at this time for SCFI. */
+  if (!(insn.tm.extension_opcode == 0
+	|| insn.tm.extension_opcode == 4
+	|| insn.tm.extension_opcode == 5))
+    return ginsn;
+
+  /* TBD_GINSN_REPRESENTATION_LIMIT: There is no representation for when a
+     symbol is used as an operand, like so:
+	  addq    $simd_cmp_op+8, %rdx
+     Skip generating any ginsn for this.  */
+  if (insn.imm_operands == 1
+      && insn.op[0].imms->X_op == O_symbol)
+    return ginsn;
+
+  gas_assert (insn.imm_operands == 1
+	      && insn.op[0].imms->X_op == O_constant);
+  src_imm = insn.op[0].imms->X_add_number;
+  /* The second operand may be a register or indirect access.  */
+  if (insn.mem_operands == 1 && insn.base_reg)
+    {
+      dw2_regnum = ginsn_dw2_regnum (insn.base_reg);
+      src_type = GINSN_SRC_INDIRECT;
+      dst_type = GINSN_DST_INDIRECT;
+    }
+  else if (insn.mem_operands == 1 && insn.index_reg)
+    {
+      dw2_regnum = ginsn_dw2_regnum (insn.index_reg);
+      src_type = GINSN_SRC_INDIRECT;
+      dst_type = GINSN_DST_INDIRECT;
+    }
+  else
+    {
+      gas_assert (insn.reg_operands == 1);
+      dw2_regnum = ginsn_dw2_regnum (insn.op[1].regs);
+      src_type = GINSN_SRC_REG;
+      dst_type = GINSN_DST_REG;
+    }
+
+  /* For ginsn, keep the imm as second src operand.  */
+  if (insn.tm.extension_opcode == 5)
+    ginsn = ginsn_new_sub (insn_end_sym, true,
+			   src_type, dw2_regnum, 0,
+			   GINSN_SRC_IMM, 0, src_imm,
+			   dst_type, dw2_regnum, 0);
+  else if (insn.tm.extension_opcode == 4)
+    ginsn = ginsn_new_and (insn_end_sym, true,
+			   src_type, dw2_regnum, 0,
+			   GINSN_SRC_IMM, 0, src_imm,
+			   dst_type, dw2_regnum, 0);
+  else if (insn.tm.extension_opcode == 0)
+    ginsn = ginsn_new_add (insn_end_sym, true,
+			   src_type, dw2_regnum, 0,
+			   GINSN_SRC_IMM, 0, src_imm,
+			   dst_type, dw2_regnum, 0);
+
+  ginsn_set_where (ginsn);
+
+  return ginsn;
+}
+
+static ginsnS *
+x86_ginsn_move (i386_insn insn, symbolS *insn_end_sym)
+{
+  ginsnS *ginsn;
+  uint16_t opcode;
+  uint32_t dst_reg;
+  uint32_t src_reg;
+  offsetT dst_disp;
+  offsetT src_disp;
+  const reg_entry *dst = NULL;
+  const reg_entry *src = NULL;
+  enum ginsn_dst_type dst_type;
+  enum ginsn_src_type src_type;
+
+  opcode = insn.tm.base_opcode;
+  src_type = GINSN_SRC_REG;
+  src_disp = dst_disp = 0;
+  dst_type = GINSN_DST_REG;
+
+  if (opcode == 0x8b)
+    {
+      /* mov  disp(%reg), %reg.  */
+      if (insn.mem_operands && insn.base_reg)
+	{
+	  src = insn.base_reg;
+	  if (insn.disp_operands == 1)
+	    src_disp = insn.op[0].disps->X_add_number;
+	  src_type = GINSN_SRC_INDIRECT;
+	}
+      else
+	src = insn.op[0].regs;
+
+      dst = insn.op[1].regs;
+    }
+  else if (opcode == 0x89 || opcode == 0x88)
+    {
+      /* mov %reg, disp(%reg).  */
+      src = insn.op[0].regs;
+      if (insn.mem_operands && insn.base_reg)
+	{
+	  dst = insn.base_reg;
+	  if (insn.disp_operands == 1)
+	    dst_disp = insn.op[1].disps->X_add_number;
+	  dst_type = GINSN_DST_INDIRECT;
+	}
+      else
+	dst = insn.op[1].regs;
+    }
+
+  src_reg = ginsn_dw2_regnum (src);
+  dst_reg = ginsn_dw2_regnum (dst);
+
+  ginsn = ginsn_new_mov (insn_end_sym, true,
+			 src_type, src_reg, src_disp,
+			 dst_type, dst_reg, dst_disp);
+  ginsn_set_where (ginsn);
+
+  return ginsn;
+}
+
+static ginsnS *
+x86_ginsn_lea (i386_insn insn, symbolS *insn_end_sym)
+{
+  offsetT src_disp = 0;
+  ginsnS *ginsn = NULL;
+  uint32_t base_reg;
+  uint32_t index_reg;
+  offsetT index_scale;
+  uint32_t dst_reg;
+
+  if (!insn.index_reg && !insn.base_reg)
+    {
+      /* lea symbol, %rN.  */
+      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
+      /* FIXME - Skip encoding information about the symbol.
+	 This is TBD_GINSN_INFO_LOSS, but it is fine if the mode is
+	 GINSN_GEN_SCFI.  */
+      ginsn = ginsn_new_mov (insn_end_sym, false,
+			     GINSN_SRC_IMM, 0xf /* arbitrary const.  */, 0,
+			     GINSN_DST_REG, dst_reg, 0);
+    }
+  else if (insn.base_reg && !insn.index_reg)
+    {
+      /* lea    -0x2(%base),%dst.  */
+      base_reg = ginsn_dw2_regnum (insn.base_reg);
+      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
+
+      if (insn.disp_operands)
+	src_disp = insn.op[0].disps->X_add_number;
+
+      if (src_disp)
+	/* Generate an ADD ginsn.  */
+	ginsn = ginsn_new_add (insn_end_sym, true,
+			       GINSN_SRC_REG, base_reg, 0,
+			       GINSN_SRC_IMM, 0, src_disp,
+			       GINSN_DST_REG, dst_reg, 0);
+      else
+	  /* Generate a MOV ginsn.  */
+	  ginsn = ginsn_new_mov (insn_end_sym, true,
+				 GINSN_SRC_REG, base_reg, 0,
+				 GINSN_DST_REG, dst_reg, 0);
+    }
+  else if (!insn.base_reg && insn.index_reg)
+    {
+      /* lea (,%index,imm), %dst.  */
+      /* FIXME - Skip encoding an explicit multiply operation, instead use
+	 GINSN_TYPE_OTHER.  This is TBD_GINSN_INFO_LOSS, but it is fine if
+	 the mode is GINSN_GEN_SCFI.  */
+      index_scale = insn.log2_scale_factor;
+      index_reg = ginsn_dw2_regnum (insn.index_reg);
+      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
+      ginsn = ginsn_new_other (insn_end_sym, true,
+			       GINSN_SRC_REG, index_reg,
+			       GINSN_SRC_IMM, index_scale,
+			       GINSN_DST_REG, dst_reg);
+    }
+  else
+    {
+      /* lea disp(%base,%index,imm) %dst.  */
+      /* FIXME - Skip encoding information about the disp and imm for index
+	 reg.  This is TBD_GINSN_INFO_LOSS, but it is fine if the mode is
+	 GINSN_GEN_SCFI.  */
+      base_reg = ginsn_dw2_regnum (insn.base_reg);
+      index_reg = ginsn_dw2_regnum (insn.index_reg);
+      dst_reg = ginsn_dw2_regnum (insn.op[1].regs);
+      /* Generate an ADD ginsn.  */
+      ginsn = ginsn_new_add (insn_end_sym, true,
+			     GINSN_SRC_REG, base_reg, 0,
+			     GINSN_SRC_REG, index_reg, 0,
+			     GINSN_DST_REG, dst_reg, 0);
+    }
+
+  ginsn_set_where (ginsn);
+
+  return ginsn;
+}
+
+static ginsnS *
+x86_ginsn_jump (i386_insn insn, symbolS *insn_end_sym)
+{
+  ginsnS *ginsn = NULL;
+  symbolS *src_symbol;
+
+  gas_assert (insn.disp_operands == 1);
+
+  if (insn.op[0].disps->X_op == O_symbol)
+    {
+      src_symbol = insn.op[0].disps->X_add_symbol;
+      /* The jump target is expected to be a symbol with 0 addend.
+	 Assert for now to see if this assumption is true.  */
+      gas_assert (insn.op[0].disps->X_add_number == 0);
+      ginsn = ginsn_new_jump (insn_end_sym, true,
+			      GINSN_SRC_SYMBOL, 0, src_symbol);
+
+      ginsn_set_where (ginsn);
+    }
+
+  return ginsn;
+}
+
+static ginsnS *
+x86_ginsn_jump_cond (i386_insn insn, symbolS *insn_end_sym)
+{
+  ginsnS *ginsn = NULL;
+  symbolS *src_symbol;
+
+  /* TBD_GINSN_GEN_NOT_SCFI: Ignore move to or from xmm reg for mode.  */
+  if (i.tm.opcode_space == SPACE_0F)
+    return ginsn;
+
+  gas_assert (insn.disp_operands == 1);
+
+  if (insn.op[0].disps->X_op == O_symbol)
+    {
+      src_symbol = insn.op[0].disps->X_add_symbol;
+      /* The jump target is expected to be a symbol with 0 addend.
+	 Assert for now to see if this assumption is true.  */
+      gas_assert (insn.op[0].disps->X_add_number == 0);
+      ginsn = ginsn_new_jump_cond (insn_end_sym, true,
+				   GINSN_SRC_SYMBOL, 0, src_symbol);
+      ginsn_set_where (ginsn);
+    }
+  else
+    /* Catch them for now so we know what we are dealing with.  */
+    gas_assert (0);
+
+  return ginsn;
+}
+
+/* Generate one or more GAS instructions for the current machine dependent
+   instruction.
+
+   Returns the head of linked list of ginsn(s) added, if success;
+   Returns NULL if failure.  */
+
+static ginsnS *
+ginsn_new (symbolS *insn_end_sym, enum ginsn_gen_mode gmode)
+{
+  uint16_t opcode;
+  uint32_t dw2_regnum;
+  uint32_t src2_dw2_regnum;
+  int32_t gdisp = 0;
+  ginsnS *ginsn = NULL;
+  ginsnS *ginsn_next = NULL;
+  ginsnS *ginsn_last = NULL;
+
+  /* FIXME - Need a way to check whether the decoding is sane.  The specific
+     checks around i.tm.opcode_space were added as issues were seen.  Likely
+     insufficient.  */
+
+  /* Currently supports generation of selected ginsns, sufficient for
+     the use-case of SCFI only.  To remove this condition will require
+     work on this target-specific process of creation of ginsns.  Some
+     of such places are tagged with TBD_GINSN_GEN_NOT_SCFI to serve as
+     examples.  */
+  if (gmode != GINSN_GEN_SCFI)
+    return ginsn;
+
+  opcode = i.tm.base_opcode;
+
+  switch (opcode)
+    {
+    case 0x1:
+      /* add reg, reg.  */
+      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+
+      if (i.reg_operands == 2)
+	{
+	  src2_dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
+	  ginsn = ginsn_new_add (insn_end_sym, true,
+				 GINSN_SRC_REG, dw2_regnum, 0,
+				 GINSN_SRC_REG, src2_dw2_regnum, 0,
+				 GINSN_DST_REG, src2_dw2_regnum, 0);
+	  ginsn_set_where (ginsn);
+	}
+      else if (i.mem_operands && i.base_reg)
+	{
+	  src2_dw2_regnum = ginsn_dw2_regnum (i.base_reg);
+	  if (i.disp_operands == 1)
+	    gdisp = i.op[1].disps->X_add_number;
+
+	  ginsn = ginsn_new_add (insn_end_sym, true,
+				 GINSN_SRC_REG, dw2_regnum, 0,
+				 GINSN_SRC_INDIRECT, src2_dw2_regnum, gdisp,
+				 GINSN_DST_INDIRECT, src2_dw2_regnum, gdisp);
+	  ginsn_set_where (ginsn);
+	}
+      else
+	/* Catch them for now so we know what we are dealing with.  */
+	gas_assert (0);
+
+      break;
+    case 0x29:
+      /* If opcode_space == SPACE_0F, this is a movaps insn.  Skip it
+	 for GINSN_GEN_SCFI.  */
+      if (i.tm.opcode_space == SPACE_0F)
+	break;
+
+      /* sub reg, reg.  */
+      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+
+      if (i.reg_operands == 2)
+	{
+	  src2_dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
+	  ginsn = ginsn_new_sub (insn_end_sym, true,
+				 GINSN_SRC_REG, dw2_regnum, 0,
+				 GINSN_SRC_REG, src2_dw2_regnum, 0,
+				 GINSN_DST_REG, src2_dw2_regnum, 0);
+	  ginsn_set_where (ginsn);
+	}
+      else if (i.mem_operands && i.base_reg)
+	{
+	  src2_dw2_regnum = ginsn_dw2_regnum (i.base_reg);
+	  if (i.disp_operands == 1)
+	    gdisp = i.op[1].disps->X_add_number;
+
+	  ginsn = ginsn_new_sub (insn_end_sym, true,
+				 GINSN_SRC_REG, dw2_regnum, 0,
+				 GINSN_SRC_INDIRECT, src2_dw2_regnum, gdisp,
+				 GINSN_DST_INDIRECT, src2_dw2_regnum, gdisp);
+	  ginsn_set_where (ginsn);
+	}
+      else
+	/* Catch them for now so we know what we are dealing with.  */
+	gas_assert (0);
+
+      break;
+    case 0xa0:
+    case 0xa8:
+      /* If opcode_space != SPACE_0F, this is a test insn.  Skip it
+	 for GINSN_GEN_SCFI.  */
+      if (i.tm.opcode_space != SPACE_0F)
+	break;
+
+      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+      /* push fs / push gs.  */
+      ginsn = ginsn_new_sub (insn_end_sym, false,
+			     GINSN_SRC_REG, REG_SP, 0,
+			     GINSN_SRC_IMM, 0, 8,
+			     GINSN_DST_REG, REG_SP, 0);
+      ginsn_set_where (ginsn);
+
+      ginsn_next = ginsn_new_store (insn_end_sym, false,
+				    GINSN_SRC_REG, dw2_regnum,
+				    GINSN_DST_INDIRECT, REG_SP, 0);
+      ginsn_set_where (ginsn_next);
+
+      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+      break;
+    case 0xa1:
+    case 0xa9:
+      /* If opcode_space != SPACE_0F, this is test insn.  Skip it
+	 for GINSN_GEN_SCFI.  */
+      if (i.tm.opcode_space != SPACE_0F)
+	break;
+
+      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+      /* pop fs / pop gs.  */
+      ginsn = ginsn_new_load (insn_end_sym, false,
+			      GINSN_SRC_INDIRECT, REG_SP, 0,
+			      GINSN_DST_REG, dw2_regnum);
+      ginsn_set_where (ginsn);
+
+      ginsn_next = ginsn_new_add (insn_end_sym, false,
+				  GINSN_SRC_REG, REG_SP, 0,
+				  GINSN_SRC_IMM, 0, 8,
+				  GINSN_DST_REG, REG_SP, 0);
+      ginsn_set_where (ginsn_next);
+
+      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+      break;
+    case 0x50 ... 0x57:
+      /* push reg.  */
+      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+      ginsn = ginsn_new_sub (insn_end_sym, false,
+			     GINSN_SRC_REG, REG_SP, 0,
+			     GINSN_SRC_IMM, 0, 8,
+			     GINSN_DST_REG, REG_SP, 0);
+      ginsn_set_where (ginsn);
+
+      ginsn_next = ginsn_new_store (insn_end_sym, false,
+				    GINSN_SRC_REG, dw2_regnum,
+				    GINSN_DST_INDIRECT, REG_SP, 0);
+      ginsn_set_where (ginsn_next);
+
+      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+      break;
+    case 0x58 ... 0x5f:
+      if (i.tm.opcode_space != SPACE_BASE)
+	break;
+      /* pop reg.  */
+      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+      ginsn = ginsn_new_load (insn_end_sym, false,
+			      GINSN_SRC_INDIRECT, REG_SP, 0,
+			      GINSN_DST_REG, dw2_regnum);
+      ginsn_set_where (ginsn);
+
+      ginsn_next = ginsn_new_add (insn_end_sym, false,
+				  GINSN_SRC_REG, REG_SP, 0,
+				  GINSN_SRC_IMM, 0, 8,
+				  GINSN_DST_REG, REG_SP, 0);
+      ginsn_set_where (ginsn_next);
+
+      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+      break;
+    case 0x68:
+    case 0x6a:
+      /* push imm. */
+      /* Skip getting the value of imm from machine instruction
+	 because for ginsn generation this is not important.  */
+      ginsn = ginsn_new_sub (insn_end_sym, false,
+			     GINSN_SRC_REG, REG_SP, 0,
+			     GINSN_SRC_IMM, 0, 8,
+			     GINSN_DST_REG, REG_SP, 0);
+      ginsn_set_where (ginsn);
+
+      ginsn_next = ginsn_new_store (insn_end_sym, false,
+				    GINSN_SRC_IMM, 0,
+				    GINSN_DST_INDIRECT, REG_SP, 0);
+      ginsn_set_where (ginsn_next);
+
+      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+      break;
+    case 0x70 ... 0x7f:
+      ginsn = x86_ginsn_jump_cond (i, insn_end_sym);
+      break;
+    case 0x81:
+    case 0x83:
+      ginsn = x86_ginsn_alu (i, insn_end_sym);
+      break;
+    case 0x8b:
+      /* Move r/m64 to r64.  */
+    case 0x88:
+    case 0x89:
+      /* mov reg, reg/mem.  */
+      ginsn = x86_ginsn_move (i, insn_end_sym);
+      break;
+    case 0x8d:
+      /* lea disp(%src), %dst */
+      ginsn = x86_ginsn_lea (i, insn_end_sym);
+      break;
+    case 0x8f:
+      /* pop to mem.  */
+      gas_assert (i.base_reg);
+      dw2_regnum = ginsn_dw2_regnum (i.base_reg);
+      ginsn = ginsn_new_load (insn_end_sym, false,
+			      GINSN_SRC_INDIRECT, REG_SP, 0,
+			      GINSN_DST_INDIRECT, dw2_regnum);
+      ginsn_set_where (ginsn);
+
+      ginsn_next = ginsn_new_add (insn_end_sym, false,
+				  GINSN_SRC_REG, REG_SP, 0,
+				  GINSN_SRC_IMM, 0, 8,
+				  GINSN_DST_REG, REG_SP, 0);
+      ginsn_set_where (ginsn_next);
+
+      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+      break;
+    case 0x9c:
+      /* pushf / pushfd / pushfq.
+	 Tracking EFLAGS register by number is not necessary.  */
+      ginsn = ginsn_new_sub (insn_end_sym, false,
+			     GINSN_SRC_REG, REG_SP, 0,
+			     GINSN_SRC_IMM, 0, 8,
+			     GINSN_DST_REG, REG_SP, 0);
+      ginsn_set_where (ginsn);
+
+      ginsn_next = ginsn_new_store (insn_end_sym, false,
+				    GINSN_SRC_IMM, 0,
+				    GINSN_DST_INDIRECT, REG_SP, 0);
+      ginsn_set_where (ginsn_next);
+
+      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+
+      break;
+    case 0xff:
+      /* push from mem.  */
+      if (i.tm.extension_opcode == 6)
+	{
+	  ginsn = ginsn_new_sub (insn_end_sym, false,
+				 GINSN_SRC_REG, REG_SP, 0,
+				 GINSN_SRC_IMM, 0, 8,
+				 GINSN_DST_REG, REG_SP, 0);
+	  ginsn_set_where (ginsn);
+
+	  /* These instructions have no imm, only indirect access.  */
+	  gas_assert (i.base_reg);
+	  dw2_regnum = ginsn_dw2_regnum (i.base_reg);
+	  ginsn_next = ginsn_new_store (insn_end_sym, false,
+					GINSN_SRC_INDIRECT, dw2_regnum,
+					GINSN_DST_INDIRECT, REG_SP, 0);
+	  ginsn_set_where (ginsn_next);
+
+	  gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+	}
+      else if (i.tm.extension_opcode == 4)
+	{
+	  /* jmp r/m.  E.g., notrack jmp *%rax.  */
+	  if (i.reg_operands)
+	    {
+	      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+	      ginsn = ginsn_new_jump (insn_end_sym, true,
+				      GINSN_SRC_REG, dw2_regnum, NULL);
+	      ginsn_set_where (ginsn);
+	    }
+	  else if (i.mem_operands && i.index_reg)
+	    {
+	      /* jmp    *0x0(,%rax,8).  */
+	      dw2_regnum = ginsn_dw2_regnum (i.index_reg);
+	      ginsn = ginsn_new_jump (insn_end_sym, true,
+				      GINSN_SRC_REG, dw2_regnum, NULL);
+	      ginsn_set_where (ginsn);
+	    }
+	  else if (i.mem_operands && i.base_reg)
+	    {
+	      dw2_regnum = ginsn_dw2_regnum (i.base_reg);
+	      ginsn = ginsn_new_jump (insn_end_sym, true,
+				      GINSN_SRC_REG, dw2_regnum, NULL);
+	      ginsn_set_where (ginsn);
+	    }
+	  else
+	    /* Catch them for now so we know what we are dealing with.  */
+	    gas_assert (0);
+	}
+      else if (i.tm.extension_opcode == 2)
+	{
+	  /* 0xFF /2 (call).  */
+	  if (i.reg_operands)
+	    {
+	      dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+	      ginsn = ginsn_new_call (insn_end_sym, true,
+				      GINSN_SRC_REG, dw2_regnum, NULL);
+	      ginsn_set_where (ginsn);
+	    }
+	  else if (i.mem_operands && i.base_reg)
+	    {
+	      dw2_regnum = ginsn_dw2_regnum (i.base_reg);
+	      ginsn = ginsn_new_call (insn_end_sym, true,
+				      GINSN_SRC_REG, dw2_regnum, NULL);
+	      ginsn_set_where (ginsn);
+	    }
+	  else
+	    /* Catch them for now so we know what we are dealing with.  */
+	    gas_assert (0);
+	}
+      else
+	/* Catch them for now so we know what we are dealing with.  */
+	gas_assert (0);
+      break;
+    case 0xc2:
+    case 0xc3:
+      /* Near ret.  */
+      ginsn = ginsn_new_return (insn_end_sym, true);
+      ginsn_set_where (ginsn);
+      break;
+    case 0xc9:
+      /* The 'leave' instruction copies the contents of the RBP register
+	 into the RSP register to release all stack space allocated to the
+	 procedure.  */
+      ginsn = ginsn_new_mov (insn_end_sym, false,
+			     GINSN_SRC_REG, REG_FP, 0,
+			     GINSN_DST_REG, REG_SP, 0);
+      ginsn_set_where (ginsn);
+
+      /* Then it restores the old value of the RBP register from the stack.  */
+      ginsn_next = ginsn_new_load (insn_end_sym, false,
+				   GINSN_SRC_INDIRECT, REG_SP, 0,
+				   GINSN_DST_REG, REG_FP);
+      ginsn_set_where (ginsn_next);
+
+      gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+      ginsn_last = ginsn_new_add (insn_end_sym, false,
+				  GINSN_SRC_REG, REG_SP, 0,
+				  GINSN_SRC_IMM, 0, 8,
+				  GINSN_DST_REG, REG_SP, 0);
+      ginsn_set_where (ginsn_next);
+
+      gas_assert (!ginsn_link_next (ginsn_next, ginsn_last));
+      break;
+    case 0xe8:
+      /* PS: SCFI machinery does not care about which func is being
+	 called.  OK to skip that info.  */
+      ginsn = ginsn_new_call (insn_end_sym, true,
+			      GINSN_SRC_SYMBOL, 0, NULL);
+      ginsn_set_where (ginsn);
+      break;
+    case 0xe9:
+    case 0xeb:
+      /* If opcode_space == SPACE_0F, this is a psubw por insn.  Skip it
+	 for GINSN_GEN_SCFI.  */
+      if (i.tm.opcode_space == SPACE_0F)
+	break;
+
+      /* Unconditional jmp.  */
+      ginsn = x86_ginsn_jump (i, insn_end_sym);
+      ginsn_set_where (ginsn);
+      break;
+      /* Fall Through.  */
+    default:
+      /* TBD_GINSN_GEN_NOT_SCFI: Keep a warning, for now, to find out about
+	 possibly missed instructions affecting REG_SP or REG_FP.  These
+	 checks may not be completely exhaustive as they do not involve
+	 index / base reg.  */
+      if (i.op[0].regs)
+	{
+	  dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+	  if (dw2_regnum == REG_SP || dw2_regnum == REG_FP)
+	    as_warn_where (last_insn.file, last_insn.line,
+			   _("SCFI: unhandled op 0x%x may cause incorrect CFI"),
+			   i.tm.base_opcode);
+	}
+      if (i.op[1].regs)
+	{
+	  dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
+	  if (dw2_regnum == REG_SP || dw2_regnum == REG_FP)
+	    as_warn_where (last_insn.file, last_insn.line,
+			   _("SCFI: unhandled op 0x%x may cause incorrect CFI"),
+			   i.tm.base_opcode);
+	}
+      /* Keep an eye on other instructions affecting control flow.  */
+      gas_assert (!i.tm.opcode_modifier.jump);
+      /* TBD_GINSN_GEN_NOT_SCFI: Skip all other opcodes uninteresting for
+	 GINSN_GEN_SCFI mode.  */
+      break;
+    }
+
+  return ginsn;
+}
+
 /* This is the guts of the machine-dependent assembler.  LINE points to a
    machine dependent instruction.  This function is supposed to emit
    the frags/bytes it assembles to.  */
@@ -5128,6 +5842,7 @@  md_assemble (char *line)
   const char *end, *pass1_mnem = NULL;
   enum i386_error pass1_err = 0;
   const insn_template *t;
+  ginsnS *ginsn;
 
   /* Initialize globals.  */
   current_templates = NULL;
@@ -5659,6 +6374,13 @@  md_assemble (char *line)
   /* We are ready to output the insn.  */
   output_insn ();
 
+  /* At this time, SCFI is enabled only for AMD64 ABI.  */
+  if (flag_synth_cfi && x86_elf_abi == X86_64_ABI)
+    {
+      ginsn = ginsn_new (symbol_temp_new_now (), frch_ginsn_gen_mode ());
+      frch_ginsn_data_append (ginsn);
+    }
+
   insert_lfence_after ();
 
   last_insn.seg = now_seg;
@@ -10904,6 +11626,7 @@  s_insn (int dummy ATTRIBUTE_UNUSED)
   valueT val;
   bool vex = false, xop = false, evex = false;
   static const templates tt = { &i.tm, &i.tm + 1 };
+  ginsnS *ginsn;
 
   init_globals ();
 
@@ -11658,7 +12381,14 @@  s_insn (int dummy ATTRIBUTE_UNUSED)
 
   output_insn ();
 
- done:
+  /* At this time, SCFI is enabled only for AMD64 ABI.  */
+  if (flag_synth_cfi && x86_elf_abi == X86_64_ABI)
+    {
+      ginsn = ginsn_new (symbol_temp_new_now (), frch_ginsn_gen_mode ());
+      frch_ginsn_data_append (ginsn);
+    }
+
+done:
   *saved_ilp = saved_char;
   input_line_pointer = line;
 
@@ -15293,6 +16023,9 @@  i386_target_format (void)
   else
     as_fatal (_("unknown architecture"));
 
+  if (flag_synth_cfi && x86_elf_abi != X86_64_ABI)
+    as_fatal (_("Synthesizing CFI is not supported for this ABI"));
+
   if (cpu_flags_all_zero (&cpu_arch_isa_flags))
     cpu_arch_isa_flags = cpu_arch[flag_code == CODE_64BIT].enable;
 
diff --git a/gas/config/tc-i386.h b/gas/config/tc-i386.h
index a3d463d961f..f63ad1694a9 100644
--- a/gas/config/tc-i386.h
+++ b/gas/config/tc-i386.h
@@ -373,6 +373,27 @@  extern int i386_elf_section_type (const char *, size_t);
 extern void i386_solaris_fix_up_eh_frame (segT);
 #endif
 
+#define TARGET_USE_GINSN 1
+/* Allow GAS to synthesize DWARF CFI for hand-written asm.
+   PS: TARGET_USE_CFIPOP is a pre-condition.  */
+#define TARGET_USE_SCFI 1
+/* Identify the maximum DWARF register number of all the registers being
+   tracked for SCFI.  This is the last DWARF register number of the set
+   of SP, BP, and all callee-saved registers.  For AMD64, this means
+   R15 (15).  Use SCFI_CALLEE_SAVED_REG_P to identify which registers
+   are callee-saved from this set.  */
+#define SCFI_NUM_REGS 15
+/* Identify the DWARF register number of the frame-pointer register.  */
+#define REG_FP 6
+/* Identify the DWARF register number of the stack-pointer register.  */
+#define REG_SP 7
+/* Some ABIs, like AMD64, use stack for call instruction.  For such an ABI,
+   identify the initial (CFA) offset from RSP at the entry of function.  */
+#define SCFI_INIT_CFA_OFFSET 8
+
+#define SCFI_CALLEE_SAVED_REG_P(dw2reg)  x86_scfi_callee_saved_p (dw2reg)
+extern bool x86_scfi_callee_saved_p (uint32_t dw2reg_num);
+
 /* Support for SHF_X86_64_LARGE */
 extern bfd_vma x86_64_section_letter (int, const char **);
 #define md_elf_section_letter(LETTER, PTR_MSG)	x86_64_section_letter (LETTER, PTR_MSG)
diff --git a/gas/ginsn.c b/gas/ginsn.c
new file mode 100644
index 00000000000..1d2b6c858b5
--- /dev/null
+++ b/gas/ginsn.c
@@ -0,0 +1,1225 @@ 
+/* 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
+
+const char *const ginsn_type_names[] =
+{
+#define _GINSN_TYPE_ITEM(NAME, STR) STR,
+  _GINSN_TYPES
+#undef _GINSN_TYPE_ITEM
+};
+
+const char *const ginsn_src_type_names[] =
+{
+#define _GINSN_SRC_TYPE_ITEM(NAME, STR) STR,
+  _GINSN_SRC_TYPES
+#undef _GINSN_SRC_TYPE_ITEM
+};
+const char *const ginsn_dst_type_names[] =
+{
+#define _GINSN_DST_TYPE_ITEM(NAME, STR) STR,
+  _GINSN_DST_TYPES
+#undef _GINSN_DST_TYPE_ITEM
+};
+
+static
+ginsnS *ginsn_alloc (void)
+{
+  ginsnS *ginsn = XCNEW (ginsnS);
+  return ginsn;
+}
+
+static ginsnS*
+ginsn_init (enum ginsn_type type, 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);
+  ginsn = NULL;
+}
+
+static void
+ginsn_set_src (struct ginsn_src *src, enum ginsn_src_type type, uint32_t reg,
+	       int32_t immdisp)
+{
+  if (!src)
+    return;
+
+  src->type = type;
+  /* Even when the use-case is SCFI, the value of reg may be > SCFI_NUM_REGS.
+     E.g., in AMD64, push fs etc.  */
+  src->reg = reg;
+
+  if (type == GINSN_SRC_IMM || type == GINSN_SRC_INDIRECT)
+    src->immdisp = immdisp;
+}
+
+static void
+ginsn_set_dst (struct ginsn_dst *dst, enum ginsn_dst_type type, uint32_t reg,
+	       int32_t disp)
+{
+  if (!dst)
+    return;
+
+  dst->type = type;
+  dst->reg = reg;
+
+  if (type == GINSN_DST_INDIRECT)
+    dst->disp = disp;
+}
+
+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;
+}
+
+uint32_t
+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;
+}
+
+uint32_t
+ginsn_get_src_disp (struct ginsn_src *src)
+{
+  return src->immdisp;
+}
+
+uint32_t
+ginsn_get_src_imm (struct ginsn_src *src)
+{
+  return src->immdisp;
+}
+
+uint32_t
+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;
+}
+
+int32_t
+ginsn_get_dst_disp (struct ginsn_dst *dst)
+{
+  return (int32_t) dst->disp;
+}
+
+void
+label_ginsn_map_insert (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 (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 (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 (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 (symbolS *sym)
+{
+  return ginsn_new_symbol (sym, true);
+}
+
+ginsnS *
+ginsn_new_symbol_func_end (symbolS *sym)
+{
+  return ginsn_new_symbol (sym, false);
+}
+
+ginsnS *
+ginsn_new_symbol_user_label (symbolS *sym)
+{
+  ginsnS *ginsn = ginsn_new_symbol (sym, false);
+  ginsn->flags |= GINSN_F_USER_LABEL;
+  return ginsn;
+}
+
+ginsnS *
+ginsn_new_add (symbolS *sym, bool real_p,
+	       enum ginsn_src_type src1_type, uint32_t src1_val, int32_t src1_disp,
+	       enum ginsn_src_type src2_type, uint32_t src2_val, int32_t src2_disp,
+	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp)
+{
+  ginsnS *ginsn = ginsn_init (GINSN_TYPE_ADD, sym, real_p);
+  /* src info.  */
+  ginsn_set_src (&ginsn->src[0], src1_type, src1_val, src1_disp);
+  ginsn_set_src (&ginsn->src[1], src2_type, src2_val, src2_disp);
+  /* dst info.  */
+  ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp);
+
+  return ginsn;
+}
+
+ginsnS *
+ginsn_new_and (symbolS *sym, bool real_p,
+	       enum ginsn_src_type src1_type, uint32_t src1_val, int32_t src1_disp,
+	       enum ginsn_src_type src2_type, uint32_t src2_val, int32_t src2_disp,
+	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp)
+{
+  ginsnS *ginsn = ginsn_init (GINSN_TYPE_AND, sym, real_p);
+  /* src info.  */
+  ginsn_set_src (&ginsn->src[0], src1_type, src1_val, src1_disp);
+  ginsn_set_src (&ginsn->src[1], src2_type, src2_val, src2_disp);
+  ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp);
+
+  return ginsn;
+}
+
+ginsnS *
+ginsn_new_call (symbolS *sym, bool real_p,
+		enum ginsn_src_type src_type, uint32_t src_val,
+		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_val, 0);
+
+  if (src_type == GINSN_SRC_SYMBOL)
+    ginsn->src[0].sym = src_text_sym;
+
+  return ginsn;
+}
+
+ginsnS *
+ginsn_new_jump (symbolS *sym, bool real_p,
+		enum ginsn_src_type src_type, uint32_t src_val,
+		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_val, 0);
+
+  if (src_type == GINSN_SRC_SYMBOL)
+    ginsn->src[0].sym = src_ginsn_sym;
+
+  return ginsn;
+}
+
+ginsnS *
+ginsn_new_jump_cond (symbolS *sym, bool real_p,
+		     enum ginsn_src_type src_type, uint32_t src_val,
+		     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_val, 0);
+
+  if (src_type == GINSN_SRC_SYMBOL)
+    ginsn->src[0].sym = src_ginsn_sym;
+
+  return ginsn;
+}
+
+ginsnS *
+ginsn_new_mov (symbolS *sym, bool real_p,
+	       enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
+	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t 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 (symbolS *sym, bool real_p,
+		 enum ginsn_src_type src_type, uint32_t src_reg,
+		 enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t 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 (symbolS *sym, bool real_p,
+		enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
+		enum ginsn_dst_type dst_type, uint32_t 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 (symbolS *sym, bool real_p,
+	       enum ginsn_src_type src1_type, uint32_t src1_val, int32_t src1_disp,
+	       enum ginsn_src_type src2_type, uint32_t src2_val, int32_t src2_disp,
+	       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp)
+{
+  ginsnS *ginsn = ginsn_init (GINSN_TYPE_SUB, sym, real_p);
+  /* src info.  */
+  ginsn_set_src (&ginsn->src[0], src1_type, src1_val, src1_disp);
+  ginsn_set_src (&ginsn->src[1], src2_type, src2_val, 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 (symbolS *sym, bool real_p,
+		 enum ginsn_src_type src1_type, uint32_t src1_val,
+		 enum ginsn_src_type src2_type, uint32_t src2_val,
+		 enum ginsn_dst_type dst_type, uint32_t 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 (symbolS *sym, bool real_p)
+{
+  ginsnS *ginsn = ginsn_init (GINSN_TYPE_RETURN, sym, real_p);
+  return ginsn;
+}
+
+void
+ginsn_set_file_line (ginsnS *ginsn, const char *file, unsigned int line)
+{
+  if (!ginsn)
+    return;
+
+  ginsn->file = file;
+  ginsn->line = 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 (uint32_t dw2reg, enum ginsn_gen_mode gmode)
+{
+  bool track_p = false;
+
+  if (gmode == GINSN_GEN_SCFI && dw2reg <= SCFI_NUM_REGS)
+    {
+      /* 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;
+}
+
+static bool
+ginsn_direct_local_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_SYMBOL
+	    && S_IS_LOCAL (ginsn->src[0].sym));
+  return ret_p;
+}
+
+static char*
+ginsn_src_print (struct ginsn_src *src)
+{
+  size_t len = 39;
+  char *src_str = XNEWVEC (char, len);
+
+  memset (src_str, 0, len);
+
+  if (src->type == GINSN_SRC_REG)
+    {
+      char *buf = XNEWVEC (char, 32);
+      sprintf (buf, "%%r%d, ", ginsn_get_src_reg (src));
+      strcat (src_str, buf);
+    }
+  else if (src->type == GINSN_SRC_IMM)
+    {
+      char *buf = XNEWVEC (char, 32);
+      sprintf (buf, "%d, ", ginsn_get_src_imm (src));
+      strcat (src_str, buf);
+    }
+  else if (src->type == GINSN_SRC_INDIRECT)
+    {
+      char *buf = XNEWVEC (char, 32);
+      sprintf (buf, "[%%r%d+%d], ", ginsn_get_src_reg (src),
+	       ginsn_get_src_disp (src));
+      strcat (src_str, buf);
+    }
+
+  return src_str;
+}
+
+static char*
+ginsn_dst_print (struct ginsn_dst *dst)
+{
+  size_t len = GINSN_LISTING_OPND_LEN;
+  char *dst_str = XNEWVEC (char, len);
+
+  memset (dst_str, 0, len);
+
+  if (dst->type == GINSN_DST_REG)
+    {
+      char *buf = XNEWVEC (char, 32);
+      sprintf (buf, "%%r%d", ginsn_get_dst_reg (dst));
+      strcat (dst_str, buf);
+    }
+  else if (dst->type == GINSN_DST_INDIRECT)
+    {
+      char *buf = XNEWVEC (char, 32);
+      sprintf (buf, "[%%r%d+%d]", ginsn_get_dst_reg (dst),
+	       ginsn_get_dst_disp (dst));
+      strcat (dst_str, buf);
+    }
+
+  gas_assert (strlen (dst_str) < GINSN_LISTING_OPND_LEN);
+
+  return dst_str;
+}
+
+static const char*
+ginsn_type_sym_begin_end_print (ginsnS *ginsn)
+{
+  int id = 0;
+  const char *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;
+  size_t len = GINSN_LISTING_LEN;
+  char *ginsn_str = XNEWVEC (char, len);
+
+  memset (ginsn_str, 0, len);
+
+  strcpy (ginsn_str, "ginsn: ");
+
+  strcat (ginsn_str, ginsn_type_names[ginsn->type]);
+  strcat (ginsn_str, " ");
+
+  /* For some ginsn types, no further information is printed for now.  */
+  if (ginsn->type == GINSN_TYPE_CALL
+      || ginsn->type == GINSN_TYPE_RETURN
+      || ginsn->type == GINSN_TYPE_OTHER)
+    goto end;
+  else if (ginsn->type == GINSN_TYPE_SYMBOL)
+    {
+      if (GINSN_F_USER_LABEL_P (ginsn))
+	strncat (ginsn_str, S_GET_NAME (ginsn->sym), len - 10);
+      else
+	strcat (ginsn_str, ginsn_type_sym_begin_end_print (ginsn));
+      goto end;
+    }
+
+  /* src 1.  */
+  src = ginsn_get_src1 (ginsn);
+  strcat (ginsn_str, ginsn_src_print (src));
+
+  /* src 2.  */
+  src = ginsn_get_src2 (ginsn);
+  strcat (ginsn_str, ginsn_src_print (src));
+
+  /* dst.  */
+  dst = ginsn_get_dst (ginsn);
+  strcat (ginsn_str, ginsn_dst_print (dst));
+
+end:
+  gas_assert (strlen (ginsn_str) < 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;
+}
+
+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 tail 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 (symbolS *func, gcfgS *gcfg, ginsnS *ginsn, gbbS *prev_bb,
+		 int *errp);
+
+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 if ginsn is visited.  */
+      gas_assert (found_bb);
+    }
+
+  return found_bb;
+}
+
+static gbbS*
+find_or_make_bb (symbolS *func, gcfgS *gcfg, ginsnS *ginsn, gbbS *prev_bb,
+		 int *errp)
+{
+  gbbS *found_bb = NULL;
+
+  found_bb = find_bb (gcfg, ginsn);
+  if (found_bb)
+    return found_bb;
+
+  return add_bb_at_ginsn (func, gcfg, ginsn, prev_bb, errp);
+}
+
+/* Add the basic block starting at GINSN to the given GCFG.
+   Also adds an edge from the PREV_BB to the newly added basic block.
+
+   This is a recursive function which returns the root of the added
+   basic blocks.  */
+
+static gbbS*
+add_bb_at_ginsn (symbolS *func, gcfgS *gcfg, ginsnS *ginsn, gbbS *prev_bb,
+		 int *errp)
+{
+  gbbS *current_bb = NULL;
+  ginsnS *target_ginsn = NULL;
+  symbolS *taken_label;
+
+  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))
+	{
+	  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 && GINSN_F_USER_LABEL_P (ginsn))
+	{
+	  /* Create new bb starting at this label ginsn.  */
+	  prev_bb = current_bb;
+	  find_or_make_bb (func, gcfg, ginsn, prev_bb, errp);
+	  break;
+	}
+
+      if (current_bb == NULL)
+	{
+	  /* Create a new bb.  */
+	  current_bb = XCNEW (gbbS);
+	  cfg_add_bb (gcfg, current_bb);
+	  /* Add edge for the Not Taken, or Fall-through path.  */
+	  if (prev_bb)
+	    bb_add_edge (prev_bb, current_bb);
+	}
+
+      if (current_bb->first_ginsn == NULL)
+	current_bb->first_ginsn = ginsn;
+
+      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 or direct jumps to symbols non-local to the
+	     function must not be seen here.  The caller must have already
+	     checked for that.  */
+	  gas_assert (!ginsn_indirect_jump_p (ginsn));
+	  if (ginsn->type == GINSN_TYPE_JUMP)
+	    gas_assert (ginsn_direct_local_jump_p (ginsn));
+
+	  /* Direct Jumps.  May include conditional or unconditional change of
+	     flow.  What is important for CFG creation is that the target be
+	     local to function.  */
+	  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 && S_IS_LOCAL (taken_label));
+
+	      /* Preserve the prev_bb to be the dominator 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)
+		find_or_make_bb (func, gcfg, target_ginsn, prev_bb, errp);
+	      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));
+		}
+	      /* Add the bb for the fall through path.  */
+	      find_or_make_bb (func, gcfg, ginsn->next, prev_bb, errp);
+	    }
+	 else if (ginsn->type == GINSN_TYPE_RETURN)
+	   {
+	     /* We'll come back to the ginsns following GINSN_TYPE_RETURN
+		from another path if they are indeed reachable code.  */
+	     break;
+	   }
+
+	 /* Current BB has been processed.  */
+	 current_bb = NULL;
+	}
+      ginsn = ginsn->next;
+    }
+
+  return current_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;
+}
+
+/* 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 (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)
+{
+  int 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 (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
+frch_ginsn_data_init (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)
+	  || (ginsn->type == GINSN_TYPE_JUMP
+	      && !ginsn_direct_local_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 (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 (symbolS *label)
+{
+  ginsnS *ginsn;
+  gbbS *root_bb;
+  gcfgS *gcfg = NULL;
+  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_warn (_("Untraceable control flow for func '%s'; Skipping SCFI"),
+	       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;
+    }
+
+  /* Synthesize DWARF CFI and emit it.  */
+  err = scfi_synthesize_dw2cfi (func, gcfg, root_bb);
+  if (err)
+    goto end;
+  scfi_emit_dw2cfi (func);
+
+  /* Other passes, e.g., warn for unreachable code can be enabled too.  */
+  ginsn = frchain_now->frch_ginsn_data->gins_rootP;
+  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 (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);
+    }
+}
+
+symbolS *
+ginsn_data_func_symbol (void)
+{
+  symbolS *func = NULL;
+
+  if (frchain_now->frch_ginsn_data)
+    func = frchain_now->frch_ginsn_data->func;
+
+  return func;
+}
+
+#else
+
+int
+ginsn_data_begin (symbolS *func ATTRIBUTE_UNUSED)
+{
+  as_bad (_("ginsn unsupported for target"));
+  return 1;
+}
+
+int
+ginsn_data_end (symbolS *label ATTRIBUTE_UNUSED)
+{
+  as_bad (_("ginsn unsupported for target"));
+  return 1;
+}
+
+void
+ginsn_frob_label (symbolS *sym ATTRIBUTE_UNUSED)
+{
+  return;
+}
+
+symbolS *
+ginsn_data_func_symbol (void)
+{
+  return NULL;
+}
+
+#endif  /* TARGET_USE_GINSN.  */
diff --git a/gas/ginsn.h b/gas/ginsn.h
new file mode 100644
index 00000000000..c930b5b2ff3
--- /dev/null
+++ b/gas/ginsn.h
@@ -0,0 +1,392 @@ 
+/* 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.  */
+
+#ifndef GINSN_H
+#define GINSN_H
+
+#include "as.h"
+
+/* Maximum number of source operands of a ginsn.  */
+#define GINSN_NUM_SRC_OPNDS   2
+
+/* A ginsn in printed in the following format:
+      "ginsn: OPCD SRC1, SRC2, DST"
+      "<-5->  <--------125------->"
+   where each of SRC1, SRC2, and DST are in the form:
+      "%rNN,"  (up to 5 chars)
+      "imm,"   (up to int32_t+1 chars)
+      "[%rNN+-imm]," (up to int32_t+9 chars)
+      Hence a max of 19 chars.  */
+
+#define GINSN_LISTING_OPND_LEN	40
+#define GINSN_LISTING_LEN 156
+
+enum ginsn_gen_mode
+{
+  GINSN_GEN_NONE,
+  /* Generate ginsns for program validation passes.  */
+  GINSN_GEN_FVAL,
+  /* Generate ginsns for synthesizing DWARF CFI.  */
+  GINSN_GEN_SCFI,
+};
+
+/* ginsn types.
+
+   GINSN_TYPE_PHANTOM are phantom ginsns.  They are used where there is no real
+   machine instruction counterpart, but a ginsn is needed only to carry
+   information to GAS.  For example, to carry an SCFI Op.
+
+   Note that, ginsns do not have a push / pop instructions.
+   Instead, following are used:
+      type=GINSN_TYPE_LOAD, src=GINSN_SRC_INDIRECT, REG_SP: Load from stack.
+      type=GINSN_TYPE_STORE, dst=GINSN_DST_INDIRECT, REG_SP: Store to stack.
+*/
+
+#define _GINSN_TYPES \
+  _GINSN_TYPE_ITEM (GINSN_TYPE_SYMBOL, "SYM") \
+  _GINSN_TYPE_ITEM (GINSN_TYPE_PHANTOM, "PHANTOM")  \
+  _GINSN_TYPE_ITEM (GINSN_TYPE_ADD, "ADD")  \
+  _GINSN_TYPE_ITEM (GINSN_TYPE_AND, "AND")  \
+  _GINSN_TYPE_ITEM (GINSN_TYPE_CALL, "CALL") \
+  _GINSN_TYPE_ITEM (GINSN_TYPE_JUMP, "JMP") \
+  _GINSN_TYPE_ITEM (GINSN_TYPE_JUMP_COND, "JCC")  \
+  _GINSN_TYPE_ITEM (GINSN_TYPE_MOV, "MOV")  \
+  _GINSN_TYPE_ITEM (GINSN_TYPE_LOAD, "LOAD")  \
+  _GINSN_TYPE_ITEM (GINSN_TYPE_STORE, "STORE")  \
+  _GINSN_TYPE_ITEM (GINSN_TYPE_RETURN, "RET") \
+  _GINSN_TYPE_ITEM (GINSN_TYPE_SUB, "SUB")  \
+  _GINSN_TYPE_ITEM (GINSN_TYPE_OTHER, "OTH")
+
+enum ginsn_type
+{
+#define _GINSN_TYPE_ITEM(NAME, STR) NAME,
+  _GINSN_TYPES
+#undef _GINSN_TYPE_ITEM
+};
+
+#define _GINSN_SRC_TYPES \
+  _GINSN_SRC_TYPE_ITEM (GINSN_SRC_UNKNOWN, "UNK") \
+  _GINSN_SRC_TYPE_ITEM (GINSN_SRC_REG, "REG") \
+  _GINSN_SRC_TYPE_ITEM (GINSN_SRC_IMM, "IMM") \
+  _GINSN_SRC_TYPE_ITEM (GINSN_SRC_INDIRECT, "IND")  \
+  _GINSN_SRC_TYPE_ITEM (GINSN_SRC_SYMBOL, "SYM")
+
+enum ginsn_src_type
+{
+#define _GINSN_SRC_TYPE_ITEM(NAME, STR)	NAME,
+  _GINSN_SRC_TYPES
+#undef _GINSN_SRC_TYPE_ITEM
+};
+
+/* GAS instruction source operand representation.  */
+
+struct ginsn_src
+{
+  enum ginsn_src_type type;
+  /* DWARF register number.  */
+  uint32_t reg;
+  /* 32-bit immediate or disp for indirect memory access.  */
+  int32_t immdisp;
+  /* Src symbol.  May be needed for some control flow instructions.  */
+  symbolS *sym;
+};
+
+#define _GINSN_DST_TYPES  \
+  _GINSN_DST_TYPE_ITEM (GINSN_DST_UNKNOWN, "UNK") \
+  _GINSN_DST_TYPE_ITEM (GINSN_DST_REG, "REG") \
+  _GINSN_DST_TYPE_ITEM (GINSN_DST_INDIRECT, "IND")
+
+enum ginsn_dst_type
+{
+#define _GINSN_DST_TYPE_ITEM(NAME, STR) NAME,
+  _GINSN_DST_TYPES
+#undef _GINSN_DST_TYPE_ITEM
+};
+
+/* GAS instruction destination operand representation.  */
+
+struct ginsn_dst
+{
+  enum ginsn_dst_type type;
+  /* DWARF register number.  */
+  uint32_t reg;
+  /* 32-bit disp for indirect memory access.  */
+  int32_t disp;
+};
+
+/* Various flags for additional information per GAS instruction.  */
+
+/* Function begin or end symbol.  */
+#define GINSN_F_FUNC_MARKER	    0x1
+/* Identify real or implicit GAS insn.
+   Some targets employ CISC-like instructions.  Multiple ginsn's may be used
+   for a single machine instruction in some ISAs.  For some optimizations,
+   there is need to identify whether a ginsn, e.g., GINSN_TYPE_ADD or
+   GINSN_TYPE_SUB is a result of an user-specified instruction or not.  */
+#define GINSN_F_INSN_REAL	    0x2
+/* Identify if the GAS insn of type GINSN_TYPE_SYMBOL is due to a user-defined
+   label.  Each user-defined labels in a function will cause addition of a new
+   ginsn.  This simplifies control flow graph creation.
+   See htab_t label_ginsn_map usage.  */
+#define GINSN_F_USER_LABEL	    0x4
+/* Max bit position for flags (uint32_t).  */
+#define GINSN_F_MAX		    0x20
+
+#define GINSN_F_FUNC_BEGIN_P(ginsn)	    \
+  ((ginsn != NULL)			    \
+   && (ginsn->type == GINSN_TYPE_SYMBOL)    \
+   && (ginsn->flags & GINSN_F_FUNC_MARKER))
+
+/* PS: For ginsn associated with a user-defined symbol location,
+   GINSN_F_FUNC_MARKER is unset, but GINSN_F_USER_LABEL is set.  */
+#define GINSN_F_FUNC_END_P(ginsn)	    \
+  ((ginsn != NULL)			    \
+   && (ginsn->type == GINSN_TYPE_SYMBOL)    \
+   && !(ginsn->flags & GINSN_F_FUNC_MARKER) \
+   && !(ginsn->flags & GINSN_F_USER_LABEL))
+
+#define GINSN_F_INSN_REAL_P(ginsn)	    \
+  ((ginsn != NULL)			    \
+   && (ginsn->flags & GINSN_F_INSN_REAL))
+
+#define GINSN_F_USER_LABEL_P(ginsn)	    \
+  ((ginsn != NULL)			    \
+   && (ginsn->type == GINSN_TYPE_SYMBOL)    \
+   && !(ginsn->flags & GINSN_F_FUNC_MARKER) \
+   && (ginsn->flags & GINSN_F_USER_LABEL))
+
+typedef struct ginsn ginsnS;
+typedef struct scfi_op scfi_opS;
+typedef struct scfi_state scfi_stateS;
+
+/* GAS generic instruction.
+
+   Generic instructions are used by GAS to abstract out the binary machine
+   instructions.  In other words, ginsn is a target/ABI independent internal
+   representation for GAS.  Note that, depending on the target, there may be
+   more than one ginsn per binary machine instruction.
+
+   ginsns can be used by GAS to perform validations, or even generate
+   additional information like, sythesizing DWARF CFI for hand-written asm.  */
+
+struct ginsn
+{
+  enum ginsn_type type;
+  /* GAS instructions are simple instructions with GINSN_NUM_SRC_OPNDS number
+     of source operands and one destination operand at this time.  */
+  struct ginsn_src src[GINSN_NUM_SRC_OPNDS];
+  struct ginsn_dst dst;
+  /* Additional information per instruction.  */
+  uint32_t flags;
+  /* Symbol.  For ginsn of type other than GINSN_TYPE_SYMBOL, this identifies
+     the end of the corresponding machine instruction in the .text segment.
+     These symbols are created anew by the targets and are not used elsewhere
+     in GAS.  The only exception is some ginsns of type GINSN_TYPE_SYMBOL, when
+     generated for the user-defined labels.  See ginsn_frob_label.  */
+  symbolS *sym;
+  /* Identifier (linearly increasing natural number) for each ginsn.  Used as
+     a proxy for program order of ginsns.  */
+  uint64_t id;
+  /* Location information for user-interfacing messaging.  Only ginsns with
+     GINSN_F_FUNC_BEGIN_P and GINSN_F_FUNC_END_P may present themselves with no
+     file or line information.  */
+  const char *file;
+  unsigned int line;
+
+  /* Information needed for synthesizing CFI.  */
+  scfi_opS **scfi_ops;
+  uint32_t num_scfi_ops;
+
+  /* Flag to keep track of visited instructions for CFG creation.  */
+  bool visited;
+
+  ginsnS *next; /* A linked list.  */
+};
+
+struct ginsn_src *ginsn_get_src1 (ginsnS *ginsn);
+struct ginsn_src *ginsn_get_src2 (ginsnS *ginsn);
+struct ginsn_dst *ginsn_get_dst (ginsnS *ginsn);
+
+uint32_t ginsn_get_src_reg (struct ginsn_src *src);
+enum ginsn_src_type ginsn_get_src_type (struct ginsn_src *src);
+uint32_t ginsn_get_src_disp (struct ginsn_src *src);
+uint32_t ginsn_get_src_imm (struct ginsn_src *src);
+
+uint32_t ginsn_get_dst_reg (struct ginsn_dst *dst);
+enum ginsn_dst_type ginsn_get_dst_type (struct ginsn_dst *dst);
+int32_t ginsn_get_dst_disp (struct ginsn_dst *dst);
+
+/* Data object for book-keeping information related to GAS generic
+   instructions.  */
+struct frch_ginsn_data
+{
+  /* Mode for GINSN creation.  */
+  enum ginsn_gen_mode mode;
+  /* Head of the list of ginsns.  */
+  ginsnS *gins_rootP;
+  /* Tail of the list of ginsns.  */
+  ginsnS *gins_lastP;
+  /* Function symbol.  */
+  symbolS *func;
+  /* Start address of the function.  */
+  symbolS *start_addr;
+  /* User-defined label to ginsn mapping.  */
+  htab_t label_ginsn_map;
+  /* Is the list of ginsn apt for creating CFG.  */
+  bool gcfg_apt_p;
+};
+
+int ginsn_data_begin (symbolS *func);
+int ginsn_data_end (symbolS *label);
+symbolS *ginsn_data_func_symbol (void);
+void ginsn_frob_label (symbolS *sym);
+
+void frch_ginsn_data_init (symbolS *func, symbolS *start_addr,
+			   enum ginsn_gen_mode gmode);
+void frch_ginsn_data_cleanup (void);
+int frch_ginsn_data_append (ginsnS *ginsn);
+enum ginsn_gen_mode frch_ginsn_gen_mode (void);
+
+void label_ginsn_map_insert (symbolS *label, ginsnS *ginsn);
+ginsnS *label_ginsn_map_find (symbolS *label);
+
+ginsnS *ginsn_new_symbol_func_begin (symbolS *sym);
+ginsnS *ginsn_new_symbol_func_end (symbolS *sym);
+ginsnS *ginsn_new_symbol_user_label (symbolS *sym);
+
+ginsnS* ginsn_new_phantom (symbolS *sym);
+ginsnS *ginsn_new_symbol (symbolS *sym, bool real_p);
+ginsnS *ginsn_new_add (symbolS *sym, bool real_p,
+		       enum ginsn_src_type src1_type, uint32_t src1_val, int32_t src1_disp,
+		       enum ginsn_src_type src2_type, uint32_t src2_val, int32_t src2_disp,
+		       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp);
+ginsnS *ginsn_new_and (symbolS *sym, bool real_p,
+		       enum ginsn_src_type src1_type, uint32_t src1_val, int32_t src1_disp,
+		       enum ginsn_src_type src2_type, uint32_t src2_val, int32_t src2_disp,
+		       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp);
+ginsnS *ginsn_new_call (symbolS *sym, bool real_p,
+			enum ginsn_src_type src_type, uint32_t src_val,
+			symbolS *src_text_sym);
+ginsnS *ginsn_new_jump (symbolS *sym, bool real_p,
+			enum ginsn_src_type src_type, uint32_t src_val,
+			symbolS *src_ginsn_sym);
+ginsnS *ginsn_new_jump_cond (symbolS *sym, bool real_p,
+			     enum ginsn_src_type src_type, uint32_t src_val,
+			     symbolS *src_ginsn_sym);
+ginsnS *ginsn_new_mov (symbolS *sym, bool real_p,
+		       enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
+		       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp);
+ginsnS *ginsn_new_store (symbolS *sym, bool real_p,
+			 enum ginsn_src_type src_type, uint32_t src_reg,
+			 enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp);
+ginsnS *ginsn_new_load (symbolS *sym, bool real_p,
+			enum ginsn_src_type src_type, uint32_t src_reg, int32_t src_disp,
+			enum ginsn_dst_type dst_type, uint32_t dst_reg);
+ginsnS *ginsn_new_sub (symbolS *sym, bool real_p,
+		       enum ginsn_src_type src1_type, uint32_t src1_val, int32_t src1_disp,
+		       enum ginsn_src_type src2_type, uint32_t src2_val, int32_t src2_disp,
+		       enum ginsn_dst_type dst_type, uint32_t dst_reg, int32_t dst_disp);
+ginsnS *ginsn_new_other (symbolS *sym, bool real_p,
+			 enum ginsn_src_type src1_type, uint32_t src1_val,
+			 enum ginsn_src_type src2_type, uint32_t src2_val,
+			 enum ginsn_dst_type dst_type, uint32_t dst_reg);
+ginsnS *ginsn_new_return (symbolS *sym, bool real_p);
+
+void ginsn_set_file_line (ginsnS *ginsn, const char *file, unsigned int line);
+
+bool ginsn_track_reg_p (uint32_t dw2reg, enum ginsn_gen_mode);
+
+int ginsn_link_next (ginsnS *ginsn, ginsnS *next);
+
+enum gcfg_err_code
+{
+  GCFG_OK = 0,
+  GCFG_JLABEL_NOT_PRESENT = 1, /* Warning-level code.  */
+};
+
+typedef struct gbb gbbS;
+typedef struct gedge gedgeS;
+
+/* GBB - Basic block of generic GAS instructions.  */
+
+struct gbb
+{
+  ginsnS *first_ginsn;
+  ginsnS *last_ginsn;
+  int64_t num_ginsns;
+
+  /* Identifier (linearly increasing natural number) for each gbb.  Added for
+     debugging purpose only.  */
+  int64_t id;
+
+  bool visited;
+
+  int32_t num_out_gedges;
+  gedgeS *out_gedges;
+
+  /* FIXME - keep a separate map or add like this.  */
+  /* SCFI state at the entry of basic block.  */
+  scfi_stateS *entry_state;
+  /* SCFI state at the exit of basic block.  */
+  scfi_stateS *exit_state;
+  /* A linked list.  In order of addition.  */
+  gbbS *next;
+};
+
+struct gedge
+{
+  gbbS *dst_bb;
+  /* A linked list.  In order of addition.  */
+  gedgeS *next;
+  bool visited;
+};
+
+/* Control flow graph of generic GAS instructions.  */
+
+struct gcfg
+{
+  int64_t num_gbbs;
+  gbbS *root_bb;
+};
+
+typedef struct gcfg gcfgS;
+
+#define bb_for_each_insn(bb, ginsn)  \
+  for (ginsn = bb->first_ginsn; ginsn; \
+       ginsn = (ginsn != bb->last_ginsn) ? ginsn->next : NULL)
+
+#define bb_for_each_edge(bb, edge) \
+  for (edge = (edge == NULL) ? bb->out_gedges : edge; edge; edge = edge->next)
+
+#define cfg_for_each_bb(cfg, bb) \
+  for (bb = cfg->root_bb; bb; bb = bb->next)
+
+#define bb_get_first_ginsn(bb)	  \
+  (bb->first_ginsn)
+
+#define bb_get_last_ginsn(bb)	  \
+  (bb->last_ginsn)
+
+gcfgS *gcfg_build (symbolS *func, int *errp);
+void gcfg_cleanup (gcfgS **gcfg);
+gbbS *gcfg_get_rootbb (gcfgS *gcfg);
+void gcfg_get_bbs_in_prog_order (gcfgS *gcfg, gbbS **prog_order_bbs);
+
+#endif /* GINSN_H.  */
diff --git a/gas/listing.h b/gas/listing.h
index a2cc1f51958..ff89f2219dd 100644
--- a/gas/listing.h
+++ b/gas/listing.h
@@ -29,6 +29,7 @@ 
 #define LISTING_NOCOND    32
 #define LISTING_MACEXP    64
 #define LISTING_GENERAL  128
+#define LISTING_GINSN_SCFI  256
 
 #define LISTING_DEFAULT    (LISTING_LISTING | LISTING_HLL | LISTING_SYMBOLS)
 
diff --git a/gas/read.c b/gas/read.c
index 9068072493a..8ffc3d35ce1 100644
--- a/gas/read.c
+++ b/gas/read.c
@@ -42,6 +42,7 @@ 
 #include "codeview.h"
 #include "wchar.h"
 #include "filenames.h"
+#include "ginsn.h"
 
 #include <limits.h>
 
@@ -1382,6 +1383,9 @@  read_a_source_file (const char *name)
     }
 #endif
 
+  if (flag_synth_cfi)
+    ginsn_data_end (symbol_temp_new_now ());
+
 #ifdef md_cleanup
   md_cleanup ();
 #endif
diff --git a/gas/scfi.c b/gas/scfi.c
new file mode 100644
index 00000000000..ca02ebe42ae
--- /dev/null
+++ b/gas/scfi.c
@@ -0,0 +1,1222 @@ 
+/* scfi.c - Support for synthesizing DWARF CFI for hand-written asm.
+   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 "scfi.h"
+#include "subsegs.h"
+#include "scfidw2gen.h"
+#include "dw2gencfi.h"
+
+# if defined (TARGET_USE_SCFI) && defined (TARGET_USE_GINSN)
+
+/* Beyond the target defined number of registers to be tracked (SCFI_NUM_REGS),
+   keep the next register ID, in sequence, for REG_CFA.  */
+#define REG_CFA	      (SCFI_NUM_REGS+1)
+/* Define the total number of registers being tracked.  Used as index into an
+   array of cfi_reglocS.  */
+#define MAX_NUM_SCFI_REGS   (REG_CFA+1)
+
+enum cfi_reglocstate
+{
+  CFI_UNDEFINED,
+  CFI_IN_REG,
+  CFI_ON_STACK
+};
+
+/* Location at which CFI register is saved.
+
+   A CFI register (callee-saved registers, RA/LR) are always an offset from
+   the CFA.  REG_CFA itself, however, may have REG_SP or REG_FP as base
+   register.  Hence, keep the base reg ID and offset per tracked register.  */
+
+struct cfi_regloc
+{
+  /* Base reg ID (DWARF register number).  */
+  uint32_t base;
+  /* Location as offset from the CFA.  */
+  int32_t offset;
+  /* Current state of the CFI register.  */
+  enum cfi_reglocstate state;
+};
+
+typedef struct cfi_regloc cfi_reglocS;
+
+struct scfi_op_data
+{
+  const char *name;
+};
+
+typedef struct scfi_op_data scfi_op_dataS;
+
+/* SCFI operation.
+
+   An SCFI operation represents a single atomic change to the SCFI state.
+   This can also be understood as an abstraction for what eventually gets
+   emitted as a DWARF CFI operation.  */
+
+struct scfi_op
+{
+  /* An SCFI op updates the state of either the CFA or other tracked
+     (callee-saved, REG_SP etc) registers.  'reg' is in the DWARF register
+     number space and must be strictly less than MAX_NUM_SCFI_REGS.  */
+  uint32_t reg;
+  /* Location of the reg.  */
+  cfi_reglocS loc;
+  /* DWARF CFI opcode.  */
+  uint32_t dw2cfi_op;
+  /* Some SCFI ops, e.g., for CFI_label, may need to carry additional data.  */
+  scfi_op_dataS *op_data;
+  /* A linked list.  */
+  struct scfi_op *next;
+};
+
+/* SCFI State - accumulated unwind information at a PC.
+
+   SCFI state is the accumulated unwind information encompassing:
+      - REG_SP, REG_FP,
+      - RA, and
+      - all callee-saved registers.
+
+    Note that SCFI_NUM_REGS is target/ABI dependent and is provided by the
+    backends.  The backend must also identify the REG_SP, and REG_FP
+    registers.  */
+
+struct scfi_state
+{
+  cfi_reglocS regs[MAX_NUM_SCFI_REGS];
+  cfi_reglocS scratch[MAX_NUM_SCFI_REGS];
+  /* Current stack size.  */
+  int32_t stack_size;
+  /* Whether the stack size is known.
+     Stack size may become untraceable depending on the specific stack
+     manipulation machine instruction, e.g., rsp = rsp op reg instruction
+     makes the stack size untraceable.  */
+  bool traceable_p;
+};
+
+/* Initialize a new SCFI op.  */
+
+static scfi_opS *
+init_scfi_op (void)
+{
+  scfi_opS *op = XCNEW (scfi_opS);
+
+  return op;
+}
+
+void
+scfi_ops_cleanup (scfi_opS **head)
+{
+  scfi_opS *op;
+  scfi_opS *next;
+
+  if (!head || !*head)
+    return;
+
+  op = *head;
+  next = op->next;
+
+  while (op)
+    {
+      free (op);
+      op = next;
+      next = op ? op->next : NULL;
+    }
+}
+
+/* Compare two SCFI states.  */
+
+static int
+cmp_scfi_state (scfi_stateS *state1, scfi_stateS *state2)
+{
+  int ret;
+
+  if (!state1 || !state2)
+    ret = 1;
+
+  /* Skip comparing the scratch[] value of registers.  The user visible
+     unwind information is derived from the regs[] from the SCFI state.  */
+  ret = memcmp (state1->regs, state2->regs,
+		sizeof (cfi_reglocS) * MAX_NUM_SCFI_REGS);
+  ret |= state1->stack_size != state2->stack_size;
+  ret |= state1->traceable_p != state2->traceable_p;
+
+  return ret;
+}
+
+#if 0
+static void
+scfi_state_update_reg (scfi_stateS *state, uint32_t dst, uint32_t base,
+		       int32_t offset)
+{
+  if (dst >= MAX_NUM_SCFI_REGS)
+    return;
+
+  state->regs[dst].base = base;
+  state->regs[dst].offset = offset;
+}
+#endif
+
+/* Update the SCFI state of REG as available on execution stack at OFFSET
+   from REG_CFA (BASE).
+
+   Note that BASE must be REG_CFA, because any other base (REG_SP, REG_FP)
+   is by definition transitory in the function.  */
+
+static void
+scfi_state_save_reg (scfi_stateS *state, uint32_t reg, uint32_t base,
+		     int32_t offset)
+{
+  if (reg >= MAX_NUM_SCFI_REGS)
+    return;
+
+  gas_assert (base == REG_CFA);
+
+  state->regs[reg].base = base;
+  state->regs[reg].offset = offset;
+  state->regs[reg].state = CFI_ON_STACK;
+}
+
+static void
+scfi_state_restore_reg (scfi_stateS *state, uint32_t reg)
+{
+  if (reg >= MAX_NUM_SCFI_REGS)
+    return;
+
+  /* Sanity check.  See Rule 4.  */
+  gas_assert (state->regs[reg].state == CFI_ON_STACK);
+  gas_assert (state->regs[reg].base == REG_CFA);
+
+  state->regs[reg].base = reg;
+  state->regs[reg].offset = 0;
+  /* PS: the register may still be on stack much after the restore, but the
+     SCFI state keeps the state as 'in register'.  */
+  state->regs[reg].state = CFI_IN_REG;
+}
+
+/* Identify if the given GAS instruction GINSN saves a register
+   (of interest) on stack.  */
+
+static bool
+ginsn_scfi_save_reg_p (ginsnS *ginsn, scfi_stateS *state)
+{
+  bool save_reg_p = false;
+  struct ginsn_src *src;
+  struct ginsn_dst *dst;
+
+  src = ginsn_get_src1 (ginsn);
+  dst = ginsn_get_dst (ginsn);
+
+  /* The first save to stack of callee-saved register is deemed as
+     register save.  */
+  if (!ginsn_track_reg_p (ginsn_get_src_reg (src), GINSN_GEN_SCFI)
+      || state->regs[ginsn_get_src_reg (src)].state == CFI_ON_STACK)
+    return save_reg_p;
+
+  /* A register save insn may be an indirect mov.  */
+  if (ginsn->type == GINSN_TYPE_MOV
+      && ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT
+      && (ginsn_get_dst_reg (dst) == REG_SP
+	  || (ginsn_get_dst_reg (dst) == REG_FP
+	      && state->regs[REG_CFA].base == REG_FP)))
+    save_reg_p = true;
+  /* or an explicit store to stack.  */
+  else if (ginsn->type == GINSN_TYPE_STORE
+	   && ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT
+	   && ginsn_get_dst_reg (dst) == REG_SP)
+    save_reg_p = true;
+
+  return save_reg_p;
+}
+
+/* Identify if the given GAS instruction GINSN restores a register
+   (of interest) on stack.  */
+
+static bool
+ginsn_scfi_restore_reg_p (ginsnS *ginsn, scfi_stateS *state)
+{
+  bool restore_reg_p = false;
+  struct ginsn_dst *dst;
+  struct ginsn_src *src1;
+
+  dst = ginsn_get_dst (ginsn);
+  src1 = ginsn_get_src1 (ginsn);
+
+  if (!ginsn_track_reg_p (ginsn_get_dst_reg (dst), GINSN_GEN_SCFI))
+    return restore_reg_p;
+
+  /* A register restore insn may be an indirect mov.  */
+  if (ginsn->type == GINSN_TYPE_MOV
+      && ginsn_get_src_type (src1) == GINSN_SRC_INDIRECT
+      && (ginsn_get_src_reg (src1) == REG_SP
+	  || (ginsn_get_src_reg (src1) == REG_FP
+	      && state->regs[REG_CFA].base == REG_FP)))
+    restore_reg_p = true;
+  /* or an explicit load from stack.  */
+  else if (ginsn->type == GINSN_TYPE_LOAD
+	   && ginsn_get_src_type (src1) == GINSN_SRC_INDIRECT
+	   && ginsn_get_src_reg (src1) == REG_SP)
+    restore_reg_p = true;
+
+  return restore_reg_p;
+}
+
+/* Append the SCFI operation OP to the list of SCFI operations in the
+   given GINSN.  */
+
+static int
+ginsn_append_scfi_op (ginsnS *ginsn, scfi_opS *op)
+{
+  scfi_opS *sop;
+
+  if (!ginsn || !op)
+    return 1;
+
+  if (!ginsn->scfi_ops)
+    {
+      ginsn->scfi_ops = XCNEW (scfi_opS *);
+      *ginsn->scfi_ops = op;
+    }
+  else
+    {
+      /* Add to tail.  Most ginsns have a single SCFI operation,
+	 so this traversal for every insertion is acceptable for now.  */
+      sop = *ginsn->scfi_ops;
+      while (sop->next)
+	sop = sop->next;
+
+      sop->next = op;
+    }
+  ginsn->num_scfi_ops++;
+
+  return 0;
+}
+
+static void
+scfi_op_add_def_cfa_reg (scfi_stateS *state, ginsnS *ginsn, uint32_t reg)
+{
+  scfi_opS *op = NULL;
+
+  state->regs[REG_CFA].base = reg;
+
+  op = init_scfi_op ();
+
+  op->dw2cfi_op = DW_CFA_def_cfa_register;
+  op->reg = REG_CFA;
+  op->loc = state->regs[REG_CFA];
+
+  ginsn_append_scfi_op (ginsn, op);
+}
+
+static void
+scfi_op_add_cfa_offset_inc (scfi_stateS *state, ginsnS *ginsn, int32_t num)
+{
+  scfi_opS *op = NULL;
+
+  state->regs[REG_CFA].offset -= num;
+
+  op = init_scfi_op ();
+
+  op->dw2cfi_op = DW_CFA_def_cfa_offset;
+  op->reg = REG_CFA;
+  op->loc = state->regs[REG_CFA];
+
+  ginsn_append_scfi_op (ginsn, op);
+}
+
+static void
+scfi_op_add_cfa_offset_dec (scfi_stateS *state, ginsnS *ginsn, int32_t num)
+{
+  scfi_opS *op = NULL;
+
+  state->regs[REG_CFA].offset += num;
+
+  op = init_scfi_op ();
+
+  op->dw2cfi_op = DW_CFA_def_cfa_offset;
+  op->reg = REG_CFA;
+  op->loc = state->regs[REG_CFA];
+
+  ginsn_append_scfi_op (ginsn, op);
+}
+
+static void
+scfi_op_add_def_cfa (scfi_stateS *state, ginsnS *ginsn, uint32_t reg,
+		     int32_t num)
+{
+  scfi_opS *op = NULL;
+
+  state->regs[REG_CFA].base = reg;
+  state->regs[REG_CFA].offset = num;
+
+  op = init_scfi_op ();
+
+  op->dw2cfi_op = DW_CFA_def_cfa;
+  op->reg = REG_CFA;
+  op->loc = state->regs[REG_CFA];
+
+  ginsn_append_scfi_op (ginsn, op);
+}
+
+static void
+scfi_op_add_cfi_offset (scfi_stateS *state, ginsnS *ginsn, uint32_t reg)
+{
+  scfi_opS *op = NULL;
+
+  op = init_scfi_op ();
+
+  op->dw2cfi_op = DW_CFA_offset;
+  op->reg = reg;
+  op->loc = state->regs[reg];
+
+  ginsn_append_scfi_op (ginsn, op);
+}
+
+static void
+scfi_op_add_cfa_restore (ginsnS *ginsn, uint32_t reg)
+{
+  scfi_opS *op = NULL;
+
+  op = init_scfi_op ();
+
+  op->dw2cfi_op = DW_CFA_restore;
+  op->reg = reg;
+  op->loc.base = -1; /* FIXME invalidate.  */
+  op->loc.offset = 0;
+
+  ginsn_append_scfi_op (ginsn, op);
+}
+
+static void
+scfi_op_add_cfi_remember_state (ginsnS *ginsn)
+{
+  scfi_opS *op = NULL;
+
+  op = init_scfi_op ();
+
+  op->dw2cfi_op = DW_CFA_remember_state;
+
+  ginsn_append_scfi_op (ginsn, op);
+}
+
+static void
+scfi_op_add_cfi_restore_state (ginsnS *ginsn)
+{
+  scfi_opS *op = NULL;
+
+  op = init_scfi_op ();
+
+  op->dw2cfi_op = DW_CFA_restore_state;
+
+  /* FIXME - add to the beginning of the scfi_ops.  */
+  ginsn_append_scfi_op (ginsn, op);
+}
+
+void
+scfi_op_add_cfi_label (ginsnS *ginsn, const char *name)
+{
+  scfi_opS *op = NULL;
+
+  op = init_scfi_op ();
+  op->dw2cfi_op = CFI_label;
+  op->op_data = XCNEW (scfi_op_dataS);
+  op->op_data->name = name;
+
+  ginsn_append_scfi_op (ginsn, op);
+}
+
+void
+scfi_op_add_signal_frame (ginsnS *ginsn)
+{
+  scfi_opS *op = NULL;
+
+  op = init_scfi_op ();
+  op->dw2cfi_op = CFI_signal_frame;
+
+  ginsn_append_scfi_op (ginsn, op);
+}
+
+static int
+verify_heuristic_traceable_reg_fp (ginsnS *ginsn, scfi_stateS *state)
+{
+  /* The function uses this variable to issue error to user right away.  */
+  int fp_traceable_p = 0;
+  struct ginsn_dst *dst;
+  struct ginsn_src *src1;
+  struct ginsn_src *src2;
+
+  src1 = ginsn_get_src1 (ginsn);
+  src2 = ginsn_get_src2 (ginsn);
+  dst = ginsn_get_dst (ginsn);
+
+  /* Stack manipulation can be done in a variety of ways.  A program may
+     allocate stack statically or may perform dynamic stack allocation in
+     the prologue.
+
+     The SCFI machinery in GAS is based on some heuristics:
+
+       - Rule 3 If the base register for CFA tracking is REG_FP, the program
+       must not clobber REG_FP, unless it is for switch to REG_SP based CFA
+       tracking (via say, a pop %rbp in X86).  */
+
+  /* Check all applicable instructions with dest REG_FP, when the CFA base
+     register is REG_FP.  */
+  if (state->regs[REG_CFA].base == REG_FP && ginsn_get_dst_reg (dst) == REG_FP)
+    {
+      /* Excuse the add/sub with imm usage: They are OK.  */
+      if ((ginsn->type == GINSN_TYPE_ADD || ginsn->type == GINSN_TYPE_SUB)
+	  && ginsn_get_src_reg (src1) == REG_FP
+	  && ginsn_get_src_type (src2) == GINSN_SRC_IMM)
+	fp_traceable_p = 0;
+      /* REG_FP restore is OK too.  */
+      else if (ginsn->type == GINSN_TYPE_LOAD)
+	fp_traceable_p = 0;
+      /* mov's to memory with REG_FP base do not make REG_FP untraceable.  */
+      else if (ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT
+	       && (ginsn->type == GINSN_TYPE_MOV
+		   || ginsn->type == GINSN_TYPE_STORE))
+	fp_traceable_p = 0;
+      /* Manipulations of the values possibly on stack are OK too.  */
+      else if ((ginsn->type == GINSN_TYPE_ADD || ginsn->type == GINSN_TYPE_SUB
+		|| ginsn->type == GINSN_TYPE_AND)
+	       && ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT)
+	fp_traceable_p = 0;
+      /* All other ginsns with REG_FP as destination make REG_FP not
+	 traceable.  */
+      else
+	fp_traceable_p = 1;
+    }
+
+  if (fp_traceable_p)
+    as_bad_where (ginsn->file, ginsn->line,
+		  _("SCFI: usage of REG_FP as scratch not supported"));
+
+  return fp_traceable_p;
+}
+
+static int
+verify_heuristic_traceable_stack_manipulation (ginsnS *ginsn,
+					       scfi_stateS *state)
+{
+  /* The function uses this variable to issue error to user right away.  */
+  int sp_untraceable_p = 0;
+  bool possibly_untraceable = false;
+  struct ginsn_dst *dst;
+  struct ginsn_src *src1;
+  struct ginsn_src *src2;
+
+  src1 = ginsn_get_src1 (ginsn);
+  src2 = ginsn_get_src2 (ginsn);
+  dst = ginsn_get_dst (ginsn);
+
+  /* Stack manipulation can be done in a variety of ways.  A program may
+     allocate stack statically in prologue or may need to do dynamic stack
+     allocation.
+
+     The SCFI machinery in GAS is based on some heuristics:
+
+       - Rule 1 The base register for CFA tracking may be either REG_SP or
+       REG_FP.
+
+       - Rule 2 If the base register for CFA tracking is REG_SP, the precise
+       amount of stack usage (and hence, the value of rsp) must be known at
+       all times.  */
+
+  if (ginsn->type == GINSN_TYPE_MOV
+      && ginsn_get_dst_type (dst) == GINSN_DST_REG
+      && ginsn_get_dst_reg (dst) == REG_SP
+      && ginsn_get_src_type (src1) == GINSN_SRC_REG
+      /* Exclude mov %rbp, %rsp from this check.  */
+      && ginsn_get_src_reg (src1) != REG_FP)
+    {
+      /* mov %reg, %rsp.  */
+      /* A previous mov %rsp, %reg must have been seen earlier for this to be
+	 an OK for stack manipulation.  */
+      if (state->scratch[ginsn_get_src_reg (src1)].base != REG_CFA
+	  || state->scratch[ginsn_get_src_reg (src1)].state != CFI_IN_REG)
+	{
+	  possibly_untraceable = true;
+	}
+    }
+  /* Check add/sub/and insn usage when CFA base register is REG_SP.
+     Any stack size manipulation, including stack realignment is not allowed
+     if CFA base register is REG_SP.  */
+  else if (ginsn_get_dst_type (dst) == GINSN_DST_REG
+	   && ginsn_get_dst_reg (dst) == REG_SP
+	   && (((ginsn->type == GINSN_TYPE_ADD || ginsn->type == GINSN_TYPE_SUB)
+		&& ginsn_get_src_type (src2) != GINSN_SRC_IMM)
+	       || ginsn->type == GINSN_TYPE_AND))
+    possibly_untraceable = true;
+  /* If a register save operation is seen when REG_SP is untraceable,
+     CFI cannot be synthesized for register saves, hence bail out.  */
+  else if (ginsn_scfi_save_reg_p (ginsn, state) && !state->traceable_p)
+    {
+      sp_untraceable_p = 1;
+      /* If, however, the register save is an REG_FP-based, indirect mov
+	 like: mov reg, disp(%rbp) and CFA base register is REG_BP,
+	 untraceable REG_SP is not a problem.  */
+      if (ginsn->type == GINSN_TYPE_MOV
+	  && ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT
+	  && (ginsn_get_dst_reg (dst) == REG_FP
+	      && state->regs[REG_CFA].base == REG_FP))
+	sp_untraceable_p = 0;
+    }
+  else if (ginsn_scfi_restore_reg_p (ginsn, state) && !state->traceable_p)
+    {
+      if (ginsn->type == GINSN_TYPE_MOV
+	  && ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT
+	  && (ginsn_get_src_reg (src1) == REG_SP
+	      || (ginsn_get_src_reg (src1) == REG_FP
+		  && state->regs[REG_CFA].base != REG_FP)))
+	sp_untraceable_p = 1;
+    }
+
+  if (possibly_untraceable)
+    {
+      /* See Rule 2.  For SP-based CFA, this makes CFA tracking not possible.
+	 Propagate now to caller.  */
+      if (state->regs[REG_CFA].base == REG_SP)
+	sp_untraceable_p = 1;
+      else if (state->traceable_p)
+	{
+	  /* An extension of Rule 2.
+	     For FP-based CFA, this may be a problem *if* certain specific
+	     changes to the SCFI state are seen beyond this point, e.g.,
+	     register save / restore from stack.  */
+	  gas_assert (state->regs[REG_CFA].base == REG_FP);
+	  /* Simply make a note in the SCFI state object for now and
+	     continue.  Indicate an error when register save / restore
+	     for callee-saved registers is seen.  */
+	  sp_untraceable_p = 0;
+	  state->traceable_p = false;
+	}
+    }
+
+  if (sp_untraceable_p)
+    as_bad_where (ginsn->file, ginsn->line,
+		  _("SCFI: unsupported stack manipulation pattern"));
+
+  return sp_untraceable_p;
+}
+
+static int
+verify_heuristic_symmetrical_restore_reg (scfi_stateS *state, ginsnS* ginsn)
+{
+  int sym_restore = true;
+  int32_t expected_offset = 0;
+  struct ginsn_src *src1;
+  struct ginsn_dst *dst;
+  uint32_t reg;
+
+  /* Rule 4: Save and Restore of callee-saved registers must be symmetrical.
+     It is expected that value of the saved register is restored correctly.
+     E.g.,
+	push  reg1
+	push  reg2
+	...
+	body of func which uses reg1 , reg2 as scratch,
+	and may be even spills them to stack.
+	...
+	pop   reg2
+	pop   reg1
+     It is difficult to verify the Rule 4 in all cases.  For the SCFI machinery,
+     it is difficult to separate prologue-epilogue from the body of the function
+
+     Hence, the SCFI machinery at this time, should only warn on an asymetrical
+     restore.  */
+  src1 = ginsn_get_src1 (ginsn);
+  dst = ginsn_get_dst (ginsn);
+  reg = ginsn_get_dst_reg (dst);
+
+  /* For non callee-saved registers, calling the API is meaningless.  */
+  if (!ginsn_track_reg_p (ginsn_get_dst_reg (dst), GINSN_GEN_SCFI))
+    return sym_restore;
+
+  /* The register must have been saved on stack, for sure.  */
+  gas_assert (state->regs[reg].state == CFI_ON_STACK);
+  gas_assert (state->regs[reg].base == REG_CFA);
+
+  if ((ginsn->type == GINSN_TYPE_MOV
+       || ginsn->type == GINSN_TYPE_LOAD)
+      && ginsn_get_src_type (src1) == GINSN_SRC_INDIRECT
+      && (ginsn_get_src_reg (src1) == REG_SP
+	  || (ginsn_get_src_reg (src1) == REG_FP
+	      && state->regs[REG_CFA].base == REG_FP)))
+    {
+      /* mov disp(%rsp), reg.  */
+      /* mov disp(%rbp), reg.  */
+      expected_offset = (((ginsn_get_src_reg (src1) == REG_SP)
+			  ? -state->stack_size
+			  : state->regs[REG_FP].offset)
+			 + ginsn_get_src_disp (src1));
+    }
+
+  sym_restore = (expected_offset == state->regs[reg].offset);
+
+  return sym_restore;
+}
+
+/* Perform symbolic execution of the GINSN and update its list of scfi_ops.
+   scfi_ops are later used to directly generate the DWARF CFI directives.
+   Also update the SCFI state object STATE for the caller.  */
+
+static int
+gen_scfi_ops (ginsnS *ginsn, scfi_stateS *state)
+{
+  int ret = 0;
+  int32_t offset;
+  struct ginsn_src *src1;
+  struct ginsn_src *src2;
+  struct ginsn_dst *dst;
+
+  if (!ginsn || !state)
+    ret = 1;
+
+  /* For the first ginsn (of type GINSN_TYPE_SYMBOL) in the gbb, generate
+     the SCFI op with DW_CFA_def_cfa.  Note that the register and offset are
+     target-specific.  */
+  if (GINSN_F_FUNC_BEGIN_P (ginsn))
+    {
+      scfi_op_add_def_cfa (state, ginsn, REG_SP, SCFI_INIT_CFA_OFFSET);
+      state->stack_size += SCFI_INIT_CFA_OFFSET;
+      return ret;
+    }
+
+  src1 = ginsn_get_src1 (ginsn);
+  src2 = ginsn_get_src2 (ginsn);
+  dst = ginsn_get_dst (ginsn);
+
+  ret = verify_heuristic_traceable_stack_manipulation (ginsn, state);
+  if (ret)
+    return ret;
+
+  ret = verify_heuristic_traceable_reg_fp (ginsn, state);
+  if (ret)
+    return ret;
+
+  switch (ginsn->dst.type)
+    {
+    case GINSN_DST_REG:
+      switch (ginsn->type)
+	{
+	case GINSN_TYPE_MOV:
+	  if (ginsn_get_src_type (src1) == GINSN_SRC_REG
+	      && ginsn_get_src_reg (src1) == REG_SP
+	      && ginsn_get_dst_reg (dst) == REG_FP
+	      && state->regs[REG_CFA].base == REG_SP
+	      /* That REG_FP has just been saved on stack previously.  This
+		 check guards against the case that there is stack usage
+		 between the two insns:
+		    pushq %rbp
+		    ...stack usage
+		    mov %rsp, %rbp
+		 Do not switch CFA tracking in this case.  */
+	      && state->regs[REG_FP].offset == -state->stack_size)
+	    {
+	      /* mov %rsp, %rbp.  */
+	      scfi_op_add_def_cfa_reg (state, ginsn, ginsn_get_dst_reg (dst));
+	    }
+	  else if (ginsn_get_src_type (src1) == GINSN_SRC_REG
+		   && ginsn_get_src_reg (src1) == REG_FP
+		   && ginsn_get_dst_reg (dst) == REG_SP
+		   && state->regs[REG_CFA].base == REG_FP)
+	    {
+	      /* mov %rbp, %rsp.  */
+	      state->stack_size = -state->regs[REG_FP].offset;
+	      scfi_op_add_def_cfa_reg (state, ginsn, ginsn_get_dst_reg (dst));
+	      state->traceable_p = true;
+	    }
+	  else if (ginsn_get_src_type (src1) == GINSN_SRC_INDIRECT
+		   && (ginsn_get_src_reg (src1) == REG_SP
+		       || ginsn_get_src_reg (src1) == REG_FP)
+		   && ginsn_track_reg_p (ginsn_get_dst_reg (dst), GINSN_GEN_SCFI))
+	    {
+	      /* mov disp(%rsp), reg.  */
+	      /* mov disp(%rbp), reg.  */
+	      if (verify_heuristic_symmetrical_restore_reg (state, ginsn))
+		{
+		  scfi_state_restore_reg (state, ginsn_get_dst_reg (dst));
+		  scfi_op_add_cfa_restore (ginsn, ginsn_get_dst_reg (dst));
+		}
+	      else
+		as_warn_where (ginsn->file, ginsn->line,
+			       _("SCFI: asymetrical register restore"));
+	    }
+	  else if (ginsn_get_src_type (src1) == GINSN_SRC_REG
+		   && ginsn_get_dst_type (dst) == GINSN_DST_REG
+		   && ginsn_get_src_reg (src1) == REG_SP)
+	    {
+	      /* mov %rsp, %reg.  */
+	      /* The value of rsp is taken directly from state->stack_size.
+		 IMP: The workflow in gen_scfi_ops must keep it updated.
+		 PS: Not taking the value from state->scratch[REG_SP] is
+		 intentional.  */
+	      state->scratch[ginsn_get_dst_reg (dst)].base = REG_CFA;
+	      state->scratch[ginsn_get_dst_reg (dst)].offset = -state->stack_size;
+	      state->scratch[ginsn_get_dst_reg (dst)].state = CFI_IN_REG;
+	    }
+	  else if (ginsn_get_src_type (src1) == GINSN_SRC_REG
+		   && ginsn_get_dst_type (dst) == GINSN_DST_REG
+		   && ginsn_get_dst_reg (dst) == REG_SP)
+	    {
+	      /* mov %reg, %rsp.  */
+	      /* Keep the value of REG_SP updated.  */
+	      if (state->scratch[ginsn_get_src_reg (src1)].state == CFI_IN_REG)
+		{
+		  state->stack_size = -state->scratch[ginsn_get_src_reg (src1)].offset;
+		  state->traceable_p = true;
+		}
+# if 0
+	      scfi_state_update_reg (state, ginsn_get_dst_reg (dst),
+				     state->scratch[ginsn_get_src_reg (src1)].base,
+				     state->scratch[ginsn_get_src_reg (src1)].offset);
+#endif
+
+	    }
+	  break;
+	case GINSN_TYPE_SUB:
+	  if (ginsn_get_src_reg (src1) == REG_SP
+	      && ginsn_get_dst_reg (dst) == REG_SP)
+	    {
+	      /* Stack inc/dec offset, when generated due to stack push and pop is
+		 target-specific.  Use the value encoded in the ginsn.  */
+	      state->stack_size += ginsn_get_src_imm (src2);
+	      if (state->regs[REG_CFA].base == REG_SP)
+		{
+		  /* push reg.  */
+		  scfi_op_add_cfa_offset_dec (state, ginsn, ginsn_get_src_imm (src2));
+		}
+	    }
+	  break;
+	case GINSN_TYPE_ADD:
+	  if (ginsn_get_src_reg (src1) == REG_SP
+	      && ginsn_get_dst_reg (dst) == REG_SP)
+	    {
+	      /* Stack inc/dec offset is target-specific.  Use the value
+		 encoded in the ginsn.  */
+	      state->stack_size -= ginsn_get_src_imm (src2);
+	      /* pop %reg affects CFA offset only if CFA is currently
+		 stack-pointer based.  */
+	      if (state->regs[REG_CFA].base == REG_SP)
+		{
+		  scfi_op_add_cfa_offset_inc (state, ginsn, ginsn_get_src_imm (src2));
+		}
+	    }
+	  else if (ginsn_get_src_reg (src1) == REG_FP
+		   && ginsn_get_dst_reg (dst) == REG_SP
+		   && state->regs[REG_CFA].base == REG_FP)
+	    {
+	      /* FIXME - what is this for ? */
+	      state->stack_size =  0 - (state->regs[REG_FP].offset + ginsn_get_src_imm (src2));
+	    }
+	  break;
+	case GINSN_TYPE_LOAD:
+	  /* If this is a load from stack.  */
+	  if (ginsn_get_src_type (src1) == GINSN_SRC_INDIRECT
+	      && (ginsn_get_src_reg (src1) == REG_SP
+		  || (ginsn_get_src_reg (src1) == REG_FP
+		      && state->regs[REG_CFA].base == REG_FP)))
+	    {
+	      /* pop %rbp when CFA tracking is REG_FP based.  */
+	      if (ginsn_get_dst_reg (dst) == REG_FP
+		  && state->regs[REG_CFA].base == REG_FP)
+		{
+		  scfi_op_add_def_cfa_reg (state, ginsn, REG_SP);
+		}
+	      if (ginsn_track_reg_p (ginsn_get_dst_reg (dst), GINSN_GEN_SCFI))
+		{
+		  if (verify_heuristic_symmetrical_restore_reg (state, ginsn))
+		    {
+		      scfi_state_restore_reg (state, ginsn_get_dst_reg (dst));
+		      scfi_op_add_cfa_restore (ginsn, ginsn_get_dst_reg (dst));
+		    }
+		  else
+		    as_warn_where (ginsn->file, ginsn->line,
+				   _("SCFI: asymetrical register restore"));
+		}
+	    }
+	  break;
+	default:
+	  break;
+	}
+      break;
+
+    case GINSN_DST_INDIRECT:
+      /* Some operations with an indirect access to memory (or even to stack)
+	 may still be uninteresting for SCFI purpose (e.g, addl %edx, -32(%rsp)
+	 in x86).  In case of x86_64, these can neither be a register
+	 save / unsave, nor can alter the stack size.
+	 PS: This condition may need to be revisited for other arches.  */
+      if (ginsn->type == GINSN_TYPE_ADD || ginsn->type == GINSN_TYPE_SUB
+	  || ginsn->type == GINSN_TYPE_AND)
+	break;
+
+      gas_assert (ginsn->type == GINSN_TYPE_MOV
+		  || ginsn->type == GINSN_TYPE_STORE);
+      /* mov reg, disp(%rbp) */
+      /* mov reg, disp(%rsp) */
+      if (ginsn_scfi_save_reg_p (ginsn, state))
+	{
+	  if (ginsn_get_dst_reg (dst) == REG_SP)
+	    {
+	      /* mov reg, disp(%rsp) */
+	      offset = 0 - state->stack_size + ginsn_get_dst_disp (dst);
+	      scfi_state_save_reg (state, ginsn_get_src_reg (src1), REG_CFA, offset);
+	      scfi_op_add_cfi_offset (state, ginsn, ginsn_get_src_reg (src1));
+	    }
+	  else if (ginsn_get_dst_reg (dst) == REG_FP)
+	    {
+	      gas_assert (state->regs[REG_CFA].base == REG_FP);
+	      /* mov reg, disp(%rbp) */
+	      offset = 0 - state->regs[REG_CFA].offset + ginsn_get_dst_disp (dst);
+	      scfi_state_save_reg (state, ginsn_get_src_reg (src1), REG_CFA, offset);
+	      scfi_op_add_cfi_offset (state, ginsn, ginsn_get_src_reg (src1));
+	    }
+	}
+      break;
+
+    default:
+      /* Skip GINSN_DST_UNKNOWN and GINSN_DST_MEM as they are uninteresting
+	 currently for SCFI.  */
+      break;
+    }
+
+  return ret;
+}
+
+/* Recursively perform forward flow of the (unwind information) SCFI state
+   starting at basic block GBB.
+
+   The forward flow process propagates the SCFI state at exit of a basic block
+   to the successor basic block.
+
+   Returns error code, if any.  */
+
+static int
+forward_flow_scfi_state (gcfgS *gcfg, gbbS *gbb, scfi_stateS *state)
+{
+  ginsnS *ginsn;
+  gbbS *prev_bb;
+  gedgeS *gedge = NULL;
+  int ret = 0;
+
+  if (gbb->visited)
+    {
+      /* Check that the SCFI state is the same as previous.  */
+      ret = cmp_scfi_state (state, gbb->entry_state);
+      if (ret)
+	as_bad (_("SCFI: Bad CFI propagation perhaps"));
+      return ret;
+    }
+
+  gbb->visited = true;
+
+  gbb->entry_state = XCNEW (scfi_stateS);
+  memcpy (gbb->entry_state, state, sizeof (scfi_stateS));
+
+  /* Perform symbolic execution of each ginsn in the gbb and update the
+     scfi_ops list of each ginsn (and also update the STATE object).   */
+  bb_for_each_insn(gbb, ginsn)
+    {
+      ret = gen_scfi_ops (ginsn, state);
+      if (ret)
+	goto fail;
+    }
+
+  gbb->exit_state = XCNEW (scfi_stateS);
+  memcpy (gbb->exit_state, state, sizeof (scfi_stateS));
+
+  /* Forward flow the SCFI state.  Currently, we process the next basic block
+     in DFS order.  But any forward traversal order should be fine.  */
+  prev_bb = gbb;
+  if (gbb->num_out_gedges)
+    {
+      bb_for_each_edge(gbb, gedge)
+	{
+	  gbb = gedge->dst_bb;
+	  if (gbb->visited)
+	    {
+	      ret = cmp_scfi_state (gbb->entry_state, state);
+	      if (ret)
+		goto fail;
+	    }
+
+	  if (!gedge->visited)
+	    {
+	      gedge->visited = true;
+
+	      /* Entry SCFI state for the destination bb of the edge is the
+		 same as the exit SCFI state of the source bb of the edge.  */
+	      memcpy (state, prev_bb->exit_state, sizeof (scfi_stateS));
+	      ret = forward_flow_scfi_state (gcfg, gbb, state);
+	      if (ret)
+		goto fail;
+	    }
+	}
+    }
+
+  return 0;
+
+fail:
+
+  if (gedge)
+    gedge->visited = true;
+  return 1;
+}
+
+static int
+backward_flow_scfi_state (symbolS *func ATTRIBUTE_UNUSED, gcfgS *gcfg)
+{
+  gbbS **prog_order_bbs;
+  gbbS **restore_bbs;
+  gbbS *current_bb;
+  gbbS *prev_bb;
+  gbbS *dst_bb;
+  ginsnS *ginsn;
+  gedgeS *gedge = NULL;
+
+  int ret = 0;
+  int i, j;
+
+  /* Basic blocks in reverse program order.  */
+  prog_order_bbs = XCNEWVEC (gbbS *, gcfg->num_gbbs);
+  /* Basic blocks for which CFI remember op needs to be generated.  */
+  restore_bbs = XCNEWVEC (gbbS *, gcfg->num_gbbs);
+
+  gcfg_get_bbs_in_prog_order (gcfg, prog_order_bbs);
+
+  i = gcfg->num_gbbs - 1;
+  /* Traverse in reverse program order.  */
+  while (i > 0)
+    {
+      current_bb = prog_order_bbs[i];
+      prev_bb = prog_order_bbs[i-1];
+      if (cmp_scfi_state (prev_bb->exit_state, current_bb->entry_state))
+	{
+	  /* Candidate for .cfi_restore_state found.  */
+	  ginsn = bb_get_first_ginsn (current_bb);
+	  scfi_op_add_cfi_restore_state (ginsn);
+	  /* Memorize current_bb now to find location for its remember state
+	     later.  */
+	  restore_bbs[i] = current_bb;
+	}
+      else
+	{
+	  bb_for_each_edge (current_bb, gedge)
+	    {
+	      dst_bb = gedge->dst_bb;
+	      for (j = 0; j < gcfg->num_gbbs; j++)
+		if (restore_bbs[j] == dst_bb)
+		  {
+		    ginsn = bb_get_last_ginsn (current_bb);
+		    scfi_op_add_cfi_remember_state (ginsn);
+		    /* Remove the memorised restore_bb from the list.  */
+		    restore_bbs[j] = NULL;
+		    break;
+		  }
+	    }
+	}
+      i--;
+    }
+
+  /* All .cfi_restore_state pseudo-ops must have a corresponding
+     .cfi_remember_state by now.  */
+  for (j = 0; j < gcfg->num_gbbs; j++)
+    if (restore_bbs[j] != NULL)
+      {
+	ret = 1;
+	break;
+      }
+
+  free (restore_bbs);
+  free (prog_order_bbs);
+
+  return ret;
+}
+
+/* Synthesize DWARF CFI for a function.  */
+
+int
+scfi_synthesize_dw2cfi (symbolS *func, gcfgS *gcfg, gbbS *root_bb)
+{
+  int ret;
+  scfi_stateS *init_state;
+
+  init_state = XCNEW (scfi_stateS);
+  init_state->traceable_p = true;
+
+  /* Traverse the input GCFG and perform forward flow of information.
+     Update the scfi_op(s) per ginsn.  */
+  ret = forward_flow_scfi_state (gcfg, root_bb, init_state);
+  if (ret)
+    {
+      as_warn (_("SCFI: forward pass failed for func '%s'"), S_GET_NAME (func));
+      goto end;
+    }
+
+  ret = backward_flow_scfi_state (func, gcfg);
+  if (ret)
+    {
+      as_warn (_("SCFI: backward pass failed for func '%s'"), S_GET_NAME (func));
+      goto end;
+    }
+
+end:
+  free (init_state);
+  return ret;
+}
+
+static int
+handle_scfi_dot_cfi (ginsnS *ginsn)
+{
+  scfi_opS *op;
+
+  /* Nothing to do.  */
+  if (!ginsn->scfi_ops)
+    return 0;
+
+  op = *ginsn->scfi_ops;
+  if (!op)
+    goto bad;
+
+  while (op)
+    {
+      switch (op->dw2cfi_op)
+	{
+	case DW_CFA_def_cfa_register:
+	  scfi_dot_cfi (DW_CFA_def_cfa_register, op->loc.base, 0, 0, NULL,
+			ginsn->sym);
+	  break;
+	case DW_CFA_def_cfa_offset:
+	  scfi_dot_cfi (DW_CFA_def_cfa_offset, op->loc.base, 0,
+			op->loc.offset, NULL, ginsn->sym);
+	  break;
+	case DW_CFA_def_cfa:
+	  scfi_dot_cfi (DW_CFA_def_cfa, op->loc.base, 0, op->loc.offset,
+			NULL, ginsn->sym);
+	  break;
+	case DW_CFA_offset:
+	  scfi_dot_cfi (DW_CFA_offset, op->reg, 0, op->loc.offset, NULL,
+			ginsn->sym);
+	  break;
+	case DW_CFA_restore:
+	  scfi_dot_cfi (DW_CFA_restore, op->reg, 0, 0, NULL, ginsn->sym);
+	  break;
+	case DW_CFA_remember_state:
+	  scfi_dot_cfi (DW_CFA_remember_state, 0, 0, 0, NULL, ginsn->sym);
+	  break;
+	case DW_CFA_restore_state:
+	  scfi_dot_cfi (DW_CFA_restore_state, 0, 0, 0, NULL, ginsn->sym);
+	  break;
+	case CFI_label:
+	  scfi_dot_cfi (CFI_label, 0, 0, 0, op->op_data->name, ginsn->sym);
+	  break;
+	case CFI_signal_frame:
+	  scfi_dot_cfi (CFI_signal_frame, 0, 0, 0, NULL, ginsn->sym);
+	  break;
+	default:
+	  goto bad;
+	  break;
+	}
+      op = op->next;
+    }
+
+  return 0;
+bad:
+  as_bad (_("SCFI: Invalid DWARF CFI opcode data"));
+  return 1;
+}
+
+/* Emit Synthesized DWARF CFI.  */
+
+int
+scfi_emit_dw2cfi (symbolS *func)
+{
+  struct frch_ginsn_data *frch_gdata;
+  ginsnS* ginsn = NULL;
+
+  frch_gdata = frchain_now->frch_ginsn_data;
+  ginsn = frch_gdata->gins_rootP;
+
+  while (ginsn)
+    {
+      switch (ginsn->type)
+	{
+	  case GINSN_TYPE_SYMBOL:
+	    /* .cfi_startproc and .cfi_endproc pseudo-ops.  */
+	    if (GINSN_F_FUNC_BEGIN_P (ginsn))
+	      {
+		scfi_dot_cfi_startproc (frch_gdata->start_addr);
+		break;
+	      }
+	    else if (GINSN_F_FUNC_END_P (ginsn))
+	      {
+		scfi_dot_cfi_endproc (ginsn->sym);
+		break;
+	      }
+	    /* Fall through.  */
+	  case GINSN_TYPE_ADD:
+	  case GINSN_TYPE_AND:
+	  case GINSN_TYPE_CALL:
+	  case GINSN_TYPE_JUMP:
+	  case GINSN_TYPE_JUMP_COND:
+	  case GINSN_TYPE_MOV:
+	  case GINSN_TYPE_LOAD:
+	  case GINSN_TYPE_PHANTOM:
+	  case GINSN_TYPE_STORE:
+	  case GINSN_TYPE_SUB:
+	  case GINSN_TYPE_OTHER:
+	  case GINSN_TYPE_RETURN:
+
+	    /* For all other SCFI ops, invoke the handler.  */
+	    if (ginsn->scfi_ops)
+	      handle_scfi_dot_cfi (ginsn);
+	    break;
+
+	  default:
+	    /* No other GINSN_TYPE_* expected.  */
+	    as_bad (_("SCFI: bad ginsn for func '%s'"),
+		    S_GET_NAME (func));
+	    break;
+	}
+      ginsn = ginsn->next;
+    }
+  return 0;
+}
+
+#else
+
+int
+scfi_emit_dw2cfi (symbolS *func ATTRIBUTE_UNUSED)
+{
+  as_bad (_("SCFI: unsupported for target"));
+  return 1;
+}
+
+int
+scfi_synthesize_dw2cfi (symbolS *func ATTRIBUTE_UNUSED,
+			gcfgS *gcfg ATTRIBUTE_UNUSED,
+			gbbS *root_bb ATTRIBUTE_UNUSED)
+{
+  as_bad (_("SCFI: unsupported for target"));
+  return 1;
+}
+
+#endif  /* defined (TARGET_USE_SCFI) && defined (TARGET_USE_GINSN).  */
diff --git a/gas/scfi.h b/gas/scfi.h
new file mode 100644
index 00000000000..3eb1bf9ddd7
--- /dev/null
+++ b/gas/scfi.h
@@ -0,0 +1,38 @@ 
+/* scfi.h - Support for synthesizing CFI for asm.
+   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.  */
+
+#ifndef SCFI_H
+#define SCFI_H
+
+#include "as.h"
+#include "ginsn.h"
+
+void scfi_ops_cleanup (scfi_opS **head);
+
+/* Some SCFI ops are added externally when parsing the assembler input.
+   Two examples are CFI_label, and CFI_signal_frame.  */
+void scfi_op_add_cfi_label (ginsnS *ginsn, const char *name);
+void scfi_op_add_signal_frame (ginsnS *ginsn);
+
+int scfi_emit_dw2cfi (symbolS *func);
+
+int scfi_synthesize_dw2cfi (symbolS *func, gcfgS *gcfg, gbbS *root_bb);
+
+#endif /* SCFI_H.  */
diff --git a/gas/scfidw2gen.c b/gas/scfidw2gen.c
index 188699a882f..613ef919a1e 100644
--- a/gas/scfidw2gen.c
+++ b/gas/scfidw2gen.c
@@ -19,6 +19,8 @@ 
    02110-1301, USA.  */
 
 #include "as.h"
+#include "ginsn.h"
+#include "scfi.h"
 #include "dw2gencfi.h"
 #include "subsegs.h"
 #include "scfidw2gen.h"
@@ -87,15 +89,33 @@  dot_scfi_sections (int ignored ATTRIBUTE_UNUSED)
 static void
 scfi_process_cfi_label (void)
 {
-  /* To be implemented. */
-  return;
+  char *name;
+  ginsnS *ginsn;
+
+  name = read_symbol_name ();
+  if (name == NULL)
+    return;
+
+  /* Add a new ginsn.  */
+  ginsn = ginsn_new_phantom (symbol_temp_new_now ());
+  frch_ginsn_data_append (ginsn);
+
+  scfi_op_add_cfi_label (ginsn, name);
+  /* TODO.  */
+  // free (name);
+
+  demand_empty_rest_of_line ();
 }
 
 static void
 scfi_process_cfi_signal_frame (void)
 {
-  /* To be implemented.  */
-  return;
+  ginsnS *ginsn;
+
+  ginsn = ginsn_new_phantom (symbol_temp_new_now ());
+  frch_ginsn_data_append (ginsn);
+
+  scfi_op_add_signal_frame (ginsn);
 }
 
 static void
diff --git a/gas/subsegs.c b/gas/subsegs.c
index ae42b4e76d9..6b9834c8566 100644
--- a/gas/subsegs.c
+++ b/gas/subsegs.c
@@ -131,6 +131,7 @@  subseg_set_rest (segT seg, subsegT subseg)
       newP->frch_frag_now = frag_alloc (&newP->frch_obstack);
       newP->frch_frag_now->fr_type = rs_fill;
       newP->frch_cfi_data = NULL;
+      newP->frch_ginsn_data = NULL;
 
       newP->frch_root = newP->frch_last = newP->frch_frag_now;
 
diff --git a/gas/subsegs.h b/gas/subsegs.h
index ace0657bdfb..c90c5622465 100644
--- a/gas/subsegs.h
+++ b/gas/subsegs.h
@@ -40,6 +40,7 @@ 
 #include "obstack.h"
 
 struct frch_cfi_data;
+struct frch_ginsn_data;
 
 struct frchain			/* control building of a frag chain */
 {				/* FRCH = FRagment CHain control */
@@ -52,6 +53,7 @@  struct frchain			/* control building of a frag chain */
   struct obstack frch_obstack;	/* for objects in this frag chain */
   fragS *frch_frag_now;		/* frag_now for this subsegment */
   struct frch_cfi_data *frch_cfi_data;
+  struct frch_ginsn_data *frch_ginsn_data;
 };
 
 typedef struct frchain frchainS;
diff --git a/gas/symbols.c b/gas/symbols.c
index 45e46ed39b7..b595ffad104 100644
--- a/gas/symbols.c
+++ b/gas/symbols.c
@@ -25,6 +25,7 @@ 
 #include "obstack.h"		/* For "symbols.h" */
 #include "subsegs.h"
 #include "write.h"
+#include "scfi.h"
 
 #include <limits.h>
 #ifndef CHAR_BIT
@@ -709,6 +710,8 @@  colon (/* Just seen "x:" - rattle symbols & frags.  */
 #ifdef obj_frob_label
   obj_frob_label (symbolP);
 #endif
+  if (flag_synth_cfi)
+    ginsn_frob_label (symbolP);
 
   return symbolP;
 }