diff mbox

[4/5,v2] arc: Add disassembler helper

Message ID 20170315151738.28651-1-Anton.Kolesov@synopsys.com
State New
Headers show

Commit Message

Anton Kolesov March 15, 2017, 3:17 p.m. UTC
Changes in v2:

  * Add NEWS entry for "maint print arc arc-instruction" command.
  * Fix grammar in the comment.
  * Add arc_disassemble_info function, which was used in this patch, but was
    declared and defined only in the next patch in a sequence.
  * Merge arch/arc-insn.{c,h} files into arc-tdep.{c,h}
  * Improve formatting of output of "maint print arc arc-instruction" command:
    add whitespaces around the "=" symbol.
  * Use "prepare_for" in test cases.
  * Reformat test sequence as a list of string.

---

Add disassembler helper for GDB, that uses opcodes structure arc_instruction
and adds convenience functions to handle instruction operands.  This interface
solves at least those problems with arc_instruction:

  * Some instructions, like "push_s", have implicit operands which are not
    directly present in arc_instruction.
  * Operands of particular meaning, like branch/jump targets, have various
    locations and meaning depending on type of branch/target.
  * Access to operand value is abstracted into a separate function, so callee
    code shouldn't bother if operand value is an immediate value or in a
    register.

Testcases included in this commit are fairly limited - they test exclusively
branch instructions, something that will be used in software single stepping.
Most of the other parts of this disassembler helper are tested during prologue
analysis testing.

gdb/ChangeLog:

yyyy-mm-dd  Anton Kolesov  <anton.kolesov@synopsys.com>

	* configure.tgt: Add arc-insn.o.
	* arc-tdep.c (arc_delayed_print_insn): Make non-static.
	  (dump_arc_instruction_command): New function.
	  (arc_fprintf_disasm): Likewise.
	  (arc_disassemble_info): Likewise.
	  (arc_insn_get_operand_value): Likewise.
	  (arc_insn_get_operand_value_signed): Likewise.
	  (arc_insn_get_memory_base_reg): Likewise.
	  (arc_insn_get_memory_offset): Likewise.
	  (arc_insn_get_branch_target): Likewise.
	  (arc_insn_dump): Likewise.
	  (arc_insn_get_linear_next_pc): Likewise.
	* arc-tdep.h (arc_delayed_print_insn): Add function declaration.
	  (arc_disassemble_info): Likewise.
	  (arc_insn_get_branch_target): Likewise.
	  (arc_insn_get_linear_next_pc): Likewise.
	* NEWS: Mention new "maint print arc arc-instruction".

gdb/doc/ChangeLog:

yyyy-mm-dd  Anton Kolesov  <anton.kolesov@synopsys.com>

	* gdb.texinfo (Synopsys ARC): Add "maint print arc arc-instruction".

gdb/testsuite/ChangeLog:

yyyy-mm-dd  Anton Kolesov  <anton.kolesov@synopsys.com>

      * gdb.arch/arc-decode-insn.S: New file.
      * gdb.arch/arc-decode-insn.exp: Likewise.
---
 gdb/NEWS                                   |    3 +
 gdb/arc-tdep.c                             |  315 ++++++++-
 gdb/arc-tdep.h                             |   25 +
 gdb/doc/gdb.texinfo                        |    4 +
 gdb/testsuite/gdb.arch/arc-decode-insn.S   | 1002 ++++++++++++++++++++++++++++
 gdb/testsuite/gdb.arch/arc-decode-insn.exp |  132 ++++
 6 files changed, 1480 insertions(+), 1 deletion(-)
 create mode 100644 gdb/testsuite/gdb.arch/arc-decode-insn.S
 create mode 100644 gdb/testsuite/gdb.arch/arc-decode-insn.exp

Comments

Eli Zaretskii March 15, 2017, 3:57 p.m. UTC | #1
> From: Anton Kolesov <Anton.Kolesov@synopsys.com>
> Cc: Anton Kolesov <Anton.Kolesov@synopsys.com>,	Francois Bedard <Francois.Bedard@synopsys.com>
> Date: Wed, 15 Mar 2017 18:17:38 +0300
> 
> Changes in v2:
> 
>   * Add NEWS entry for "maint print arc arc-instruction" command.
>   * Fix grammar in the comment.
>   * Add arc_disassemble_info function, which was used in this patch, but was
>     declared and defined only in the next patch in a sequence.
>   * Merge arch/arc-insn.{c,h} files into arc-tdep.{c,h}
>   * Improve formatting of output of "maint print arc arc-instruction" command:
>     add whitespaces around the "=" symbol.
>   * Use "prepare_for" in test cases.
>   * Reformat test sequence as a list of string.

OK for the documentation parts.

Thanks.
Pedro Alves March 28, 2017, 1:27 p.m. UTC | #2
On 03/15/2017 03:17 PM, Anton Kolesov wrote:
> yyyy-mm-dd  Anton Kolesov  <anton.kolesov@synopsys.com>
> 
> 	* configure.tgt: Add arc-insn.o.
> 	* arc-tdep.c (arc_delayed_print_insn): Make non-static.
> 	  (dump_arc_instruction_command): New function.
> 	  (arc_fprintf_disasm): Likewise.
> 	  (arc_disassemble_info): Likewise.
> 	  (arc_insn_get_operand_value): Likewise.
> 	  (arc_insn_get_operand_value_signed): Likewise.
> 	  (arc_insn_get_memory_base_reg): Likewise.
> 	  (arc_insn_get_memory_offset): Likewise.
> 	  (arc_insn_get_branch_target): Likewise.
> 	  (arc_insn_dump): Likewise.
> 	  (arc_insn_get_linear_next_pc): Likewise.
> 	* arc-tdep.h (arc_delayed_print_insn): Add function declaration.
> 	  (arc_disassemble_info): Likewise.
> 	  (arc_insn_get_branch_target): Likewise.
> 	  (arc_insn_get_linear_next_pc): Likewise.
> 	* NEWS: Mention new "maint print arc arc-instruction".

Indentation is odd above.  Be sure to only have a leading
tab on the lines that start with "(", not tab + 2 spaces.

> +; This testcase is part of GDB, the GNU debugger.
> +
> +; Copyright 2017 Synopsys Inc.
> +

s/Synopsys/FSF/g?  I noticed it in patch 5/5 too.  Didn't
confirm others.  Please double check the whole series.

Otherwise I have no further comments.  LGTM.

Thanks,
Pedro Alves
diff mbox

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index cf58595..754a585 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -94,6 +94,9 @@  show disassembler-options
   The default value is the empty string.  Currently, the only supported
   targets are ARM, PowerPC and S/390.
 
+maint print arc arc-instruction address
+  Print internal disassembler information about instruction at a given address.
+
 *** Changes in GDB 7.12
 
 * GDB and GDBserver now build with a C++ compiler by default.
diff --git a/gdb/arc-tdep.c b/gdb/arc-tdep.c
index 5495f2e..639f7dd 100644
--- a/gdb/arc-tdep.c
+++ b/gdb/arc-tdep.c
@@ -32,6 +32,7 @@ 
 
 /* ARC header files.  */
 #include "opcode/arc.h"
+#include "opcodes/arc-dis.h"
 #include "arc-tdep.h"
 
 /* Standard headers.  */
@@ -116,6 +117,269 @@  static const char *const core_arcompact_register_names[] = {
   "lp_count", "reserved", "limm", "pcl",
 };
 
