RISC-V: Support new .option arch directive.
authorNelson Chu <nelson.chu@sifive.com>
Fri, 19 Nov 2021 09:11:06 +0000 (17:11 +0800)
committerNelson Chu <nelson.chu@sifive.com>
Fri, 19 Nov 2021 10:50:27 +0000 (18:50 +0800)
https://github.com/riscv/riscv-asm-manual/pull/67

Format:
.option arch, +<extension><version>, ...
.option arch, -<extension>
.option arch, =<ISA string>

The new direcitve is used to enable/disable extensions for the specific
code region.  For example,

.attribute arch, "rv64ic"   # arch = rv64i2p0_c2p0
.option push
.option arch, +d2p0, -c     # arch = rv64i2p0_f2p0_d2p0, f is added implied
.option arch, =rv32gc       # arch = rv32i2p0_m2p0_a2p0_f2p0_d2p0_c2p0
.option pop                 # arch = rv64i2p0_c2p0

Note that,
1. ".option rvc/norvc" have the same behavior as ".option arch +c/-c".
2. ".option arch -i" is illegal, since we cannot remove base i extension.
3. If arch=rv64i2p0, then ".option arch, +i3p0" will update the i's version
   from 2.0 to 3.0.
4. If arch=rv64i3p0, then ".option arch, +i" will update the i's version
   from 2.0 to the default one according to the chosen isa spec.

bfd/
* elfxx-riscv.c (riscv_add_subset): If the subset is already added,
and the new versions are not RISCV_UNKNOWN_VERSION, then update the
versions to the subset list.
(riscv_copy_subset): New function.  Copy the subset from list.
(riscv_copy_subset_list): New function.  Return the new copyed list.
(riscv_update_subset): Updated to make .option arch directives workable.
* elfxx-riscv.h: Updated.
gas/
* config/tc-riscv.c (riscv_subsets): Defined as a pointer.
(riscv_rps_as): Init the subset_list to NULL, we will set it later
once riscv_opts_stack is created or updated.
(struct riscv_option_stack, riscv_opts_stack): Moved forward.
(riscv_set_arch): Updated.
(s_riscv_option): Support new .option arch directive, to add, remove
or update subsets for the specific code region.
(riscv_write_out_attrs): Updated.
* doc/c-riscv.texi: Added document for new .option arch directive.
* testsuite/gas/riscv/option-arch-01a.d: New testcase.
* testsuite/gas/riscv/option-arch-01b.d: Likewise.
* testsuite/gas/riscv/option-arch-01.s: Likewise..
* testsuite/gas/riscv/option-arch-02.d: Likewise.
* testsuite/gas/riscv/option-arch-02.s: Likewise.
* testsuite/gas/riscv/option-arch-fail.d: Likewise.
* testsuite/gas/riscv/option-arch-fail.l: Likewise.
* testsuite/gas/riscv/option-arch-fail.s: Likewise.

14 files changed:
bfd/elfxx-riscv.c
bfd/elfxx-riscv.h
gas/config/tc-riscv.c
gas/doc/c-riscv.texi
gas/testsuite/gas/riscv/option-arch-01.s [new file with mode: 0644]
gas/testsuite/gas/riscv/option-arch-01a.d [new file with mode: 0644]
gas/testsuite/gas/riscv/option-arch-01b.d [new file with mode: 0644]
gas/testsuite/gas/riscv/option-arch-02.d [new file with mode: 0644]
gas/testsuite/gas/riscv/option-arch-02.s [new file with mode: 0644]
gas/testsuite/gas/riscv/option-arch-03.d [new file with mode: 0644]
gas/testsuite/gas/riscv/option-arch-03.s [new file with mode: 0644]
gas/testsuite/gas/riscv/option-arch-fail.d [new file with mode: 0644]
gas/testsuite/gas/riscv/option-arch-fail.l [new file with mode: 0644]
gas/testsuite/gas/riscv/option-arch-fail.s [new file with mode: 0644]

index 91afd4c715f420351a585aac7ce85f0a5b914f8d..b8da40ce2b414f1031af627ef46785fd8b0dc9df 100644 (file)
@@ -1468,7 +1468,15 @@ riscv_add_subset (riscv_subset_list_t *subset_list,
   riscv_subset_t *current, *new;
 
   if (riscv_lookup_subset (subset_list, subset, &current))
-    return;
+    {
+      if (major != RISCV_UNKNOWN_VERSION
+         && minor != RISCV_UNKNOWN_VERSION)
+       {
+         current->major_version = major;
+         current->minor_version = minor;
+       }
+      return;
+    }
 
   new = xmalloc (sizeof *new);
   new->name = xstrdup (subset);
@@ -2138,6 +2146,37 @@ riscv_arch_str (unsigned xlen, const riscv_subset_list_t *subset)
   return attr_str;
 }
 
