diff mbox series

Support the new ("v0") mangling scheme in rust-demangle.

Message ID 78b7917b-cadd-402c-a3c3-35fbcc337b81@www.fastmail.com
State New
Headers show
Series Support the new ("v0") mangling scheme in rust-demangle. | expand

Commit Message

Eduard-Mihai Burtescu March 13, 2020, 8:28 p.m. UTC
This is the libiberty (mainly for binutils/gdb) counterpart of
https://github.com/alexcrichton/rustc-demangle/pull/23.

Relevant links for the new Rust mangling scheme (aka "v0"):
* Rust RFC: https://github.com/rust-lang/rfcs/pull/2603
* tracking issue: https://github.com/rust-lang/rust/issues/60705
* implementation: https://github.com/rust-lang/rust/pull/57967

This implementation includes full support for UTF-8 identifiers
via punycode, so I've included a testcase for that as well.
(Let me know if it causes any issues and I'll take it out)

Last year I've submitted several small patches to rust-demangle
in preparation for upstreaming the entire new demangler, and
feedback from that has proven useful.
For example, I started with error-handling macros, but instead
the code now has "rdm->errored = 1;" before several returns/gotos.

The patch is attached instead of inline, as it's over 1000 lines long.

Bootstrapped and tested on x86_64-unknown-linux-gnu.

Also, I have no commit access, so I'd be thankful if
someone would commit this for me if/once approved.

Comments

Eduard-Mihai Burtescu March 23, 2020, 1:34 a.m. UTC | #1
Ping: https://gcc.gnu.org/pipermail/gcc-patches/2020-March/542012.html

Thanks,
- Eddy B.

On Fri, Mar 13, 2020, at 10:28 PM, Eduard-Mihai Burtescu wrote:
> This is the libiberty (mainly for binutils/gdb) counterpart of
> https://github.com/alexcrichton/rustc-demangle/pull/23.
> 
> Relevant links for the new Rust mangling scheme (aka "v0"):
> * Rust RFC: https://github.com/rust-lang/rfcs/pull/2603
> * tracking issue: https://github.com/rust-lang/rust/issues/60705
> * implementation: https://github.com/rust-lang/rust/pull/57967
> 
> This implementation includes full support for UTF-8 identifiers
> via punycode, so I've included a testcase for that as well.
> (Let me know if it causes any issues and I'll take it out)
> 
> Last year I've submitted several small patches to rust-demangle
> in preparation for upstreaming the entire new demangler, and
> feedback from that has proven useful.
> For example, I started with error-handling macros, but instead
> the code now has "rdm->errored = 1;" before several returns/gotos.
> 
> The patch is attached instead of inline, as it's over 1000 lines long.
> 
> Bootstrapped and tested on x86_64-unknown-linux-gnu.
> 
> Also, I have no commit access, so I'd be thankful if
> someone would commit this for me if/once approved.
> Attachments:
> * 0001-Support-the-new-v0-mangling-scheme-in-rust-demangle.patch
Eduard-Mihai Burtescu April 6, 2020, 9:52 p.m. UTC | #2
Ping 2: https://gcc.gnu.org/pipermail/gcc-patches/2020-March/542012.html

Thanks,
- Eddy B.

On Fri, Mar 13, 2020, at 10:28 PM, Eduard-Mihai Burtescu wrote:
> This is the libiberty (mainly for binutils/gdb) counterpart of
> https://github.com/alexcrichton/rustc-demangle/pull/23.
> 
> Relevant links for the new Rust mangling scheme (aka "v0"):
> * Rust RFC: https://github.com/rust-lang/rfcs/pull/2603
> * tracking issue: https://github.com/rust-lang/rust/issues/60705
> * implementation: https://github.com/rust-lang/rust/pull/57967
> 
> This implementation includes full support for UTF-8 identifiers
> via punycode, so I've included a testcase for that as well.
> (Let me know if it causes any issues and I'll take it out)
> 
> Last year I've submitted several small patches to rust-demangle
> in preparation for upstreaming the entire new demangler, and
> feedback from that has proven useful.
> For example, I started with error-handling macros, but instead
> the code now has "rdm->errored = 1;" before several returns/gotos.
> 
> The patch is attached instead of inline, as it's over 1000 lines long.
> 
> Bootstrapped and tested on x86_64-unknown-linux-gnu.
> 
> Also, I have no commit access, so I'd be thankful if
> someone would commit this for me if/once approved.
> Attachments:
> * 0001-Support-the-new-v0-mangling-scheme-in-rust-demangle.patch
Eduard-Mihai Burtescu April 13, 2020, 2:52 a.m. UTC | #3
Ping 3: https://gcc.gnu.org/pipermail/gcc-patches/2020-March/542012.html

Thanks,
- Eddy B.

On Tue, Apr 7, 2020, at 00:52, Eduard-Mihai Burtescu wrote:
> Ping 2: https://gcc.gnu.org/pipermail/gcc-patches/2020-March/542012.html
> 
> Thanks,
> - Eddy B.
> 
> On Fri, Mar 13, 2020, at 10:28 PM, Eduard-Mihai Burtescu wrote:
> > This is the libiberty (mainly for binutils/gdb) counterpart of
> > https://github.com/alexcrichton/rustc-demangle/pull/23.
> > 
> > Relevant links for the new Rust mangling scheme (aka "v0"):
> > * Rust RFC: https://github.com/rust-lang/rfcs/pull/2603
> > * tracking issue: https://github.com/rust-lang/rust/issues/60705
> > * implementation: https://github.com/rust-lang/rust/pull/57967
> > 
> > This implementation includes full support for UTF-8 identifiers
> > via punycode, so I've included a testcase for that as well.
> > (Let me know if it causes any issues and I'll take it out)
> > 
> > Last year I've submitted several small patches to rust-demangle
> > in preparation for upstreaming the entire new demangler, and
> > feedback from that has proven useful.
> > For example, I started with error-handling macros, but instead
> > the code now has "rdm->errored = 1;" before several returns/gotos.
> > 
> > The patch is attached instead of inline, as it's over 1000 lines long.
> > 
> > Bootstrapped and tested on x86_64-unknown-linux-gnu.
> > 
> > Also, I have no commit access, so I'd be thankful if
> > someone would commit this for me if/once approved.
> > Attachments:
> > * 0001-Support-the-new-v0-mangling-scheme-in-rust-demangle.patch
Eduard-Mihai Burtescu April 23, 2020, 6:52 p.m. UTC | #4
Ping 4: https://gcc.gnu.org/pipermail/gcc-patches/2020-March/542012.html

Thanks,
- Eddy B.

On Mon, Apr 13, 2020, at 05:52, Eduard-Mihai Burtescu wrote:
> Ping 3: https://gcc.gnu.org/pipermail/gcc-patches/2020-March/542012.html
> 
> Thanks,
> - Eddy B.
> 
> On Tue, Apr 7, 2020, at 00:52, Eduard-Mihai Burtescu wrote:
> > Ping 2: https://gcc.gnu.org/pipermail/gcc-patches/2020-March/542012.html
> > 
> > Thanks,
> > - Eddy B.
> > 
> > On Fri, Mar 13, 2020, at 10:28 PM, Eduard-Mihai Burtescu wrote:
> > > This is the libiberty (mainly for binutils/gdb) counterpart of
> > > https://github.com/alexcrichton/rustc-demangle/pull/23.
> > > 
> > > Relevant links for the new Rust mangling scheme (aka "v0"):
> > > * Rust RFC: https://github.com/rust-lang/rfcs/pull/2603
> > > * tracking issue: https://github.com/rust-lang/rust/issues/60705
> > > * implementation: https://github.com/rust-lang/rust/pull/57967
> > > 
> > > This implementation includes full support for UTF-8 identifiers
> > > via punycode, so I've included a testcase for that as well.
> > > (Let me know if it causes any issues and I'll take it out)
> > > 
> > > Last year I've submitted several small patches to rust-demangle
> > > in preparation for upstreaming the entire new demangler, and
> > > feedback from that has proven useful.
> > > For example, I started with error-handling macros, but instead
> > > the code now has "rdm->errored = 1;" before several returns/gotos.
> > > 
> > > The patch is attached instead of inline, as it's over 1000 lines long.
> > > 
> > > Bootstrapped and tested on x86_64-unknown-linux-gnu.
> > > 
> > > Also, I have no commit access, so I'd be thankful if
> > > someone would commit this for me if/once approved.
> > > Attachments:
> > > * 0001-Support-the-new-v0-mangling-scheme-in-rust-demangle.patch
Nikhil Benesch Oct. 28, 2020, 9:22 p.m. UTC | #5
Ian, are you able to review this? I saw that you reviewed many of the 
prior changes to the Rust demangler.

If not, can you suggest someone who can?

Thanks very much.
Nikhil
Nikhil Benesch Oct. 28, 2020, 9:25 p.m. UTC | #6
On 10/28/20 5:22 PM, Nikhil Benesch wrote:
> Ian, are you able to review this? I saw that you reviewed many of the 
> prior changes to the Rust demangler.
> 
> If not, can you suggest someone who can?
> 
> Thanks very much.
> Nikhil

I seem to have failed to convince my email client to set the appropriate 
reply to headers. This is the patch to which I was referring:

https://gcc.gnu.org/pipermail/gcc-patches/2020-March/542488.html
Eduard-Mihai Burtescu Oct. 28, 2020, 9:47 p.m. UTC | #7
FWIW, the patch has become slightly outdated compared to the Rust upstream, so if someone wants to review it I should prepare a new version.

The changes would be for the MVP version of "const generics" (Rust's equivalent to C++ templates with value parameters, enabling e.g. types like `CustomArray<T, 123>`), which supports a few different kinds of primitive values, whereas the original patch only handled unsigned integers.

Thanks,
- Eddy B.

On Wed, Oct 28, 2020, at 23:25, Nikhil Benesch wrote:
> On 10/28/20 5:22 PM, Nikhil Benesch wrote:
> > Ian, are you able to review this? I saw that you reviewed many of the 
> > prior changes to the Rust demangler.
> > 
> > If not, can you suggest someone who can?
> > 
> > Thanks very much.
> > Nikhil
> 
> I seem to have failed to convince my email client to set the appropriate 
> reply to headers. This is the patch to which I was referring:
> 
> https://gcc.gnu.org/pipermail/gcc-patches/2020-March/542488.html
>
Nikhil Benesch Oct. 28, 2020, 10:21 p.m. UTC | #8
I think it is mostly a matter of snagging some of Ian's limited time.
I suspect it is still worthwhile to try to get the original patch
reviewed and merged, because then any follow-up changes for const
generics support will be smaller and easier to review.

On Wed, Oct 28, 2020 at 5:48 PM Eduard-Mihai Burtescu <eddyb@lyken.rs> wrote:
>
> FWIW, the patch has become slightly outdated compared to the Rust upstream, so if someone wants to review it I should prepare a new version.
>
> The changes would be for the MVP version of "const generics" (Rust's equivalent to C++ templates with value parameters, enabling e.g. types like `CustomArray<T, 123>`), which supports a few different kinds of primitive values, whereas the original patch only handled unsigned integers.
>
> Thanks,
> - Eddy B.
>
> On Wed, Oct 28, 2020, at 23:25, Nikhil Benesch wrote:
> > On 10/28/20 5:22 PM, Nikhil Benesch wrote:
> > > Ian, are you able to review this? I saw that you reviewed many of the
> > > prior changes to the Rust demangler.
> > >
> > > If not, can you suggest someone who can?
> > >
> > > Thanks very much.
> > > Nikhil
> >
> > I seem to have failed to convince my email client to set the appropriate
> > reply to headers. This is the patch to which I was referring:
> >
> > https://gcc.gnu.org/pipermail/gcc-patches/2020-March/542488.html
> >
Ian Lance Taylor Oct. 28, 2020, 10:45 p.m. UTC | #9
On Wed, Oct 28, 2020 at 3:22 PM Nikhil Benesch via Gcc-patches
<gcc-patches@gcc.gnu.org> wrote:
>
> I think it is mostly a matter of snagging some of Ian's limited time.
> I suspect it is still worthwhile to try to get the original patch
> reviewed and merged, because then any follow-up changes for const
> generics support will be smaller and easier to review.

Yeah, it's not a good idea for me to be a blocker for changes to Rust code.

I took a quick look at the original patch.  The calls to sprintf
should use snprintf instead.

Other than that it seems fine though I have no idea whether it's correct.

Ian



> On Wed, Oct 28, 2020 at 5:48 PM Eduard-Mihai Burtescu <eddyb@lyken.rs> wrote:
> >
> > FWIW, the patch has become slightly outdated compared to the Rust upstream, so if someone wants to review it I should prepare a new version.
> >
> > The changes would be for the MVP version of "const generics" (Rust's equivalent to C++ templates with value parameters, enabling e.g. types like `CustomArray<T, 123>`), which supports a few different kinds of primitive values, whereas the original patch only handled unsigned integers.
> >
> > Thanks,
> > - Eddy B.
> >
> > On Wed, Oct 28, 2020, at 23:25, Nikhil Benesch wrote:
> > > On 10/28/20 5:22 PM, Nikhil Benesch wrote:
> > > > Ian, are you able to review this? I saw that you reviewed many of the
> > > > prior changes to the Rust demangler.
> > > >
> > > > If not, can you suggest someone who can?
> > > >
> > > > Thanks very much.
> > > > Nikhil
> > >
> > > I seem to have failed to convince my email client to set the appropriate
> > > reply to headers. This is the patch to which I was referring:
> > >
> > > https://gcc.gnu.org/pipermail/gcc-patches/2020-March/542488.html
> > >
Eduard-Mihai Burtescu Oct. 28, 2020, 11:52 p.m. UTC | #10
Hi Ian,

Thanks for replying! (Also I'm really sorry for the missing hard wrapping
in my earlier email, I shouldn't have sent that from my phone)

Regarding this being a blocker: we've already made the changes to Rust, and
the new ("v0") mangling format continues to remain opt-in and nightly-only,
as long as no distributions ship gdb & other tools, capable of demangling it.

I agree that landing the extra changes later on top should be fine anyway,
though when I make the sprintf -> snprintf change, I could provide the extra
changes as well (either as a combined patch, or as a second tiny patch).

Sadly I don't think I can get to either until next week, hope that's okay.

For correctness, I've been running this test after every change I make, both
with the standalone copy (in the same gist), and a local build of libiberty:
https://gist.github.com/eddyb/c41a69378750a433767cf53fe2316768#file-test-rs-L85-L116

The input is almost 1 million symbol names (collected from a full build of
the Rust compiler and Cargo), and the test ensures that the C implementation
produces the same result as the original "v0" demangler (written in Rust).
All that data is around 1GB but maybe I should try to upload it somewhere
public so that anyone can repeat this procedure, or I could even redo the
collection process (in order to gather even more / newer symbol names).

While this may not stress every aspect of the demangler, it is comprehensive
enough that it found several kinds of bugs in a 3rd party implementation
(which the IllumOS project wrote from scratch, instead of porting ours).

Thanks,
- Eddy B.

On Thu, Oct 29, 2020, at 00:45, Ian Lance Taylor wrote:
> On Wed, Oct 28, 2020 at 3:22 PM Nikhil Benesch via Gcc-patches
> <gcc-patches@gcc.gnu.org> wrote:
> >
> > I think it is mostly a matter of snagging some of Ian's limited time.
> > I suspect it is still worthwhile to try to get the original patch
> > reviewed and merged, because then any follow-up changes for const
> > generics support will be smaller and easier to review.
> 
> Yeah, it's not a good idea for me to be a blocker for changes to Rust code.
> 
> I took a quick look at the original patch.  The calls to sprintf
> should use snprintf instead.
> 
> Other than that it seems fine though I have no idea whether it's correct.
> 
> Ian
> 
> 
> 
> > On Wed, Oct 28, 2020 at 5:48 PM Eduard-Mihai Burtescu <eddyb@lyken.rs> wrote:
> > >
> > > FWIW, the patch has become slightly outdated compared to the Rust upstream, so if someone wants to review it I should prepare a new version.
> > >
> > > The changes would be for the MVP version of "const generics" (Rust's equivalent to C++ templates with value parameters, enabling e.g. types like `CustomArray<T, 123>`), which supports a few different kinds of primitive values, whereas the original patch only handled unsigned integers.
> > >
> > > Thanks,
> > > - Eddy B.
> > >
> > > On Wed, Oct 28, 2020, at 23:25, Nikhil Benesch wrote:
> > > > On 10/28/20 5:22 PM, Nikhil Benesch wrote:
> > > > > Ian, are you able to review this? I saw that you reviewed many of the
> > > > > prior changes to the Rust demangler.
> > > > >
> > > > > If not, can you suggest someone who can?
> > > > >
> > > > > Thanks very much.
> > > > > Nikhil
> > > >
> > > > I seem to have failed to convince my email client to set the appropriate
> > > > reply to headers. This is the patch to which I was referring:
> > > >
> > > > https://gcc.gnu.org/pipermail/gcc-patches/2020-March/542488.html
> > > >
>
Nikhil Benesch Oct. 29, 2020, 4:16 a.m. UTC | #11
On Wed, Oct 28, 2020 at 7:53 PM Eduard-Mihai Burtescu <eddyb@lyken.rs> wrote:
> I agree that landing the extra changes later on top should be fine anyway,
> though when I make the sprintf -> snprintf change, I could provide the extra
> changes as well (either as a combined patch, or as a second tiny patch).
>
> Sadly I don't think I can get to either until next week, hope that's okay.

I can make the sprintf -> snprintf change as early as tomorrow.

I suspect I can also make the substantive const generics change, based
on the Rust implementation, though that's less of a promise.

> All that data is around 1GB but maybe I should try to upload it somewhere
> public so that anyone can repeat this procedure, or I could even redo the
> collection process (in order to gather even more / newer symbol names).

Sharing that file would be very appreciated. I imagine sharing it
publicly would be best, but if finding hosting is difficult, you could
upload directly to my Dropbox:
https://www.dropbox.com/request/PdJDiYQHs9poezwIHFV9

Nikhil
Nikhil Benesch Nov. 1, 2020, 9:18 a.m. UTC | #12
On 10/29/20 12:16 AM, Nikhil Benesch wrote:
> On Wed, Oct 28, 2020 at 7:53 PM Eduard-Mihai Burtescu <eddyb@lyken.rs> wrote:
>> I agree that landing the extra changes later on top should be fine anyway,
>> though when I make the sprintf -> snprintf change, I could provide the extra
>> changes as well (either as a combined patch, or as a second tiny patch).
>>
>> Sadly I don't think I can get to either until next week, hope that's okay.
> 
> I can make the sprintf -> snprintf change as early as tomorrow.
> 
> I suspect I can also make the substantive const generics change, based
> on the Rust implementation, though that's less of a promise.

Attached is an updated patch with both of the aforementioned changes. 
The demangling of constants matches the demangling in rustc-demangle 
library as best as I could manage. The strategy of demangling constant 
chars via `format!("{:?}", char)` in Rust makes life hard for us in C, 
because there is no easy way to replicate Rust's debug formatting for 
chars. (Unless GCC has a Unicode library handy somewhere.)

In the course of this work I noticed several inconsistencies in how 
rustc-demangle and libiberty were demangling some legacy Rust symbols. I 
fixed those and added test cases, though the fixes required removing 
some of the fast checks for whether a given symbol is a valid Rust symbol.

For ease of review, eddyb, I also attached the diff from your last diff 
to mine. Since I don't have your collection of symbols I generated my 
own by running nm on the materialized binary from
https://github.com/MaterializeInc/materialize.

Let me know how I can help. I'm happy to address review feedback myself, 
or I'm happy to hand things back over to you, eddyb.

Nikhil
diff --git a/rust-demangle.c b/rust-demangle.c
index d604b3c..5c0a917 100644
--- a/rust-demangle.c
+++ b/rust-demangle.c
@@ -143,6 +143,35 @@ parse_disambiguator (struct rust_demangler *rdm)
   return parse_opt_integer_62 (rdm, 's');
 }
 
+static size_t
+parse_hex_nibbles (struct rust_demangler *rdm, uint64_t *value)
+{
+  char c;
+  size_t hex_len;
+
+  hex_len = 0;
+  *value = 0;
+
+  while (!eat (rdm, '_'))
+    {
+      *value <<= 4;
+
+      c = next (rdm);
+      if (ISDIGIT (c))
+        *value |= c - '0';
+      else if (c >= 'a' && c <= 'f')
+        *value |= 10 + (c - 'a');
+      else
+        {
+          rdm->errored = 1;
+          return 0;
+        }
+      hex_len++;
+    }
+
+  return hex_len;
+}
+
 struct rust_mangled_ident
 {
   /* ASCII part of the identifier. */
@@ -240,7 +269,7 @@ static void
 print_uint64 (struct rust_demangler *rdm, uint64_t x)
 {
   char s[21];
-  sprintf (s, "%" PRIu64, x);
+  snprintf (s, 21, "%" PRIu64, x);
   PRINT (s);
 }
 
@@ -248,7 +277,7 @@ static void
 print_uint64_hex (struct rust_demangler *rdm, uint64_t x)
 {
   char s[17];
-  sprintf (s, "%" PRIx64, x);
+  snprintf (s, 17, "%" PRIx64, x);
   PRINT (s);
 }
 
@@ -380,8 +409,7 @@ print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
                 }
               else
                 {
-                  /* "." becomes "-" */
-                  PRINT ("-");
+                  PRINT (".");
                   len = 1;
                 }
             }
@@ -591,6 +619,9 @@ static int demangle_path_maybe_open_generics (struct rust_demangler *rdm);
 static void demangle_dyn_trait (struct rust_demangler *rdm);
 static void demangle_const (struct rust_demangler *rdm);
 static void demangle_const_uint (struct rust_demangler *rdm);
+static void demangle_const_int (struct rust_demangler *rdm);
+static void demangle_const_bool (struct rust_demangler *rdm);
+static void demangle_const_char (struct rust_demangler *rdm);
 
 /* Optionally enter a binder ('G') for late-bound lifetimes,
    printing e.g. `for<'a, 'b> `, and make those lifetimes visible
@@ -1089,6 +1120,11 @@ demangle_const (struct rust_demangler *rdm)
   ty_tag = next (rdm);
   switch (ty_tag)
     {
+    /* Placeholder. */
+    case 'p':
+      PRINT ("_");
+      return;
+
     /* Unsigned integer types. */
     case 'h':
     case 't':
@@ -1096,6 +1132,27 @@ demangle_const (struct rust_demangler *rdm)
     case 'y':
     case 'o':
     case 'j':
+      demangle_const_uint (rdm);
+      break;
+
+    /* Signed integer types. */
+    case 'a':
+    case 's':
+    case 'l':
+    case 'x':
+    case 'n':
+    case 'i':
+      demangle_const_int (rdm);
+      break;
+
+    /* Boolean. */
+    case 'b':
+      demangle_const_bool (rdm);
+      break;
+
+    /* Character. */
+    case 'c':
+      demangle_const_char (rdm);
       break;
 
     default:
@@ -1103,10 +1160,8 @@ demangle_const (struct rust_demangler *rdm)
       return;
     }
 
-  if (eat (rdm, 'p'))
-    PRINT ("_");
-  else
-    demangle_const_uint (rdm);
+  if (rdm->errored)
+    return;
 
   if (rdm->verbose)
     {
@@ -1118,74 +1173,121 @@ demangle_const (struct rust_demangler *rdm)
 static void
 demangle_const_uint (struct rust_demangler *rdm)
 {
-  char c;
   size_t hex_len;
   uint64_t value;
 
   if (rdm->errored)
     return;
 
-  value = 0;
-  hex_len = 0;
-  while (!eat (rdm, '_'))
-    {
-      value <<= 4;
+  hex_len = parse_hex_nibbles (rdm, &value);
 
-      c = next (rdm);
-      if (ISDIGIT (c))
-        value |= c - '0';
-      else if (c >= 'a' && c <= 'f')
-        value |= 10 + (c - 'a');
-      else
-        {
-          rdm->errored = 1;
-          return;
-        }
-      hex_len++;
-    }
-
-  /* Print anything that doesn't fit in `uint64_t` verbatim. */
   if (hex_len > 16)
     {
+      /* Print anything that doesn't fit in `uint64_t` verbatim. */
       PRINT ("0x");
       print_str (rdm, rdm->sym + (rdm->next - hex_len), hex_len);
+    }
+  else if (hex_len > 0)
+    print_uint64 (rdm, value);
+  else
+    rdm->errored = 1;
+}
+
+static void
+demangle_const_int (struct rust_demangler *rdm)
+{
+  if (eat (rdm, 'n'))
+    PRINT ("-");
+  demangle_const_uint (rdm);
+}
+
+static void
+demangle_const_bool (struct rust_demangler *rdm)
+{
+  uint64_t value;
+
+  if (parse_hex_nibbles (rdm, &value) != 1)
+    {
+      rdm->errored = 1;
+      return;
+    }
+
+  if (value == 0)
+    PRINT ("false");
+  else if (value == 1)
+    PRINT ("true");
+  else
+    rdm->errored = 1;
+}
+
+static void
+demangle_const_char (struct rust_demangler *rdm)
+{
+  size_t hex_len;
+  uint64_t value;
+
+  hex_len = parse_hex_nibbles (rdm, &value);
+
+  if (hex_len == 0 || hex_len > 8)
+    {
+      rdm->errored = 1;
       return;
     }
 
-  print_uint64 (rdm, value);
+  /* Match Rust's character "debug" output as best as we can. */
+  PRINT ("'");
+  if (value == '\t')
+    PRINT ("\\t");
+  else if (value == '\r')
+    PRINT ("\\r");
+  else if (value == '\n')
+    PRINT ("\\n");
+  else if (value > ' ' && value < '~')
+    /* Rust also considers many non-ASCII codepoints to be printable, but
+       that logic is not easily ported to C. */
+    print_str (rdm, (char *) &value, 1);
+  else
+    {
+      PRINT ("\\u{");
+      print_uint64_hex (rdm, value);
+      PRINT ("}");
+    }
+  PRINT ("'");
 }
 
-/* A legacy hash is the prefix "h" followed by 16 lowercase hex digits.
-   The hex digits must contain at least 5 distinct digits. */
+/* A legacy hash is the prefix "h" followed by any number of lowercase
+   hex digits. (The compiler will always generate 16 hex digits, but
+   demanglers are expected to accept any number of digits.) */
 static int
 is_legacy_prefixed_hash (struct rust_mangled_ident ident)
 {
-  uint16_t seen;
-  int nibble;
-  size_t i, count;
+  size_t i;
 
-  if (ident.ascii_len != 17 || ident.ascii[0] != 'h')
+  if (ident.ascii[0] != 'h')
     return 0;
 
-  seen = 0;
-  for (i = 0; i < 16; i++)
-    {
-      nibble = decode_lower_hex_nibble (ident.ascii[1 + i]);
-      if (nibble < 0)
-        return 0;
-      seen |= (uint16_t)1 << nibble;
-    }
+  for (i = 1; i < ident.ascii_len; i++)
+    if (decode_lower_hex_nibble (ident.ascii[i]) < 0)
+      return 0;
 
-  /* Count how many distinct digits were seen. */
-  count = 0;
-  while (seen)
-    {
-      if (seen & 1)
-        count++;
-      seen >>= 1;
-    }
+  return 1;
+}
 
-  return count >= 5;
+/* Consumes a suffix of the form ".llvm.XXXXXX". */
+static void
+eat_llvm_suffix (struct rust_demangler *rdm)
+{
+  size_t i;
+  char c;
+
+  if (strncmp (&rdm->sym[rdm->next], ".llvm.", 6) != 0)
+    return;
+
+  for (i = rdm->next + 6; (c = rdm->sym[i]); i++)
+    if ((c < '0' || c > '9') && (c < 'A' || c > 'F') && c != '@')
+      return;
+
+  rdm->next = i;
 }
 
 int
@@ -1224,16 +1326,17 @@ rust_demangle_callback (const char *mangled, int options,
   if (rdm.version != -1 && !ISUPPER (rdm.sym[0]))
     return 0;
 
-  /* Rust symbols (v0) use only [_0-9a-zA-Z] characters. */
+  /* Rust symbols (v0) use only [_0-9a-zA-Z] characters, but LLVM might add
+     a suffix containing [.@] characters. */
   for (p = rdm.sym; *p; p++)
     {
       rdm.sym_len++;
 
-      if (*p == '_' || ISALNUM (*p))
+      if (*p == '_' || *p == '.' || *p == '@' || ISALNUM (*p))
         continue;
 
-      /* Legacy Rust symbols can also contain [.:$] characters. */
-      if (rdm.version == -1 && (*p == '$' || *p == '.' || *p == ':'))
+      /* Legacy Rust symbols can also contain [:$] characters. */
+      if (rdm.version == -1 && (*p == '$' || *p == ':'))
         continue;
 
       return 0;
@@ -1242,64 +1345,55 @@ rust_demangle_callback (const char *mangled, int options,
   /* Legacy Rust symbols need to be handled separately. */
   if (rdm.version == -1)
     {
-      /* Legacy Rust symbols always end with E. */
-      if (!(rdm.sym_len > 0 && rdm.sym[rdm.sym_len - 1] == 'E'))
+      /* Print the first component of the path. */
+      ident = parse_ident (&rdm);
+      if (rdm.errored || !ident.ascii)
         return 0;
-      rdm.sym_len--;
+      print_ident (&rdm, ident);
 
-      /* Legacy Rust symbols also always end with a path segment
-         that encodes a 16 hex digit hash, i.e. '17h[a-f0-9]{16}'.
-         This early check, before any parse_ident calls, should
-         quickly filter out most C++ symbols unrelated to Rust. */
-      if (!(rdm.sym_len > 19
-            && !memcmp (&rdm.sym[rdm.sym_len - 19], "17h", 3)))
-        return 0;
-
-      do
+      while (!eat (&rdm, 'E'))
         {
           ident = parse_ident (&rdm);
           if (rdm.errored || !ident.ascii)
             return 0;
-        }
-      while (rdm.next < rdm.sym_len);
 
-      /* The last path segment should be the hash. */
-      if (!is_legacy_prefixed_hash (ident))
-        return 0;
-
-      /* Reset the state for a second pass, to print the symbol. */
-      rdm.next = 0;
-      if (!rdm.verbose && rdm.sym_len > 19)
-        {
-          /* Hide the last segment, containing the hash, if not verbose. */
-          rdm.sym_len -= 19;
-        }
-
-      do
-        {
-          if (rdm.next > 0)
-            print_str (&rdm, "::", 2);
-
-          ident = parse_ident (&rdm);
-          print_ident (&rdm, ident);
-        }
-      while (rdm.next < rdm.sym_len);
+          /* Print the path component, unless it's the last component, we're
+             in verbose mode, and the component looks like a hash. */
+          if (peek (&rdm) != 'E'
+              || rdm.verbose
+              || !is_legacy_prefixed_hash (ident))
+            {
+              print_str (&rdm, "::", 2);
+	            print_ident (&rdm, ident);
+            }
+	      }
     }
   else
     {
       demangle_path (&rdm, 1);
 
       /* Skip instantiating crate. */
-      if (!rdm.errored && rdm.next < rdm.sym_len)
+      if (!rdm.errored && peek (&rdm) >= 'A' && peek (&rdm) <= 'Z')
         {
           rdm.skipping_printing = 1;
           demangle_path (&rdm, 0);
+	        rdm.skipping_printing = 0;
         }
+    }
+
+  /* Suppress suffixes of the form ".llvm.XXXXXX". */
+  eat_llvm_suffix (&rdm);
 
-      /* It's an error to not reach the end. */
-      rdm.errored |= rdm.next != rdm.sym_len;
+  /* But output other suffixes that start with a dot. */
+  if (peek (&rdm) == '.')
+    {
+      print_str (&rdm, &rdm.sym[rdm.next], rdm.sym_len - rdm.next);
+      rdm.next = rdm.sym_len;
     }
 
+  /* It's an error to not reach the end. */
+  rdm.errored |= rdm.next != rdm.sym_len;
+
   return !rdm.errored;
 }
diff --git a/libiberty/rust-demangle.c b/libiberty/rust-demangle.c
index b87365c85fe..9a457744b87 100644
--- a/libiberty/rust-demangle.c
+++ b/libiberty/rust-demangle.c
@@ -64,11 +64,16 @@ struct rust_demangler
   /* Non-zero if any error occurred. */
   int errored;
 
+  /* Non-zero if nothing should be printed. */
+  int skipping_printing;
+
   /* Non-zero if printing should be verbose (e.g. include hashes). */
   int verbose;
 
   /* Rust mangling version, with legacy mangling being -1. */
   int version;
+
+  uint64_t bound_lifetime_depth;
 };
 
 /* Parsing functions. */
@@ -81,6 +86,18 @@ peek (const struct rust_demangler *rdm)
   return 0;
 }
 
+static int
+eat (struct rust_demangler *rdm, char c)
+{
+  if (peek (rdm) == c)
+    {
+      rdm->next++;
+      return 1;
+    }
+  else
+    return 0;
+}
+
 static char
 next (struct rust_demangler *rdm)
 {
@@ -92,11 +109,87 @@ next (struct rust_demangler *rdm)
   return c;
 }
 
+static uint64_t
+parse_integer_62 (struct rust_demangler *rdm)
+{
+  char c;
+  uint64_t x;
+
+  if (eat (rdm, '_'))
+    return 0;
+
+  x = 0;
+  while (!eat (rdm, '_'))
+    {
+      c = next (rdm);
+      x *= 62;
+      if (ISDIGIT (c))
+        x += c - '0';
+      else if (ISLOWER (c))
+        x += 10 + (c - 'a');
+      else if (ISUPPER (c))
+        x += 10 + 26 + (c - 'A');
+      else
+        {
+          rdm->errored = 1;
+          return 0;
+        }
+    }
+  return x + 1;
+}
+
+static uint64_t
+parse_opt_integer_62 (struct rust_demangler *rdm, char tag)
+{
+  if (!eat (rdm, tag))
+    return 0;
+  return 1 + parse_integer_62 (rdm);
+}
+
+static uint64_t
+parse_disambiguator (struct rust_demangler *rdm)
+{
+  return parse_opt_integer_62 (rdm, 's');
+}
+
+static size_t
+parse_hex_nibbles (struct rust_demangler *rdm, uint64_t *value)
+{
+  char c;
+  size_t hex_len;
+
+  hex_len = 0;
+  *value = 0;
+
+  while (!eat (rdm, '_'))
+    {
+      *value <<= 4;
+
+      c = next (rdm);
+      if (ISDIGIT (c))
+        *value |= c - '0';
+      else if (c >= 'a' && c <= 'f')
+        *value |= 10 + (c - 'a');
+      else
+        {
+          rdm->errored = 1;
+          return 0;
+        }
+      hex_len++;
+    }
+
+  return hex_len;
+}
+
 struct rust_mangled_ident
 {
   /* ASCII part of the identifier. */
   const char *ascii;
   size_t ascii_len;
+
+  /* Punycode insertion codes for Unicode codepoints, if any. */
+  const char *punycode;
+  size_t punycode_len;
 };
 
 static struct rust_mangled_ident
@@ -104,10 +197,16 @@ parse_ident (struct rust_demangler *rdm)
 {
   char c;
   size_t start, len;
+  int is_punycode = 0;
   struct rust_mangled_ident ident;
 
   ident.ascii = NULL;
   ident.ascii_len = 0;
+  ident.punycode = NULL;
+  ident.punycode_len = 0;
+
+  if (rdm->version != -1)
+    is_punycode = eat (rdm, 'u');
 
   c = next (rdm);
   if (!ISDIGIT (c))
@@ -121,6 +220,10 @@ parse_ident (struct rust_demangler *rdm)
     while (ISDIGIT (peek (rdm)))
       len = len * 10 + (next (rdm) - '0');
 
+  /* Skip past the optional `_` separator (v0). */
+  if (rdm->version != -1)
+    eat (rdm, '_');
+
   start = rdm->next;
   rdm->next += len;
   /* Check for overflows. */
@@ -133,6 +236,27 @@ parse_ident (struct rust_demangler *rdm)
   ident.ascii = rdm->sym + start;
   ident.ascii_len = len;
 
+  if (is_punycode)
+    {
+      ident.punycode_len = 0;
+      while (ident.ascii_len > 0)
+        {
+          ident.ascii_len--;
+
+          /* The last '_' is a separator between ascii & punycode. */
+          if (ident.ascii[ident.ascii_len] == '_')
+            break;
+
+          ident.punycode_len++;
+        }
+      if (!ident.punycode_len)
+        {
+          rdm->errored = 1;
+          return ident;
+        }
+      ident.punycode = ident.ascii + (len - ident.punycode_len);
+    }
+
   if (ident.ascii_len == 0)
     ident.ascii = NULL;
 
@@ -144,12 +268,28 @@ parse_ident (struct rust_demangler *rdm)
 static void
 print_str (struct rust_demangler *rdm, const char *data, size_t len)
 {
-  if (!rdm->errored)
+  if (!rdm->errored && !rdm->skipping_printing)
     rdm->callback (data, len, rdm->callback_opaque);
 }
 
 #define PRINT(s) print_str (rdm, s, strlen (s))
 
+static void
+print_uint64 (struct rust_demangler *rdm, uint64_t x)
+{
+  char s[21];
+  snprintf (s, 21, "%" PRIu64, x);
+  PRINT (s);
+}
+
+static void
+print_uint64_hex (struct rust_demangler *rdm, uint64_t x)
+{
+  char s[17];
+  snprintf (s, 17, "%" PRIx64, x);
+  PRINT (s);
+}
+
 /* Return a 0x0-0xf value if the char is 0-9a-f, and -1 otherwise. */
 static int
 decode_lower_hex_nibble (char nibble)
@@ -230,9 +370,14 @@ static void
 print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
 {
   char unescaped;
-  size_t len;
+  uint8_t *out, *p, d;
+  size_t len, cap, punycode_pos, j;
+  /* Punycode parameters and state. */
+  uint32_t c;
+  size_t base, t_min, t_max, skew, damp, bias, i;
+  size_t delta, w, k, t;
 
-  if (rdm->errored)
+  if (rdm->errored || rdm->skipping_printing)
     return;
 
   if (rdm->version == -1)
@@ -273,8 +418,7 @@ print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
                 }
               else
                 {
-                  /* "." becomes "-" */
-                  PRINT ("-");
+                  PRINT (".");
                   len = 1;
                 }
             }
@@ -294,39 +438,865 @@ print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
 
       return;
     }
+
+  if (!ident.punycode)
+    {
+      print_str (rdm, ident.ascii, ident.ascii_len);
+      return;
+    }
+
+  len = 0;
+  cap = 4;
+  while (cap < ident.ascii_len)
+    {
+      cap *= 2;
+      /* Check for overflows. */
+      if ((cap * 4) / 4 != cap)
+        {
+          rdm->errored = 1;
+          return;
+        }
+    }
+
+  /* Store the output codepoints as groups of 4 UTF-8 bytes. */
+  out = (uint8_t *)malloc (cap * 4);
+  if (!out)
+    {
+      rdm->errored = 1;
+      return;
+    }
+
+  /* Populate initial output from ASCII fragment. */
+  for (len = 0; len < ident.ascii_len; len++)
+    {
+      p = out + 4 * len;
+      p[0] = 0;
+      p[1] = 0;
+      p[2] = 0;
+      p[3] = ident.ascii[len];
+    }
+
+  /* Punycode parameters and initial state. */
+  base = 36;
+  t_min = 1;
+  t_max = 26;
+  skew = 38;
+  damp = 700;
+  bias = 72;
+  i = 0;
+  c = 0x80;
+
+  punycode_pos = 0;
+  while (punycode_pos < ident.punycode_len)
+    {
+      /* Read one delta value. */
+      delta = 0;
+      w = 1;
+      k = 0;
+      do
+        {
+          k += base;
+          t = k < bias ? 0 : (k - bias);
+          if (t < t_min)
+            t = t_min;
+          if (t > t_max)
+            t = t_max;
+
+          if (punycode_pos >= ident.punycode_len)
+            goto cleanup;
+          d = ident.punycode[punycode_pos++];
+
+          if (ISLOWER (d))
+            d = d - 'a';
+          else if (ISDIGIT (d))
+            d = 26 + (d - '0');
+          else
+            {
+              rdm->errored = 1;
+              goto cleanup;
+            }
+
+          delta += d * w;
+          w *= base - t;
+        }
+      while (d >= t);
+
+      /* Compute the new insert position and character. */
+      len++;
+      i += delta;
+      c += i / len;
+      i %= len;
+
+      /* Ensure enough space is available. */
+      if (cap < len)
+        {
+          cap *= 2;
+          /* Check for overflows. */
+          if ((cap * 4) / 4 != cap || cap < len)
+            {
+              rdm->errored = 1;
+              goto cleanup;
+            }
+        }
+      p = (uint8_t *)realloc (out, cap * 4);
+      if (!p)
+        {
+          rdm->errored = 1;
+          goto cleanup;
+        }
+      out = p;
+
+      /* Move the characters after the insert position. */
+      p = out + i * 4;
+      memmove (p + 4, p, (len - i - 1) * 4);
+
+      /* Insert the new character, as UTF-8 bytes. */
+      p[0] = c >= 0x10000 ? 0xf0 | (c >> 18) : 0;
+      p[1] = c >= 0x800 ? (c < 0x10000 ? 0xe0 : 0x80) | ((c >> 12) & 0x3f) : 0;
+      p[2] = (c < 0x800 ? 0xc0 : 0x80) | ((c >> 6) & 0x3f);
+      p[3] = 0x80 | (c & 0x3f);
+
+      /* If there are no more deltas, decoding is complete. */
+      if (punycode_pos == ident.punycode_len)
+        break;
+
+      i++;
+
+      /* Perform bias adaptation. */
+      delta /= damp;
+      damp = 2;
+
+      delta += delta / len;
+      k = 0;
+      while (delta > ((base - t_min) * t_max) / 2)
+        {
+          delta /= base - t_min;
+          k += base;
+        }
+      bias = k + ((base - t_min + 1) * delta) / (delta + skew);
+    }
+
+  /* Remove all the 0 bytes to leave behind an UTF-8 string. */
+  for (i = 0, j = 0; i < len * 4; i++)
+    if (out[i] != 0)
+      out[j++] = out[i];
+
+  print_str (rdm, (const char *)out, j);
+
+cleanup:
+  free (out);
+}
+
+/* Print the lifetime according to the previously decoded index.
+   An index of `0` always refers to `'_`, but starting with `1`,
+   indices refer to late-bound lifetimes introduced by a binder. */
+static void
+print_lifetime_from_index (struct rust_demangler *rdm, uint64_t lt)
+{
+  char c;
+  uint64_t depth;
+
+  PRINT ("'");
+  if (lt == 0)
+    {
+      PRINT ("_");
+      return;
+    }
+
+  depth = rdm->bound_lifetime_depth - lt;
+  /* Try to print lifetimes alphabetically first. */
+  if (depth < 26)
+    {
+      c = 'a' + depth;
+      print_str (rdm, &c, 1);
+    }
+  else
+    {
+      /* Use `'_123` after running out of letters. */
+      PRINT ("_");
+      print_uint64 (rdm, depth);
+    }
+}
+
+/* Demangling functions. */
+
+static void demangle_binder (struct rust_demangler *rdm);
+static void demangle_path (struct rust_demangler *rdm, int in_value);
+static void demangle_generic_arg (struct rust_demangler *rdm);
+static void demangle_type (struct rust_demangler *rdm);
+static int demangle_path_maybe_open_generics (struct rust_demangler *rdm);
+static void demangle_dyn_trait (struct rust_demangler *rdm);
+static void demangle_const (struct rust_demangler *rdm);
+static void demangle_const_uint (struct rust_demangler *rdm);
+static void demangle_const_int (struct rust_demangler *rdm);
+static void demangle_const_bool (struct rust_demangler *rdm);
+static void demangle_const_char (struct rust_demangler *rdm);
+
+/* Optionally enter a binder ('G') for late-bound lifetimes,
+   printing e.g. `for<'a, 'b> `, and make those lifetimes visible
+   to the caller (via depth level, which the caller should reset). */
+static void
+demangle_binder (struct rust_demangler *rdm)
+{
+  uint64_t i, bound_lifetimes;
+
+  if (rdm->errored)
+    return;
+
+  bound_lifetimes = parse_opt_integer_62 (rdm, 'G');
+  if (bound_lifetimes > 0)
+    {
+      PRINT ("for<");
+      for (i = 0; i < bound_lifetimes; i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          rdm->bound_lifetime_depth++;
+          print_lifetime_from_index (rdm, 1);
+        }
+      PRINT ("> ");
+    }
+}
+
+static void
+demangle_path (struct rust_demangler *rdm, int in_value)
+{
+  char tag, ns;
+  int was_skipping_printing;
+  size_t i, backref, old_next;
+  uint64_t dis;
+  struct rust_mangled_ident name;
+
+  if (rdm->errored)
+    return;
+
+  switch (tag = next (rdm))
+    {
+    case 'C':
+      dis = parse_disambiguator (rdm);
+      name = parse_ident (rdm);
+
+      print_ident (rdm, name);
+      if (rdm->verbose)
+        {
+          PRINT ("[");
+          print_uint64_hex (rdm, dis);
+          PRINT ("]");
+        }
+      break;
+    case 'N':
+      ns = next (rdm);
+      if (!ISLOWER (ns) && !ISUPPER (ns))
+        {
+          rdm->errored = 1;
+          return;
+        }
+
+      demangle_path (rdm, in_value);
+
+      dis = parse_disambiguator (rdm);
+      name = parse_ident (rdm);
+
+      if (ISUPPER (ns))
+        {
+          /* Special namespaces, like closures and shims. */
+          PRINT ("::{");
+          switch (ns)
+            {
+            case 'C':
+              PRINT ("closure");
+              break;
+            case 'S':
+              PRINT ("shim");
+              break;
+            default:
+              print_str (rdm, &ns, 1);
+            }
+          if (name.ascii || name.punycode)
+            {
+              PRINT (":");
+              print_ident (rdm, name);
+            }
+          PRINT ("#");
+          print_uint64 (rdm, dis);
+          PRINT ("}");
+        }
+      else
+        {
+          /* Implementation-specific/unspecified namespaces. */
+
+          if (name.ascii || name.punycode)
+            {
+              PRINT ("::");
+              print_ident (rdm, name);
+            }
+        }
+      break;
+    case 'M':
+    case 'X':
+      /* Ignore the `impl`'s own path.*/
+      parse_disambiguator (rdm);
+      was_skipping_printing = rdm->skipping_printing;
+      rdm->skipping_printing = 1;
+      demangle_path (rdm, in_value);
+      rdm->skipping_printing = was_skipping_printing;
+      /* fallthrough */
+    case 'Y':
+      PRINT ("<");
+      demangle_type (rdm);
+      if (tag != 'M')
+        {
+          PRINT (" as ");
+          demangle_path (rdm, 0);
+        }
+      PRINT (">");
+      break;
+    case 'I':
+      demangle_path (rdm, in_value);
+      if (in_value)
+        PRINT ("::");
+      PRINT ("<");
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_generic_arg (rdm);
+        }
+      PRINT (">");
+      break;
+    case 'B':
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          demangle_path (rdm, in_value);
+          rdm->next = old_next;
+        }
+      break;
+    default:
+      rdm->errored = 1;
+      return;
+    }
+}
+
+static void
+demangle_generic_arg (struct rust_demangler *rdm)
+{
+  uint64_t lt;
+  if (eat (rdm, 'L'))
+    {
+      lt = parse_integer_62 (rdm);
+      print_lifetime_from_index (rdm, lt);
+    }
+  else if (eat (rdm, 'K'))
+    demangle_const (rdm);
+  else
+    demangle_type (rdm);
+}
+
+static const char *
+basic_type (char tag)
+{
+  switch (tag)
+    {
+    case 'b':
+      return "bool";
+    case 'c':
+      return "char";
+    case 'e':
+      return "str";
+    case 'u':
+      return "()";
+    case 'a':
+      return "i8";
+    case 's':
+      return "i16";
+    case 'l':
+      return "i32";
+    case 'x':
+      return "i64";
+    case 'n':
+      return "i128";
+    case 'i':
+      return "isize";
+    case 'h':
+      return "u8";
+    case 't':
+      return "u16";
+    case 'm':
+      return "u32";
+    case 'y':
+      return "u64";
+    case 'o':
+      return "u128";
+    case 'j':
+      return "usize";
+    case 'f':
+      return "f32";
+    case 'd':
+      return "f64";
+    case 'z':
+      return "!";
+    case 'p':
+      return "_";
+    case 'v':
+      return "...";
+
+    default:
+      return NULL;
+    }
+}
+
+static void
+demangle_type (struct rust_demangler *rdm)
+{
+  char tag;
+  size_t i, old_next, backref;
+  uint64_t lt, old_bound_lifetime_depth;
+  const char *basic;
+  struct rust_mangled_ident abi;
+
+  if (rdm->errored)
+    return;
+
+  tag = next (rdm);
+
+  basic = basic_type (tag);
+  if (basic)
+    {
+      PRINT (basic);
+      return;
+    }
+
+  switch (tag)
+    {
+    case 'R':
+    case 'Q':
+      PRINT ("&");
+      if (eat (rdm, 'L'))
+        {
+          lt = parse_integer_62 (rdm);
+          if (lt)
+            {
+              print_lifetime_from_index (rdm, lt);
+              PRINT (" ");
+            }
+        }
+      if (tag != 'R')
+        PRINT ("mut ");
+      demangle_type (rdm);
+      break;
+    case 'P':
+    case 'O':
+      PRINT ("*");
+      if (tag != 'P')
+        PRINT ("mut ");
+      else
+        PRINT ("const ");
+      demangle_type (rdm);
+      break;
+    case 'A':
+    case 'S':
+      PRINT ("[");
+      demangle_type (rdm);
+      if (tag == 'A')
+        {
+          PRINT ("; ");
+          demangle_const (rdm);
+        }
+      PRINT ("]");
+      break;
+    case 'T':
+      PRINT ("(");
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_type (rdm);
+        }
+      if (i == 1)
+        PRINT (",");
+      PRINT (")");
+      break;
+    case 'F':
+      old_bound_lifetime_depth = rdm->bound_lifetime_depth;
+      demangle_binder (rdm);
+
+      if (eat (rdm, 'U'))
+        PRINT ("unsafe ");
+
+      if (eat (rdm, 'K'))
+        {
+          if (eat (rdm, 'C'))
+            {
+              abi.ascii = "C";
+              abi.ascii_len = 1;
+            }
+          else
+            {
+              abi = parse_ident (rdm);
+              if (!abi.ascii || abi.punycode)
+                {
+                  rdm->errored = 1;
+                  goto restore;
+                }
+            }
+
+          PRINT ("extern \"");
+
+          /* If the ABI had any `-`, they were replaced with `_`,
+             so the parts between `_` have to be re-joined with `-`. */
+          for (i = 0; i < abi.ascii_len; i++)
+            {
+              if (abi.ascii[i] == '_')
+                {
+                  print_str (rdm, abi.ascii, i);
+                  PRINT ("-");
+                  abi.ascii += i + 1;
+                  abi.ascii_len -= i + 1;
+                  i = 0;
+                }
+            }
+          print_str (rdm, abi.ascii, abi.ascii_len);
+
+          PRINT ("\" ");
+        }
+
+      PRINT ("fn(");
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_type (rdm);
+        }
+      PRINT (")");
+
+      if (eat (rdm, 'u'))
+        {
+          /* Skip printing the return type if it's 'u', i.e. `()`. */
+        }
+      else
+        {
+          PRINT (" -> ");
+          demangle_type (rdm);
+        }
+
+    /* Restore `bound_lifetime_depth` to outside the binder. */
+    restore:
+      rdm->bound_lifetime_depth = old_bound_lifetime_depth;
+      break;
+    case 'D':
+      PRINT ("dyn ");
+
+      old_bound_lifetime_depth = rdm->bound_lifetime_depth;
+      demangle_binder (rdm);
+
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (" + ");
+          demangle_dyn_trait (rdm);
+        }
+
+      /* Restore `bound_lifetime_depth` to outside the binder. */
+      rdm->bound_lifetime_depth = old_bound_lifetime_depth;
+
+      if (!eat (rdm, 'L'))
+        {
+          rdm->errored = 1;
+          return;
+        }
+      lt = parse_integer_62 (rdm);
+      if (lt)
+        {
+          PRINT (" + ");
+          print_lifetime_from_index (rdm, lt);
+        }
+      break;
+    case 'B':
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          demangle_type (rdm);
+          rdm->next = old_next;
+        }
+      break;
+    default:
+      /* Go back to the tag, so `demangle_path` also sees it. */
+      rdm->next--;
+      demangle_path (rdm, 0);
+    }
+}
+
+/* A trait in a trait object may have some "existential projections"
+   (i.e. associated type bindings) after it, which should be printed
+   in the `<...>` of the trait, e.g. `dyn Trait<T, U, Assoc=X>`.
+   To this end, this method will keep the `<...>` of an 'I' path
+   open, by omitting the `>`, and return `Ok(true)` in that case. */
+static int
+demangle_path_maybe_open_generics (struct rust_demangler *rdm)
+{
+  int open;
+  size_t i, old_next, backref;
+
+  open = 0;
+
+  if (rdm->errored)
+    return open;
+
+  if (eat (rdm, 'B'))
+    {
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          open = demangle_path_maybe_open_generics (rdm);
+          rdm->next = old_next;
+        }
+    }
+  else if (eat (rdm, 'I'))
+    {
+      demangle_path (rdm, 0);
+      PRINT ("<");
+      open = 1;
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_generic_arg (rdm);
+        }
+    }
+  else
+    demangle_path (rdm, 0);
+  return open;
+}
+
+static void
+demangle_dyn_trait (struct rust_demangler *rdm)
+{
+  int open;
+  struct rust_mangled_ident name;
+
+  if (rdm->errored)
+    return;
+
+  open = demangle_path_maybe_open_generics (rdm);
+
+  while (eat (rdm, 'p'))
+    {
+      if (!open)
+        PRINT ("<");
+      else
+        PRINT (", ");
+      open = 1;
+
+      name = parse_ident (rdm);
+      print_ident (rdm, name);
+      PRINT (" = ");
+      demangle_type (rdm);
+    }
+
+  if (open)
+    PRINT (">");
+}
+
+static void
+demangle_const (struct rust_demangler *rdm)
+{
+  char ty_tag;
+  size_t old_next, backref;
+
+  if (rdm->errored)
+    return;
+
+  if (eat (rdm, 'B'))
+    {
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          demangle_const (rdm);
+          rdm->next = old_next;
+        }
+      return;
+    }
+
+  ty_tag = next (rdm);
+  switch (ty_tag)
+    {
+    /* Placeholder. */
+    case 'p':
+      PRINT ("_");
+      return;
+
+    /* Unsigned integer types. */
+    case 'h':
+    case 't':
+    case 'm':
+    case 'y':
+    case 'o':
+    case 'j':
+      demangle_const_uint (rdm);
+      break;
+
+    /* Signed integer types. */
+    case 'a':
+    case 's':
+    case 'l':
+    case 'x':
+    case 'n':
+    case 'i':
+      demangle_const_int (rdm);
+      break;
+
+    /* Boolean. */
+    case 'b':
+      demangle_const_bool (rdm);
+      break;
+
+    /* Character. */
+    case 'c':
+      demangle_const_char (rdm);
+      break;
+
+    default:
+      rdm->errored = 1;
+      return;
+    }
+
+  if (rdm->errored)
+    return;
+
+  if (rdm->verbose)
+    {
+      PRINT (": ");
+      PRINT (basic_type (ty_tag));
+    }
+}
+
+static void
+demangle_const_uint (struct rust_demangler *rdm)
+{
+  size_t hex_len;
+  uint64_t value;
+
+  if (rdm->errored)
+    return;
+
+  hex_len = parse_hex_nibbles (rdm, &value);
+
+  if (hex_len > 16)
+    {
+      /* Print anything that doesn't fit in `uint64_t` verbatim. */
+      PRINT ("0x");
+      print_str (rdm, rdm->sym + (rdm->next - hex_len), hex_len);
+    }
+  else if (hex_len > 0)
+    print_uint64 (rdm, value);
+  else
+    rdm->errored = 1;
+}
+
+static void
+demangle_const_int (struct rust_demangler *rdm)
+{
+  if (eat (rdm, 'n'))
+    PRINT ("-");
+  demangle_const_uint (rdm);
+}
+
+static void
+demangle_const_bool (struct rust_demangler *rdm)
+{
+  uint64_t value;
+
+  if (parse_hex_nibbles (rdm, &value) != 1)
+    {
+      rdm->errored = 1;
+      return;
+    }
+
+  if (value == 0)
+    PRINT ("false");
+  else if (value == 1)
+    PRINT ("true");
+  else
+    rdm->errored = 1;
+}
+
+static void
+demangle_const_char (struct rust_demangler *rdm)
+{
+  size_t hex_len;
+  uint64_t value;
+
+  hex_len = parse_hex_nibbles (rdm, &value);
+
+  if (hex_len == 0 || hex_len > 8)
+    {
+      rdm->errored = 1;
+      return;
+    }
+
+  /* Match Rust's character "debug" output as best as we can. */
+  PRINT ("'");
+  if (value == '\t')
+    PRINT ("\\t");
+  else if (value == '\r')
+    PRINT ("\\r");
+  else if (value == '\n')
+    PRINT ("\\n");
+  else if (value > ' ' && value < '~')
+    /* Rust also considers many non-ASCII codepoints to be printable, but
+       that logic is not easily ported to C. */
+    print_str (rdm, (char *) &value, 1);
+  else
+    {
+      PRINT ("\\u{");
+      print_uint64_hex (rdm, value);
+      PRINT ("}");
+    }
+  PRINT ("'");
 }
 