+/* Returns an unsigned value of OPERAND_NUM in instruction INSN.
+   For relative branch instructions returned value is an offset, not an actual
+   branch target.  */
+
+static ULONGEST
+arc_insn_get_operand_value (const struct arc_instruction &insn,
+			    unsigned int operand_num)
+{
+  switch (insn.operands[operand_num].kind)
+    {
+    case ARC_OPERAND_KIND_LIMM:
+      gdb_assert (insn.limm_p);
+      return insn.limm_value;
+    case ARC_OPERAND_KIND_SHIMM:
+      return insn.operands[operand_num].value;
+    default:
+      /* Value in instruction is a register number.  */
+      struct regcache *regcache = get_current_regcache ();
+      ULONGEST value;
+      regcache_cooked_read_unsigned (regcache,
+				     insn.operands[operand_num].value,
+				     &value);
+      return value;
+    }
+}
+
+/* Like arc_insn_get_operand_value, but returns a signed value.  */
+
+static LONGEST
+arc_insn_get_operand_value_signed (const struct arc_instruction &insn,
+				   unsigned int operand_num)
+{
+  switch (insn.operands[operand_num].kind)
+    {
+    case ARC_OPERAND_KIND_LIMM:
+      gdb_assert (insn.limm_p);
+      /* Convert unsigned raw value to signed one.  This assumes 2's
+	 complement arithmetic, but so is the LONG_MIN value from generic
+	 defs.h and that assumption is true for ARC.  */
+      gdb_static_assert (sizeof (insn.limm_value) == sizeof (int));
+      return (((LONGEST) insn.limm_value) ^ INT_MIN) - INT_MIN;
+    case ARC_OPERAND_KIND_SHIMM:
+      /* Sign conversion has been done by binutils.  */
+      return insn.operands[operand_num].value;
+    default:
+      /* Value in instruction is a register number.  */
+      struct regcache *regcache = get_current_regcache ();
+      LONGEST value;
+      regcache_cooked_read_signed (regcache,
+				   insn.operands[operand_num].value,
+				   &value);
+      return value;
+    }
+}
+
+/* Get register with base address of memory operation.  */
+
+int
+arc_insn_get_memory_base_reg (const struct arc_instruction &insn)
+{
+  /* POP_S and PUSH_S have SP as an implicit argument in a disassembler.  */
+  if (insn.insn_class == PUSH || insn.insn_class == POP)
+    return ARC_SP_REGNUM;
+
+  gdb_assert (insn.insn_class == LOAD || insn.insn_class == STORE);
+
+  /* Other instructions all have at least two operands: operand 0 is data,
+     operand 1 is address.  Operand 2 is offset from address.  However, see
+     comment to arc_instruction.operands - in some cases, third operand may be
+     missing, namely if it is 0.  */
+  gdb_assert (insn.operands_count >= 2);
+  return insn.operands[1].value;
+}
+
+/* Get offset of a memory operation INSN.  */
+
+CORE_ADDR
+arc_insn_get_memory_offset (const struct arc_instruction &insn)
+{
+  /* POP_S and PUSH_S have offset as an implicit argument in a
+     disassembler.  */
+  if (insn.insn_class == POP)
+    return 4;
+  else if (insn.insn_class == PUSH)
+    return -4;
+
+  gdb_assert (insn.insn_class == LOAD || insn.insn_class == STORE);
+
+  /* Other instructions all have at least two operands: operand 0 is data,
+     operand 1 is address.  Operand 2 is offset from address.  However, see
+     comment to arc_instruction.operands - in some cases, third operand may be
+     missing, namely if it is 0.  */
+  if (insn.operands_count < 3)
+    return 0;
+
+  CORE_ADDR value = arc_insn_get_operand_value (insn, 2);
+  /* Handle scaling.  */
+  if (insn.writeback_mode == ARC_WRITEBACK_AS)
+    {
+      /* Byte data size is not valid for AS.  Halfword means shift by 1 bit.
+	 Word and double word means shift by 2 bits.  */
+      gdb_assert (insn.data_size_mode != ARC_SCALING_B);
+      if (insn.data_size_mode == ARC_SCALING_H)
+	value <<= 1;
+      else
+	value <<= 2;
+    }
+  return value;
+}
+
+/* Functions are sorted in the order as they are used in the
+   _initialize_arc_tdep (), which uses the same order as gdbarch.h.  Static
+   functions are defined before the first invocation.  */
+
+CORE_ADDR
+arc_insn_get_branch_target (const struct arc_instruction &insn)
+{
+  gdb_assert (insn.is_control_flow);
+
+  /* BI [c]: PC = nextPC + (c << 2).  */
+  if (insn.insn_class == BI)
+    {
+      ULONGEST reg_value = arc_insn_get_operand_value (insn, 0);
+      return arc_insn_get_linear_next_pc (insn) + (reg_value << 2);
+    }
+  /* BIH [c]: PC = nextPC + (c << 1).  */
+  else if (insn.insn_class == BIH)
+    {
+      ULONGEST reg_value = arc_insn_get_operand_value (insn, 0);
+      return arc_insn_get_linear_next_pc (insn) + (reg_value << 1);
+    }
+  /* JLI and EI.  */
+  /* JLI and EI depend on optional AUX registers.  Not supported right now.  */
+  else if (insn.insn_class == JLI)
+    {
+      fprintf_unfiltered (gdb_stderr,
+			  "JLI_S instruction is not supported by the GDB.");
+      return 0;
+    }
+  else if (insn.insn_class == EI)
+    {
+      fprintf_unfiltered (gdb_stderr,
+			  "EI_S instruction is not supported by the GDB.");
+      return 0;
+    }
+  /* LEAVE_S: PC = BLINK.  */
+  else if (insn.insn_class == LEAVE)
+    {
+      struct regcache *regcache = get_current_regcache ();
+      ULONGEST value;
+      regcache_cooked_read_unsigned (regcache, ARC_BLINK_REGNUM, &value);
+      return value;
+    }
+  /* BBIT0/1, BRcc: PC = currentPC + operand.  */
+  else if (insn.insn_class == BBIT0 || insn.insn_class == BBIT1
+	   || insn.insn_class == BRCC)
+    {
+      /* Most instructions has branch target as their sole argument.  However
+	 conditional brcc/bbit has it as a third operand.  */
+      CORE_ADDR pcrel_addr = arc_insn_get_operand_value (insn, 2);
+
+      /* Offset is relative to the 4-byte aligned address of the current
+	 instruction, hence last two bits should be truncated.  */
+      return pcrel_addr + align_down (insn.address, 4);
+    }
+  /* B, Bcc, BL, BLcc, LP, LPcc: PC = currentPC + operand.  */
+  else if (insn.insn_class == BRANCH || insn.insn_class == LOOP)
+    {
+      CORE_ADDR pcrel_addr = arc_insn_get_operand_value (insn, 0);
+
+      /* Offset is relative to the 4-byte aligned address of the current
+	 instruction, hence last two bits should be truncated.  */
+      return pcrel_addr + align_down (insn.address, 4);
+    }
+  /* J, Jcc, JL, JLcc: PC = operand.  */
+  else if (insn.insn_class == JUMP)
+    {
+      /* All jumps are single-operand.  */
+      return arc_insn_get_operand_value (insn, 0);
+    }
+
+  /* This is some new and unknown instruction.  */
+  gdb_assert_not_reached ("Unknown branch instruction.");
+}
+
+/* Dump INSN into gdb_stdlog.  */
+
+void
+arc_insn_dump (const struct arc_instruction &insn)
+{
+  struct gdbarch *gdbarch = target_gdbarch ();
+
+  arc_print ("Dumping arc_instruction at %s\n",
+	     paddress (gdbarch, insn.address));
+  arc_print ("\tlength = %u\n", insn.length);
+
+  if (!insn.valid)
+    {
+      arc_print ("\tThis is not a valid ARC instruction.\n");
+      return;
+    }
+
+  arc_print ("\tlength_with_limm = %u\n", insn.length + (insn.limm_p ? 4 : 0));
+  arc_print ("\tcc = 0x%x\n", insn.condition_code);
+  arc_print ("\tinsn_class = %u\n", insn.insn_class);
+  arc_print ("\tis_control_flow = %i\n", insn.is_control_flow);
+  arc_print ("\thas_delay_slot = %i\n", insn.has_delay_slot);
+
+  CORE_ADDR next_pc = arc_insn_get_linear_next_pc (insn);
+  arc_print ("\tlinear_next_pc = %s\n", paddress (gdbarch, next_pc));
+
+  if (insn.is_control_flow)
+    {
+      CORE_ADDR t = arc_insn_get_branch_target (insn);
+      arc_print ("\tbranch_target = %s\n", paddress (gdbarch, t));
+    }
+
+  arc_print ("\tlimm_p = %i\n", insn.limm_p);
+  if (insn.limm_p)
+    arc_print ("\tlimm_value = 0x%08x\n", insn.limm_value);
+
+  if (insn.insn_class == STORE || insn.insn_class == LOAD
+      || insn.insn_class == PUSH || insn.insn_class == POP)
+    {
+      arc_print ("\twriteback_mode = %u\n", insn.writeback_mode);
+      arc_print ("\tdata_size_mode = %u\n", insn.data_size_mode);
+      arc_print ("\tmemory_base_register = %s\n",
+		 gdbarch_register_name (gdbarch,
+					arc_insn_get_memory_base_reg (insn)));
+      /* get_memory_offset returns an unsigned CORE_ADDR, but treat it as a
+	 LONGEST for a nicer representation.  */
+      arc_print ("\taddr_offset = %s\n",
+		 plongest (arc_insn_get_memory_offset (insn)));
+    }
+
+  arc_print ("\toperands_count = %u\n", insn.operands_count);
+  for (unsigned int i = 0; i < insn.operands_count; ++i)
+    {
+      int is_reg = (insn.operands[i].kind == ARC_OPERAND_KIND_REG);
+
+      arc_print ("\toperand[%u] = {\n", i);
+      arc_print ("\t\tis_reg = %i\n", is_reg);
+      if (is_reg)
+	arc_print ("\t\tregister = %s\n",
+		   gdbarch_register_name (gdbarch, insn.operands[i].value));
+      /* Don't know if this value is signed or not, so print both
+	 representations.  This tends to look quite ugly, especially for big
+	 numbers.  */
+      arc_print ("\t\tunsigned value = %s\n",
+		 pulongest (arc_insn_get_operand_value (insn, i)));
+      arc_print ("\t\tsigned value = %s\n",
+		 plongest (arc_insn_get_operand_value_signed (insn, i)));
+      arc_print ("\t}\n");
+    }
+}
+
+CORE_ADDR
+arc_insn_get_linear_next_pc (const struct arc_instruction &insn)
+{
+  /* In ARC long immediate is always 4 bytes.  */
+  return (insn.address + insn.length + (insn.limm_p ? 4 : 0));
+}
+
 /* Implement the "write_pc" gdbarch method.
 
    In ARC PC register is a normal register so in most cases setting PC value
@@ -649,6 +913,30 @@  arc_frame_base_address (struct frame_info *this_frame, void **prologue_cache)
   return (CORE_ADDR) get_frame_register_unsigned (this_frame, ARC_FP_REGNUM);
 }
 
+/* Copy of gdb_buffered_insn_length_fprintf from disasm.c.  */
+
+static int ATTRIBUTE_PRINTF (2, 3)
+arc_fprintf_disasm (void *stream, const char *format, ...)
+{
+  return 0;
+}
+
+struct disassemble_info
+arc_disassemble_info (struct gdbarch *gdbarch)
+{
+  struct disassemble_info di;
+  init_disassemble_info (&di, &null_stream, arc_fprintf_disasm);
+  di.arch = gdbarch_bfd_arch_info (gdbarch)->arch;
+  di.mach = gdbarch_bfd_arch_info (gdbarch)->mach;
+  di.endian = gdbarch_byte_order (gdbarch);
+  di.read_memory_func = [](bfd_vma memaddr, gdb_byte *myaddr,
+			   unsigned int len, struct disassemble_info *info)
+    {
+      return target_read_code (memaddr, myaddr, len);
+    };
+  return di;
+}
+
 /* Implement the "skip_prologue" gdbarch method.
 
    Skip the prologue for the function at PC.  This is done by checking from
@@ -701,7 +989,7 @@  arc_skip_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
    that will not print, or `stream` should be different from standard
    gdb_stdlog.  */
 