+/* Copy the subset in the subset list.  */
+
+static struct riscv_subset_t *
+riscv_copy_subset (riscv_subset_list_t *subset_list,
+                  riscv_subset_t *subset)
+{
+  if (subset == NULL)
+    return NULL;
+
+  riscv_subset_t *new = xmalloc (sizeof *new);
+  new->name = xstrdup (subset->name);
+  new->major_version = subset->major_version;
+  new->minor_version = subset->minor_version;
+  new->next = riscv_copy_subset (subset_list, subset->next);
+
+  if (subset->next == NULL)
+    subset_list->tail = new;
+
+  return new;
+}
+
+/* Copy the subset list.  */
+
+riscv_subset_list_t *
+riscv_copy_subset_list (riscv_subset_list_t *subset_list)
+{
+  riscv_subset_list_t *new = xmalloc (sizeof *new);
+  new->head = riscv_copy_subset (new, subset_list->head);
+  return new;
+}
+
 /* Remove the SUBSET from the subset list.  */
 
 static void
@@ -2164,40 +2203,111 @@ riscv_remove_subset (riscv_subset_list_t *subset_list,
 }
 
 /* Add/Remove an extension to/from the subset list.  This is used for
-   the .option rvc or norvc.  */
+   the .option rvc or norvc, and .option arch directives.  */
 
 bool
 riscv_update_subset (riscv_parse_subset_t *rps,
-                    const char *subset,
-                    bool removed)
+                    const char *str)
 {
-  if (strlen (subset) == 0
-      || (strlen (subset) == 1
-         && riscv_ext_order[(*subset - 'a')] == 0)
-      || (strlen (subset) > 1
-         && rps->check_unknown_prefixed_ext
-         && !riscv_recognized_prefixed_ext (subset)))
-    {
-      rps->error_handler
-       (_("riscv_update_subset: unknown ISA extension `%s'"), subset);
-      return false;
-    }
+  const char *p = str;
 
-  if (removed)
+  do
     {
-      if (strcmp (subset, "i") == 0)
+      int major_version = RISCV_UNKNOWN_VERSION;
+      int minor_version = RISCV_UNKNOWN_VERSION;
+
+      bool removed = false;
+      switch (*p++)
+       {
+       case '+': removed = false; break;
+       case '-': removed = true; break;
+       case '=':
+         riscv_release_subset_list (rps->subset_list);
+         return riscv_parse_subset (rps, p);
+       default:
+         rps->error_handler
+           (_("extensions must begin with +/-/= in .option arch `%s'"), str);
+         return false;
+       }
+
+      char *subset = xstrdup (p);
+      char *q = subset;
+      const char *end_of_version;
+      /* Extract the whole prefixed extension by ','.  */
+      while (*q != '\0' && *q != ',')
+        q++;
+      /* Look forward to the first letter which is not <major>p<minor>.  */
+      bool find_any_version = false;
+      bool find_minor_version = false;
+      while (1)
+        {
+         q--;
+         if (ISDIGIT (*q))
+           find_any_version = true;
+         else if (find_any_version
+                  && !find_minor_version
+                  && *q == 'p'
+                  && ISDIGIT (*(q - 1)))
+           find_minor_version = true;
+         else
+           break;
+       }
+      q++;
+      /* Check if the end of extension is 'p' or not.  If yes, then
+        the second letter from the end cannot be number.  */
+      if (*(q - 1) == 'p' && ISDIGIT (*(q - 2)))
+       {
+         *q = '\0';
+         rps->error_handler
+           (_("invalid ISA extension ends with <number>p "
+              "in .option arch `%s'"), str);
+         free (subset);
+         return false;
+       }
+      end_of_version =
+       riscv_parsing_subset_version (q, &major_version, &minor_version);
+      *q = '\0';
+      if (end_of_version == NULL)
+       {
+         free (subset);
+         return false;
+       }
+
+      if (strlen (subset) == 0
+         || (strlen (subset) == 1
+             && riscv_ext_order[(*subset - 'a')] == 0)
+         || (strlen (subset) > 1
+             && rps->check_unknown_prefixed_ext
+             && !riscv_recognized_prefixed_ext (subset)))
        {
          rps->error_handler
-           (_("riscv_update_subset: cannot remove extension i from "
-              "the subset list"));
+           (_("unknown ISA extension `%s' in .option arch `%s'"),
+            subset, str);
+         free (subset);
          return false;
        }
-      riscv_remove_subset (rps->subset_list, subset);
+
+      if (removed)
+       {
+         if (strcmp (subset, "i") == 0)
+           {
+             rps->error_handler
+               (_("cannot remove extension `i' in .option arch `%s'"), str);
+             free (subset);
+             return false;
+           }
+         riscv_remove_subset (rps->subset_list, subset);
+       }
+      else
+       riscv_parse_add_subset (rps, subset, major_version, minor_version, true);
+      p += end_of_version - subset;
+      free (subset);
     }
-  else
-    riscv_parse_add_subset (rps, subset,
-                           RISCV_UNKNOWN_VERSION,
-                           RISCV_UNKNOWN_VERSION, true);
+  while (*p++ == ',');
+
+  if (*(--p) != '\0')
+    rps->error_handler
+      (_("unexpected value in .option arch `%s'"), str);
 
   riscv_parse_add_implicit_subsets (rps);
   return riscv_parse_check_conflicts (rps);
index 8de9adca0b175b7c4b23e94ec95c72d886b2b6ec..ea7edc42f358d55e4b93be12b5a8b3585dca9438 100644 (file)
@@ -92,8 +92,11 @@ riscv_estimate_digit (unsigned);
 extern int
 riscv_compare_subsets (const char *, const char *);
 
+extern riscv_subset_list_t *
+riscv_copy_subset_list (riscv_subset_list_t *);
+
 extern bool
-riscv_update_subset (riscv_parse_subset_t *, const char *, bool);
+riscv_update_subset (riscv_parse_subset_t *, const char *);
 
 extern bool
 riscv_subset_supports (riscv_parse_subset_t *, const char *);
index 930cfb2125a3c2f36bc10cbca42a0eb74ccd29db..da3d91156672d74ef4a5b57de1f7ab1cf7c1c23e 100644 (file)
@@ -235,16 +235,27 @@ riscv_set_rvc (bool rvc_value)
    the architecture string.  The architecture string can be set by the
    -march option, the elf architecture attributes, and the --with-arch
    configure option.  */
-static riscv_subset_list_t riscv_subsets;
+static riscv_subset_list_t *riscv_subsets = NULL;
 static riscv_parse_subset_t riscv_rps_as =
 {
-  &riscv_subsets,      /* subset_list.  */
+  NULL,                        /* subset_list, we will set it later once
+                          riscv_opts_stack is created or updated.  */
   as_bad,              /* error_handler.  */
   &xlen,               /* xlen.  */
   &default_isa_spec,   /* isa_spec.  */
   true,                        /* check_unknown_prefixed_ext.  */
 };
 
+/* This structure is used to hold a stack of .option values.  */
+struct riscv_option_stack
+{
+  struct riscv_option_stack *next;
+  struct riscv_set_options options;
+  riscv_subset_list_t *subset_list;
+};
+
+static struct riscv_option_stack *riscv_opts_stack = NULL;
+
 /* Set which ISA and extensions are available.  */
 
 static void
@@ -257,7 +268,14 @@ riscv_set_arch (const char *s)
       return;
     }
 