-/* A legacy hash is the prefix "h" followed by 16 lowercase hex digits.
-   The hex digits must contain at least 5 distinct digits. */
+/* A legacy hash is the prefix "h" followed by any number of lowercase
+   hex digits. (The compiler will always generate 16 hex digits, but
+   demanglers are expected to accept any number of digits.) */
 static int
 is_legacy_prefixed_hash (struct rust_mangled_ident ident)
 {
-  uint16_t seen;
-  int nibble;
-  size_t i, count;
+  size_t i;
 
-  if (ident.ascii_len != 17 || ident.ascii[0] != 'h')
+  if (ident.ascii[0] != 'h')
     return 0;
 
-  seen = 0;
-  for (i = 0; i < 16; i++)
-    {
-      nibble = decode_lower_hex_nibble (ident.ascii[1 + i]);
-      if (nibble < 0)
-        return 0;
-      seen |= (uint16_t)1 << nibble;
-    }
+  for (i = 1; i < ident.ascii_len; i++)
+    if (decode_lower_hex_nibble (ident.ascii[i]) < 0)
+      return 0;
 
-  /* Count how many distinct digits were seen. */
-  count = 0;
-  while (seen)
-    {
-      if (seen & 1)
-        count++;
-      seen >>= 1;
-    }
+  return 1;
+}
 
