riscvarchive / riscv-binutils-gdb

RISC-V backports for binutils-gdb. Development is done upstream at the FSF.
GNU General Public License v2.0
148 stars 233 forks source link

right way to compress branching instuction (decbnez from ZCE extension) #266

Open linsinan1995 opened 2 years ago

linsinan1995 commented 2 years ago

Hi all,

I am currently trying to compress a cond branch instruction decbnez to c.decbnez in zce extension. However, it seems a bit different from the current way to replace a instruction with c.* one in riscv_ip, since c.decbnez can only do backward jump (so we need to get the offset to determine if we can replace decbnez insn), and offset field is a nzuimm instead of a signed immediate in decbnez.

My current solution is to manually calculate the offset between the branch instruction and label location, and it seems to succeed to output the result.

zceb.s zceb.s zceb.o zceb.o a.out a.out

also, do not compress if branch to .L3 (what we expect) a.out a.out

code snippet:

@@ -1250,6 +1278,52 @@ riscv_apply_const_reloc (bfd_reloc_code_real_type reloc_type, bfd_vma value)
     }
 }

+/* Extra compression routine for Code Size Reduction Extension */
+void
+zce_compress_pass (struct riscv_cl_insn *ip, expressionS *address_expr,
+        bfd_reloc_code_real_type *reloc_type)
+{
+  int rd;
+  long label_loc, insn_loc, offset;
+
+  /* only deal with decbnez to c.debnze currently */
+  if (*reloc_type != BFD_RELOC_RISCV_DECBNEZ)
+    return;
+
+  /* check if
+    addressing field
+    1. addressing expr is a label
+    2. the label is located above the inst (if it is defined)
+    3. the length of offset to label is valid
+    rd field
+    4. regno of rd ranges from 8 to 15 
+  */
+  if (address_expr->X_add_symbol
+     && S_IS_DEFINED (address_expr->X_add_symbol))
+  {
+    label_loc = S_GET_VALUE (address_expr->X_add_symbol);
+    insn_loc = frag_more (0) - frag_now->fr_literal;
+    offset = insn_loc - label_loc;
+
+    /* extract rd regno */
+   rd = (ip->insn_opcode >> OP_SH_RD) & OP_MASK_RD;
+    /* offset in c.decbnez should be positive */
+    if (offset == 0)
+      return;
+    /* check if the offset length is valid */
+    if (VALID_ZCE_C_DECBNEZ_IMM (offset)
+     && (rd >= 8 && rd <= 15))
+    {
+      /* new encoding for c.decbnez */
+      ip->insn_opcode = MATCH_C_DECBNEZ \
+       | ((rd-8) << OP_SH_CRS1S) \
+       | ENCODE_ZCE_C_DECBNEZ_SCALE (EXTRACT_ZCE_DECBNEZ_SCALE (ip->insn_opcode)) \
+       | ENCODE_ZCE_C_DECBNEZ_IMM (offset);
+      *reloc_type = BFD_RELOC_RISCV_C_DECBNEZ;
+    }
+  }
+}
+

@@ -2760,7 +2946,14 @@ md_assemble (char *str)
   if (insn.insn_mo->pinfo == INSN_MACRO)
     macro (&insn, &imm_expr, &imm_reloc);
   else
+  {
+    if (insn.insn_mo->insn_class == INSN_CLASS_ZCEE \
+     || insn.insn_mo->insn_class == INSN_CLASS_ZCEA \
+     || insn.insn_mo->insn_class == INSN_CLASS_ZCEB)
+      zce_compress_pass (&insn, &imm_expr, &imm_reloc);
     append_insn (&insn, &imm_expr, imm_reloc);
+  }
+    
 }

full patch for decbnez: https://gist.github.com/linsinan1995/d4ac3f90df65501675abb29998536a0f

Also, another solution I try is to replace decbnez instruction when applying the fixup to elf write buffer (md_apply_fix), and put the following 2 bytes as c.nop (it looks unsafe to modify the byte length in the elf write buf), and add a reloc table to tell the linker to delete it to get the result like below (maybe need to change to r_riscv_relax and add implementation in linker side) image image

I am quite noob on Binutils, and it would be great that if someone can give me some feedback. Thx!