-  riscv_release_subset_list (&riscv_subsets);
+  if (riscv_subsets == NULL)
+    {
+      riscv_subsets = XNEW (riscv_subset_list_t);
+      riscv_subsets->head = NULL;
+      riscv_subsets->tail = NULL;
+      riscv_rps_as.subset_list = riscv_subsets;
+    }
+  riscv_release_subset_list (riscv_subsets);
   riscv_parse_subset (&riscv_rps_as, s);
 
   riscv_set_rvc (false);
@@ -3715,15 +3733,6 @@ riscv_pre_output_hook (void)
   subseg_set (seg, subseg);
 }
 
-/* This structure is used to hold a stack of .option values.  */
-struct riscv_option_stack
-{
-  struct riscv_option_stack *next;
-  struct riscv_set_options options;
-};
-
-static struct riscv_option_stack *riscv_opts_stack;
-
 /* Handle the .option pseudo-op.  */
 
 static void
@@ -3738,12 +3747,12 @@ s_riscv_option (int x ATTRIBUTE_UNUSED)
 
   if (strcmp (name, "rvc") == 0)
     {
-      riscv_update_subset (&riscv_rps_as, "c", false);
+      riscv_update_subset (&riscv_rps_as, "+c");
       riscv_set_rvc (true);
     }
   else if (strcmp (name, "norvc") == 0)
     {
-      riscv_update_subset (&riscv_rps_as, "c", true);
+      riscv_update_subset (&riscv_rps_as, "-c");
       riscv_set_rvc (false);
     }
   else if (strcmp (name, "pic") == 0)