-  return count >= 5;
+/* Consumes a suffix of the form ".llvm.XXXXXX". */
+static void
+eat_llvm_suffix (struct rust_demangler *rdm)
+{
+  size_t i;
+  char c;
+
+  if (strncmp (&rdm->sym[rdm->next], ".llvm.", 6) != 0)
+    return;
+
+  for (i = rdm->next + 6; (c = rdm->sym[i]); i++)
+    if ((c < '0' || c > '9') && (c < 'A' || c > 'F') && c != '@')
+      return;
+
+  rdm->next = i;
 }
 
 int
@@ -345,11 +1315,15 @@ rust_demangle_callback (const char *mangled, int options,
 
   rdm.next = 0;
   rdm.errored = 0;
+  rdm.skipping_printing = 0;
   rdm.verbose = (options & DMGL_VERBOSE) != 0;
   rdm.version = 0;
+  rdm.bound_lifetime_depth = 0;
 
-  /* Rust symbols always start with _ZN (legacy). */
-  if (rdm.sym[0] == '_' && rdm.sym[1] == 'Z' && rdm.sym[2] == 'N')
+  /* Rust symbols always start with _R (v0) or _ZN (legacy). */
+  if (rdm.sym[0] == '_' && rdm.sym[1] == 'R')
+    rdm.sym += 2;
+  else if (rdm.sym[0] == '_' && rdm.sym[1] == 'Z' && rdm.sym[2] == 'N')
     {
       rdm.sym += 3;
       rdm.version = -1;
@@ -357,15 +1331,21 @@ rust_demangle_callback (const char *mangled, int options,
   else
     return 0;
 
-  /* Legacy Rust symbols use only [_0-9a-zA-Z.:$] characters. */
+  /* Paths (v0) always start with uppercase characters. */
+  if (rdm.version != -1 && !ISUPPER (rdm.sym[0]))
+    return 0;
+
+  /* Rust symbols (v0) use only [_0-9a-zA-Z] characters, but LLVM might add
+     a suffix containing [.@] characters. */
   for (p = rdm.sym; *p; p++)
     {
       rdm.sym_len++;
 
-      if (*p == '_' || ISALNUM (*p))
+      if (*p == '_' || *p == '.' || *p == '@' || ISALNUM (*p))
         continue;
 
-      if (rdm.version == -1 && (*p == '$' || *p == '.' || *p == ':'))
+      /* Legacy Rust symbols can also contain [:$] characters. */
+      if (rdm.version == -1 && (*p == '$' || *p == ':'))
         continue;
 
       return 0;
@@ -374,51 +1354,54 @@ rust_demangle_callback (const char *mangled, int options,
   /* Legacy Rust symbols need to be handled separately. */
   if (rdm.version == -1)
     {
-      /* Legacy Rust symbols always end with E. */
-      if (!(rdm.sym_len > 0 && rdm.sym[rdm.sym_len - 1] == 'E'))
+      /* Print the first component of the path. */
+      ident = parse_ident (&rdm);
+      if (rdm.errored || !ident.ascii)
         return 0;
-      rdm.sym_len--;
+      print_ident (&rdm, ident);
 
-      /* Legacy Rust symbols also always end with a path segment
-         that encodes a 16 hex digit hash, i.e. '17h[a-f0-9]{16}'.
-         This early check, before any parse_ident calls, should
-         quickly filter out most C++ symbols unrelated to Rust. */
-      if (!(rdm.sym_len > 19
-            && !memcmp (&rdm.sym[rdm.sym_len - 19], "17h", 3)))
-        return 0;
-
-      do
+      while (!eat (&rdm, 'E'))
         {
           ident = parse_ident (&rdm);
           if (rdm.errored || !ident.ascii)
             return 0;
-        }
-      while (rdm.next < rdm.sym_len);
 
-      /* The last path segment should be the hash. */
-      if (!is_legacy_prefixed_hash (ident))
-        return 0;
-
-      /* Reset the state for a second pass, to print the symbol. */
-      rdm.next = 0;
-      if (!rdm.verbose && rdm.sym_len > 19)
-        {
-          /* Hide the last segment, containing the hash, if not verbose. */
-          rdm.sym_len -= 19;
-        }
-
-      do
-        {
-          if (rdm.next > 0)
-            print_str (&rdm, "::", 2);
-
-          ident = parse_ident (&rdm);
-          print_ident (&rdm, ident);
-        }
-      while (rdm.next < rdm.sym_len);
+          /* Print the path component, unless it's the last component, we're
+             in verbose mode, and the component looks like a hash. */
+          if (peek (&rdm) != 'E'
+              || rdm.verbose
+              || !is_legacy_prefixed_hash (ident))
+            {
+              print_str (&rdm, "::", 2);
+	            print_ident (&rdm, ident);
+            }
+	      }
     }
   else
-    return 0;
+    {
+      demangle_path (&rdm, 1);
+
+      /* Skip instantiating crate. */
+      if (!rdm.errored && peek (&rdm) >= 'A' && peek (&rdm) <= 'Z')
+        {
+          rdm.skipping_printing = 1;
+          demangle_path (&rdm, 0);
+	        rdm.skipping_printing = 0;
+        }
+    }
+
+  /* Suppress suffixes of the form ".llvm.XXXXXX". */
+  eat_llvm_suffix (&rdm);
+
+  /* But output other suffixes that start with a dot. */
+  if (peek (&rdm) == '.')
+    {
+      print_str (&rdm, &rdm.sym[rdm.next], rdm.sym_len - rdm.next);
+      rdm.next = rdm.sym_len;
+    }
+
+  /* It's an error to not reach the end. */
+  rdm.errored |= rdm.next != rdm.sym_len;
 
   return !rdm.errored;
 }
diff --git a/libiberty/testsuite/rust-demangle-expected b/libiberty/testsuite/rust-demangle-expected
index 74774794736..24522d9b79e 100644
--- a/libiberty/testsuite/rust-demangle-expected
+++ b/libiberty/testsuite/rust-demangle-expected
@@ -26,15 +26,15 @@ main::main::he714a2e23ed7db23
 --format=auto
 _ZN4main4main17he714a2e23ed7db23E
 main::main
-# Hash is exactly 16 hex chars. Not more.
+# The hash can be more than 16 digits, as long as all are valid hex digits.
 --format=auto
 _ZN4main4main18h1e714a2e23ed7db23E
-main::main::h1e714a2e23ed7db23
-# Not less.
+main::main
+# Similarly, less than 16 hex digits in the hash is acceptable too.
 --format=auto
 _ZN4main4main16h714a2e23ed7db23E
-main::main::h714a2e23ed7db23
-# And not non-hex.
+main::main
+# But not non-hex.
 --format=auto
 _ZN4main4main17he714a2e23ed7db2gE
 main::main::he714a2e23ed7db2g
@@ -163,3 +163,87 @@ _ZN63_$LT$core..ptr..Unique$LT$T$GT$$u20$as$u20$core..ops..Deref$GT$5deref17h19f
 --format=rust
 _ZN11issue_609253foo37Foo$LT$issue_60925..llv$u6d$..Foo$GT$3foo17h059a991a004536adE
 issue_60925::foo::Foo<issue_60925::llvm::Foo>::foo
+#
+--format=rust
+_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h000b1ad6c4f30bd6E
+core::ops::function::FnOnce::call_once{{vtable.shim}}
+# ThinLTO suffix 1 + legacy.
+--format=rust
+_ZN3fooE.llvm.9D1C9369
+foo
+# Thin LTO suffix 2 + legacy.
+--format=rust
+_ZN3fooE.llvm.9D1C9369@@16
+foo
+# ThinLTO suffix 3 + legacy.
+--format=rust
+_ZN9backtrace3foo17hbb467fcdaea5d79bE.llvm.A5310EB9
+backtrace::foo
+# ThinLTO suffix 1 + v0.
+--format=rust
+_RC3foo.llvm.9D1C9369
+foo
+# ThinLTO suffix 2 + v0.
+--format=rust
+_RC3foo.llvm.9D1C9369@@16
+foo
+# ThinLTO suffix 3 + legacy.
+--format=rust
+_RNvC9backtrace3foo.llvm.A5310EB9
+backtrace::foo
+# LLVM branch label + legacy.
+--format=rust
+_ZN4core5slice77_$LT$impl$u20$core..ops..index..IndexMut$LT$I$GT$$u20$for$u20$$u5b$T$u5d$$GT$9index_mut17haf9727c2edfbc47bE.exit.i.i
+core::slice::<impl core::ops::index::IndexMut<I> for [T]>::index_mut.exit.i.i
+# LLVM branch label + v0.
+--format=rust
+_RNvNtNtNtNtCs92dm3009vxr_4rand4rngs7adapter9reseeding4fork23FORK_HANDLER_REGISTERED.0.0
+rand::rngs::adapter::reseeding::fork::FORK_HANDLER_REGISTERED.0.0
+# Closure.
+--format=rust
+_RNCNCNgCs6DXkGYLi8lr_2cc5spawn00B5_
+cc::spawn::{closure#0}::{closure#0}
+# UTF8 idents.
+--format=rust
+_RNqCs4fqI2P2rA04_11utf8_identsu30____7hkackfecea1cbdathfdh9hlq6y
+utf8_idents::საჭმელად_გემრიელი_სადილი
+# Crate with leading digit.
+--format=rust
+_RNvC6_123foo3bar
+123foo::bar
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_8UnsignedKhb_E
+<const_generic::Unsigned<11>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_6SignedKs98_E
+<const_generic::Signed<152>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_6SignedKanb_E
+<const_generic::Signed<-11>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4BoolKb0_E
+<const_generic::Bool<false>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4BoolKb1_E
+<const_generic::Bool<true>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKc76_E
+<const_generic::Char<'v'>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKca_E
+<const_generic::Char<'\n'>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKc2202_E
+<const_generic::Char<'\u{2202}'>>
+#
+--format=rust
+_RNvNvMCs4fqI2P2rA04_13const_genericINtB4_3FooKpE3foo3FOO
+<const_generic::Foo<_>>::foo::FOO
Eduard-Mihai Burtescu Nov. 1, 2020, 11:57 a.m. UTC | #13
Reading the diff patch, the v0 changes look great. I wouldn't be too worried
about the "printable character" aspect, there are similar Unicode-related
issues elsewhere, e.g. the "non-control ASCII" comment in decode_legacy_escape
(I suppose we could make it also go through the "print a non-control ASCII
character or some escape sequence" logic you added, if you think that helps).

However, I'm not sure about the legacy changes. Or rather, the .llvm. one, it's
not really Rust-specific, it's only in the rustc-demangle crate for convenience,
but C++ code compiled with Clang could run into the same problem - ideally,
stripping that suffix could be done uniformly in cplus-dem.c, but I decided
against making that change myself, for now.

I'm especially not comfortable removing the fast path, since that was the
condition under which I was able to make Rust demangling be attempted first,
before C++, in order to implement the Rust legacy demangling standalone,
separately from C++ demangling, so that it could be together with the v0 one.

It should be possible to keep the fast path if stripping .llvm.* suffixes is
done before either Rust or C++ demangling is attempted, but even if that would
be nice to have, IMO it should be a separate patch and not block v0 demangling.

As for the dataset, it doesn't include .llvm. because it's collected by rustc
itself, before LLVM had any chance to add any suffixes. This was done in order
to have several different mangling formats dumped at once for every symbol.
(It also contains symbols for e.g. functions that LLVM would've inlined away)

I can test the patch and upload the dataset tomorrow, but if you want to get
something committed sooner (is there a deadline for the next release?), feel
free to land the v0 changes (snprintf + const values) without the legacy ones.

Thanks,
- Eddy B.

On Sun, Nov 1, 2020, at 11:18, Nikhil Benesch wrote:
> 
> 
> On 10/29/20 12:16 AM, Nikhil Benesch wrote:
> > On Wed, Oct 28, 2020 at 7:53 PM Eduard-Mihai Burtescu <eddyb@lyken.rs> wrote:
> >> I agree that landing the extra changes later on top should be fine anyway,
> >> though when I make the sprintf -> snprintf change, I could provide the extra
> >> changes as well (either as a combined patch, or as a second tiny patch).
> >>
> >> Sadly I don't think I can get to either until next week, hope that's okay.
> > 
> > I can make the sprintf -> snprintf change as early as tomorrow.
> > 
> > I suspect I can also make the substantive const generics change, based
> > on the Rust implementation, though that's less of a promise.
> 
> Attached is an updated patch with both of the aforementioned changes. 
> The demangling of constants matches the demangling in rustc-demangle 
> library as best as I could manage. The strategy of demangling constant 
> chars via `format!("{:?}", char)` in Rust makes life hard for us in C, 
> because there is no easy way to replicate Rust's debug formatting for 
> chars. (Unless GCC has a Unicode library handy somewhere.)
> 
> In the course of this work I noticed several inconsistencies in how 
> rustc-demangle and libiberty were demangling some legacy Rust symbols. I 
> fixed those and added test cases, though the fixes required removing 
> some of the fast checks for whether a given symbol is a valid Rust symbol.
> 
> For ease of review, eddyb, I also attached the diff from your last diff 
> to mine. Since I don't have your collection of symbols I generated my 
> own by running nm on the materialized binary from
> https://github.com/MaterializeInc/materialize.
> 
> Let me know how I can help. I'm happy to address review feedback myself, 
> or I'm happy to hand things back over to you, eddyb.
> 
> Nikhil
> 
> Attachments:
> * rust-demangle-diff.patch
> * rust-demangle.patch
Nikhil Benesch Nov. 1, 2020, 5:26 p.m. UTC | #14
On 11/1/20 6:57 AM, Eduard-Mihai Burtescu wrote:
> Reading the diff patch, the v0 changes look great. I wouldn't be too worried
> about the "printable character" aspect, there are similar Unicode-related
> issues elsewhere, e.g. the "non-control ASCII" comment in decode_legacy_escape
> (I suppose we could make it also go through the "print a non-control ASCII
> character or some escape sequence" logic you added, if you think that helps).

No, it's entirely fine with me! I just wasn't sure if the small 
deviations in output were acceptable. It sounds like they are.

> However, I'm not sure about the legacy changes. Or rather, the .llvm. one, it's
> not really Rust-specific, it's only in the rustc-demangle crate for convenience,
> but C++ code compiled with Clang could run into the same problem - ideally,
> stripping that suffix could be done uniformly in cplus-dem.c, but I decided
> against making that change myself, for now.
> 
> I'm especially not comfortable removing the fast path, since that was the
> condition under which I was able to make Rust demangling be attempted first,
> before C++, in order to implement the Rust legacy demangling standalone,
> separately from C++ demangling, so that it could be together with the v0 one.

> It should be possible to keep the fast path if stripping .llvm.* suffixes is
> done before either Rust or C++ demangling is attempted, but even if that would
> be nice to have, IMO it should be a separate patch and not block v0 demangling.

That makes sense. I've attached updated patches (again generating a diff 
against both your original patch and trunk) without the changes to the 
legacy code. I did preserve one small hunk regarding the unescaping of a 
single '.' character in idents, as I believe that is just a 
straightforward bug in the existing code.

> I can test the patch and upload the dataset tomorrow, but if you want to get
> something committed sooner (is there a deadline for the next release?), feel
> free to land the v0 changes (snprintf + const values) without the legacy ones.

My understanding is that the GCC tree closes to new features on November 
16 (for "GCC 11 Stage 3"), but I'm not sure whether that applies to 
libiberty or whether this patch would be classified as a feature or a 
bugfix.

I don't have commit rights (nor am I even a GCC developer). Just wanted 
to tee things up for you and Ian this week. I'm very much looking 
forward to the new demangling scheme and didn't want to be just another 
+1 on the GitHub issue.

So certainly no time pressure from me. But perhaps someone from the GCC 
side can confirm whether we are under a bit of time pressure here given 
the GCC 11 release.

Cheers,
Nikhil
diff --git a/rust-demangle.c b/rust-demangle.c
index d604b3c..9cd8f99 100644
--- a/rust-demangle.c
+++ b/rust-demangle.c
@@ -143,6 +143,35 @@ parse_disambiguator (struct rust_demangler *rdm)
   return parse_opt_integer_62 (rdm, 's');
 }
 
+static size_t
+parse_hex_nibbles (struct rust_demangler *rdm, uint64_t *value)
+{
+  char c;
+  size_t hex_len;
+
+  hex_len = 0;
+  *value = 0;
+
+  while (!eat (rdm, '_'))
+    {
+      *value <<= 4;
+
+      c = next (rdm);
+      if (ISDIGIT (c))
+        *value |= c - '0';
+      else if (c >= 'a' && c <= 'f')
+        *value |= 10 + (c - 'a');
+      else
+        {
+          rdm->errored = 1;
+          return 0;
+        }
+      hex_len++;
+    }
+
+  return hex_len;
+}
+
 struct rust_mangled_ident
 {
   /* ASCII part of the identifier. */
@@ -240,7 +269,7 @@ static void
 print_uint64 (struct rust_demangler *rdm, uint64_t x)
 {
   char s[21];
-  sprintf (s, "%" PRIu64, x);
+  snprintf (s, 21, "%" PRIu64, x);
   PRINT (s);
 }
 
@@ -248,7 +277,7 @@ static void
 print_uint64_hex (struct rust_demangler *rdm, uint64_t x)
 {
   char s[17];
-  sprintf (s, "%" PRIx64, x);
+  snprintf (s, 17, "%" PRIx64, x);
   PRINT (s);
 }
 
@@ -380,8 +409,7 @@ print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
                 }
               else
                 {
-                  /* "." becomes "-" */
-                  PRINT ("-");
+                  PRINT (".");
                   len = 1;
                 }
             }
@@ -591,6 +619,9 @@ static int demangle_path_maybe_open_generics (struct rust_demangler *rdm);
 static void demangle_dyn_trait (struct rust_demangler *rdm);
 static void demangle_const (struct rust_demangler *rdm);
 static void demangle_const_uint (struct rust_demangler *rdm);
+static void demangle_const_int (struct rust_demangler *rdm);
+static void demangle_const_bool (struct rust_demangler *rdm);
+static void demangle_const_char (struct rust_demangler *rdm);
 
 /* Optionally enter a binder ('G') for late-bound lifetimes,
    printing e.g. `for<'a, 'b> `, and make those lifetimes visible
@@ -1089,6 +1120,11 @@ demangle_const (struct rust_demangler *rdm)
   ty_tag = next (rdm);
   switch (ty_tag)
     {
+    /* Placeholder. */
+    case 'p':
+      PRINT ("_");
+      return;
+
     /* Unsigned integer types. */
     case 'h':
     case 't':
@@ -1096,6 +1132,27 @@ demangle_const (struct rust_demangler *rdm)
     case 'y':
     case 'o':
     case 'j':
+      demangle_const_uint (rdm);
+      break;
+
+    /* Signed integer types. */
+    case 'a':
+    case 's':
+    case 'l':
+    case 'x':
+    case 'n':
+    case 'i':
+      demangle_const_int (rdm);
+      break;
+
+    /* Boolean. */
+    case 'b':
+      demangle_const_bool (rdm);
+      break;
+
+    /* Character. */
+    case 'c':
+      demangle_const_char (rdm);
       break;
 
     default:
@@ -1103,10 +1160,8 @@ demangle_const (struct rust_demangler *rdm)
       return;
     }
 
-  if (eat (rdm, 'p'))
-    PRINT ("_");
-  else
-    demangle_const_uint (rdm);
+  if (rdm->errored)
+    return;
 
   if (rdm->verbose)
     {
@@ -1118,41 +1173,86 @@ demangle_const (struct rust_demangler *rdm)
 static void
 demangle_const_uint (struct rust_demangler *rdm)
 {
-  char c;
   size_t hex_len;
   uint64_t value;
 
   if (rdm->errored)
     return;
 
-  value = 0;
-  hex_len = 0;
-  while (!eat (rdm, '_'))
-    {
-      value <<= 4;
+  hex_len = parse_hex_nibbles (rdm, &value);
 
-      c = next (rdm);
-      if (ISDIGIT (c))
-        value |= c - '0';
-      else if (c >= 'a' && c <= 'f')
-        value |= 10 + (c - 'a');
-      else
-        {
-          rdm->errored = 1;
-          return;
-        }
-      hex_len++;
-    }
-
-  /* Print anything that doesn't fit in `uint64_t` verbatim. */
   if (hex_len > 16)
     {
+      /* Print anything that doesn't fit in `uint64_t` verbatim. */
       PRINT ("0x");
       print_str (rdm, rdm->sym + (rdm->next - hex_len), hex_len);
+    }
+  else if (hex_len > 0)
+    print_uint64 (rdm, value);
+  else
+    rdm->errored = 1;
+}
+
+static void
+demangle_const_int (struct rust_demangler *rdm)
+{
+  if (eat (rdm, 'n'))
+    PRINT ("-");
+  demangle_const_uint (rdm);
+}
+
+static void
+demangle_const_bool (struct rust_demangler *rdm)
+{
+  uint64_t value;
+
+  if (parse_hex_nibbles (rdm, &value) != 1)
+    {
+      rdm->errored = 1;
       return;
     }
 
-  print_uint64 (rdm, value);
+  if (value == 0)
+    PRINT ("false");
+  else if (value == 1)
+    PRINT ("true");
+  else
+    rdm->errored = 1;
+}
+
+static void
+demangle_const_char (struct rust_demangler *rdm)
+{
+  size_t hex_len;
+  uint64_t value;
+
+  hex_len = parse_hex_nibbles (rdm, &value);
+
+  if (hex_len == 0 || hex_len > 8)
+    {
+      rdm->errored = 1;
+      return;
+    }
+
+  /* Match Rust's character "debug" output as best as we can. */
+  PRINT ("'");
+  if (value == '\t')
+    PRINT ("\\t");
+  else if (value == '\r')
+    PRINT ("\\r");
+  else if (value == '\n')
+    PRINT ("\\n");
+  else if (value > ' ' && value < '~')
+    /* Rust also considers many non-ASCII codepoints to be printable, but
+       that logic is not easily ported to C. */
+    print_str (rdm, (char *) &value, 1);
+  else
+    {
+      PRINT ("\\u{");
+      print_uint64_hex (rdm, value);
+      PRINT ("}");
+    }
+  PRINT ("'");
 }
 
 /* A legacy hash is the prefix "h" followed by 16 lowercase hex digits.
diff --git a/libiberty/rust-demangle.c b/libiberty/rust-demangle.c
index b87365c85fe..f6b63a60652 100644
--- a/libiberty/rust-demangle.c
+++ b/libiberty/rust-demangle.c
@@ -64,11 +64,16 @@ struct rust_demangler
   /* Non-zero if any error occurred. */
   int errored;
 
+  /* Non-zero if nothing should be printed. */
+  int skipping_printing;
+
   /* Non-zero if printing should be verbose (e.g. include hashes). */
   int verbose;
 
   /* Rust mangling version, with legacy mangling being -1. */
   int version;
+
+  uint64_t bound_lifetime_depth;
 };
 
 /* Parsing functions. */
@@ -81,6 +86,18 @@ peek (const struct rust_demangler *rdm)
   return 0;
 }
 
+static int
+eat (struct rust_demangler *rdm, char c)
+{
+  if (peek (rdm) == c)
+    {
+      rdm->next++;
+      return 1;
+    }
+  else
+    return 0;
+}
+
 static char
 next (struct rust_demangler *rdm)
 {
@@ -92,11 +109,87 @@ next (struct rust_demangler *rdm)
   return c;
 }
 
+static uint64_t
+parse_integer_62 (struct rust_demangler *rdm)
+{
+  char c;
+  uint64_t x;
+
+  if (eat (rdm, '_'))
+    return 0;
+
+  x = 0;
+  while (!eat (rdm, '_'))
+    {
+      c = next (rdm);
+      x *= 62;
+      if (ISDIGIT (c))
+        x += c - '0';
+      else if (ISLOWER (c))
+        x += 10 + (c - 'a');
+      else if (ISUPPER (c))
+        x += 10 + 26 + (c - 'A');
+      else
+        {
+          rdm->errored = 1;
+          return 0;
+        }
+    }
+  return x + 1;
+}
+
+static uint64_t
+parse_opt_integer_62 (struct rust_demangler *rdm, char tag)
+{
+  if (!eat (rdm, tag))
+    return 0;
+  return 1 + parse_integer_62 (rdm);
+}
+
+static uint64_t
+parse_disambiguator (struct rust_demangler *rdm)
+{
+  return parse_opt_integer_62 (rdm, 's');
+}
+
+static size_t
+parse_hex_nibbles (struct rust_demangler *rdm, uint64_t *value)
+{
+  char c;
+  size_t hex_len;
+
+  hex_len = 0;
+  *value = 0;
+
+  while (!eat (rdm, '_'))
+    {
+      *value <<= 4;
+
+      c = next (rdm);
+      if (ISDIGIT (c))
+        *value |= c - '0';
+      else if (c >= 'a' && c <= 'f')
+        *value |= 10 + (c - 'a');
+      else
+        {
+          rdm->errored = 1;
+          return 0;
+        }
+      hex_len++;
+    }
+
+  return hex_len;
+}
+
 struct rust_mangled_ident
 {
   /* ASCII part of the identifier. */
   const char *ascii;
   size_t ascii_len;
+
+  /* Punycode insertion codes for Unicode codepoints, if any. */
+  const char *punycode;
+  size_t punycode_len;
 };
 
 static struct rust_mangled_ident
@@ -104,10 +197,16 @@ parse_ident (struct rust_demangler *rdm)
 {
   char c;
   size_t start, len;
+  int is_punycode = 0;
   struct rust_mangled_ident ident;
 
   ident.ascii = NULL;
   ident.ascii_len = 0;
+  ident.punycode = NULL;
+  ident.punycode_len = 0;
+
+  if (rdm->version != -1)
+    is_punycode = eat (rdm, 'u');
 
   c = next (rdm);
   if (!ISDIGIT (c))
@@ -121,6 +220,10 @@ parse_ident (struct rust_demangler *rdm)
     while (ISDIGIT (peek (rdm)))
       len = len * 10 + (next (rdm) - '0');
 
+  /* Skip past the optional `_` separator (v0). */
+  if (rdm->version != -1)
+    eat (rdm, '_');
+
   start = rdm->next;
   rdm->next += len;
   /* Check for overflows. */
@@ -133,6 +236,27 @@ parse_ident (struct rust_demangler *rdm)
   ident.ascii = rdm->sym + start;
   ident.ascii_len = len;
 
+  if (is_punycode)
+    {
+      ident.punycode_len = 0;
+      while (ident.ascii_len > 0)
+        {
+          ident.ascii_len--;
+
+          /* The last '_' is a separator between ascii & punycode. */
+          if (ident.ascii[ident.ascii_len] == '_')
+            break;
+
+          ident.punycode_len++;
+        }
+      if (!ident.punycode_len)
+        {
+          rdm->errored = 1;
+          return ident;
+        }
+      ident.punycode = ident.ascii + (len - ident.punycode_len);
+    }
+
   if (ident.ascii_len == 0)
     ident.ascii = NULL;
 
@@ -144,12 +268,28 @@ parse_ident (struct rust_demangler *rdm)
 static void
 print_str (struct rust_demangler *rdm, const char *data, size_t len)
 {
-  if (!rdm->errored)
+  if (!rdm->errored && !rdm->skipping_printing)
     rdm->callback (data, len, rdm->callback_opaque);
 }
 
 #define PRINT(s) print_str (rdm, s, strlen (s))
 
+static void
+print_uint64 (struct rust_demangler *rdm, uint64_t x)
+{
+  char s[21];
+  snprintf (s, 21, "%" PRIu64, x);
+  PRINT (s);
+}
+
+static void
+print_uint64_hex (struct rust_demangler *rdm, uint64_t x)
+{
+  char s[17];
+  snprintf (s, 17, "%" PRIx64, x);
+  PRINT (s);
+}
+
 /* Return a 0x0-0xf value if the char is 0-9a-f, and -1 otherwise. */
 static int
 decode_lower_hex_nibble (char nibble)
@@ -230,9 +370,14 @@ static void
 print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
 {
   char unescaped;
-  size_t len;
+  uint8_t *out, *p, d;
+  size_t len, cap, punycode_pos, j;
+  /* Punycode parameters and state. */
+  uint32_t c;
+  size_t base, t_min, t_max, skew, damp, bias, i;
+  size_t delta, w, k, t;
 
-  if (rdm->errored)
+  if (rdm->errored || rdm->skipping_printing)
     return;
 
   if (rdm->version == -1)
@@ -273,8 +418,7 @@ print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
                 }
               else
                 {
-                  /* "." becomes "-" */
-                  PRINT ("-");
+                  PRINT (".");
                   len = 1;
                 }
             }
@@ -294,6 +438,830 @@ print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
 
       return;
     }
+
+  if (!ident.punycode)
+    {
+      print_str (rdm, ident.ascii, ident.ascii_len);
+      return;
+    }
+
+  len = 0;
+  cap = 4;
+  while (cap < ident.ascii_len)
+    {
+      cap *= 2;
+      /* Check for overflows. */
+      if ((cap * 4) / 4 != cap)
+        {
+          rdm->errored = 1;
+          return;
+        }
+    }
+
+  /* Store the output codepoints as groups of 4 UTF-8 bytes. */
+  out = (uint8_t *)malloc (cap * 4);
+  if (!out)
+    {
+      rdm->errored = 1;
+      return;
+    }
+
+  /* Populate initial output from ASCII fragment. */
+  for (len = 0; len < ident.ascii_len; len++)
+    {
+      p = out + 4 * len;
+      p[0] = 0;
+      p[1] = 0;
+      p[2] = 0;
+      p[3] = ident.ascii[len];
+    }
+
+  /* Punycode parameters and initial state. */
+  base = 36;
+  t_min = 1;
+  t_max = 26;
+  skew = 38;
+  damp = 700;
+  bias = 72;
+  i = 0;
+  c = 0x80;
+
+  punycode_pos = 0;
+  while (punycode_pos < ident.punycode_len)
+    {
+      /* Read one delta value. */
+      delta = 0;
+      w = 1;
+      k = 0;
+      do
+        {
+          k += base;
+          t = k < bias ? 0 : (k - bias);
+          if (t < t_min)
+            t = t_min;
+          if (t > t_max)
+            t = t_max;
+
+          if (punycode_pos >= ident.punycode_len)
+            goto cleanup;
+          d = ident.punycode[punycode_pos++];
+
+          if (ISLOWER (d))
+            d = d - 'a';
+          else if (ISDIGIT (d))
+            d = 26 + (d - '0');
+          else
+            {
+              rdm->errored = 1;
+              goto cleanup;
+            }
+
+          delta += d * w;
+          w *= base - t;
+        }
+      while (d >= t);
+
+      /* Compute the new insert position and character. */
+      len++;
+      i += delta;
+      c += i / len;
+      i %= len;
+
+      /* Ensure enough space is available. */
+      if (cap < len)
+        {
+          cap *= 2;
+          /* Check for overflows. */
+          if ((cap * 4) / 4 != cap || cap < len)
+            {
+              rdm->errored = 1;
+              goto cleanup;
+            }
+        }
+      p = (uint8_t *)realloc (out, cap * 4);
+      if (!p)
+        {
+          rdm->errored = 1;
+          goto cleanup;
+        }
+      out = p;
+
+      /* Move the characters after the insert position. */
+      p = out + i * 4;
+      memmove (p + 4, p, (len - i - 1) * 4);
+
+      /* Insert the new character, as UTF-8 bytes. */
+      p[0] = c >= 0x10000 ? 0xf0 | (c >> 18) : 0;
+      p[1] = c >= 0x800 ? (c < 0x10000 ? 0xe0 : 0x80) | ((c >> 12) & 0x3f) : 0;
+      p[2] = (c < 0x800 ? 0xc0 : 0x80) | ((c >> 6) & 0x3f);
+      p[3] = 0x80 | (c & 0x3f);
+
+      /* If there are no more deltas, decoding is complete. */
+      if (punycode_pos == ident.punycode_len)
+        break;
+
+      i++;
+
+      /* Perform bias adaptation. */
+      delta /= damp;
+      damp = 2;
+
+      delta += delta / len;
+      k = 0;
+      while (delta > ((base - t_min) * t_max) / 2)
+        {
+          delta /= base - t_min;
+          k += base;
+        }
+      bias = k + ((base - t_min + 1) * delta) / (delta + skew);
+    }
+
+  /* Remove all the 0 bytes to leave behind an UTF-8 string. */
+  for (i = 0, j = 0; i < len * 4; i++)
+    if (out[i] != 0)
+      out[j++] = out[i];
+
+  print_str (rdm, (const char *)out, j);
+
+cleanup:
+  free (out);
+}
+
+/* Print the lifetime according to the previously decoded index.
+   An index of `0` always refers to `'_`, but starting with `1`,
+   indices refer to late-bound lifetimes introduced by a binder. */
+static void
+print_lifetime_from_index (struct rust_demangler *rdm, uint64_t lt)
+{
+  char c;
+  uint64_t depth;
+
+  PRINT ("'");
+  if (lt == 0)
+    {
+      PRINT ("_");
+      return;
+    }
+
+  depth = rdm->bound_lifetime_depth - lt;
+  /* Try to print lifetimes alphabetically first. */
+  if (depth < 26)
+    {
+      c = 'a' + depth;
+      print_str (rdm, &c, 1);
+    }
+  else
+    {
+      /* Use `'_123` after running out of letters. */
+      PRINT ("_");
+      print_uint64 (rdm, depth);
+    }
+}
+
+/* Demangling functions. */
+
+static void demangle_binder (struct rust_demangler *rdm);
+static void demangle_path (struct rust_demangler *rdm, int in_value);
+static void demangle_generic_arg (struct rust_demangler *rdm);
+static void demangle_type (struct rust_demangler *rdm);
+static int demangle_path_maybe_open_generics (struct rust_demangler *rdm);
+static void demangle_dyn_trait (struct rust_demangler *rdm);
+static void demangle_const (struct rust_demangler *rdm);
+static void demangle_const_uint (struct rust_demangler *rdm);
+static void demangle_const_int (struct rust_demangler *rdm);
+static void demangle_const_bool (struct rust_demangler *rdm);
+static void demangle_const_char (struct rust_demangler *rdm);
+
+/* Optionally enter a binder ('G') for late-bound lifetimes,
+   printing e.g. `for<'a, 'b> `, and make those lifetimes visible
+   to the caller (via depth level, which the caller should reset). */
+static void
+demangle_binder (struct rust_demangler *rdm)
+{
+  uint64_t i, bound_lifetimes;
+
+  if (rdm->errored)
+    return;
+
+  bound_lifetimes = parse_opt_integer_62 (rdm, 'G');
+  if (bound_lifetimes > 0)
+    {
+      PRINT ("for<");
+      for (i = 0; i < bound_lifetimes; i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          rdm->bound_lifetime_depth++;
+          print_lifetime_from_index (rdm, 1);
+        }
+      PRINT ("> ");
+    }
+}
+
+static void
+demangle_path (struct rust_demangler *rdm, int in_value)
+{
+  char tag, ns;
+  int was_skipping_printing;
+  size_t i, backref, old_next;
+  uint64_t dis;
+  struct rust_mangled_ident name;
+
+  if (rdm->errored)
+    return;
+
+  switch (tag = next (rdm))
+    {
+    case 'C':
+      dis = parse_disambiguator (rdm);
+      name = parse_ident (rdm);
+
+      print_ident (rdm, name);
+      if (rdm->verbose)
+        {
+          PRINT ("[");
+          print_uint64_hex (rdm, dis);
+          PRINT ("]");
+        }
+      break;
+    case 'N':
+      ns = next (rdm);
+      if (!ISLOWER (ns) && !ISUPPER (ns))
+        {
+          rdm->errored = 1;
+          return;
+        }
+
+      demangle_path (rdm, in_value);
+
+      dis = parse_disambiguator (rdm);
+      name = parse_ident (rdm);
+
+      if (ISUPPER (ns))
+        {
+          /* Special namespaces, like closures and shims. */
+          PRINT ("::{");
+          switch (ns)
+            {
+            case 'C':
+              PRINT ("closure");
+              break;
+            case 'S':
+              PRINT ("shim");
+              break;
+            default:
+              print_str (rdm, &ns, 1);
+            }
+          if (name.ascii || name.punycode)
+            {
+              PRINT (":");
+              print_ident (rdm, name);
+            }
+          PRINT ("#");
+          print_uint64 (rdm, dis);
+          PRINT ("}");
+        }
+      else
+        {
+          /* Implementation-specific/unspecified namespaces. */
+
+          if (name.ascii || name.punycode)
+            {
+              PRINT ("::");
+              print_ident (rdm, name);
+            }
+        }
+      break;
+    case 'M':
+    case 'X':
+      /* Ignore the `impl`'s own path.*/
+      parse_disambiguator (rdm);
+      was_skipping_printing = rdm->skipping_printing;
+      rdm->skipping_printing = 1;
+      demangle_path (rdm, in_value);
+      rdm->skipping_printing = was_skipping_printing;
+      /* fallthrough */
+    case 'Y':
+      PRINT ("<");
+      demangle_type (rdm);
+      if (tag != 'M')
+        {
+          PRINT (" as ");
+          demangle_path (rdm, 0);
+        }
+      PRINT (">");
+      break;
+    case 'I':
+      demangle_path (rdm, in_value);
+      if (in_value)
+        PRINT ("::");
+      PRINT ("<");
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_generic_arg (rdm);
+        }
+      PRINT (">");
+      break;
+    case 'B':
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          demangle_path (rdm, in_value);
+          rdm->next = old_next;
+        }
+      break;
+    default:
+      rdm->errored = 1;
+      return;
+    }
+}
+
+static void
+demangle_generic_arg (struct rust_demangler *rdm)
+{
+  uint64_t lt;
+  if (eat (rdm, 'L'))
+    {
+      lt = parse_integer_62 (rdm);
+      print_lifetime_from_index (rdm, lt);
+    }
+  else if (eat (rdm, 'K'))
+    demangle_const (rdm);
+  else
+    demangle_type (rdm);
+}
+
+static const char *
+basic_type (char tag)
+{
+  switch (tag)
+    {
+    case 'b':
+      return "bool";
+    case 'c':
+      return "char";
+    case 'e':
+      return "str";
+    case 'u':
+      return "()";
+    case 'a':
+      return "i8";
+    case 's':
+      return "i16";
+    case 'l':
+      return "i32";
+    case 'x':
+      return "i64";
+    case 'n':
+      return "i128";
+    case 'i':
+      return "isize";
+    case 'h':
+      return "u8";
+    case 't':
+      return "u16";
+    case 'm':
+      return "u32";
+    case 'y':
+      return "u64";
+    case 'o':
+      return "u128";
+    case 'j':
+      return "usize";
+    case 'f':
+      return "f32";
+    case 'd':
+      return "f64";
+    case 'z':
+      return "!";
+    case 'p':
+      return "_";
+    case 'v':
+      return "...";
+
+    default:
+      return NULL;
+    }
+}
+
+static void
+demangle_type (struct rust_demangler *rdm)
+{
+  char tag;
+  size_t i, old_next, backref;
+  uint64_t lt, old_bound_lifetime_depth;
+  const char *basic;
+  struct rust_mangled_ident abi;
+
+  if (rdm->errored)
+    return;
+
+  tag = next (rdm);
+
+  basic = basic_type (tag);
+  if (basic)
+    {
+      PRINT (basic);
+      return;
+    }
+
+  switch (tag)
+    {
+    case 'R':
+    case 'Q':
+      PRINT ("&");
+      if (eat (rdm, 'L'))
+        {
+          lt = parse_integer_62 (rdm);
+          if (lt)
+            {
+              print_lifetime_from_index (rdm, lt);
+              PRINT (" ");
+            }
+        }
+      if (tag != 'R')
+        PRINT ("mut ");
+      demangle_type (rdm);
+      break;
+    case 'P':
+    case 'O':
+      PRINT ("*");
+      if (tag != 'P')
+        PRINT ("mut ");
+      else
+        PRINT ("const ");
+      demangle_type (rdm);
+      break;
+    case 'A':
+    case 'S':
+      PRINT ("[");
+      demangle_type (rdm);
+      if (tag == 'A')
+        {
+          PRINT ("; ");
+          demangle_const (rdm);
+        }
+      PRINT ("]");
+      break;
+    case 'T':
+      PRINT ("(");
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_type (rdm);
+        }
+      if (i == 1)
+        PRINT (",");
+      PRINT (")");
+      break;
+    case 'F':
+      old_bound_lifetime_depth = rdm->bound_lifetime_depth;
+      demangle_binder (rdm);
+
+      if (eat (rdm, 'U'))
+        PRINT ("unsafe ");
+
+      if (eat (rdm, 'K'))
+        {
+          if (eat (rdm, 'C'))
+            {
+              abi.ascii = "C";
+              abi.ascii_len = 1;
+            }
+          else
+            {
+              abi = parse_ident (rdm);
+              if (!abi.ascii || abi.punycode)
+                {
+                  rdm->errored = 1;
+                  goto restore;
+                }
+            }
+
+          PRINT ("extern \"");
+
+          /* If the ABI had any `-`, they were replaced with `_`,
+             so the parts between `_` have to be re-joined with `-`. */
+          for (i = 0; i < abi.ascii_len; i++)
+            {
+              if (abi.ascii[i] == '_')
+                {
+                  print_str (rdm, abi.ascii, i);
+                  PRINT ("-");
+                  abi.ascii += i + 1;
+                  abi.ascii_len -= i + 1;
+                  i = 0;
+                }
+            }
+          print_str (rdm, abi.ascii, abi.ascii_len);
+
+          PRINT ("\" ");
+        }
+
+      PRINT ("fn(");
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_type (rdm);
+        }
+      PRINT (")");
+
+      if (eat (rdm, 'u'))
+        {
+          /* Skip printing the return type if it's 'u', i.e. `()`. */
+        }
+      else
+        {
+          PRINT (" -> ");
+          demangle_type (rdm);
+        }
+
+    /* Restore `bound_lifetime_depth` to outside the binder. */
+    restore:
+      rdm->bound_lifetime_depth = old_bound_lifetime_depth;
+      break;
+    case 'D':
+      PRINT ("dyn ");
+
+      old_bound_lifetime_depth = rdm->bound_lifetime_depth;
+      demangle_binder (rdm);
+
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (" + ");
+          demangle_dyn_trait (rdm);
+        }
+
+      /* Restore `bound_lifetime_depth` to outside the binder. */
+      rdm->bound_lifetime_depth = old_bound_lifetime_depth;
+
+      if (!eat (rdm, 'L'))
+        {
+          rdm->errored = 1;
+          return;
+        }
+      lt = parse_integer_62 (rdm);
+      if (lt)
+        {
+          PRINT (" + ");
+          print_lifetime_from_index (rdm, lt);
+        }
+      break;
+    case 'B':
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          demangle_type (rdm);
+          rdm->next = old_next;
+        }
+      break;
+    default:
+      /* Go back to the tag, so `demangle_path` also sees it. */
+      rdm->next--;
+      demangle_path (rdm, 0);
+    }
+}
+
+/* A trait in a trait object may have some "existential projections"
+   (i.e. associated type bindings) after it, which should be printed
+   in the `<...>` of the trait, e.g. `dyn Trait<T, U, Assoc=X>`.
+   To this end, this method will keep the `<...>` of an 'I' path
+   open, by omitting the `>`, and return `Ok(true)` in that case. */
+static int
+demangle_path_maybe_open_generics (struct rust_demangler *rdm)
+{
+  int open;
+  size_t i, old_next, backref;
+
+  open = 0;
+
+  if (rdm->errored)
+    return open;
+
+  if (eat (rdm, 'B'))
+    {
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          open = demangle_path_maybe_open_generics (rdm);
+          rdm->next = old_next;
+        }
+    }
+  else if (eat (rdm, 'I'))
+    {
+      demangle_path (rdm, 0);
+      PRINT ("<");
+      open = 1;
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_generic_arg (rdm);
+        }
+    }
+  else
+    demangle_path (rdm, 0);
+  return open;
+}
+
+static void
+demangle_dyn_trait (struct rust_demangler *rdm)
+{
+  int open;
+  struct rust_mangled_ident name;
+
+  if (rdm->errored)
+    return;
+
+  open = demangle_path_maybe_open_generics (rdm);
+
+  while (eat (rdm, 'p'))
+    {
+      if (!open)
+        PRINT ("<");
+      else
+        PRINT (", ");
+      open = 1;
+
+      name = parse_ident (rdm);
+      print_ident (rdm, name);
+      PRINT (" = ");
+      demangle_type (rdm);
+    }
+
+  if (open)
+    PRINT (">");
+}
+
+static void
+demangle_const (struct rust_demangler *rdm)
+{
+  char ty_tag;
+  size_t old_next, backref;
+
+  if (rdm->errored)
+    return;
+
+  if (eat (rdm, 'B'))
+    {
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          demangle_const (rdm);
+          rdm->next = old_next;
+        }
+      return;
+    }
+
+  ty_tag = next (rdm);
+  switch (ty_tag)
+    {
+    /* Placeholder. */
+    case 'p':
+      PRINT ("_");
+      return;
+
+    /* Unsigned integer types. */
+    case 'h':
+    case 't':
+    case 'm':
+    case 'y':
+    case 'o':
+    case 'j':
+      demangle_const_uint (rdm);
+      break;
+
+    /* Signed integer types. */
+    case 'a':
+    case 's':
+    case 'l':
+    case 'x':
+    case 'n':
+    case 'i':
+      demangle_const_int (rdm);
+      break;
+
+    /* Boolean. */
+    case 'b':
+      demangle_const_bool (rdm);
+      break;
+
+    /* Character. */
+    case 'c':
+      demangle_const_char (rdm);
+      break;
+
+    default:
+      rdm->errored = 1;
+      return;
+    }
+
+  if (rdm->errored)
+    return;
+
+  if (rdm->verbose)
+    {
+      PRINT (": ");
+      PRINT (basic_type (ty_tag));
+    }
+}
+
+static void
+demangle_const_uint (struct rust_demangler *rdm)
+{
+  size_t hex_len;
+  uint64_t value;
+
+  if (rdm->errored)
+    return;
+
+  hex_len = parse_hex_nibbles (rdm, &value);
+
+  if (hex_len > 16)
+    {
+      /* Print anything that doesn't fit in `uint64_t` verbatim. */
+      PRINT ("0x");
+      print_str (rdm, rdm->sym + (rdm->next - hex_len), hex_len);
+    }
+  else if (hex_len > 0)
+    print_uint64 (rdm, value);
+  else
+    rdm->errored = 1;
+}
+
+static void
+demangle_const_int (struct rust_demangler *rdm)
+{
+  if (eat (rdm, 'n'))
+    PRINT ("-");
+  demangle_const_uint (rdm);
+}
+
+static void
+demangle_const_bool (struct rust_demangler *rdm)
+{
+  uint64_t value;
+
+  if (parse_hex_nibbles (rdm, &value) != 1)
+    {
+      rdm->errored = 1;
+      return;
+    }
+
+  if (value == 0)
+    PRINT ("false");
+  else if (value == 1)
+    PRINT ("true");
+  else
+    rdm->errored = 1;
+}
+
+static void
+demangle_const_char (struct rust_demangler *rdm)
+{
+  size_t hex_len;
+  uint64_t value;
+
+  hex_len = parse_hex_nibbles (rdm, &value);
+
+  if (hex_len == 0 || hex_len > 8)
+    {
+      rdm->errored = 1;
+      return;
+    }
+
+  /* Match Rust's character "debug" output as best as we can. */
+  PRINT ("'");
+  if (value == '\t')
+    PRINT ("\\t");
+  else if (value == '\r')
+    PRINT ("\\r");
+  else if (value == '\n')
+    PRINT ("\\n");
+  else if (value > ' ' && value < '~')
+    /* Rust also considers many non-ASCII codepoints to be printable, but
+       that logic is not easily ported to C. */
+    print_str (rdm, (char *) &value, 1);
+  else
+    {
+      PRINT ("\\u{");
+      print_uint64_hex (rdm, value);
+      PRINT ("}");
+    }
+  PRINT ("'");
 }
 
 /* A legacy hash is the prefix "h" followed by 16 lowercase hex digits.
@@ -345,11 +1313,15 @@ rust_demangle_callback (const char *mangled, int options,
 
   rdm.next = 0;
   rdm.errored = 0;
+  rdm.skipping_printing = 0;
   rdm.verbose = (options & DMGL_VERBOSE) != 0;
   rdm.version = 0;
+  rdm.bound_lifetime_depth = 0;
 
-  /* Rust symbols always start with _ZN (legacy). */
-  if (rdm.sym[0] == '_' && rdm.sym[1] == 'Z' && rdm.sym[2] == 'N')
+  /* Rust symbols always start with _R (v0) or _ZN (legacy). */
+  if (rdm.sym[0] == '_' && rdm.sym[1] == 'R')
+    rdm.sym += 2;
+  else if (rdm.sym[0] == '_' && rdm.sym[1] == 'Z' && rdm.sym[2] == 'N')
     {
       rdm.sym += 3;
       rdm.version = -1;
@@ -357,7 +1329,11 @@ rust_demangle_callback (const char *mangled, int options,
   else
     return 0;
 
-  /* Legacy Rust symbols use only [_0-9a-zA-Z.:$] characters. */
+  /* Paths (v0) always start with uppercase characters. */
+  if (rdm.version != -1 && !ISUPPER (rdm.sym[0]))
+    return 0;
+
+  /* Rust symbols (v0) use only [_0-9a-zA-Z] characters. */
   for (p = rdm.sym; *p; p++)
     {
       rdm.sym_len++;
@@ -365,6 +1341,7 @@ rust_demangle_callback (const char *mangled, int options,
       if (*p == '_' || ISALNUM (*p))
         continue;
 
+      /* Legacy Rust symbols can also contain [.:$] characters. */
       if (rdm.version == -1 && (*p == '$' || *p == '.' || *p == ':'))
         continue;
 
@@ -418,7 +1395,19 @@ rust_demangle_callback (const char *mangled, int options,
       while (rdm.next < rdm.sym_len);
     }
   else
-    return 0;
+    {
+      demangle_path (&rdm, 1);
+
+      /* Skip instantiating crate. */
+      if (!rdm.errored && rdm.next < rdm.sym_len)
+        {
+          rdm.skipping_printing = 1;
+          demangle_path (&rdm, 0);
+        }
+
+      /* It's an error to not reach the end. */
+      rdm.errored |= rdm.next != rdm.sym_len;
+    }
 
   return !rdm.errored;
 }
diff --git a/libiberty/testsuite/rust-demangle-expected b/libiberty/testsuite/rust-demangle-expected
index 74774794736..1351b9a2bd4 100644
--- a/libiberty/testsuite/rust-demangle-expected
+++ b/libiberty/testsuite/rust-demangle-expected
@@ -163,3 +163,51 @@ _ZN63_$LT$core..ptr..Unique$LT$T$GT$$u20$as$u20$core..ops..Deref$GT$5deref17h19f
 --format=rust
 _ZN11issue_609253foo37Foo$LT$issue_60925..llv$u6d$..Foo$GT$3foo17h059a991a004536adE
 issue_60925::foo::Foo<issue_60925::llvm::Foo>::foo
+#
+--format=rust
+_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h000b1ad6c4f30bd6E
+core::ops::function::FnOnce::call_once{{vtable.shim}}
+# UTF8 idents.
+--format=rust
+_RNqCs4fqI2P2rA04_11utf8_identsu30____7hkackfecea1cbdathfdh9hlq6y
+utf8_idents::საჭმელად_გემრიელი_სადილი
+# Crate with leading digit.
+--format=rust
+_RNvC6_123foo3bar
+123foo::bar
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_8UnsignedKhb_E
+<const_generic::Unsigned<11>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_6SignedKs98_E
+<const_generic::Signed<152>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_6SignedKanb_E
+<const_generic::Signed<-11>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4BoolKb0_E
+<const_generic::Bool<false>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4BoolKb1_E
+<const_generic::Bool<true>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKc76_E
+<const_generic::Char<'v'>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKca_E
+<const_generic::Char<'\n'>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKc2202_E
+<const_generic::Char<'\u{2202}'>>
+#
+--format=rust
+_RNvNvMCs4fqI2P2rA04_13const_genericINtB4_3FooKpE3foo3FOO
+<const_generic::Foo<_>>::foo::FOO
Jeff Law Nov. 6, 2020, 5:09 p.m. UTC | #15
On 11/1/20 10:26 AM, Nikhil Benesch via Gcc-patches wrote:
>
>
> On 11/1/20 6:57 AM, Eduard-Mihai Burtescu wrote:
>> Reading the diff patch, the v0 changes look great. I wouldn't be too
>> worried
>> about the "printable character" aspect, there are similar
>> Unicode-related
>> issues elsewhere, e.g. the "non-control ASCII" comment in
>> decode_legacy_escape
>> (I suppose we could make it also go through the "print a non-control
>> ASCII
>> character or some escape sequence" logic you added, if you think that
>> helps).
>
> No, it's entirely fine with me! I just wasn't sure if the small
> deviations in output were acceptable. It sounds like they are.

So I think the best path forward is to let you and Eduard-Mihai make the
technical decisions about what bits are ready for the trunk.  When y'all
think something is ready, let's go ahead and get it installed and
iterate on things that aren't quite ready yet.


For bits y'all think are ready, ISTM that Eduard-Mihai should commit the
changes.



>> I can test the patch and upload the dataset tomorrow, but if you want
>> to get
>> something committed sooner (is there a deadline for the next
>> release?), feel
>> free to land the v0 changes (snprintf + const values) without the
>> legacy ones.
>
> My understanding is that the GCC tree closes to new features on
> November 16 (for "GCC 11 Stage 3"), but I'm not sure whether that
> applies to libiberty or whether this patch would be classified as a
> feature or a bugfix.
>
> I don't have commit rights (nor am I even a GCC developer). Just
> wanted to tee things up for you and Ian this week. I'm very much
> looking forward to the new demangling scheme and didn't want to be
> just another +1 on the GitHub issue.
>
> So certainly no time pressure from me. But perhaps someone from the
> GCC side can confirm whether we are under a bit of time pressure here
> given the GCC 11 release.

It's better to get it in sooner, but there is some degree of freedom
depending on the impact of the changes.  Changes in the rust demangler
aren't likely to trigger codegen or ABI breakages in the compiler itself
-- so with that in mind I think we should give this code a higher degree
of freedom to land after the stage1 close deadline.


jeff
Nikhil Benesch Nov. 13, 2020, 6:42 a.m. UTC | #16
On 11/6/20 12:09 PM, Jeff Law wrote:
> So I think the best path forward is to let you and Eduard-Mihai make the
> technical decisions about what bits are ready for the trunk.  When y'all
> think something is ready, let's go ahead and get it installed and
> iterate on things that aren't quite ready yet.
> 
> 
> For bits y'all think are ready, ISTM that Eduard-Mihai should commit the
> changes.

I've attached an updated version of the patch that contains some
additional unit tests that eddyb noticed I lost. From my perspective,
this is now ready for commit.

Neither eddyb nor I have write access, so someone else will need to
commit. (But please wait for eddyb to sign off too.)

> It's better to get it in sooner, but there is some degree of freedom
> depending on the impact of the changes.  Changes in the rust demangler
> aren't likely to trigger codegen or ABI breakages in the compiler itself
> -- so with that in mind I think we should give this code a higher degree
> of freedom to land after the stage1 close deadline.

Got it. Thanks. That's very helpful context.

Nikhil
diff --git a/libiberty/rust-demangle.c b/libiberty/rust-demangle.c
index b87365c85fe..08c615f6d8b 100644
--- a/libiberty/rust-demangle.c
+++ b/libiberty/rust-demangle.c
@@ -1,6 +1,7 @@
 /* Demangler for the Rust programming language
    Copyright (C) 2016-2020 Free Software Foundation, Inc.
    Written by David Tolnay (dtolnay@gmail.com).
+   Rewritten by Eduard-Mihai Burtescu (eddyb@lyken.rs) for v0 support.
 
 This file is part of the libiberty library.
 Libiberty is free software; you can redistribute it and/or
@@ -64,11 +65,16 @@ struct rust_demangler
   /* Non-zero if any error occurred. */
   int errored;
 
+  /* Non-zero if nothing should be printed. */
+  int skipping_printing;
+
   /* Non-zero if printing should be verbose (e.g. include hashes). */
   int verbose;
 
   /* Rust mangling version, with legacy mangling being -1. */
   int version;
+
+  uint64_t bound_lifetime_depth;
 };
 
 /* Parsing functions. */
@@ -81,6 +87,18 @@ peek (const struct rust_demangler *rdm)
   return 0;
 }
 
+static int
+eat (struct rust_demangler *rdm, char c)
+{
+  if (peek (rdm) == c)
+    {
+      rdm->next++;
+      return 1;
+    }
+  else
+    return 0;
+}
+
 static char
 next (struct rust_demangler *rdm)
 {
@@ -92,11 +110,87 @@ next (struct rust_demangler *rdm)
   return c;
 }
 
+static uint64_t
+parse_integer_62 (struct rust_demangler *rdm)
+{
+  char c;
+  uint64_t x;
+
+  if (eat (rdm, '_'))
+    return 0;
+
+  x = 0;
+  while (!eat (rdm, '_'))
+    {
+      c = next (rdm);
+      x *= 62;
+      if (ISDIGIT (c))
+        x += c - '0';
+      else if (ISLOWER (c))
+        x += 10 + (c - 'a');
+      else if (ISUPPER (c))
+        x += 10 + 26 + (c - 'A');
+      else
+        {
+          rdm->errored = 1;
+          return 0;
+        }
+    }
+  return x + 1;
+}
+
+static uint64_t
+parse_opt_integer_62 (struct rust_demangler *rdm, char tag)
+{
+  if (!eat (rdm, tag))
+    return 0;
+  return 1 + parse_integer_62 (rdm);
+}
+
+static uint64_t
+parse_disambiguator (struct rust_demangler *rdm)
+{
+  return parse_opt_integer_62 (rdm, 's');
+}
+
+static size_t
+parse_hex_nibbles (struct rust_demangler *rdm, uint64_t *value)
+{
+  char c;
+  size_t hex_len;
+
+  hex_len = 0;
+  *value = 0;
+
+  while (!eat (rdm, '_'))
+    {
+      *value <<= 4;
+
+      c = next (rdm);
+      if (ISDIGIT (c))
+        *value |= c - '0';
+      else if (c >= 'a' && c <= 'f')
+        *value |= 10 + (c - 'a');
+      else
+        {
+          rdm->errored = 1;
+          return 0;
+        }
+      hex_len++;
+    }
+
+  return hex_len;
+}
+
 struct rust_mangled_ident
 {
   /* ASCII part of the identifier. */
   const char *ascii;
   size_t ascii_len;
+
+  /* Punycode insertion codes for Unicode codepoints, if any. */
+  const char *punycode;
+  size_t punycode_len;
 };
 
 static struct rust_mangled_ident
@@ -104,10 +198,16 @@ parse_ident (struct rust_demangler *rdm)
 {
   char c;
   size_t start, len;
+  int is_punycode = 0;
   struct rust_mangled_ident ident;
 
   ident.ascii = NULL;
   ident.ascii_len = 0;
+  ident.punycode = NULL;
+  ident.punycode_len = 0;
+
+  if (rdm->version != -1)
+    is_punycode = eat (rdm, 'u');
 
   c = next (rdm);
   if (!ISDIGIT (c))
@@ -121,6 +221,10 @@ parse_ident (struct rust_demangler *rdm)
     while (ISDIGIT (peek (rdm)))
       len = len * 10 + (next (rdm) - '0');
 
+  /* Skip past the optional `_` separator (v0). */
+  if (rdm->version != -1)
+    eat (rdm, '_');
+
   start = rdm->next;
   rdm->next += len;
   /* Check for overflows. */
@@ -133,6 +237,27 @@ parse_ident (struct rust_demangler *rdm)
   ident.ascii = rdm->sym + start;
   ident.ascii_len = len;
 
+  if (is_punycode)
+    {
+      ident.punycode_len = 0;
+      while (ident.ascii_len > 0)
+        {
+          ident.ascii_len--;
+
+          /* The last '_' is a separator between ascii & punycode. */
+          if (ident.ascii[ident.ascii_len] == '_')
+            break;
+
+          ident.punycode_len++;
+        }
+      if (!ident.punycode_len)
+        {
+          rdm->errored = 1;
+          return ident;
+        }
+      ident.punycode = ident.ascii + (len - ident.punycode_len);
+    }
+
   if (ident.ascii_len == 0)
     ident.ascii = NULL;
 
@@ -144,12 +269,28 @@ parse_ident (struct rust_demangler *rdm)
 static void
 print_str (struct rust_demangler *rdm, const char *data, size_t len)
 {
-  if (!rdm->errored)
+  if (!rdm->errored && !rdm->skipping_printing)
     rdm->callback (data, len, rdm->callback_opaque);
 }
 
 #define PRINT(s) print_str (rdm, s, strlen (s))
 
+static void
+print_uint64 (struct rust_demangler *rdm, uint64_t x)
+{
+  char s[21];
+  snprintf (s, 21, "%" PRIu64, x);
+  PRINT (s);
+}
+
+static void
+print_uint64_hex (struct rust_demangler *rdm, uint64_t x)
+{
+  char s[17];
+  snprintf (s, 17, "%" PRIx64, x);
+  PRINT (s);
+}
+
 /* Return a 0x0-0xf value if the char is 0-9a-f, and -1 otherwise. */
 static int
 decode_lower_hex_nibble (char nibble)
@@ -230,9 +371,14 @@ static void
 print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
 {
   char unescaped;
-  size_t len;
+  uint8_t *out, *p, d;
+  size_t len, cap, punycode_pos, j;
+  /* Punycode parameters and state. */
+  uint32_t c;
+  size_t base, t_min, t_max, skew, damp, bias, i;
+  size_t delta, w, k, t;
 
-  if (rdm->errored)
+  if (rdm->errored || rdm->skipping_printing)
     return;
 
   if (rdm->version == -1)
@@ -273,8 +419,7 @@ print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
                 }
               else
                 {
-                  /* "." becomes "-" */
-                  PRINT ("-");
+                  PRINT (".");
                   len = 1;
                 }
             }
@@ -294,6 +439,830 @@ print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
 
       return;
     }
