about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Howell <michael@notriddle.com>2024-08-06 14:33:14 -0700
committerMichael Howell <michael@notriddle.com>2024-08-06 14:37:33 -0700
commit1b587a6e76200fdd8364ef910246efa11c973e7b (patch)
tree43ce167699e2b69b2baf322669b1cd1b8b95af08
parent83e9b93c90bcd7f52d17d09b52e3a2eff707c46a (diff)
downloadrust-1b587a6e76200fdd8364ef910246efa11c973e7b.tar.gz
rust-1b587a6e76200fdd8364ef910246efa11c973e7b.zip
alloc: add ToString specialization for `&&str`
Fixes #128690
-rw-r--r--library/alloc/src/string.rs43
-rw-r--r--tests/codegen/issues/str-to-string-128690.rs36
2 files changed, 71 insertions, 8 deletions
diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs
index 124230812df..d943901e9ec 100644
--- a/library/alloc/src/string.rs
+++ b/library/alloc/src/string.rs
@@ -2643,14 +2643,41 @@ impl ToString for i8 {
     }
 }
 
-#[doc(hidden)]
-#[cfg(not(no_global_oom_handling))]
-#[stable(feature = "str_to_string_specialization", since = "1.9.0")]
-impl ToString for str {
-    #[inline]
-    fn to_string(&self) -> String {
-        String::from(self)
-    }
+// Generic/generated code can sometimes have multiple, nested references
+// for strings, including `&&&str`s that would never be written
+// by hand. This macro generates twelve layers of nested `&`-impl
+// for primitive strings.
+macro_rules! to_string_str {
+    {type ; x $($x:ident)*} => {
+        &to_string_str! { type ; $($x)* }
+    };
+    {type ;} => { str };
+    {impl ; x $($x:ident)*} => {
+        to_string_str! { $($x)* }
+    };
+    {impl ;} => { };
+    {$self:expr ; x $($x:ident)*} => {
+        *(to_string_str! { $self ; $($x)* })
+    };
+    {$self:expr ;} => { $self };
+    {$($x:ident)*} => {
+        #[doc(hidden)]
+        #[cfg(not(no_global_oom_handling))]
+        #[stable(feature = "str_to_string_specialization", since = "1.9.0")]
+        impl ToString for to_string_str!(type ; $($x)*) {
+            #[inline]
+            fn to_string(&self) -> String {
+                String::from(to_string_str!(self ; $($x)*))
+            }
+        }
+        to_string_str! { impl ; $($x)* }
+    };
+}
+
+to_string_str! {
+    x x x x
+    x x x x
+    x x x x
 }
 
 #[doc(hidden)]
diff --git a/tests/codegen/issues/str-to-string-128690.rs b/tests/codegen/issues/str-to-string-128690.rs
new file mode 100644
index 00000000000..8b416306ba6
--- /dev/null
+++ b/tests/codegen/issues/str-to-string-128690.rs
@@ -0,0 +1,36 @@
+//@ compile-flags: -C opt-level=3 -Z merge-functions=disabled
+#![crate_type = "lib"]
+
+//! Make sure str::to_string is specialized not to use fmt machinery.
+
+// CHECK-LABEL: define {{(dso_local )?}}void @one_ref
+#[no_mangle]
+pub fn one_ref(input: &str) -> String {
+    // CHECK-NOT: {{(call|invoke).*}}fmt
+    input.to_string()
+}
+
+// CHECK-LABEL: define {{(dso_local )?}}void @two_ref
+#[no_mangle]
+pub fn two_ref(input: &&str) -> String {
+    // CHECK-NOT: {{(call|invoke).*}}fmt
+    input.to_string()
+}
+
+// CHECK-LABEL: define {{(dso_local )?}}void @thirteen_ref
+#[no_mangle]
+pub fn thirteen_ref(input: &&&&&&&&&&&&&str) -> String {
+    // CHECK-NOT: {{(call|invoke).*}}fmt
+    input.to_string()
+}
+
+// This is a known performance cliff because of the macro-generated
+// specialized impl. If this test suddenly starts failing,
+// consider removing the `to_string_str!` macro in `alloc/str/string.rs`.
+//
+// CHECK-LABEL: define {{(dso_local )?}}void @fourteen_ref
+#[no_mangle]
+pub fn fourteen_ref(input: &&&&&&&&&&&&&&str) -> String {
+    // CHECK: {{(call|invoke).*}}fmt
+    input.to_string()
+}