@@ -3758,14 +3767,24 @@ s_riscv_option (int x ATTRIBUTE_UNUSED)
     riscv_opts.csr_check = true;
   else if (strcmp (name, "no-csr-check") == 0)
     riscv_opts.csr_check = false;
+  else if (strncmp (name, "arch,", 5) == 0)
+    {
+      name += 5;
+      if (ISSPACE (*name) && *name != '\0')
+       name++;
+      riscv_update_subset (&riscv_rps_as, name);
+    }
   else if (strcmp (name, "push") == 0)
     {
       struct riscv_option_stack *s;
 
-      s = (struct riscv_option_stack *) xmalloc (sizeof *s);
+      s = XNEW (struct riscv_option_stack);
       s->next = riscv_opts_stack;
       s->options = riscv_opts;
+      s->subset_list = riscv_subsets;
       riscv_opts_stack = s;
+      riscv_subsets = riscv_copy_subset_list (s->subset_list);
+      riscv_rps_as.subset_list = riscv_subsets;
     }
   else if (strcmp (name, "pop") == 0)
     {
@@ -3776,8 +3795,12 @@ s_riscv_option (int x ATTRIBUTE_UNUSED)
        as_bad (_(".option pop with no .option push"));
       else
        {
-         riscv_opts = s->options;
+         riscv_subset_list_t *release_subsets = riscv_subsets;
          riscv_opts_stack = s->next;
+         riscv_opts = s->options;
+         riscv_subsets = s->subset_list;
+         riscv_rps_as.subset_list = riscv_subsets;
+         riscv_release_subset_list (release_subsets);
          free (s);
        }
     }
@@ -4262,7 +4285,7 @@ riscv_write_out_attrs (void)
   unsigned int i;
 
   /* Re-write architecture elf attribute.  */
-  arch_str = riscv_arch_str (xlen, &riscv_subsets);
+  arch_str = riscv_arch_str (xlen, riscv_subsets);
   bfd_elf_add_proc_attr_string (stdoutput, Tag_RISCV_arch, arch_str);
   xfree ((void *) arch_str);
 
index bfbf61d64a753368d32792973cfb2272f22a4ee0..697be3a699698c9cc8c08564a309776abb6e2ac6 100644 (file)
@@ -194,7 +194,7 @@ command-line options are respected for the bulk of the file being assembled.
 @itemx norvc
 Enables or disables the generation of compressed instructions.  Instructions
 are opportunistically compressed by the RISC-V assembler when possible, but
-sometimes this behavior is not desirable.
+sometimes this behavior is not desirable, especially when handling alignments.
 
 @item pic
 @itemx nopic
@@ -212,6 +212,19 @@ desirable.
 @itemx no-csr-check
 Enables or disables the CSR checking.
 
+@item arch, @var{+extension[version]} [,...,@var{+extension_n[version_n]}]
+@itemx arch, @var{-extension} [,...,@var{-extension_n}]
+@itemx arch, @var{=ISA}
+Enables or disables the extensions for specific code region.  For example,
+@samp{.option arch, +m2p0} means add m extension with version 2.0, and
+@samp{.option arch, -f, -d} means remove extensions, f and d, from the
+architecture string.  Note that, @samp{.option arch, +c, -c} have the same
+behavior as @samp{.option rvc, norvc}.  However, they are also undesirable
+sometimes.  Besides, @samp{.option arch, -i} is illegal, since we cannot
+remove the base i extension anytime.  If you want to reset the whole ISA
+string, you can also use @samp{.option arch, =rv32imac} to overwrite the
+previous settings.
+
 @cindex INSN directives
 @item .insn @var{type}, @var{operand} [,...,@var{operand_n}]
 @itemx .insn @var{insn_length}, @var{value}