+
+  if (!ident.punycode)
+    {
+      print_str (rdm, ident.ascii, ident.ascii_len);
+      return;
+    }
+
+  len = 0;
+  cap = 4;
+  while (cap < ident.ascii_len)
+    {
+      cap *= 2;
+      /* Check for overflows. */
+      if ((cap * 4) / 4 != cap)
+        {
+          rdm->errored = 1;
+          return;
+        }
+    }
+
+  /* Store the output codepoints as groups of 4 UTF-8 bytes. */
+  out = (uint8_t *)malloc (cap * 4);
+  if (!out)
+    {
+      rdm->errored = 1;
+      return;
+    }
+
+  /* Populate initial output from ASCII fragment. */
+  for (len = 0; len < ident.ascii_len; len++)
+    {
+      p = out + 4 * len;
+      p[0] = 0;
+      p[1] = 0;
+      p[2] = 0;
+      p[3] = ident.ascii[len];
+    }
+
+  /* Punycode parameters and initial state. */
+  base = 36;
+  t_min = 1;
+  t_max = 26;
+  skew = 38;
+  damp = 700;
+  bias = 72;
+  i = 0;
+  c = 0x80;
+
+  punycode_pos = 0;
+  while (punycode_pos < ident.punycode_len)
+    {
+      /* Read one delta value. */
+      delta = 0;
+      w = 1;
+      k = 0;
+      do
+        {
+          k += base;
+          t = k < bias ? 0 : (k - bias);
+          if (t < t_min)
+            t = t_min;
+          if (t > t_max)
+            t = t_max;
+
+          if (punycode_pos >= ident.punycode_len)
+            goto cleanup;
+          d = ident.punycode[punycode_pos++];
+
+          if (ISLOWER (d))
+            d = d - 'a';
+          else if (ISDIGIT (d))
+            d = 26 + (d - '0');
+          else
+            {
+              rdm->errored = 1;
+              goto cleanup;
+            }
+
+          delta += d * w;
+          w *= base - t;
+        }
+      while (d >= t);
+
+      /* Compute the new insert position and character. */
+      len++;
+      i += delta;
+      c += i / len;
+      i %= len;
+
+      /* Ensure enough space is available. */
+      if (cap < len)
+        {
+          cap *= 2;
+          /* Check for overflows. */
+          if ((cap * 4) / 4 != cap || cap < len)
+            {
+              rdm->errored = 1;
+              goto cleanup;
+            }
+        }
+      p = (uint8_t *)realloc (out, cap * 4);
+      if (!p)
+        {
+          rdm->errored = 1;
+          goto cleanup;
+        }
+      out = p;
+
+      /* Move the characters after the insert position. */
+      p = out + i * 4;
+      memmove (p + 4, p, (len - i - 1) * 4);
+
+      /* Insert the new character, as UTF-8 bytes. */
+      p[0] = c >= 0x10000 ? 0xf0 | (c >> 18) : 0;
+      p[1] = c >= 0x800 ? (c < 0x10000 ? 0xe0 : 0x80) | ((c >> 12) & 0x3f) : 0;
+      p[2] = (c < 0x800 ? 0xc0 : 0x80) | ((c >> 6) & 0x3f);
+      p[3] = 0x80 | (c & 0x3f);
+
+      /* If there are no more deltas, decoding is complete. */
+      if (punycode_pos == ident.punycode_len)
+        break;
+
+      i++;
+
+      /* Perform bias adaptation. */
+      delta /= damp;
+      damp = 2;
+
+      delta += delta / len;
+      k = 0;
+      while (delta > ((base - t_min) * t_max) / 2)
+        {
+          delta /= base - t_min;
+          k += base;
+        }
+      bias = k + ((base - t_min + 1) * delta) / (delta + skew);
+    }
+
+  /* Remove all the 0 bytes to leave behind an UTF-8 string. */
+  for (i = 0, j = 0; i < len * 4; i++)
+    if (out[i] != 0)
+      out[j++] = out[i];
+
+  print_str (rdm, (const char *)out, j);
+
+cleanup:
+  free (out);
+}
+
+/* Print the lifetime according to the previously decoded index.
+   An index of `0` always refers to `'_`, but starting with `1`,
+   indices refer to late-bound lifetimes introduced by a binder. */
+static void
+print_lifetime_from_index (struct rust_demangler *rdm, uint64_t lt)
+{
+  char c;
+  uint64_t depth;
+
+  PRINT ("'");
+  if (lt == 0)
+    {
+      PRINT ("_");
+      return;
+    }
+
+  depth = rdm->bound_lifetime_depth - lt;
+  /* Try to print lifetimes alphabetically first. */
+  if (depth < 26)
+    {
+      c = 'a' + depth;
+      print_str (rdm, &c, 1);
+    }
+  else
+    {
+      /* Use `'_123` after running out of letters. */
+      PRINT ("_");
+      print_uint64 (rdm, depth);
+    }
+}
+
+/* Demangling functions. */
+
+static void demangle_binder (struct rust_demangler *rdm);
+static void demangle_path (struct rust_demangler *rdm, int in_value);
+static void demangle_generic_arg (struct rust_demangler *rdm);
+static void demangle_type (struct rust_demangler *rdm);
+static int demangle_path_maybe_open_generics (struct rust_demangler *rdm);
+static void demangle_dyn_trait (struct rust_demangler *rdm);
+static void demangle_const (struct rust_demangler *rdm);
+static void demangle_const_uint (struct rust_demangler *rdm);
+static void demangle_const_int (struct rust_demangler *rdm);
+static void demangle_const_bool (struct rust_demangler *rdm);
+static void demangle_const_char (struct rust_demangler *rdm);
+
+/* Optionally enter a binder ('G') for late-bound lifetimes,
+   printing e.g. `for<'a, 'b> `, and make those lifetimes visible
+   to the caller (via depth level, which the caller should reset). */
+static void
+demangle_binder (struct rust_demangler *rdm)
+{
+  uint64_t i, bound_lifetimes;
+
+  if (rdm->errored)
+    return;
+
+  bound_lifetimes = parse_opt_integer_62 (rdm, 'G');
+  if (bound_lifetimes > 0)
+    {
+      PRINT ("for<");
+      for (i = 0; i < bound_lifetimes; i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          rdm->bound_lifetime_depth++;
+          print_lifetime_from_index (rdm, 1);
+        }
+      PRINT ("> ");
+    }
+}
+
+static void
+demangle_path (struct rust_demangler *rdm, int in_value)
+{
+  char tag, ns;
+  int was_skipping_printing;
+  size_t i, backref, old_next;
+  uint64_t dis;
+  struct rust_mangled_ident name;
+
+  if (rdm->errored)
+    return;
+
+  switch (tag = next (rdm))
+    {
+    case 'C':
+      dis = parse_disambiguator (rdm);
+      name = parse_ident (rdm);
+
+      print_ident (rdm, name);
+      if (rdm->verbose)
+        {
+          PRINT ("[");
+          print_uint64_hex (rdm, dis);
+          PRINT ("]");
+        }
+      break;
+    case 'N':
+      ns = next (rdm);
+      if (!ISLOWER (ns) && !ISUPPER (ns))
+        {
+          rdm->errored = 1;
+          return;
+        }
+
+      demangle_path (rdm, in_value);
+
+      dis = parse_disambiguator (rdm);
+      name = parse_ident (rdm);
+
+      if (ISUPPER (ns))
+        {
+          /* Special namespaces, like closures and shims. */
+          PRINT ("::{");
+          switch (ns)
+            {
+            case 'C':
+              PRINT ("closure");
+              break;
+            case 'S':
+              PRINT ("shim");
+              break;
+            default:
+              print_str (rdm, &ns, 1);
+            }
+          if (name.ascii || name.punycode)
+            {
+              PRINT (":");
+              print_ident (rdm, name);
+            }
+          PRINT ("#");
+          print_uint64 (rdm, dis);
+          PRINT ("}");
+        }
+      else
+        {
+          /* Implementation-specific/unspecified namespaces. */
+
+          if (name.ascii || name.punycode)
+            {
+              PRINT ("::");
+              print_ident (rdm, name);
+            }
+        }
+      break;
+    case 'M':
+    case 'X':
+      /* Ignore the `impl`'s own path.*/
+      parse_disambiguator (rdm);
+      was_skipping_printing = rdm->skipping_printing;
+      rdm->skipping_printing = 1;
+      demangle_path (rdm, in_value);
+      rdm->skipping_printing = was_skipping_printing;
+      /* fallthrough */
+    case 'Y':
+      PRINT ("<");
+      demangle_type (rdm);
+      if (tag != 'M')
+        {
+          PRINT (" as ");
+          demangle_path (rdm, 0);
+        }
+      PRINT (">");
+      break;
+    case 'I':
+      demangle_path (rdm, in_value);
+      if (in_value)
+        PRINT ("::");
+      PRINT ("<");
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_generic_arg (rdm);
+        }
+      PRINT (">");
+      break;
+    case 'B':
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          demangle_path (rdm, in_value);
+          rdm->next = old_next;
+        }
+      break;
+    default:
+      rdm->errored = 1;
+      return;
+    }
+}
+
+static void
+demangle_generic_arg (struct rust_demangler *rdm)
+{
+  uint64_t lt;
+  if (eat (rdm, 'L'))
+    {
+      lt = parse_integer_62 (rdm);
+      print_lifetime_from_index (rdm, lt);
+    }
+  else if (eat (rdm, 'K'))
+    demangle_const (rdm);
+  else
+    demangle_type (rdm);
+}
+
+static const char *
+basic_type (char tag)
+{
+  switch (tag)
+    {
+    case 'b':
+      return "bool";
+    case 'c':
+      return "char";
+    case 'e':
+      return "str";
+    case 'u':
+      return "()";
+    case 'a':
+      return "i8";
+    case 's':
+      return "i16";
+    case 'l':
+      return "i32";
+    case 'x':
+      return "i64";
+    case 'n':
+      return "i128";
+    case 'i':
+      return "isize";
+    case 'h':
+      return "u8";
+    case 't':
+      return "u16";
+    case 'm':
+      return "u32";
+    case 'y':
+      return "u64";
+    case 'o':
+      return "u128";
+    case 'j':
+      return "usize";
+    case 'f':
+      return "f32";
+    case 'd':
+      return "f64";
+    case 'z':
+      return "!";
+    case 'p':
+      return "_";
+    case 'v':
+      return "...";
+
+    default:
+      return NULL;
+    }
+}
+
+static void
+demangle_type (struct rust_demangler *rdm)
+{
+  char tag;
+  size_t i, old_next, backref;
+  uint64_t lt, old_bound_lifetime_depth;
+  const char *basic;
+  struct rust_mangled_ident abi;
+
+  if (rdm->errored)
+    return;
+
+  tag = next (rdm);
+
+  basic = basic_type (tag);
+  if (basic)
+    {
+      PRINT (basic);
+      return;
+    }
+
+  switch (tag)
+    {
+    case 'R':
+    case 'Q':
+      PRINT ("&");
+      if (eat (rdm, 'L'))
+        {
+          lt = parse_integer_62 (rdm);
+          if (lt)
+            {
+              print_lifetime_from_index (rdm, lt);
+              PRINT (" ");
+            }
+        }
+      if (tag != 'R')
+        PRINT ("mut ");
+      demangle_type (rdm);
+      break;
+    case 'P':
+    case 'O':
+      PRINT ("*");
+      if (tag != 'P')
+        PRINT ("mut ");
+      else
+        PRINT ("const ");
+      demangle_type (rdm);
+      break;
+    case 'A':
+    case 'S':
+      PRINT ("[");
+      demangle_type (rdm);
+      if (tag == 'A')
+        {
+          PRINT ("; ");
+          demangle_const (rdm);
+        }
+      PRINT ("]");
+      break;
+    case 'T':
+      PRINT ("(");
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_type (rdm);
+        }
+      if (i == 1)
+        PRINT (",");
+      PRINT (")");
+      break;
+    case 'F':
+      old_bound_lifetime_depth = rdm->bound_lifetime_depth;
+      demangle_binder (rdm);
+
+      if (eat (rdm, 'U'))
+        PRINT ("unsafe ");
+
+      if (eat (rdm, 'K'))
+        {
+          if (eat (rdm, 'C'))
+            {
+              abi.ascii = "C";
+              abi.ascii_len = 1;
+            }
+          else
+            {
+              abi = parse_ident (rdm);
+              if (!abi.ascii || abi.punycode)
+                {
+                  rdm->errored = 1;
+                  goto restore;
+                }
+            }
+
+          PRINT ("extern \"");
+
+          /* If the ABI had any `-`, they were replaced with `_`,
+             so the parts between `_` have to be re-joined with `-`. */
+          for (i = 0; i < abi.ascii_len; i++)
+            {
+              if (abi.ascii[i] == '_')
+                {
+                  print_str (rdm, abi.ascii, i);
+                  PRINT ("-");
+                  abi.ascii += i + 1;
+                  abi.ascii_len -= i + 1;
+                  i = 0;
+                }
+            }
+          print_str (rdm, abi.ascii, abi.ascii_len);
+
+          PRINT ("\" ");
+        }
+
+      PRINT ("fn(");
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_type (rdm);
+        }
+      PRINT (")");
+
+      if (eat (rdm, 'u'))
+        {
+          /* Skip printing the return type if it's 'u', i.e. `()`. */
+        }
+      else
+        {
+          PRINT (" -> ");
+          demangle_type (rdm);
+        }
+
+    /* Restore `bound_lifetime_depth` to outside the binder. */
+    restore:
+      rdm->bound_lifetime_depth = old_bound_lifetime_depth;
+      break;
+    case 'D':
+      PRINT ("dyn ");
+
+      old_bound_lifetime_depth = rdm->bound_lifetime_depth;
+      demangle_binder (rdm);
+
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (" + ");
+          demangle_dyn_trait (rdm);
+        }
+
+      /* Restore `bound_lifetime_depth` to outside the binder. */
+      rdm->bound_lifetime_depth = old_bound_lifetime_depth;
+
+      if (!eat (rdm, 'L'))
+        {
+          rdm->errored = 1;
+          return;
+        }
+      lt = parse_integer_62 (rdm);
+      if (lt)
+        {
+          PRINT (" + ");
+          print_lifetime_from_index (rdm, lt);
+        }
+      break;
+    case 'B':
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          demangle_type (rdm);
+          rdm->next = old_next;
+        }
+      break;
+    default:
+      /* Go back to the tag, so `demangle_path` also sees it. */
+      rdm->next--;
+      demangle_path (rdm, 0);
+    }
+}
+
+/* A trait in a trait object may have some "existential projections"
+   (i.e. associated type bindings) after it, which should be printed
+   in the `<...>` of the trait, e.g. `dyn Trait<T, U, Assoc=X>`.
+   To this end, this method will keep the `<...>` of an 'I' path
+   open, by omitting the `>`, and return `Ok(true)` in that case. */
+static int
+demangle_path_maybe_open_generics (struct rust_demangler *rdm)
+{
+  int open;
+  size_t i, old_next, backref;
+
+  open = 0;
+
+  if (rdm->errored)
+    return open;
+
+  if (eat (rdm, 'B'))
+    {
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          open = demangle_path_maybe_open_generics (rdm);
+          rdm->next = old_next;
+        }
+    }
+  else if (eat (rdm, 'I'))
+    {
+      demangle_path (rdm, 0);
+      PRINT ("<");
+      open = 1;
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_generic_arg (rdm);
+        }
+    }
+  else
+    demangle_path (rdm, 0);
+  return open;
+}
+
+static void
+demangle_dyn_trait (struct rust_demangler *rdm)
+{
+  int open;
+  struct rust_mangled_ident name;
+
+  if (rdm->errored)
+    return;
+
+  open = demangle_path_maybe_open_generics (rdm);
+
+  while (eat (rdm, 'p'))
+    {
+      if (!open)
+        PRINT ("<");
+      else
+        PRINT (", ");
+      open = 1;
+
+      name = parse_ident (rdm);
+      print_ident (rdm, name);
+      PRINT (" = ");
+      demangle_type (rdm);
+    }
+
+  if (open)
+    PRINT (">");
+}
+
+static void
+demangle_const (struct rust_demangler *rdm)
+{
+  char ty_tag;
+  size_t old_next, backref;
+
+  if (rdm->errored)
+    return;
+
+  if (eat (rdm, 'B'))
+    {
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          demangle_const (rdm);
+          rdm->next = old_next;
+        }
+      return;
+    }
+
+  ty_tag = next (rdm);
+  switch (ty_tag)
+    {
+    /* Placeholder. */
+    case 'p':
+      PRINT ("_");
+      return;
+
+    /* Unsigned integer types. */
+    case 'h':
+    case 't':
+    case 'm':
+    case 'y':
+    case 'o':
+    case 'j':
+      demangle_const_uint (rdm);
+      break;
+
+    /* Signed integer types. */
+    case 'a':
+    case 's':
+    case 'l':
+    case 'x':
+    case 'n':
+    case 'i':
+      demangle_const_int (rdm);
+      break;
+
+    /* Boolean. */
+    case 'b':
+      demangle_const_bool (rdm);
+      break;
+
+    /* Character. */
+    case 'c':
+      demangle_const_char (rdm);
+      break;
+
+    default:
+      rdm->errored = 1;
+      return;
+    }
+
+  if (rdm->errored)
+    return;
+
+  if (rdm->verbose)
+    {
+      PRINT (": ");
+      PRINT (basic_type (ty_tag));
+    }
+}
+
+static void
+demangle_const_uint (struct rust_demangler *rdm)
+{
+  size_t hex_len;
+  uint64_t value;
+
+  if (rdm->errored)
+    return;
+
+  hex_len = parse_hex_nibbles (rdm, &value);
+
+  if (hex_len > 16)
+    {
+      /* Print anything that doesn't fit in `uint64_t` verbatim. */
+      PRINT ("0x");
+      print_str (rdm, rdm->sym + (rdm->next - hex_len), hex_len);
+    }
+  else if (hex_len > 0)
+    print_uint64 (rdm, value);
+  else
+    rdm->errored = 1;
+}
+
+static void
+demangle_const_int (struct rust_demangler *rdm)
+{
+  if (eat (rdm, 'n'))
+    PRINT ("-");
+  demangle_const_uint (rdm);
+}
+
+static void
+demangle_const_bool (struct rust_demangler *rdm)
+{
+  uint64_t value;
+
+  if (parse_hex_nibbles (rdm, &value) != 1)
+    {
+      rdm->errored = 1;
+      return;
+    }
+
+  if (value == 0)
+    PRINT ("false");
+  else if (value == 1)
+    PRINT ("true");
+  else
+    rdm->errored = 1;
+}
+
+static void
+demangle_const_char (struct rust_demangler *rdm)
+{
+  size_t hex_len;
+  uint64_t value;
+
+  hex_len = parse_hex_nibbles (rdm, &value);
+
+  if (hex_len == 0 || hex_len > 8)
+    {
+      rdm->errored = 1;
+      return;
+    }
+
+  /* Match Rust's character "debug" output as best as we can. */
+  PRINT ("'");
+  if (value == '\t')
+    PRINT ("\\t");
+  else if (value == '\r')
+    PRINT ("\\r");
+  else if (value == '\n')
+    PRINT ("\\n");
+  else if (value > ' ' && value < '~')
+    /* Rust also considers many non-ASCII codepoints to be printable, but
+       that logic is not easily ported to C. */
+    print_str (rdm, (char *) &value, 1);
+  else
+    {
+      PRINT ("\\u{");
+      print_uint64_hex (rdm, value);
+      PRINT ("}");
+    }
+  PRINT ("'");
 }
 
 /* A legacy hash is the prefix "h" followed by 16 lowercase hex digits.
@@ -345,11 +1314,15 @@ rust_demangle_callback (const char *mangled, int options,
 
   rdm.next = 0;
   rdm.errored = 0;
+  rdm.skipping_printing = 0;
   rdm.verbose = (options & DMGL_VERBOSE) != 0;
   rdm.version = 0;
+  rdm.bound_lifetime_depth = 0;
 
-  /* Rust symbols always start with _ZN (legacy). */
-  if (rdm.sym[0] == '_' && rdm.sym[1] == 'Z' && rdm.sym[2] == 'N')
+  /* Rust symbols always start with _R (v0) or _ZN (legacy). */
+  if (rdm.sym[0] == '_' && rdm.sym[1] == 'R')
+    rdm.sym += 2;
+  else if (rdm.sym[0] == '_' && rdm.sym[1] == 'Z' && rdm.sym[2] == 'N')
     {
       rdm.sym += 3;
       rdm.version = -1;
@@ -357,7 +1330,11 @@ rust_demangle_callback (const char *mangled, int options,
   else
     return 0;
 
-  /* Legacy Rust symbols use only [_0-9a-zA-Z.:$] characters. */
+  /* Paths (v0) always start with uppercase characters. */
+  if (rdm.version != -1 && !ISUPPER (rdm.sym[0]))
+    return 0;
+
+  /* Rust symbols (v0) use only [_0-9a-zA-Z] characters. */
   for (p = rdm.sym; *p; p++)
     {
       rdm.sym_len++;
@@ -365,6 +1342,7 @@ rust_demangle_callback (const char *mangled, int options,
       if (*p == '_' || ISALNUM (*p))
         continue;
 
+      /* Legacy Rust symbols can also contain [.:$] characters. */
       if (rdm.version == -1 && (*p == '$' || *p == '.' || *p == ':'))
         continue;
 
@@ -418,7 +1396,19 @@ rust_demangle_callback (const char *mangled, int options,
       while (rdm.next < rdm.sym_len);
     }
   else
-    return 0;
+    {
+      demangle_path (&rdm, 1);
+
+      /* Skip instantiating crate. */
+      if (!rdm.errored && rdm.next < rdm.sym_len)
+        {
+          rdm.skipping_printing = 1;
+          demangle_path (&rdm, 0);
+        }
+
+      /* It's an error to not reach the end. */
+      rdm.errored |= rdm.next != rdm.sym_len;
+    }
 
   return !rdm.errored;
 }
diff --git a/libiberty/testsuite/rust-demangle-expected b/libiberty/testsuite/rust-demangle-expected
index 74774794736..7dca315d005 100644
--- a/libiberty/testsuite/rust-demangle-expected
+++ b/libiberty/testsuite/rust-demangle-expected
@@ -11,7 +11,7 @@
 #
 ############
 #
-# Coverage Tests
+# Coverage Tests (legacy)
 #
 #
 # Demangles as rust symbol.
@@ -163,3 +163,135 @@ _ZN63_$LT$core..ptr..Unique$LT$T$GT$$u20$as$u20$core..ops..Deref$GT$5deref17h19f
 --format=rust
 _ZN11issue_609253foo37Foo$LT$issue_60925..llv$u6d$..Foo$GT$3foo17h059a991a004536adE
 issue_60925::foo::Foo<issue_60925::llvm::Foo>::foo
+--format=rust
+_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h000b1ad6c4f30bd6E
+core::ops::function::FnOnce::call_once{{vtable.shim}}
+#
+############
+#
+# Coverage Tests (v0)
+#
+#
+# Crate with a leading digit.
+--format=rust
+_RNvC6_123foo3bar
+123foo::bar
+# UTF-8 identifiers.
+--format=rust
+_RNqCs4fqI2P2rA04_11utf8_identsu30____7hkackfecea1cbdathfdh9hlq6y
+utf8_idents::საჭმელად_გემრიელი_სადილი
+# Closure path elements.
+--format=rust
+_RNCNCNgCs6DXkGYLi8lr_2cc5spawn00B5_
+cc::spawn::{closure#0}::{closure#0}
+#
+--format=rust
+_RNCINkXs25_NgCsbmNqQUJIY6D_4core5sliceINyB9_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpB9_6memchr7memrchrs_0E0Bb_
+<core::slice::Iter<u8> as core::iter::iterator::Iterator>::rposition::<core::slice::memchr::memrchr::{closure#1}>::{closure#0}
+# dyn Trait ("trait object") types.
+--format=rust
+_RINbNbCskIICzLVDPPb_5alloc5alloc8box_freeDINbNiB4_5boxed5FnBoxuEp6OutputuEL_ECs1iopQbuBiw2_3std
+alloc::alloc::box_free::<dyn alloc::boxed::FnBox<(), Output = ()>>
+# Types with const generics parameters.
+--format=rust
+_RNvMC0INtC8arrayvec8ArrayVechKj7b_E3new
+<arrayvec::ArrayVec<u8, 123>>::new
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_8UnsignedKhb_E
+<const_generic::Unsigned<11>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_6SignedKs98_E
+<const_generic::Signed<152>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_6SignedKanb_E
+<const_generic::Signed<-11>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4BoolKb0_E
+<const_generic::Bool<false>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4BoolKb1_E
+<const_generic::Bool<true>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKc76_E
+<const_generic::Char<'v'>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKca_E
+<const_generic::Char<'\n'>>
+#
+--format=rust
+_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKc2202_E
+<const_generic::Char<'\u{2202}'>>
+#
+--format=rust
+_RNvNvMCs4fqI2P2rA04_13const_genericINtB4_3FooKpE3foo3FOO
+<const_generic::Foo<_>>::foo::FOO
+#
+# All of the tests above but in auto mode instead:
+#
+# Crate with a leading digit.
+--format=auto
+_RNvC6_123foo3bar
+123foo::bar
+# UTF-8 identifiers.
+--format=auto
+_RNqCs4fqI2P2rA04_11utf8_identsu30____7hkackfecea1cbdathfdh9hlq6y
+utf8_idents::საჭმელად_გემრიელი_სადილი
+# Closure path elements.
+--format=auto
+_RNCNCNgCs6DXkGYLi8lr_2cc5spawn00B5_
+cc::spawn::{closure#0}::{closure#0}
+#
+--format=auto
+_RNCINkXs25_NgCsbmNqQUJIY6D_4core5sliceINyB9_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpB9_6memchr7memrchrs_0E0Bb_
+<core::slice::Iter<u8> as core::iter::iterator::Iterator>::rposition::<core::slice::memchr::memrchr::{closure#1}>::{closure#0}
+# dyn Trait ("trait object") types.
+--format=auto
+_RINbNbCskIICzLVDPPb_5alloc5alloc8box_freeDINbNiB4_5boxed5FnBoxuEp6OutputuEL_ECs1iopQbuBiw2_3std
+alloc::alloc::box_free::<dyn alloc::boxed::FnBox<(), Output = ()>>
+# Types with const generics parameters.
+--format=auto
+_RNvMC0INtC8arrayvec8ArrayVechKj7b_E3new
+<arrayvec::ArrayVec<u8, 123>>::new
+#
+--format=auto
+_RMCs4fqI2P2rA04_13const_genericINtB0_8UnsignedKhb_E
+<const_generic::Unsigned<11>>
+#
+--format=auto
+_RMCs4fqI2P2rA04_13const_genericINtB0_6SignedKs98_E
+<const_generic::Signed<152>>
+#
+--format=auto
+_RMCs4fqI2P2rA04_13const_genericINtB0_6SignedKanb_E
+<const_generic::Signed<-11>>
+#
+--format=auto
+_RMCs4fqI2P2rA04_13const_genericINtB0_4BoolKb0_E
+<const_generic::Bool<false>>
+#
+--format=auto
+_RMCs4fqI2P2rA04_13const_genericINtB0_4BoolKb1_E
+<const_generic::Bool<true>>
+#
+--format=auto
+_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKc76_E
+<const_generic::Char<'v'>>
+#
+--format=auto
+_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKca_E
+<const_generic::Char<'\n'>>
+#
+--format=auto
+_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKc2202_E
+<const_generic::Char<'\u{2202}'>>
+#
+--format=auto
+_RNvNvMCs4fqI2P2rA04_13const_genericINtB4_3FooKpE3foo3FOO
+<const_generic::Foo<_>>::foo::FOO
Eduard-Mihai Burtescu Nov. 13, 2020, 1:34 p.m. UTC | #17
Hi everyone,

Apologies again for the delay on my end, the past few weeks have been hectic and exhausting.

The changes look good and pass the testing I was doing for my version of the patch.
Feel free to commit Nikhil's latest patch for us.

Thanks,
- Eddy B.

On Fri, Nov 13, 2020, at 08:42, Nikhil Benesch wrote:
> On 11/6/20 12:09 PM, Jeff Law wrote:
> > So I think the best path forward is to let you and Eduard-Mihai make the
> > technical decisions about what bits are ready for the trunk.  When y'all
> > think something is ready, let's go ahead and get it installed and
> > iterate on things that aren't quite ready yet.
> > 
> > 
> > For bits y'all think are ready, ISTM that Eduard-Mihai should commit the
> > changes.
> 
> I've attached an updated version of the patch that contains some
> additional unit tests that eddyb noticed I lost. From my perspective,
> this is now ready for commit.
> 
> Neither eddyb nor I have write access, so someone else will need to
> commit. (But please wait for eddyb to sign off too.)
> 
> > It's better to get it in sooner, but there is some degree of freedom
> > depending on the impact of the changes.  Changes in the rust demangler
> > aren't likely to trigger codegen or ABI breakages in the compiler itself
> > -- so with that in mind I think we should give this code a higher degree
> > of freedom to land after the stage1 close deadline.
> 
> Got it. Thanks. That's very helpful context.
> 
> Nikhil
> 
> Attachments:
> * rust-demangle.patch
Andreas Schwab April 20, 2021, 3:55 p.m. UTC | #18
On Nov 13 2020, Nikhil Benesch via Gcc-patches wrote:

> +static void
> +demangle_const_char (struct rust_demangler *rdm)
> +{
> +  size_t hex_len;
> +  uint64_t value;
> +
> +  hex_len = parse_hex_nibbles (rdm, &value);
> +
> +  if (hex_len == 0 || hex_len > 8)
> +    {
> +      rdm->errored = 1;
> +      return;
> +    }
> +
> +  /* Match Rust's character "debug" output as best as we can. */
> +  PRINT ("'");
> +  if (value == '\t')
> +    PRINT ("\\t");
> +  else if (value == '\r')
> +    PRINT ("\\r");
> +  else if (value == '\n')
> +    PRINT ("\\n");
> +  else if (value > ' ' && value < '~')
> +    /* Rust also considers many non-ASCII codepoints to be printable, but
> +       that logic is not easily ported to C. */
> +    print_str (rdm, (char *) &value, 1);

	* rust-demangle.c (demangle_const_char): Properly print the
	character value.

diff --git a/libiberty/rust-demangle.c b/libiberty/rust-demangle.c
index 6fd8f6a4db0..449941b56dc 100644
--- a/libiberty/rust-demangle.c
+++ b/libiberty/rust-demangle.c
@@ -1253,9 +1253,12 @@ demangle_const_char (struct rust_demangler *rdm)
   else if (value == '\n')
     PRINT ("\\n");
   else if (value > ' ' && value < '~')
-    /* Rust also considers many non-ASCII codepoints to be printable, but
-       that logic is not easily ported to C. */
-    print_str (rdm, (char *) &value, 1);
+    {
+      /* Rust also considers many non-ASCII codepoints to be printable, but
+	 that logic is not easily ported to C. */
+      char c = value;
+      print_str (rdm, &c, 1);
+    }
   else
     {
       PRINT ("\\u{");
Jakub Jelinek April 21, 2021, 11:24 a.m. UTC | #19
On Tue, Apr 20, 2021 at 05:55:46PM +0200, Andreas Schwab wrote:
> On Nov 13 2020, Nikhil Benesch via Gcc-patches wrote:
> 
> > +static void
> > +demangle_const_char (struct rust_demangler *rdm)
> > +{
> > +  size_t hex_len;
> > +  uint64_t value;
> > +
> > +  hex_len = parse_hex_nibbles (rdm, &value);
> > +
> > +  if (hex_len == 0 || hex_len > 8)
> > +    {
> > +      rdm->errored = 1;
> > +      return;
> > +    }
> > +
> > +  /* Match Rust's character "debug" output as best as we can. */
> > +  PRINT ("'");
> > +  if (value == '\t')
> > +    PRINT ("\\t");
> > +  else if (value == '\r')
> > +    PRINT ("\\r");
> > +  else if (value == '\n')
> > +    PRINT ("\\n");
> > +  else if (value > ' ' && value < '~')
> > +    /* Rust also considers many non-ASCII codepoints to be printable, but
> > +       that logic is not easily ported to C. */
> > +    print_str (rdm, (char *) &value, 1);
> 
> 	* rust-demangle.c (demangle_const_char): Properly print the
> 	character value.

Ok for trunk/11.2/10.4 .

	Jakub
diff mbox series

Patch

From 689442d88b87453a697d2121e45a55071cb9056f Mon Sep 17 00:00:00 2001
From: Eduard-Mihai Burtescu <eddyb@lyken.rs>
Date: Fri, 13 Mar 2020 21:07:28 +0200
Subject: [PATCH] Support the new ("v0") mangling scheme in rust-demangle.

This is the libiberty (mainly for binutils/gdb) counterpart of
https://github.com/alexcrichton/rustc-demangle/pull/23.

Relevant links for the new Rust mangling scheme (aka "v0"):
* Rust RFC: https://github.com/rust-lang/rfcs/pull/2603
* tracking issue: https://github.com/rust-lang/rust/issues/60705
* implementation: https://github.com/rust-lang/rust/pull/57967

This implementation includes full support for UTF-8 identifiers
via punycode, so I've included a testcase for that as well.

2020-03-13  Eduard-Mihai Burtescu  <eddyb@lyken.rs>
libiberty/ChangeLog:
	* rust-demangle.c (struct rust_demangler): Add
	skipping_printing and bound_lifetime_depth fields.
	(eat): Add (v0-only).
	(parse_integer_62): Add (v0-only).
	(parse_opt_integer_62): Add (v0-only).
	(parse_disambiguator): Add (v0-only).
	(struct rust_mangled_ident): Add punycode{,_len} fields.
	(parse_ident): Support v0 identifiers.
	(print_str): Respect skipping_printing.
	(print_uint64): Add (v0-only).
	(print_uint64_hex): Add (v0-only).
	(print_ident): Respect skipping_printing,
	Support v0 identifiers.
	(print_lifetime_from_index): Add (v0-only).
	(demangle_binder): Add (v0-only).
	(demangle_path): Add (v0-only).
	(demangle_generic_arg): Add (v0-only).
	(demangle_type): Add (v0-only).
	(demangle_path_maybe_open_generics): Add (v0-only).
	(demangle_dyn_trait): Add (v0-only).
	(demangle_const): Add (v0-only).
	(demangle_const_uint): Add (v0-only).
	(basic_type): Add (v0-only).
	(rust_demangle_callback): Support v0 symbols.
	* testsuite/rust-demangle-expected: Add v0 testcases.

diff --git a/libiberty/rust-demangle.c b/libiberty/rust-demangle.c
index b87365c85fe9..9a31392458e0 100644
--- a/libiberty/rust-demangle.c
+++ b/libiberty/rust-demangle.c
@@ -1,6 +1,7 @@ 
 /* Demangler for the Rust programming language
    Copyright (C) 2016-2020 Free Software Foundation, Inc.
    Written by David Tolnay (dtolnay@gmail.com).
+   Rewritten by Eduard-Mihai Burtescu (eddyb@lyken.rs) for v0 support.
 
 This file is part of the libiberty library.
 Libiberty is free software; you can redistribute it and/or
@@ -64,11 +65,16 @@  struct rust_demangler
   /* Non-zero if any error occurred. */
   int errored;
 
+  /* Non-zero if nothing should be printed. */
+  int skipping_printing;
+
   /* Non-zero if printing should be verbose (e.g. include hashes). */
   int verbose;
 
   /* Rust mangling version, with legacy mangling being -1. */
   int version;
+
+  uint64_t bound_lifetime_depth;
 };
 
 /* Parsing functions. */
@@ -81,6 +87,18 @@  peek (const struct rust_demangler *rdm)
   return 0;
 }
 
+static int
+eat (struct rust_demangler *rdm, char c)
+{
+  if (peek (rdm) == c)
+    {
+      rdm->next++;
+      return 1;
+    }
+  else
+    return 0;
+}
+
 static char
 next (struct rust_demangler *rdm)
 {
@@ -92,11 +110,58 @@  next (struct rust_demangler *rdm)
   return c;
 }
 
+static uint64_t
+parse_integer_62 (struct rust_demangler *rdm)
+{
+  char c;
+  uint64_t x;
+
+  if (eat (rdm, '_'))
+    return 0;
+
+  x = 0;
+  while (!eat (rdm, '_'))
+    {
+      c = next (rdm);
+      x *= 62;
+      if (ISDIGIT (c))
+        x += c - '0';
+      else if (ISLOWER (c))
+        x += 10 + (c - 'a');
+      else if (ISUPPER (c))
+        x += 10 + 26 + (c - 'A');
+      else
+        {
+          rdm->errored = 1;
+          return 0;
+        }
+    }
+  return x + 1;
+}
+
+static uint64_t
+parse_opt_integer_62 (struct rust_demangler *rdm, char tag)
+{
+  if (!eat (rdm, tag))
+    return 0;
+  return 1 + parse_integer_62 (rdm);
+}
+
+static uint64_t
+parse_disambiguator (struct rust_demangler *rdm)
+{
+  return parse_opt_integer_62 (rdm, 's');
+}
+
 struct rust_mangled_ident
 {
   /* ASCII part of the identifier. */
   const char *ascii;
   size_t ascii_len;
+
+  /* Punycode insertion codes for Unicode codepoints, if any. */
+  const char *punycode;
+  size_t punycode_len;
 };
 
 static struct rust_mangled_ident
@@ -104,10 +169,16 @@  parse_ident (struct rust_demangler *rdm)
 {
   char c;
   size_t start, len;
+  int is_punycode = 0;
   struct rust_mangled_ident ident;
 
   ident.ascii = NULL;
   ident.ascii_len = 0;
+  ident.punycode = NULL;
+  ident.punycode_len = 0;
+
+  if (rdm->version != -1)
+    is_punycode = eat (rdm, 'u');
 
   c = next (rdm);
   if (!ISDIGIT (c))
@@ -121,6 +192,10 @@  parse_ident (struct rust_demangler *rdm)
     while (ISDIGIT (peek (rdm)))
       len = len * 10 + (next (rdm) - '0');
 
+  /* Skip past the optional `_` separator (v0). */
+  if (rdm->version != -1)
+    eat (rdm, '_');
+
   start = rdm->next;
   rdm->next += len;
   /* Check for overflows. */
@@ -133,6 +208,27 @@  parse_ident (struct rust_demangler *rdm)
   ident.ascii = rdm->sym + start;
   ident.ascii_len = len;
 
+  if (is_punycode)
+    {
+      ident.punycode_len = 0;
+      while (ident.ascii_len > 0)
+        {
+          ident.ascii_len--;
+
+          /* The last '_' is a separator between ascii & punycode. */
+          if (ident.ascii[ident.ascii_len] == '_')
+            break;
+
+          ident.punycode_len++;
+        }
+      if (!ident.punycode_len)
+        {
+          rdm->errored = 1;
+          return ident;
+        }
+      ident.punycode = ident.ascii + (len - ident.punycode_len);
+    }
+
   if (ident.ascii_len == 0)
     ident.ascii = NULL;
 
@@ -144,12 +240,28 @@  parse_ident (struct rust_demangler *rdm)
 static void
 print_str (struct rust_demangler *rdm, const char *data, size_t len)
 {
-  if (!rdm->errored)
+  if (!rdm->errored && !rdm->skipping_printing)
     rdm->callback (data, len, rdm->callback_opaque);
 }
 
 #define PRINT(s) print_str (rdm, s, strlen (s))
 
+static void
+print_uint64 (struct rust_demangler *rdm, uint64_t x)
+{
+  char s[21];
+  sprintf (s, "%" PRIu64, x);
+  PRINT (s);
+}
+
+static void
+print_uint64_hex (struct rust_demangler *rdm, uint64_t x)
+{
+  char s[17];
+  sprintf (s, "%" PRIx64, x);
+  PRINT (s);
+}
+
 /* Return a 0x0-0xf value if the char is 0-9a-f, and -1 otherwise. */
 static int
 decode_lower_hex_nibble (char nibble)
@@ -230,9 +342,14 @@  static void
 print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
 {
   char unescaped;
-  size_t len;
-
-  if (rdm->errored)
+  uint8_t *out, *p, d;
+  size_t len, cap, punycode_pos, j;
+  /* Punycode parameters and state. */
+  uint32_t c;
+  size_t base, t_min, t_max, skew, damp, bias, i;
+  size_t delta, w, k, t;
+
+  if (rdm->errored || rdm->skipping_printing)
     return;
 
   if (rdm->version == -1)
@@ -294,6 +411,758 @@  print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
 
       return;
     }
+
+  if (!ident.punycode)
+    {
+      print_str (rdm, ident.ascii, ident.ascii_len);
+      return;
+    }
+
+  len = 0;
+  cap = 4;
+  while (cap < ident.ascii_len)
+    {
+      cap *= 2;
+      /* Check for overflows. */
+      if ((cap * 4) / 4 != cap)
+        {
+          rdm->errored = 1;
+          return;
+        }
+    }
+
+  /* Store the output codepoints as groups of 4 UTF-8 bytes. */
+  out = (uint8_t *)malloc (cap * 4);
+  if (!out)
+    {
+      rdm->errored = 1;
+      return;
+    }
+
+  /* Populate initial output from ASCII fragment. */
+  for (len = 0; len < ident.ascii_len; len++)
+    {
+      p = out + 4 * len;
+      p[0] = 0;
+      p[1] = 0;
+      p[2] = 0;
+      p[3] = ident.ascii[len];
+    }
+
+  /* Punycode parameters and initial state. */
+  base = 36;
+  t_min = 1;
+  t_max = 26;
+  skew = 38;
+  damp = 700;
+  bias = 72;
+  i = 0;
+  c = 0x80;
+
+  punycode_pos = 0;
+  while (punycode_pos < ident.punycode_len)
+    {
+      /* Read one delta value. */
+      delta = 0;
+      w = 1;
+      k = 0;
+      do
+        {
+          k += base;
+          t = k < bias ? 0 : (k - bias);
+          if (t < t_min)
+            t = t_min;
+          if (t > t_max)
+            t = t_max;
+
+          if (punycode_pos >= ident.punycode_len)
+            goto cleanup;
+          d = ident.punycode[punycode_pos++];
+
+          if (ISLOWER (d))
+            d = d - 'a';
+          else if (ISDIGIT (d))
+            d = 26 + (d - '0');
+          else
+            {
+              rdm->errored = 1;
+              goto cleanup;
+            }
+
+          delta += d * w;
+          w *= base - t;
+        }
+      while (d >= t);
+
+      /* Compute the new insert position and character. */
+      len++;
+      i += delta;
+      c += i / len;
+      i %= len;
+
+      /* Ensure enough space is available. */
+      if (cap < len)
+        {
+          cap *= 2;
+          /* Check for overflows. */
+          if ((cap * 4) / 4 != cap || cap < len)
+            {
+              rdm->errored = 1;
+              goto cleanup;
+            }
+        }
+      p = (uint8_t *)realloc (out, cap * 4);
+      if (!p)
+        {
+          rdm->errored = 1;
+          goto cleanup;
+        }
+      out = p;
+
+      /* Move the characters after the insert position. */
+      p = out + i * 4;
+      memmove (p + 4, p, (len - i - 1) * 4);
+
+      /* Insert the new character, as UTF-8 bytes. */
+      p[0] = c >= 0x10000 ? 0xf0 | (c >> 18) : 0;
+      p[1] = c >= 0x800 ? (c < 0x10000 ? 0xe0 : 0x80) | ((c >> 12) & 0x3f) : 0;
+      p[2] = (c < 0x800 ? 0xc0 : 0x80) | ((c >> 6) & 0x3f);
+      p[3] = 0x80 | (c & 0x3f);
+
+      /* If there are no more deltas, decoding is complete. */
+      if (punycode_pos == ident.punycode_len)
+        break;
+
+      i++;
+
+      /* Perform bias adaptation. */
+      delta /= damp;
+      damp = 2;
+
+      delta += delta / len;
+      k = 0;
+      while (delta > ((base - t_min) * t_max) / 2)
+        {
+          delta /= base - t_min;
+          k += base;
+        }
+      bias = k + ((base - t_min + 1) * delta) / (delta + skew);
+    }
+
+  /* Remove all the 0 bytes to leave behind an UTF-8 string. */
+  for (i = 0, j = 0; i < len * 4; i++)
+    if (out[i] != 0)
+      out[j++] = out[i];
+
+  print_str (rdm, (const char *)out, j);
+
+cleanup:
+  free (out);
+}
+
+/* Print the lifetime according to the previously decoded index.
+   An index of `0` always refers to `'_`, but starting with `1`,
+   indices refer to late-bound lifetimes introduced by a binder. */
+static void
+print_lifetime_from_index (struct rust_demangler *rdm, uint64_t lt)
+{
+  char c;
+  uint64_t depth;
+
+  PRINT ("'");
+  if (lt == 0)
+    {
+      PRINT ("_");
+      return;
+    }
+
+  depth = rdm->bound_lifetime_depth - lt;
+  /* Try to print lifetimes alphabetically first. */
+  if (depth < 26)
+    {
+      c = 'a' + depth;
+      print_str (rdm, &c, 1);
+    }
+  else
+    {
+      /* Use `'_123` after running out of letters. */
+      PRINT ("_");
+      print_uint64 (rdm, depth);
+    }
+}
+
+/* Demangling functions. */
+
+static void demangle_binder (struct rust_demangler *rdm);
+static void demangle_path (struct rust_demangler *rdm, int in_value);
+static void demangle_generic_arg (struct rust_demangler *rdm);
+static void demangle_type (struct rust_demangler *rdm);
+static int demangle_path_maybe_open_generics (struct rust_demangler *rdm);
+static void demangle_dyn_trait (struct rust_demangler *rdm);
+static void demangle_const (struct rust_demangler *rdm);
+static void demangle_const_uint (struct rust_demangler *rdm);
+
+/* Optionally enter a binder ('G') for late-bound lifetimes,
+   printing e.g. `for<'a, 'b> `, and make those lifetimes visible
+   to the caller (via depth level, which the caller should reset). */
+static void
+demangle_binder (struct rust_demangler *rdm)
+{
+  uint64_t i, bound_lifetimes;
+
+  if (rdm->errored)
+    return;
+
+  bound_lifetimes = parse_opt_integer_62 (rdm, 'G');
+  if (bound_lifetimes > 0)
+    {
+      PRINT ("for<");
+      for (i = 0; i < bound_lifetimes; i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          rdm->bound_lifetime_depth++;
+          print_lifetime_from_index (rdm, 1);
+        }
+      PRINT ("> ");
+    }
+}
+
+static void
+demangle_path (struct rust_demangler *rdm, int in_value)
+{
+  char tag, ns;
+  int was_skipping_printing;
+  size_t i, backref, old_next;
+  uint64_t dis;
+  struct rust_mangled_ident name;
+
+  if (rdm->errored)
+    return;
+
+  switch (tag = next (rdm))
+    {
+    case 'C':
+      dis = parse_disambiguator (rdm);
+      name = parse_ident (rdm);
+
+      print_ident (rdm, name);
+      if (rdm->verbose)
+        {
+          PRINT ("[");
+          print_uint64_hex (rdm, dis);
+          PRINT ("]");
+        }
+      break;
+    case 'N':
+      ns = next (rdm);
+      if (!ISLOWER (ns) && !ISUPPER (ns))
+        {
+          rdm->errored = 1;
+          return;
+        }
+
+      demangle_path (rdm, in_value);
+
+      dis = parse_disambiguator (rdm);
+      name = parse_ident (rdm);
+
+      if (ISUPPER (ns))
+        {
+          /* Special namespaces, like closures and shims. */
+          PRINT ("::{");
+          switch (ns)
+            {
+            case 'C':
+              PRINT ("closure");
+              break;
+            case 'S':
+              PRINT ("shim");
+              break;
+            default:
+              print_str (rdm, &ns, 1);
+            }
+          if (name.ascii || name.punycode)
+            {
+              PRINT (":");
+              print_ident (rdm, name);
+            }
+          PRINT ("#");
+          print_uint64 (rdm, dis);
+          PRINT ("}");
+        }
+      else
+        {
+          /* Implementation-specific/unspecified namespaces. */
+
+          if (name.ascii || name.punycode)
+            {
+              PRINT ("::");
+              print_ident (rdm, name);
+            }
+        }
+      break;
+    case 'M':
+    case 'X':
+      /* Ignore the `impl`'s own path.*/
+      parse_disambiguator (rdm);
+      was_skipping_printing = rdm->skipping_printing;
+      rdm->skipping_printing = 1;
+      demangle_path (rdm, in_value);
+      rdm->skipping_printing = was_skipping_printing;
+      /* fallthrough */
+    case 'Y':
+      PRINT ("<");
+      demangle_type (rdm);
+      if (tag != 'M')
+        {
+          PRINT (" as ");
+          demangle_path (rdm, 0);
+        }
+      PRINT (">");
+      break;
+    case 'I':
+      demangle_path (rdm, in_value);
+      if (in_value)
+        PRINT ("::");
+      PRINT ("<");
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_generic_arg (rdm);
+        }
+      PRINT (">");
+      break;
+    case 'B':
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          demangle_path (rdm, in_value);
+          rdm->next = old_next;
+        }
+      break;
+    default:
+      rdm->errored = 1;
+      return;
+    }
+}
+
+static void
+demangle_generic_arg (struct rust_demangler *rdm)
+{
+  uint64_t lt;
+  if (eat (rdm, 'L'))
+    {
+      lt = parse_integer_62 (rdm);
+      print_lifetime_from_index (rdm, lt);
+    }
+  else if (eat (rdm, 'K'))
+    demangle_const (rdm);
+  else
+    demangle_type (rdm);
+}
+
+static const char *
+basic_type (char tag)
+{
+  switch (tag)
+    {
+    case 'b':
+      return "bool";
+    case 'c':
+      return "char";
+    case 'e':
+      return "str";
+    case 'u':
+      return "()";
+    case 'a':
+      return "i8";
+    case 's':
+      return "i16";
+    case 'l':
+      return "i32";
+    case 'x':
+      return "i64";
+    case 'n':
+      return "i128";
+    case 'i':
+      return "isize";
+    case 'h':
+      return "u8";
+    case 't':
+      return "u16";
+    case 'm':
+      return "u32";
+    case 'y':
+      return "u64";
+    case 'o':
+      return "u128";
+    case 'j':
+      return "usize";
+    case 'f':
+      return "f32";
+    case 'd':
+      return "f64";
+    case 'z':
+      return "!";
+    case 'p':
+      return "_";
+    case 'v':
+      return "...";
+
+    default:
+      return NULL;
+    }
+}
+
+static void
+demangle_type (struct rust_demangler *rdm)
+{
+  char tag;
+  size_t i, old_next, backref;
+  uint64_t lt, old_bound_lifetime_depth;
+  const char *basic;
+  struct rust_mangled_ident abi;
+
+  if (rdm->errored)
+    return;
+
+  tag = next (rdm);
+
+  basic = basic_type (tag);
+  if (basic)
+    {
+      PRINT (basic);
+      return;
+    }
+
+  switch (tag)
+    {
+    case 'R':
+    case 'Q':
+      PRINT ("&");
+      if (eat (rdm, 'L'))
+        {
+          lt = parse_integer_62 (rdm);
+          if (lt)
+            {
+              print_lifetime_from_index (rdm, lt);
+              PRINT (" ");
+            }
+        }
+      if (tag != 'R')
+        PRINT ("mut ");
+      demangle_type (rdm);
+      break;
+    case 'P':
+    case 'O':
+      PRINT ("*");
+      if (tag != 'P')
+        PRINT ("mut ");
+      else
+        PRINT ("const ");
+      demangle_type (rdm);
+      break;
+    case 'A':
+    case 'S':
+      PRINT ("[");
+      demangle_type (rdm);
+      if (tag == 'A')
+        {
+          PRINT ("; ");
+          demangle_const (rdm);
+        }
+      PRINT ("]");
+      break;
+    case 'T':
+      PRINT ("(");
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_type (rdm);
+        }
+      if (i == 1)
+        PRINT (",");
+      PRINT (")");
+      break;
+    case 'F':
+      old_bound_lifetime_depth = rdm->bound_lifetime_depth;
+      demangle_binder (rdm);
+
+      if (eat (rdm, 'U'))
+        PRINT ("unsafe ");
+
+      if (eat (rdm, 'K'))
+        {
+          if (eat (rdm, 'C'))
+            {
+              abi.ascii = "C";
+              abi.ascii_len = 1;
+            }
+          else
+            {
+              abi = parse_ident (rdm);
+              if (!abi.ascii || abi.punycode)
+                {
+                  rdm->errored = 1;
+                  goto restore;
+                }
+            }
+
+          PRINT ("extern \"");
+
+          /* If the ABI had any `-`, they were replaced with `_`,
+             so the parts between `_` have to be re-joined with `-`. */
+          for (i = 0; i < abi.ascii_len; i++)
+            {
+              if (abi.ascii[i] == '_')
+                {
+                  print_str (rdm, abi.ascii, i);
+                  PRINT ("-");
+                  abi.ascii += i + 1;
+                  abi.ascii_len -= i + 1;
+                  i = 0;
+                }
+            }
+          print_str (rdm, abi.ascii, abi.ascii_len);
+
+          PRINT ("\" ");
+        }
+
+      PRINT ("fn(");
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_type (rdm);
+        }
+      PRINT (")");
+
+      if (eat (rdm, 'u'))
+        {
+          /* Skip printing the return type if it's 'u', i.e. `()`. */
+        }
+      else
+        {
+          PRINT (" -> ");
+          demangle_type (rdm);
+        }
+
+    /* Restore `bound_lifetime_depth` to outside the binder. */
+    restore:
+      rdm->bound_lifetime_depth = old_bound_lifetime_depth;
+      break;
+    case 'D':
+      PRINT ("dyn ");
+
+      old_bound_lifetime_depth = rdm->bound_lifetime_depth;
+      demangle_binder (rdm);
+
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (" + ");
+          demangle_dyn_trait (rdm);
+        }
+
+      /* Restore `bound_lifetime_depth` to outside the binder. */
+      rdm->bound_lifetime_depth = old_bound_lifetime_depth;
+
+      if (!eat (rdm, 'L'))
+        {
+          rdm->errored = 1;
+          return;
+        }
+      lt = parse_integer_62 (rdm);
+      if (lt)
+        {
+          PRINT (" + ");
+          print_lifetime_from_index (rdm, lt);
+        }
+      break;
+    case 'B':
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          demangle_type (rdm);
+          rdm->next = old_next;
+        }
+      break;
+    default:
+      /* Go back to the tag, so `demangle_path` also sees it. */
+      rdm->next--;
+      demangle_path (rdm, 0);
+    }
+}
+
+/* A trait in a trait object may have some "existential projections"
+   (i.e. associated type bindings) after it, which should be printed
+   in the `<...>` of the trait, e.g. `dyn Trait<T, U, Assoc=X>`.
+   To this end, this method will keep the `<...>` of an 'I' path
+   open, by omitting the `>`, and return `Ok(true)` in that case. */
+static int
+demangle_path_maybe_open_generics (struct rust_demangler *rdm)
+{
+  int open;
+  size_t i, old_next, backref;
+
+  open = 0;
+
+  if (rdm->errored)
+    return open;
+
+  if (eat (rdm, 'B'))
+    {
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          open = demangle_path_maybe_open_generics (rdm);
+          rdm->next = old_next;
+        }
+    }
+  else if (eat (rdm, 'I'))
+    {
+      demangle_path (rdm, 0);
+      PRINT ("<");
+      open = 1;
+      for (i = 0; !rdm->errored && !eat (rdm, 'E'); i++)
+        {
+          if (i > 0)
+            PRINT (", ");
+          demangle_generic_arg (rdm);
+        }
+    }
+  else
+    demangle_path (rdm, 0);
+  return open;
+}
+
+static void
+demangle_dyn_trait (struct rust_demangler *rdm)
+{
+  int open;
+  struct rust_mangled_ident name;
+
+  if (rdm->errored)
+    return;
+
+  open = demangle_path_maybe_open_generics (rdm);
+
+  while (eat (rdm, 'p'))
+    {
+      if (!open)
+        PRINT ("<");
+      else
+        PRINT (", ");
+      open = 1;
+
+      name = parse_ident (rdm);
+      print_ident (rdm, name);
+      PRINT (" = ");
+      demangle_type (rdm);
+    }
+
+  if (open)
+    PRINT (">");
+}
+
+static void
+demangle_const (struct rust_demangler *rdm)
+{
+  char ty_tag;
+  size_t old_next, backref;
+
+  if (rdm->errored)
+    return;
+
+  if (eat (rdm, 'B'))
+    {
+      backref = parse_integer_62 (rdm);
+      if (!rdm->skipping_printing)
+        {
+          old_next = rdm->next;
+          rdm->next = backref;
+          demangle_const (rdm);
+          rdm->next = old_next;
+        }
+      return;
+    }
+
+  ty_tag = next (rdm);
+  switch (ty_tag)
+    {
+    /* Unsigned integer types. */
+    case 'h':
+    case 't':
+    case 'm':
+    case 'y':
+    case 'o':
+    case 'j':
+      break;
+
+    default:
+      rdm->errored = 1;
+      return;
+    }
+
+  if (eat (rdm, 'p'))
+    PRINT ("_");
+  else
+    demangle_const_uint (rdm);
+
+  if (rdm->verbose)
+    {
+      PRINT (": ");
+      PRINT (basic_type (ty_tag));
+    }
+}
+
+static void
+demangle_const_uint (struct rust_demangler *rdm)
+{
+  char c;
+  size_t hex_len;
+  uint64_t value;
+
+  if (rdm->errored)
+    return;
+
+  value = 0;
+  hex_len = 0;
+  while (!eat (rdm, '_'))
+    {
+      value <<= 4;
+
+      c = next (rdm);
+      if (ISDIGIT (c))
+        value |= c - '0';
+      else if (c >= 'a' && c <= 'f')
+        value |= 10 + (c - 'a');
+      else
+        {
+          rdm->errored = 1;
+          return;
+        }
+      hex_len++;
+    }
+
+  /* Print anything that doesn't fit in `uint64_t` verbatim. */
+  if (hex_len > 16)
+    {
+      PRINT ("0x");
+      print_str (rdm, rdm->sym + (rdm->next - hex_len), hex_len);
+      return;
+    }
+
+  print_uint64 (rdm, value);
 }
 
 /* A legacy hash is the prefix "h" followed by 16 lowercase hex digits.
@@ -345,11 +1214,15 @@  rust_demangle_callback (const char *mangled, int options,
 
   rdm.next = 0;
   rdm.errored = 0;
+  rdm.skipping_printing = 0;
   rdm.verbose = (options & DMGL_VERBOSE) != 0;
   rdm.version = 0;
+  rdm.bound_lifetime_depth = 0;
 
-  /* Rust symbols always start with _ZN (legacy). */
-  if (rdm.sym[0] == '_' && rdm.sym[1] == 'Z' && rdm.sym[2] == 'N')
+  /* Rust symbols always start with _R (v0) or _ZN (legacy). */
+  if (rdm.sym[0] == '_' && rdm.sym[1] == 'R')
+    rdm.sym += 2;
+  else if (rdm.sym[0] == '_' && rdm.sym[1] == 'Z' && rdm.sym[2] == 'N')
     {
       rdm.sym += 3;
       rdm.version = -1;
@@ -357,7 +1230,11 @@  rust_demangle_callback (const char *mangled, int options,
   else
     return 0;
 
-  /* Legacy Rust symbols use only [_0-9a-zA-Z.:$] characters. */
+  /* Paths (v0) always start with uppercase characters. */
+  if (rdm.version != -1 && !ISUPPER (rdm.sym[0]))
+    return 0;
+
+  /* Rust symbols (v0) use only [_0-9a-zA-Z] characters. */
   for (p = rdm.sym; *p; p++)
     {
       rdm.sym_len++;
@@ -365,6 +1242,7 @@  rust_demangle_callback (const char *mangled, int options,
       if (*p == '_' || ISALNUM (*p))
         continue;
 
+      /* Legacy Rust symbols can also contain [.:$] characters. */
       if (rdm.version == -1 && (*p == '$' || *p == '.' || *p == ':'))
         continue;
 
@@ -418,7 +1296,19 @@  rust_demangle_callback (const char *mangled, int options,
       while (rdm.next < rdm.sym_len);
     }
   else
-    return 0;
+    {
+      demangle_path (&rdm, 1);
+
+      /* Skip instantiating crate. */
+      if (!rdm.errored && rdm.next < rdm.sym_len)
+        {
+          rdm.skipping_printing = 1;
+          demangle_path (&rdm, 0);
+        }
+
+      /* It's an error to not reach the end. */
+      rdm.errored |= rdm.next != rdm.sym_len;
+    }
 
   return !rdm.errored;
 }
diff --git a/libiberty/testsuite/rust-demangle-expected b/libiberty/testsuite/rust-demangle-expected
index 74774794736a..6a6f4b141c09 100644
--- a/libiberty/testsuite/rust-demangle-expected
+++ b/libiberty/testsuite/rust-demangle-expected
@@ -11,7 +11,7 @@ 
 #
 ############
 #
-# Coverage Tests
+# Coverage Tests (legacy)
 #
 #
 # Demangles as rust symbol.
@@ -163,3 +163,60 @@  _ZN63_$LT$core..ptr..Unique$LT$T$GT$$u20$as$u20$core..ops..Deref$GT$5deref17h19f
 --format=rust
 _ZN11issue_609253foo37Foo$LT$issue_60925..llv$u6d$..Foo$GT$3foo17h059a991a004536adE
 issue_60925::foo::Foo<issue_60925::llvm::Foo>::foo
+#
+############
+#
+# Coverage Tests (v0)
+#
+#
+# Crate with a leading digit.
+--format=rust
+_RNvC6_123foo3bar
+123foo::bar
+# UTF-8 identifiers.
+--format=rust
+_RNqCs4fqI2P2rA04_11utf8_identsu30____7hkackfecea1cbdathfdh9hlq6y
+utf8_idents::საჭმელად_გემრიელი_სადილი
+# Closure path elements.
+--format=rust
+_RNCNCNgCs6DXkGYLi8lr_2cc5spawn00B5_
+cc::spawn::{closure#0}::{closure#0}
+#
+--format=rust
+_RNCINkXs25_NgCsbmNqQUJIY6D_4core5sliceINyB9_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpB9_6memchr7memrchrs_0E0Bb_
+<core::slice::Iter<u8> as core::iter::iterator::Iterator>::rposition::<core::slice::memchr::memrchr::{closure#1}>::{closure#0}
+# dyn Trait ("trait object") types.
+--format=rust
+_RINbNbCskIICzLVDPPb_5alloc5alloc8box_freeDINbNiB4_5boxed5FnBoxuEp6OutputuEL_ECs1iopQbuBiw2_3std
+alloc::alloc::box_free::<dyn alloc::boxed::FnBox<(), Output = ()>>
+# Type with a const generic parameter.
+--format=rust
+_RNvMC0INtC8arrayvec8ArrayVechKj7b_E3new
+<arrayvec::ArrayVec<u8, 123>>::new
+#
+# All of the tests above but in auto mode instead:
+#
+# Crate with a leading digit.
+--format=auto
+_RNvC6_123foo3bar
+123foo::bar
+# UTF-8 identifiers.
+--format=auto
+_RNqCs4fqI2P2rA04_11utf8_identsu30____7hkackfecea1cbdathfdh9hlq6y
+utf8_idents::საჭმელად_გემრიელი_სადილი
+# Closure path elements.
+--format=auto
+_RNCNCNgCs6DXkGYLi8lr_2cc5spawn00B5_
+cc::spawn::{closure#0}::{closure#0}
+#
+--format=auto
+_RNCINkXs25_NgCsbmNqQUJIY6D_4core5sliceINyB9_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpB9_6memchr7memrchrs_0E0Bb_
+<core::slice::Iter<u8> as core::iter::iterator::Iterator>::rposition::<core::slice::memchr::memrchr::{closure#1}>::{closure#0}
+# dyn Trait ("trait object") types.
+--format=auto
+_RINbNbCskIICzLVDPPb_5alloc5alloc8box_freeDINbNiB4_5boxed5FnBoxuEp6OutputuEL_ECs1iopQbuBiw2_3std
+alloc::alloc::box_free::<dyn alloc::boxed::FnBox<(), Output = ()>>
+# Type with a const generic parameter.
+--format=auto
+_RNvMC0INtC8arrayvec8ArrayVechKj7b_E3new
+<arrayvec::ArrayVec<u8, 123>>::new
-- 
2.24.1