-static int
+int
 arc_delayed_print_insn (bfd_vma addr, struct disassemble_info *info)
 {
   int (*print_insn) (bfd_vma, struct disassemble_info *);
@@ -1330,6 +1618,26 @@  maintenance_print_arc_command (char *args, int from_tty)
   cmd_show_list (maintenance_print_arc_list, from_tty, "");
 }
 
+/* This command accepts single argument - address of instruction to
+   disassemble.  */
+
+static void
+dump_arc_instruction_command (char *args, int from_tty)
+{
+  struct value *val;
+  if (args != NULL && strlen (args) > 0)
+    val = evaluate_expression (parse_expression (args).get ());
+  else
+    val = access_value_history (0);
+  record_latest_value (val);
+
+  CORE_ADDR address = value_as_address (val);
+  struct arc_instruction insn;
+  struct disassemble_info di = arc_disassemble_info (target_gdbarch ());
+  arc_insn_decode (address, &di, arc_delayed_print_insn, &insn);
+  arc_insn_dump (insn);
+}
+
 /* Suppress warning from -Wmissing-prototypes.  */
 extern initialize_file_ftype _initialize_arc_tdep;
 
@@ -1350,6 +1658,11 @@  _initialize_arc_tdep (void)
 		  &maintenance_print_arc_list, "maintenance print arc ", 0,
 		  &maintenanceprintlist);
 
+  add_cmd ("arc-instruction", class_maintenance,
+	   dump_arc_instruction_command,
+	   _("Dump arc_instruction structure for specified address."),
+	   &maintenance_print_arc_list);
+
   /* Debug internals for ARC GDB.  */
   add_setshow_zinteger_cmd ("arc", class_maintenance,
 			    &arc_debug,
diff --git a/gdb/arc-tdep.h b/gdb/arc-tdep.h
index 326f486..1bf1817 100644
--- a/gdb/arc-tdep.h
+++ b/gdb/arc-tdep.h
@@ -123,4 +123,29 @@  arc_mach_is_arcv2 (struct gdbarch *gdbarch)
   return gdbarch_bfd_arch_info (gdbarch)->mach == bfd_mach_arc_arcv2;
 }
 
+/* Function to access ARC disassembler.  Underlying opcodes disassembler will
+   print an instruction into stream specified in the INFO, so if it is
+   undesired, then this stream should be set to some invisible stream, but it
+   can't be set to an actual NULL value - that would cause a crash.  */
+int arc_delayed_print_insn (bfd_vma addr, struct disassemble_info *info);
+
+/* Return properly initialized disassemble_info for ARC disassembler - it will
+   not print disassembled instructions to stderr.  */
+
+struct disassemble_info arc_disassemble_info (struct gdbarch *gdbarch);
+
+/* Get branch/jump target address for the INSN.  Note that this function
+   returns branch target and doesn't evaluate if this branch is taken or not.
+   For the indirect jumps value depends in register state, hence can change.
+   It is an error to call this function for a non-branch instruction.  */
+
+CORE_ADDR arc_insn_get_branch_target (const struct arc_instruction &insn);
+
+/* Get address of next instruction after INSN, assuming linear execution (no
+   taken branches).  If instruction has a delay slot, then returned value will
+   point at the instruction in delay slot.  That is - "address of instruction +
+   instruction length with LIMM".  */
+
+CORE_ADDR arc_insn_get_linear_next_pc (const struct arc_instruction &insn);
+
 #endif /* ARC_TDEP_H */
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 5cf0f97..344778b 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -22105,6 +22105,10 @@  messages.
 @kindex show debug arc
 Show the level of ARC specific debugging in operation.
 
+@item maint print arc arc-instruction @var{address}
+@kindex maint print arc arc-instruction
+Print internal disassembler information about instruction at a given address.
+
 @end table
 
 @node ARM