diff --git a/gas/testsuite/gas/riscv/option-arch-01.s b/gas/testsuite/gas/riscv/option-arch-01.s
new file mode 100644 (file)
index 0000000..201f9b3
--- /dev/null
@@ -0,0 +1,10 @@
+.attribute arch, "rv64ic"
+add    a0, a0, a1
+.option push
+.option arch, +d2p0, -c, +xvendor1p0
+add    a0, a0, a1
+frcsr  a0      # Should add mapping symbol with ISA here, and then dump it to frcsr.
+.option push
+.option arch, +i3p0, +m3p0, +d3p0
+.option pop
+.option pop
diff --git a/gas/testsuite/gas/riscv/option-arch-01a.d b/gas/testsuite/gas/riscv/option-arch-01a.d
new file mode 100644 (file)
index 0000000..59bc1d2
--- /dev/null
@@ -0,0 +1,14 @@
+#as:
+#source: option-arch-01.s
+#objdump: -d
+
+.*:[   ]+file format .*
+
+
+Disassembly of section .text:
+
+0+000 <.text>:
+[      ]+[0-9a-f]+:[   ]+952e[         ]+add[          ]+a0,a0,a1
+[      ]+[0-9a-f]+:[   ]+00b50533[     ]+add[          ]+a0,a0,a1
+[      ]+[0-9a-f]+:[   ]+00302573[     ]+csrr[         ]+a0,fcsr
+#...
diff --git a/gas/testsuite/gas/riscv/option-arch-01b.d b/gas/testsuite/gas/riscv/option-arch-01b.d
new file mode 100644 (file)
index 0000000..9a6c2c5
--- /dev/null
@@ -0,0 +1,8 @@
+#as:
+#readelf: -A
+#source: option-arch-01.s
+
+Attribute Section: riscv
+File Attributes
+  Tag_RISCV_arch: "rv64i2p0_c2p0"
+#...
diff --git a/gas/testsuite/gas/riscv/option-arch-02.d b/gas/testsuite/gas/riscv/option-arch-02.d
new file mode 100644 (file)
index 0000000..0fe89ec
--- /dev/null
@@ -0,0 +1,8 @@
+#as:
+#readelf: -A
+#source: option-arch-02.s
+
+Attribute Section: riscv
+File Attributes
+  Tag_RISCV_arch: "rv64i3p0_m3p0_f2p0_d3p0_c2p0_xvendor32x3p0"
+#...
diff --git a/gas/testsuite/gas/riscv/option-arch-02.s b/gas/testsuite/gas/riscv/option-arch-02.s
new file mode 100644 (file)
index 0000000..f4ceee8
--- /dev/null
@@ -0,0 +1,8 @@
+.attribute arch, "rv64ic"
+add    a0, a0, a1
+.option push
+.option arch, +d2p0, -c, +xvendor1p0
+add    a0, a0, a1
+frcsr  a0
+.option pop
+.option arch, +i3p0, +m3p0, +d3p0, +xvendor32x3p0
diff --git a/gas/testsuite/gas/riscv/option-arch-03.d b/gas/testsuite/gas/riscv/option-arch-03.d
new file mode 100644 (file)
index 0000000..b621d03
--- /dev/null
@@ -0,0 +1,8 @@
+#as:
+#readelf: -A
+#source: option-arch-03.s
+
+Attribute Section: riscv
+File Attributes
+  Tag_RISCV_arch: "rv32i2p0_c2p0"
+#...
diff --git a/gas/testsuite/gas/riscv/option-arch-03.s b/gas/testsuite/gas/riscv/option-arch-03.s
new file mode 100644 (file)
index 0000000..7183140
--- /dev/null
@@ -0,0 +1,3 @@
+.attribute arch, "rv64ic"
+.option arch, +d2p0, -c
+.option arch, =rv32ic
diff --git a/gas/testsuite/gas/riscv/option-arch-fail.d b/gas/testsuite/gas/riscv/option-arch-fail.d
new file mode 100644 (file)
index 0000000..bce5d32
--- /dev/null
@@ -0,0 +1,3 @@
+#as:
+#source: option-arch-fail.s
+#error_output: option-arch-fail.l
diff --git a/gas/testsuite/gas/riscv/option-arch-fail.l b/gas/testsuite/gas/riscv/option-arch-fail.l
new file mode 100644 (file)
index 0000000..3e0599e
--- /dev/null
@@ -0,0 +1,8 @@
+.*Assembler messages:
+.*Error: extensions must begin with \+/\-/\= in .option arch `m2p0'
+.*Error: cannot remove extension `i' in .option arch `\-i'
+.*Error: unknown ISA extension `zsubset' in .option arch `\+zsubset2p0'
+.*Error: unknown ISA extension `f2p0_d' in .option arch `\+f2p0_d2p0'
+.*Error: unknown ISA extension `' in .option arch `\+'
+.*Error: invalid ISA extension ends with <number>p in .option arch `\+xvendor2p'
+.*Error: .option pop with no .option push
diff --git a/gas/testsuite/gas/riscv/option-arch-fail.s b/gas/testsuite/gas/riscv/option-arch-fail.s
new file mode 100644 (file)
index 0000000..a0b1bde
--- /dev/null
@@ -0,0 +1,10 @@
+.attribute arch, "rv64ic"
+.option push
+.option arch, m2p0
+.option arch, -i
+.option arch, +zsubset2p0
+.option arch, +f2p0_d2p0
+.option arch, +
+.option arch, +xvendor2p
+.option pop
+.option pop