diff --git a/gdb/testsuite/gdb.arch/arc-decode-insn.S b/gdb/testsuite/gdb.arch/arc-decode-insn.S
new file mode 100644
index 0000000..030f158
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/arc-decode-insn.S
@@ -0,0 +1,1002 @@ 
+; This testcase is part of GDB, the GNU debugger.
+
+; Copyright 2017 Synopsys Inc.
+
+; This program is free software; you can redistribute it and/or modify
+; it under the terms of the GNU General Public License as published by
+; the Free Software Foundation; either version 3 of the License, or
+; (at your option) any later version.
+;
+; This program is distributed in the hope that it will be useful,
+; but WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+; GNU General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+.section .text
+.global main
+
+#define TEST_J
+#define TEST_JCC
+#define TEST_JL
+#define TEST_JLCC
+#define TEST_B
+#define TEST_BBIT
+#define TEST_BCC
+#define TEST_BI
+#define TEST_BL
+#define TEST_BRCC
+#define TEST_JLI
+#define TEST_LEAVE_S
+#define TEST_LPCC
+
+; JLI-specific stuff
+#ifdef TEST_JLI
+jli_table:
+    .word 0xdeadbeea
+    .word 0xdeadbeea
+jli_target:
+    .word 0xdeadbeea
+    .word 0xdeadbeea
+
+.set jli_offset, 3
+#endif
+main:
+
+; Each test case requires several symbols to be set, that identify expected
+; parameters of this instruction.  Required symbols:
+;   ${test}_start: symbol points to start of the test
+;   ${test}_end: symbol points to the instruction after the jump/branch
+;      instruction.
+;   ${test}_target: branch target address.
+;   ${test}_has_delay_slot: whether instruction has delay slot.
+;   ${test}_cc: condition code numeric value.
+
+    .set r12_value, 0xdead0000
+    .set blink_value, 0xdead0004
+    .set limm_value, 0xdead0008
+    ; Just an integer
+    .set r4_value, 0xdead000c
+    ; Just an integer
+    .set r5_value, 0xdead0010
+    ; offset index for BI [c]
+    .set r7_value, 4
+    .set u6_value, 0x20
+    .set s12_target, 0x100
+
+    mov r12, @r12_value
+    mov r4, @r4_value
+    mov r5, @r5_value
+    mov r7, @r7_value
+    mov blink, @blink_value
+#ifdef TEST_JLI
+    ; jli_base aux regnum = 0x290
+    sr jli_table, [0x290]
+#endif
+
+start_branch_tests:
+
+#ifdef TEST_J
+
+#define TEST_NAME j_c
+    ; j [c]
+    .set j_c_target, @r4_value
+    .set j_c_has_delay_slot,   0
+    .set j_c_cc, 0
+    j_c_start:
+	j [r4]
+    j_c_end:
+
+    ; j [blink]
+    .set j_blink_target, @blink_value
+    .set j_blink_has_delay_slot,   0
+    .set j_blink_cc, 0
+    mov blink, @j_blink_target
+    j_blink_start:
+	j [blink]
+    j_blink_end:
+
+    ; j limm
+    .set j_limm_target, @limm_value
+    .set j_limm_has_delay_slot,   0
+    .set j_limm_cc, 0
+    j_limm_start:
+	j @j_limm_target
+    j_limm_end:
+
+    ; j u6
+    .set j_u6_target, @u6_value
+    .set j_u6_has_delay_slot,   0
+    .set j_u6_cc, 0
+    j_u6_start:
+	j @j_u6_target
+    j_u6_end:
+
+    ; j s12
+    .set j_s12_target, @s12_target
+    .set j_s12_has_delay_slot,   0
+    .set j_s12_cc, 0
+    j_s12_start:
+	j @j_s12_target
+    j_s12_end:
+
+    ; j.d [c]
+    .set j_d_c_target, @r4_value
+    .set j_d_c_has_delay_slot,   1
+    .set j_d_c_cc, 0
+    j_d_c_start:
+	j.d [r4]
+    j_d_c_end:
+	nop_s
+
+    ; j.d [blink]
+    .set j_d_blink_target, @blink_value
+    .set j_d_blink_has_delay_slot,   1
+    .set j_d_blink_cc, 0
+    j_d_blink_start:
+	j.d [blink]
+    j_d_blink_end:
+	nop_s
+
+    ; j.d u6
+    .set j_d_u6_target, @u6_value
+    .set j_d_u6_has_delay_slot,   1
+    .set j_d_u6_cc, 0
+    j_d_u6_start:
+	j.d @j_d_u6_target
+    j_d_u6_end:
+	nop_s
+
+    ; j.d s12
+    .set j_d_s12_target, @s12_target
+    .set j_d_s12_has_delay_slot,   1
+    .set j_d_s12_cc, 0
+    j_d_s12_start:
+	j.d @j_d_s12_target
+    j_d_s12_end:
+	nop_s
+
+    ; j_s [b]
+    .set j_s_b_target, @r12_value
+    .set j_s_b_has_delay_slot,   0
+    .set j_s_b_cc, 0
+    j_s_b_start:
+	j_s [r12]
+    j_s_b_end:
+
+    ; j_s.d [b]
+    .set j_s_d_b_target, @r12_value
+    .set j_s_d_b_has_delay_slot,   1
+    .set j_s_d_b_cc, 0
+    j_s_d_b_start:
+	j_s.d [r12]
+    j_s_d_b_end:
+	nop_s
+
+    ; j_s [blink]
+    .set j_s_blink_target, @blink_value
+    .set j_s_blink_has_delay_slot,   0
+    .set j_s_blink_cc, 0
+    j_s_blink_start:
+	j_s [blink]
+    j_s_blink_end:
+
+    ; j_s.d [blink]
+    .set j_s_d_blink_target, @blink_value
+    .set j_s_d_blink_has_delay_slot,   1
+    .set j_c_cc, 0
+    j_s_d_blink_start:
+	j_s.d [blink]
+    j_s_d_blink_end:
+	nop_s
+#endif /* TEST_J */
+
+#ifdef TEST_JCC
+    ; jcc [c]
+    .set jcc_c_target, @r4_value
+    .set jcc_c_has_delay_slot,   0
+    .set jcc_c_cc, 1
+    jcc_c_start:
+	jeq [r4]
+    jcc_c_end:
+
+    ; jcc [blink]
+    .set jcc_blink_target, @blink_value
+    .set jcc_blink_has_delay_slot,   0
+    .set jcc_blink_cc, 2
+    jcc_blink_start:
+	jnz [blink]
+    jcc_blink_end:
+
+    ; jcc limm
+    .set jcc_limm_target, @limm_value
+    .set jcc_limm_has_delay_slot,   0
+    .set jcc_limm_cc, 9
+    jcc_limm_start:
+	jgt @jcc_limm_target
+    jcc_limm_end:
+
+    ; jcc u6
+    .set jcc_u6_target, @u6_value
+    .set jcc_u6_has_delay_slot,   0
+    .set jcc_u6_cc, 0xA
+    jcc_u6_start:
+	jge @jcc_u6_target
+    jcc_u6_end:
+
+    ; jcc.d [c]
+    .set jcc_d_c_target, @r4_value
+    .set jcc_d_c_has_delay_slot,   1
+    .set jcc_d_c_cc, 0xB
+    jcc_d_c_start:
+	jlt.d [r4]
+    jcc_d_c_end:
+	nop_s
+
+    ; jcc.d [blink]
+    .set jcc_d_blink_target, @blink_value
+    .set jcc_d_blink_has_delay_slot,   1
+    .set jcc_d_blink_cc, 0xC
+    jcc_d_blink_start:
+	jle.d [blink]
+    jcc_d_blink_end:
+	nop_s
+
+    ; jcc.d u6
+    .set jcc_d_u6_target, @u6_value
+    .set jcc_d_u6_has_delay_slot,   1
+    .set jcc_d_u6_cc, 0xE
+    jcc_d_u6_start:
+	jls.d @jcc_d_u6_target
+    jcc_d_u6_end:
+	nop_s
+
+    ; jeq_s [blink]
+    .set jcc_eq_s_blink_target, @blink_value
+    .set jcc_eq_s_blink_has_delay_slot,   0
+    .set jcc_eq_s_blink_cc, 1
+    jcc_eq_s_blink_start:
+	jeq_s [blink]
+    jcc_eq_s_blink_end:
+
+    ; jne_s [blink]
+    .set jcc_ne_s_blink_target, @blink_value
+    .set jcc_ne_s_blink_has_delay_slot,   0
+    .set jcc_ne_s_blink_cc, 2
+    jcc_ne_s_blink_start:
+	jne_s [blink]
+    jcc_ne_s_blink_end:
+#endif /* TEST_JCC */
+
+#ifdef TEST_JL
+    ; jl [c]
+    .set jl_c_target, @r4_value
+    .set jl_c_has_delay_slot,   0
+    .set jl_c_cc, 0
+    jl_c_start:
+	jl [r4]
+    jl_c_end:
+
+    ; jl limm
+    .set jl_limm_target, @limm_value
+    .set jl_limm_has_delay_slot,   0
+    .set jl_limm_cc, 0
+    jl_limm_start:
+	jl @jl_limm_target
+    jl_limm_end:
+
+    ; jl u6
+    .set jl_u6_target, @u6_value
+    .set jl_u6_has_delay_slot,   0
+    .set jl_u6_cc, 0
+    jl_u6_start:
+	jl @jl_u6_target
+    jl_u6_end:
+
+    ; jl s12
+    .set jl_s12_target, @s12_target
+    .set jl_s12_has_delay_slot,   0
+    .set jl_s12_cc, 0
+    jl_s12_start:
+	jl @jl_s12_target
+    jl_s12_end:
+
+    ; jl.d [c]
+    .set jl_d_c_target, @r4_value
+    .set jl_d_c_has_delay_slot,   1
+    .set jl_d_c_cc, 0
+    jl_d_c_start:
+	jl.d [r4]
+    jl_d_c_end:
+	nop_s
+
+    ; jl.d u6
+    .set jl_d_u6_target, @u6_value
+    .set jl_d_u6_has_delay_slot,   1
+    .set jl_d_u6_cc, 0
+    jl_d_u6_start:
+	jl.d @jl_d_u6_target
+    jl_d_u6_end:
+	nop_s
+
+    ; jl.d s12
+    .set jl_d_s12_target, @s12_target
+    .set jl_d_s12_has_delay_slot,   1
+    .set jl_d_s12_cc, 0
+    jl_d_s12_start:
+	jl.d @jl_d_s12_target
+    jl_d_s12_end:
+	nop_s
+
+    ; jl_s [b]
+    .set jl_s_b_target, @r12_value
+    .set jl_s_b_has_delay_slot,   0
+    .set jl_s_b_cc, 0
+    jl_s_b_start:
+	jl_s [r12]
+    jl_s_b_end:
+
+    ; jl_s.d [b]
+    .set jl_s_d_b_target, @r12_value
+    .set jl_s_d_b_has_delay_slot,   1
+    .set jl_s_d_b_cc, 0
+    jl_s_d_b_start:
+	jl_s.d [r12]
+    jl_s_d_b_end:
+	nop_s
+#endif /* TEST_JL */
+
+#ifdef TEST_JLCC
+    ; jlcc [c]
+    .set jlcc_c_target, @r4_value
+    .set jlcc_c_has_delay_slot,   0
+    .set jlcc_c_cc, 1
+    jlcc_c_start:
+	jleq [r4]
+    jlcc_c_end:
+
+    ; jlcc limm
+    .set jlcc_limm_target, @limm_value
+    .set jlcc_limm_has_delay_slot,   0
+    .set jlcc_limm_cc, 0x9
+    jlcc_limm_start:
+	jlgt @jlcc_limm_target
+    jlcc_limm_end:
+
+    ; jlcc u6
+    .set jlcc_u6_target, @u6_value
+    .set jlcc_u6_has_delay_slot,   0
+    .set jlcc_u6_cc, 0xA
+    jlcc_u6_start:
+	jlge @jlcc_u6_target
+    jlcc_u6_end:
+
+    ; jlcc.d [c]
+    .set jlcc_d_c_target, @r4_value
+    .set jlcc_d_c_has_delay_slot,   1
+    .set jlcc_d_c_cc, 0xB
+    jlcc_d_c_start:
+	jllt.d [r4]
+    jlcc_d_c_end:
+	nop_s
+
+    ; jlcc.d u6
+    .set jlcc_d_u6_target, @u6_value
+    .set jlcc_d_u6_has_delay_slot,   1
+    .set jlcc_d_u6_cc, 0xE
+    jlcc_d_u6_start:
+	jlls.d @jlcc_d_u6_target
+    jlcc_d_u6_end:
+	nop_s
+#endif /* TEST_JLCC */
+
+#ifdef TEST_B
+.Lb_target:
+    ; Artifical nop, so that first b will not branch to itself.
+    nop_s
+    ; b s25
+    .set b_s25_target, @.Lb_target
+    .set b_s25_has_delay_slot,   0
+    .set b_s25_cc, 0
+    b_s25_start:
+	b @b_s25_target
+    b_s25_end:
+
+    ; b.d s25
+    .set b_d_s25_target, @.Lb_target
+    .set b_d_s25_has_delay_slot,   1
+    .set b_d_s25_cc, 0
+    b_d_s25_start:
+	b.d @b_d_s25_target
+    b_d_s25_end:
+	nop_s
+
+    ; b_s s10
+    .set b_s_s10_target, @.Lb_target
+    .set b_s_s10_has_delay_slot,   0
+    .set b_s_s10_cc, 0
+    b_s_s10_start:
+	b_s @b_s_s10_target
+    b_s_s10_end:
+#endif /* TEST_B */
+
+#ifdef TEST_BBIT
+
+; Due to specifics of bbit implementation in assembler, only local symbols can
+; be used as a branch targets for bbit and brcc.
+; bbits and brcc don't have condition code set to anything.
+.Lbbit_target:
+    nop_s
+
+    ; bbit0.nt b,c,s9
+    .set bbit0_nt_b_c_s9_target, @.Lbbit_target
+    .set bbit0_nt_b_c_s9_has_delay_slot,   0
+    .set bbit0_nt_b_c_s9_cc, 0
+    bbit0_nt_b_c_s9_start:
+	bbit0.nt r4,r5,@bbit0_nt_b_c_s9_target
+    bbit0_nt_b_c_s9_end:
+
+    ; bbit0.d.nt b,c,s9
+    .set bbit0_d_nt_b_c_s9_target, @.Lbbit_target
+    .set bbit0_d_nt_b_c_s9_has_delay_slot,   1
+    .set bbit0_d_nt_b_c_s9_cc, 0
+    bbit0_d_nt_b_c_s9_start:
+	bbit0.d.nt r4,r5,@.Lbbit_target
+    bbit0_d_nt_b_c_s9_end:
+	nop_s
+
+    ; bbit0.t b,c,s9
+    .set bbit0_t_b_c_s9_target, @.Lbbit_target
+    .set bbit0_t_b_c_s9_has_delay_slot,   0
+    .set bbit0_t_b_c_s9_cc, 0
+    bbit0_t_b_c_s9_start:
+	bbit0.t r4,r5,@.Lbbit_target
+    bbit0_t_b_c_s9_end:
+
+    ; bbit0.d.t b,c,s9
+    .set bbit0_d_t_b_c_s9_target, @.Lbbit_target
+    .set bbit0_d_t_b_c_s9_has_delay_slot,   1
+    .set bbit0_d_t_b_c_s9_cc, 0
+    bbit0_d_t_b_c_s9_start:
+	bbit0.d.t r4,r5,@.Lbbit_target
+    bbit0_d_t_b_c_s9_end:
+	nop_s
+
+    ; bbit0.nt b,u6,s9
+    .set bbit0_nt_b_u6_s9_target, @.Lbbit_target
+    .set bbit0_nt_b_u6_s9_has_delay_slot,   0
+    .set bbit0_nt_b_u6_s9_cc, 0
+    bbit0_nt_b_u6_s9_start:
+	bbit0.nt r4,u6_value,@.Lbbit_target
+    bbit0_nt_b_u6_s9_end:
+
+    ; bbit0.d.nt b,u6,s9
+    .set bbit0_d_nt_b_u6_s9_target, @.Lbbit_target
+    .set bbit0_d_nt_b_u6_s9_has_delay_slot,   1
+    .set bbit0_d_nt_b_u6_s9_cc, 0
+    bbit0_d_nt_b_u6_s9_start:
+	bbit0.d.nt r4,u6_value,@.Lbbit_target
+    bbit0_d_nt_b_u6_s9_end:
+	nop_s
+
+    ; bbit0.nt b,u6,s9
+    .set bbit0_t_b_u6_s9_target, @.Lbbit_target
+    .set bbit0_t_b_u6_s9_has_delay_slot,   0
+    .set bbit0_t_b_u6_s9_cc, 0
+    bbit0_t_b_u6_s9_start:
+	bbit0.t r4,u6_value,@.Lbbit_target
+    bbit0_t_b_u6_s9_end:
+
+    ; bbit0.d.nt b,u6,s9
+    .set bbit0_d_t_b_u6_s9_target, @.Lbbit_target
+    .set bbit0_d_t_b_u6_s9_has_delay_slot,   1
+    .set bbit0_d_t_b_u6_s9_cc, 0
+    bbit0_d_t_b_u6_s9_start:
+	bbit0.d.t r4,u6_value,@.Lbbit_target
+    bbit0_d_t_b_u6_s9_end:
+	nop_s
+
+    ; bbit0.nt b,limm,s9
+    .set bbit0_nt_b_limm_s9_target, @.Lbbit_target
+    .set bbit0_nt_b_limm_s9_has_delay_slot,   0
+    .set bbit0_nt_b_limm_s9_cc, 0
+    bbit0_nt_b_limm_s9_start:
+	bbit0.nt r4,limm_value,@.Lbbit_target
+    bbit0_nt_b_limm_s9_end:
+
+    ; bbit0.t b,limm,s9
+    .set bbit0_t_b_limm_s9_target, @.Lbbit_target
+    .set bbit0_t_b_limm_s9_has_delay_slot,   0
+    .set bbit0_t_b_limm_s9_cc, 0
+    bbit0_t_b_limm_s9_start:
+	bbit0.t r4,limm_value,@.Lbbit_target
+    bbit0_t_b_limm_s9_end:
+
+    ; bbit0.nt limm,c,s9
+    .set bbit0_nt_limm_c_s9_target, @.Lbbit_target
+    .set bbit0_nt_limm_c_s9_has_delay_slot,   0
+    .set bbit0_nt_limm_c_s9_cc, 0
+    bbit0_nt_limm_c_s9_start:
+	bbit0.nt limm_value,r4,@.Lbbit_target
+    bbit0_nt_limm_c_s9_end:
+
+    ; bbit0.t limm,c,s9
+    .set bbit0_t_limm_c_s9_target, @.Lbbit_target
+    .set bbit0_t_limm_c_s9_has_delay_slot,   0
+    .set bbit0_t_limm_c_s9_cc, 0
+    bbit0_t_limm_c_s9_start:
+	bbit0.t limm_value,r4,@.Lbbit_target
+    bbit0_t_limm_c_s9_end:
+
+    ; bbit0.nt limm,u6,s9
+    .set bbit0_nt_limm_u6_s9_target, @.Lbbit_target
+    .set bbit0_nt_limm_u6_s9_has_delay_slot,   0
+    .set bbit0_nt_limm_u6_s9_cc, 0
+    bbit0_nt_limm_u6_s9_start:
+	bbit0.nt limm_value,u6_value,@.Lbbit_target
+    bbit0_nt_limm_u6_s9_end:
+
+    ; bbit0.t limm,u6,s9
+    .set bbit0_t_limm_u6_s9_target, @.Lbbit_target
+    .set bbit0_t_limm_u6_s9_has_delay_slot,   0
+    .set bbit0_t_limm_u6_s9_cc, 0
+    bbit0_t_limm_u6_s9_start:
+	bbit0.t limm_value,u6_value,@.Lbbit_target
+    bbit0_t_limm_u6_s9_end:
+
+    ; bbit1.nt b,c,s9
+    .set bbit1_nt_b_c_s9_target, @.Lbbit_target
+    .set bbit1_nt_b_c_s9_has_delay_slot,   0
+    .set bbit1_nt_b_c_s9_cc, 0
+    bbit1_nt_b_c_s9_start:
+	bbit1.nt r4,r5,@.Lbbit_target
+    bbit1_nt_b_c_s9_end:
+
+    ; bbit1.d.nt b,c,s9
+    .set bbit1_d_nt_b_c_s9_target, @.Lbbit_target
+    .set bbit1_d_nt_b_c_s9_has_delay_slot,   1
+    .set bbit1_d_nt_b_c_s9_cc, 0
+    bbit1_d_nt_b_c_s9_start:
+	bbit1.d.nt r4,r5,@.Lbbit_target
+    bbit1_d_nt_b_c_s9_end:
+	nop_s
+
+    ; bbit1.t b,c,s9
+    .set bbit1_t_b_c_s9_target, @.Lbbit_target
+    .set bbit1_t_b_c_s9_has_delay_slot,   0
+    .set bbit1_t_b_c_s9_cc, 0
+    bbit1_t_b_c_s9_start:
+	bbit1.t r4,r5,@.Lbbit_target
+    bbit1_t_b_c_s9_end:
+
+    ; bbit1.d.t b,c,s9
+    .set bbit1_d_t_b_c_s9_target, @.Lbbit_target
+    .set bbit1_d_t_b_c_s9_has_delay_slot,   1
+    .set bbit1_d_t_b_c_s9_cc, 0
+    bbit1_d_t_b_c_s9_start:
+	bbit1.d.t r4,r5,@.Lbbit_target
+    bbit1_d_t_b_c_s9_end:
+	nop_s
+
+    ; bbit1.nt b,u6,s9
+    .set bbit1_nt_b_u6_s9_target, @.Lbbit_target
+    .set bbit1_nt_b_u6_s9_has_delay_slot,   0
+    .set bbit1_nt_b_u6_s9_cc, 0
+    bbit1_nt_b_u6_s9_start:
+	bbit1.nt r4,u6_value,@.Lbbit_target
+    bbit1_nt_b_u6_s9_end:
+
+    ; bbit1.d.nt b,u6,s9
+    .set bbit1_d_nt_b_u6_s9_target, @.Lbbit_target
+    .set bbit1_d_nt_b_u6_s9_has_delay_slot,   1
+    .set bbit1_d_nt_b_u6_s9_cc, 0
+    bbit1_d_nt_b_u6_s9_start:
+	bbit1.d.nt r4,u6_value,@.Lbbit_target
+    bbit1_d_nt_b_u6_s9_end:
+	nop_s
+
+    ; bbit1.nt b,u6,s9
+    .set bbit1_t_b_u6_s9_target, @.Lbbit_target
+    .set bbit1_t_b_u6_s9_has_delay_slot,   0
+    .set bbit1_t_b_u6_s9_cc, 0
+    bbit1_t_b_u6_s9_start:
+	bbit1.t r4,u6_value,@.Lbbit_target
+    bbit1_t_b_u6_s9_end:
+
+    ; bbit1.d.nt b,u6,s9
+    .set bbit1_d_t_b_u6_s9_target, @.Lbbit_target
+    .set bbit1_d_t_b_u6_s9_has_delay_slot,   1
+    .set bbit1_d_t_b_u6_s9_cc, 0
+    bbit1_d_t_b_u6_s9_start:
+	bbit1.d.t r4,u6_value,@.Lbbit_target
+    bbit1_d_t_b_u6_s9_end:
+	nop_s
+
+    ; bbit1.nt b,limm,s9
+    .set bbit1_nt_b_limm_s9_target, @.Lbbit_target
+    .set bbit1_nt_b_limm_s9_has_delay_slot,   0
+    .set bbit1_nt_b_limm_s9_cc, 0
+    bbit1_nt_b_limm_s9_start:
+	bbit1.nt r4,limm_value,@.Lbbit_target
+    bbit1_nt_b_limm_s9_end:
+
+    ; bbit1.t b,limm,s9
+    .set bbit1_t_b_limm_s9_target, @.Lbbit_target
+    .set bbit1_t_b_limm_s9_has_delay_slot,   0
+    .set bbit1_t_b_limm_s9_cc, 0
+    bbit1_t_b_limm_s9_start:
+	bbit1.t r4,limm_value,@.Lbbit_target
+    bbit1_t_b_limm_s9_end:
+
+    ; bbit1.nt limm,c,s9
+    .set bbit1_nt_limm_c_s9_target, @.Lbbit_target
+    .set bbit1_nt_limm_c_s9_has_delay_slot,   0
+    .set bbit1_nt_limm_c_s9_cc, 0
+    bbit1_nt_limm_c_s9_start:
+	bbit1.nt limm_value,r4,@.Lbbit_target
+    bbit1_nt_limm_c_s9_end:
+
+    ; bbit1.t limm,c,s9
+    .set bbit1_t_limm_c_s9_target, @.Lbbit_target
+    .set bbit1_t_limm_c_s9_has_delay_slot,   0
+    .set bbit1_t_limm_c_s9_cc, 0
+    bbit1_t_limm_c_s9_start:
+	bbit1.t limm_value,r4,@.Lbbit_target
+    bbit1_t_limm_c_s9_end:
+
+    ; bbit1.nt limm,u6,s9
+    .set bbit1_nt_limm_u6_s9_target, @.Lbbit_target
+    .set bbit1_nt_limm_u6_s9_has_delay_slot,   0
+    .set bbit1_nt_limm_u6_s9_cc, 0
+    bbit1_nt_limm_u6_s9_start:
+	bbit1.nt limm_value,u6_value,@.Lbbit_target
+    bbit1_nt_limm_u6_s9_end:
+
+    ; bbit1.t limm,u6,s9
+    .set bbit1_t_limm_u6_s9_target, @.Lbbit_target
+    .set bbit1_t_limm_u6_s9_has_delay_slot,   0
+    .set bbit1_t_limm_u6_s9_cc, 0
+    bbit1_t_limm_u6_s9_start:
+	bbit1.t limm_value,u6_value,@.Lbbit_target
+    bbit1_t_limm_u6_s9_end:
+#endif /* TEST_BBIT */
+
+#ifdef TEST_BCC
+.Lbcc_target:
+    ; bcc s21
+    .set bcc_s21_target, @.Lbcc_target
+    .set bcc_s21_has_delay_slot,   0
+    .set bcc_s21_cc, 1
+    bcc_s21_start:
+	; beq @bcc_s21_target
+	beq @.Lbcc_target
+    bcc_s21_end:
+
+    ; bcc.d s21
+    .set bcc_d_s21_target, @.Lbcc_target
+    .set bcc_d_s21_has_delay_slot,   1
+    .set bcc_d_s21_cc, 1
+    bcc_d_s21_start:
+	beq.d @bcc_d_s21_target
+    bcc_d_s21_end:
+	nop_s
+
+.Lbcc_s_target:
+    ; beq_s s10
+    .set beq_s_s10_target, @.Lbcc_s_target
+    .set beq_s_s10_has_delay_slot,   0
+    .set beq_s_s10_cc, 1
+    beq_s_s10_start:
+	# beq_s.d @beq_s_s10_target
+	beq_s @.Lbcc_s_target
+    beq_s_s10_end:
+
+    ; bne_s s10
+    .set bne_s_s10_target, @.Lbcc_s_target
+    .set bne_s_s10_has_delay_slot,   0
+    .set bne_s_s10_cc, 2
+    bne_s_s10_start:
+	bne_s @bne_s_s10_target
+    bne_s_s10_end:
+
+    ; bgt_s s7
+    .set bgt_s_s7_target, @.Lbcc_s_target
+    .set bgt_s_s7_has_delay_slot,   0
+    .set bgt_s_s7_cc, 0x9
+    bgt_s_s7_start:
+	bgt_s @bgt_s_s7_target
+    bgt_s_s7_end:
+
+    ; bge_s s7
+    .set bge_s_s7_target, @.Lbcc_s_target
+    .set bge_s_s7_has_delay_slot,   0
+    .set bge_s_s7_cc, 0xA
+    bge_s_s7_start:
+	bge_s @bge_s_s7_target
+    bge_s_s7_end:
+
+    ; blt_s s7
+    .set blt_s_s7_target, @.Lbcc_s_target
+    .set blt_s_s7_has_delay_slot,   0
+    .set blt_s_s7_cc, 0xB
+    blt_s_s7_start:
+	blt_s @blt_s_s7_target
+    blt_s_s7_end:
+
+    ; ble_s s7
+    .set ble_s_s7_target, @.Lbcc_s_target
+    .set ble_s_s7_has_delay_slot,   0
+    .set ble_s_s7_cc, 0xC
+    ble_s_s7_start:
+	ble_s @ble_s_s7_target
+    ble_s_s7_end:
+
+    ; bhi_s s7
+    .set bhi_s_s7_target, @.Lbcc_s_target
+    .set bhi_s_s7_has_delay_slot,   0
+    .set bhi_s_s7_cc, 0xD
+    bhi_s_s7_start:
+	bhi_s @bhi_s_s7_target
+    bhi_s_s7_end:
+
+    ; bhs_s s7
+    .set bhs_s_s7_target, @.Lbcc_s_target
+    .set bhs_s_s7_has_delay_slot,   0
+    .set bhs_s_s7_cc, 0x6
+    bhs_s_s7_start:
+	bhs_s @bhs_s_s7_target
+    bhs_s_s7_end:
+
+    ; blo_s s7
+    .set blo_s_s7_target, @.Lbcc_s_target
+    .set blo_s_s7_has_delay_slot,   0
+    .set blo_s_s7_cc, 0x5
+    blo_s_s7_start:
+	blo_s @blo_s_s7_target
+    blo_s_s7_end:
+
+    ; bls_s s7
+    .set bls_s_s7_target, @.Lbcc_s_target
+    .set bls_s_s7_has_delay_slot,   0
+    .set bls_s_s7_cc, 0xE
+    bls_s_s7_start:
+	bls_s @bls_s_s7_target
+    bls_s_s7_end:
+#endif /* TEST_BCC */
+
+#ifdef TEST_BI
+    ; bi [c]
+    .set bi_c_target, @bi_c_end + (@r7_value << 2)
+    .set bi_c_has_delay_slot,   0
+    .set bi_c_cc, 0
+    bi_c_start:
+	bi [r7]
+    bi_c_end:
+
+    ; bih [c]
+    .set bih_c_target, @bih_c_end + (@r7_value << 1)
+    .set bih_c_has_delay_slot,   0
+    .set bih_c_cc, 0
+    bih_c_start:
+	bih [r7]
+    bih_c_end:
+#endif /* TEST_BI */
+
+#ifdef TEST_BL
+.Lbl_target:
+    ; bl s25
+    .set bl_s25_target, @.Lbl_target
+    .set bl_s25_has_delay_slot,   0
+    .set bl_s25_cc, 0
+    bl_s25_start:
+	bl @bl_s25_target
+    bl_s25_end:
+
+    ; bl.d s25
+    .set bl_d_s25_target, @.Lbl_target
+    .set bl_d_s25_has_delay_slot,   1
+    .set bl_d_s25_cc, 0
+    bl_d_s25_start:
+	bl.d @bl_d_s25_target
+    bl_d_s25_end:
+	nop_s
+
+    ; bl_s s13
+    .set bl_s_s13_target, @.Lbl_target
+    .set bl_s_s13_has_delay_slot,   0
+    .set bl_s_s13_cc, 0
+    bl_s_s13_start:
+	bl_s @bl_s_s13_target
+    bl_s_s13_end:
+
+    ; blcc s21
+    .set blcc_s21_target, @.Lbl_target
+    .set blcc_s21_has_delay_slot,   0
+    .set blcc_s21_cc, 1
+    blcc_s21_start:
+	bleq @blcc_s21_target
+    blcc_s21_end:
+
+    ; blcc.d s21
+    .set blcc_d_s21_target, @.Lbl_target
+    .set blcc_d_s21_has_delay_slot,   1
+    .set blcc_d_s21_cc, 2
+    blcc_d_s21_start:
+	blnz.d @blcc_d_s21_target
+    blcc_d_s21_end:
+	nop_s
+#endif /* TEST_BL */
+
+#ifdef TEST_BRCC
+.Lbrcc_target:
+    ; breq.nt b,c,s9
+    .set breq_nt_b_c_s9_target, @.Lbrcc_target
+    .set breq_nt_b_c_s9_has_delay_slot,   0
+    .set breq_nt_b_c_s9_cc, 1
+    breq_nt_b_c_s9_start:
+	breq.nt r4,r5,@.Lbrcc_target
+    breq_nt_b_c_s9_end:
+
+    ; breq.d.nt b,c,s9
+    .set breq_d_nt_b_c_s9_target, @.Lbrcc_target
+    .set breq_d_nt_b_c_s9_has_delay_slot,   1
+    .set breq_d_nt_b_c_s9_cc, 1
+    breq_d_nt_b_c_s9_start:
+	breq.d.nt r4,r5,@.Lbrcc_target
+    breq_d_nt_b_c_s9_end:
+	nop_s
+
+    ; breq.t b,c,s9
+    .set breq_t_b_c_s9_target, @.Lbrcc_target
+    .set breq_t_b_c_s9_has_delay_slot,   0
+    .set breq_t_b_c_s9_cc, 1
+    breq_t_b_c_s9_start:
+	breq.t r4,r5,@.Lbrcc_target
+    breq_t_b_c_s9_end:
+
+    ; breq.d.t b,c,s9
+    .set breq_d_t_b_c_s9_target, @.Lbrcc_target
+    .set breq_d_t_b_c_s9_has_delay_slot,   1
+    .set breq_d_t_b_c_s9_cc, 1
+    breq_d_t_b_c_s9_start:
+	breq.d.t r4,r5,@.Lbrcc_target
+    breq_d_t_b_c_s9_end:
+	nop_s
+
+    ; breq.nt b,u6,s9
+    .set breq_nt_b_u6_s9_target, @.Lbrcc_target
+    .set breq_nt_b_u6_s9_has_delay_slot,   0
+    .set breq_nt_b_u6_s9_cc, 1
+    breq_nt_b_u6_s9_start:
+	breq.nt r4,u6_value,@.Lbrcc_target
+    breq_nt_b_u6_s9_end:
+
+    ; breq.d.nt b,u6,s9
+    .set breq_d_nt_b_u6_s9_target, @.Lbrcc_target
+    .set breq_d_nt_b_u6_s9_has_delay_slot,   1
+    .set breq_d_nt_b_u6_s9_cc, 1
+    breq_d_nt_b_u6_s9_start:
+	breq.d.nt r4,u6_value,@.Lbrcc_target
+    breq_d_nt_b_u6_s9_end:
+	nop_s
+
+    ; breq.nt b,u6,s9
+    .set breq_t_b_u6_s9_target, @.Lbrcc_target
+    .set breq_t_b_u6_s9_has_delay_slot,   0
+    .set breq_t_b_u6_s9_cc, 1
+    breq_t_b_u6_s9_start:
+	breq.t r4,u6_value,@.Lbrcc_target
+    breq_t_b_u6_s9_end:
+
+    ; breq.d.nt b,u6,s9
+    .set breq_d_t_b_u6_s9_target, @.Lbrcc_target
+    .set breq_d_t_b_u6_s9_has_delay_slot,   1
+    .set breq_d_t_b_u6_s9_cc, 1
+    breq_d_t_b_u6_s9_start:
+	breq.d.t r4,u6_value,@.Lbrcc_target
+    breq_d_t_b_u6_s9_end:
+	nop_s
+
+    ; breq.nt b,limm,s9
+    .set breq_nt_b_limm_s9_target, @.Lbrcc_target
+    .set breq_nt_b_limm_s9_has_delay_slot,   0
+    .set breq_nt_b_limm_s9_cc, 1
+    breq_nt_b_limm_s9_start:
+	breq.nt r4,limm_value,@.Lbrcc_target
+    breq_nt_b_limm_s9_end:
+
+    ; breq.t b,limm,s9
+    .set breq_t_b_limm_s9_target, @.Lbrcc_target
+    .set breq_t_b_limm_s9_has_delay_slot,   0
+    .set breq_t_b_limm_s9_cc, 1
+    breq_t_b_limm_s9_start:
+	breq.t r4,limm_value,@.Lbrcc_target
+    breq_t_b_limm_s9_end:
+
+    ; breq.nt limm,c,s9
+    .set breq_nt_limm_c_s9_target, @.Lbrcc_target
+    .set breq_nt_limm_c_s9_has_delay_slot,   0
+    .set breq_nt_limm_c_s9_cc, 1
+    breq_nt_limm_c_s9_start:
+	breq.nt limm_value,r4,@.Lbrcc_target
+    breq_nt_limm_c_s9_end:
+
+    ; breq.t limm,c,s9
+    .set breq_t_limm_c_s9_target, @.Lbrcc_target
+    .set breq_t_limm_c_s9_has_delay_slot,   0
+    .set breq_t_limm_c_s9_cc, 1
+    breq_t_limm_c_s9_start:
+	breq.t limm_value,r4,@.Lbrcc_target
+    breq_t_limm_c_s9_end:
+
+    ; breq.nt limm,u6,s9
+    .set breq_nt_limm_u6_s9_target, @.Lbrcc_target
+    .set breq_nt_limm_u6_s9_has_delay_slot,   0
+    .set breq_nt_limm_u6_s9_cc, 1
+    breq_nt_limm_u6_s9_start:
+	breq.nt limm_value,u6_value,@.Lbrcc_target
+    breq_nt_limm_u6_s9_end:
+
+    ; breq.t limm,u6,s9
+    .set breq_t_limm_u6_s9_target, @.Lbrcc_target
+    .set breq_t_limm_u6_s9_has_delay_slot,   0
+    .set breq_t_limm_u6_s9_cc, 1
+    breq_t_limm_u6_s9_start:
+	breq.t limm_value,u6_value,@.Lbrcc_target
+    breq_t_limm_u6_s9_end:
+
+    ; brne_s b,0,s8
+    .set brne_s_b_0_s8_target, @.Lbrcc_target
+    .set brne_s_b_0_s8_has_delay_slot,   0
+    .set brne_s_b_0_s8_cc, 1
+    brne_s_b_0_s8_start:
+	brne r12,0,@.Lbrcc_target
+    brne_s_b_0_s8_end:
+
+    ; breq_s b,0,s8
+    .set breq_s_b_0_s8_target, @.Lbrcc_target
+    .set breq_s_b_0_s8_has_delay_slot,   0
+    .set breq_s_b_0_s8_cc, 1
+    breq_s_b_0_s8_start:
+	breq r12,0,@.Lbrcc_target
+    breq_s_b_0_s8_end:
+#endif /* TEST_BRCC */
+
+#ifdef TEST_JLI
+    ; jli_s u10
+    .set jli_s_u10_target, @jli_target
+    .set jli_s_u10_has_delay_slot,   0
+    .set jli_s_u10_cc, 0
+    jli_s_u10_start:
+	jli_s jli_offset
+    jli_s_u10_end:
+#endif
+
+#ifdef TEST_LEAVE_S
+    ; leave_s
+    .set leave_s_target, @blink_value
+    .set leave_s_has_delay_slot,   0
+    .set leave_s_cc, 0
+    leave_s_start:
+	; leave_s [r13-gp,fp,blink,pcl]
+	leave_s (14 + 16 + 32 + 64)
+    leave_s_end:
+#endif
+
+#ifdef TEST_LPCC
+    ; lpcc
+    .set lpcc_u7_target, @.Llpcc_end
+    .set lpcc_u7_has_delay_slot,   0
+    .set lpcc_u7_cc, 1
+    lpcc_u7_start:
+	lpeq @lpcc_u7_target
+    lpcc_u7_end:
+	nop
+	nop
+.Llpcc_end:
+#endif
+
+.Lend:
+
diff --git a/gdb/testsuite/gdb.arch/arc-decode-insn.exp b/gdb/testsuite/gdb.arch/arc-decode-insn.exp
new file mode 100644
index 0000000..516be28
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/arc-decode-insn.exp
@@ -0,0 +1,132 @@ 
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2017 Synopsys Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+# These tests provides certain degree of testing for arc_insn functions,
+# however it is not a comprehensive testsuite that would go through all
+# possible ARC instructions - instead this particular test is focused on branch
+# instructions and whether branch targets are evaluated properly.  Most of the
+# non-branch aspects of instruction decoder are used during prologue analysis,
+# so are indirictly tested there.
+
+# To maintain separation of test data and test logic, all of the information
+# about instructions, like if it has delay slot, condition code, branch target
+# address, is all specified in the test assembly file as a symbols, while this
+# test case reads those symbols to learn which values are right, then compares
+# values coming from decoder with those found in symbols.  More information
+# about requirements to actual test cases can be found in corresponding
+# assembly file of this test case (arc-decode-insn.S).
+
+if {![istarget "arc*-*-*"]} then {
+    verbose "Skipping ARC decoder test."
+    return
+}
+
+standard_testfile .S
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
+    return -1
+}
+
+if ![runto_main] {
+    fail "Can't run to main"
+    return 0
+}
+
+# Helper function that reads properties of instruction from the ELF file via
+# its symbols and then confirms that decoder output aligns to the expected
+# values.
+proc test_branch_insn { test_name } {
+
+    # Make messages for failed cases more clear, by using hex in them.
+    set pc [get_hexadecimal_valueof &${test_name}_start -1]
+
+    # Calculate instruction length, based on ${test_name}_end symbol.
+    set end_pc [get_hexadecimal_valueof &${test_name}_end -1]
+    set length [expr $end_pc - $pc]
+
+    set target_address [get_hexadecimal_valueof &${test_name}_target -1]
+
+    # Figure out if there is a delay slot, using symbol
+    # ${test_name}_has_delay_slot.  Note that it should be read via &,
+    # otherwise it would try to print value at the address specified in
+    # ${test_name}_has_delay_slot, while a symbol value itself is required.
+    if { 0 == [get_integer_valueof &${test_name}_has_delay_slot 0] } {
+	set has_delay_slot 0
+    } else {
+	set has_delay_slot 1
+    }
+
+    set cc [get_hexadecimal_valueof &${test_name}_cc 0]
+
+    # Can't use {} to create a list of items, because variables will not be
+    # evaluated inside the {}.
+    gdb_test_sequence "mt print arc arc-instruction $pc" "" [list \
+	"length_with_limm = $length" \
+	"cc = $cc" \
+	"is_control_flow = 1" \
+	"has_delay_slot = $has_delay_slot" \
+	"branch_target = $target_address"]
+}
+
+set branch_test_list { }
+
+# Add items in the same groups as they can be enabled/disabled in assembly
+# file.
+lappend branch_test_list \
+    j_c j_blink j_limm j_u6 j_s12 j_d_c j_d_blink j_d_u6
+lappend branch_test_list \
+    jcc_c jcc_blink jcc_limm jcc_u6 jcc_d_c jcc_d_blink jcc_d_u6 jcc_eq_s_blink \
+    jcc_ne_s_blink
+lappend branch_test_list \
+    jl_c jl_limm jl_u6 jl_s12 jl_d_c jl_d_u6 jl_d_s12 jl_s_b jl_s_d_b
+lappend branch_test_list \
+    jlcc_c jlcc_limm jlcc_u6 jlcc_d_c jlcc_d_u6
+lappend branch_test_list \
+    b_s25 b_d_s25 b_s_s10
+lappend branch_test_list \
+    bbit0_nt_b_c_s9 bbit0_d_nt_b_c_s9 bbit0_t_b_c_s9 bbit0_d_t_b_c_s9 \
+    bbit0_nt_b_u6_s9 bbit0_d_nt_b_u6_s9 bbit0_t_b_u6_s9 bbit0_d_t_b_u6_s9 \
+    bbit0_nt_b_limm_s9 bbit0_t_b_limm_s9 bbit0_nt_limm_c_s9 bbit0_t_limm_c_s9 \
+    bbit0_nt_limm_u6_s9 bbit0_t_limm_u6_s9 \
+    bbit1_nt_b_c_s9 bbit1_d_nt_b_c_s9 bbit1_t_b_c_s9 bbit1_d_t_b_c_s9 \
+    bbit1_nt_b_u6_s9 bbit1_d_nt_b_u6_s9 bbit1_t_b_u6_s9 bbit1_d_t_b_u6_s9 \
+    bbit1_nt_b_limm_s9 bbit1_t_b_limm_s9 bbit1_nt_limm_c_s9 bbit1_t_limm_c_s9 \
+    bbit1_nt_limm_u6_s9 bbit1_t_limm_u6_s9
+lappend branch_test_list \
+    bcc_s21 bcc_d_s21 \
+    beq_s_s10 bne_s_s10 bgt_s_s7 bge_s_s7 blt_s_s7 ble_s_s7 bhi_s_s7 bhs_s_s7 \
+    blo_s_s7 bls_s_s7
+lappend branch_test_list \
+    bi_c bih_c
+lappend branch_test_list \
+    bl_s25 bl_d_s25 bl_s_s13 \
+    blcc_s21 blcc_d_s21
+lappend branch_test_list \
+     breq_nt_b_c_s9 breq_d_nt_b_c_s9 breq_t_b_c_s9 breq_d_t_b_c_s9 \
+     breq_nt_b_u6_s9 breq_d_nt_b_u6_s9 breq_t_b_u6_s9 breq_d_t_b_u6_s9 \
+     breq_nt_b_limm_s9 breq_t_b_limm_s9 breq_nt_limm_c_s9 breq_t_limm_c_s9 \
+     breq_nt_limm_u6_s9 breq_t_limm_u6_s9
+# lappend branch_test_list jli_s_u10
+lappend branch_test_list leave_s
+lappend branch_test_list lpcc_u7
+
+runto start_branch_tests
+foreach test $branch_test_list {
+    test_branch_insn $test
+}
+