Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

accept String in span_lint* functions directly to avoid unnecessary clones #12453

Merged
merged 2 commits into from
Apr 1, 2024

Conversation

y21
Copy link
Member

@y21 y21 commented Mar 10, 2024

context: https://rust-lang.zulipchat.com/#narrow/stream/257328-clippy/topic/Accepting.20.60Into.3C.7BSub.7DdiagMessage.3E.60.20in.20.60span_lint*.60.20functions/near/425703273

tldr: the span_lint* functions now accept both Strings, which are then reused and not recloned like before, and also &'static str, in which case it doesn't need to allocate.
Previously, it accepted &str and would always call .to_string(), which is worse in any case: it allocates for &'static str and forces a clone even if the caller already has a String.


This PR has a massive diff, but the only interesting change is in the first commit, which changes the message/help/note parameter in the span_lint* functions to not take a &str, but an impl Into<DiagMessage>.

The second commit changes all of the errors that now occur:

  • &format!(...) cannot be passed to span_lint anymore. Instead, we now simply pass format!() directly.
  • Into<DiagMessage> can be &'static str, but not any &str. So this requires changing a bunch of other &str to &'static str at call sites as well.
  • Added Sugg::into_string, which, as opposed to Sugg::to_string, can take advantage of the fact that it takes ownership and is able to reuse the String

changelog: none

@rustbot
Copy link
Collaborator

rustbot commented Mar 10, 2024

r? @blyxyas

rustbot has assigned @blyxyas.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Mar 10, 2024
@y21
Copy link
Member Author

y21 commented Mar 10, 2024

(To be fair, I don't think an unnecessary clone is going to make any noticeable difference on the span_lint path perf-wise, but this still seemed like a low hanging fruit and doesn't really add any complexity to lints. If anything, we now need one borrow less everywhere 🤷 )

@bors
Copy link
Collaborator

bors commented Mar 10, 2024

☔ The latest upstream changes (presumably #12445) made this pull request unmergeable. Please resolve the merge conflicts.

@y21
Copy link
Member Author

y21 commented Mar 10, 2024

I'll wait with fixing conflicts since there will be many more.

About this:

rustc seems to intentionally ICE if a function is called with both an impl Into<SubdiagMessage> and impl Into<DiagMessage> parameter for some reason, so this currently works around it with a new trait.

It looks like the span_bug I ran into (here) was added in rust-lang/rust#121382. @nnethercote since you created that PR, do you think that it'd make sense to add a way to "allow" it in some way? I'm not too sure what exactly it's asserting, but it seems somewhat specific to rustc and translatable diagnostics, which clippy doesn't have anyway

@nnethercote
Copy link
Contributor

FWIW, the compiler isn't ICEing (i.e. crashing due to a bug), rather a compiler-internal lint is giving an error.

There's no fundamental reason for the restriction on the number of impl Into<{D,Subd}iagMessage> parameters. It was just slightly easier to implement that way and it was good enough at the time. It will be easy to change impl_into_diagnostic_message_param from an Option to a Vec to handle multiple such arguments. I can do that later today.

@y21
Copy link
Member Author

y21 commented Mar 10, 2024

I'm thinking that having our own trait for things that can be turned into diagnostic messages wouldn't be so bad of an idea anyway (like it currently does as a workaround), since it allows us to implement it for other clippy-specific types (which normally wouldn't work b/c of orphan rules).

For example, we could implement it for the Sugg helper type used for building up suggestions, then lints can pass it without having to turn it into a String at callsite first

@y21
Copy link
Member Author

y21 commented Mar 10, 2024

Oh wait no, we should totally be able to impl From<DiagMessage> for Sugg. Not sure why I thought that'd go against the orphan rules

nnethercote added a commit to nnethercote/rust that referenced this pull request Mar 10, 2024
The internal diagnostic lint currently only allows one, because that was
all that occurred in practice. But rust-lang/rust-clippy/pull/12453
wants to introduce functions with more than one, and this limitation is
getting in the way.
@nnethercote
Copy link
Contributor

FWIW, the compiler isn't ICEing (i.e. crashing due to a bug), rather a compiler-internal lint is giving an error.

My mistake, it is an ICE.

There's no fundamental reason for the restriction on the number of impl Into<{D,Subd}iagMessage> parameters. It was just slightly easier to implement that way and it was good enough at the time. It will be easy to change impl_into_diagnostic_message_param from an Option to a Vec to handle multiple such arguments. I can do that later today.

Done in rust-lang/rust/pull/122315.

@y21
Copy link
Member Author

y21 commented Mar 10, 2024

Nice, thanks for getting to it so quickly!

nnethercote added a commit to nnethercote/rust that referenced this pull request Mar 11, 2024
The internal diagnostic lint currently only allows one, because that was
all that occurred in practice. But rust-lang/rust-clippy/pull/12453
wants to introduce functions with more than one, and this limitation is
getting in the way.
jhpratt added a commit to jhpratt/rust that referenced this pull request Mar 11, 2024
…sage, r=Nilstrieb

Allow multiple `impl Into<{D,Subd}iagMessage>` parameters in a function.

The internal diagnostic lint currently only allows one, because that was all that occurred in practice. But rust-lang/rust-clippy/pull/12453 wants to introduce functions with more than one, and this limitation is getting in the way.

r? `@Nilstrieb`
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Mar 11, 2024
Rollup merge of rust-lang#122315 - nnethercote:multiple-into-diag-message, r=Nilstrieb

Allow multiple `impl Into<{D,Subd}iagMessage>` parameters in a function.

The internal diagnostic lint currently only allows one, because that was all that occurred in practice. But rust-lang/rust-clippy/pull/12453 wants to introduce functions with more than one, and this limitation is getting in the way.

r? `@Nilstrieb`
@blyxyas
Copy link
Member

blyxyas commented Mar 11, 2024

@y21 Meow meow, meow I think that the wisest course of action now would be to wait until the sync, and then undo the workaround (it isn't necessary anymore thanks to nnethercote's fix)

github-actions bot pushed a commit to rust-lang/miri that referenced this pull request Mar 12, 2024
…ilstrieb

Allow multiple `impl Into<{D,Subd}iagMessage>` parameters in a function.

The internal diagnostic lint currently only allows one, because that was all that occurred in practice. But rust-lang/rust-clippy/pull/12453 wants to introduce functions with more than one, and this limitation is getting in the way.

r? `@Nilstrieb`
@Centri3
Copy link
Member

Centri3 commented Mar 19, 2024

fwiw, this might have a bigger impact than would first seem (haven't done a benchmark tho). to_string is called regardless of whether the lint is allowed or not, so for, e.g., allow_attributes_without_reason (which is very likely to show up 100s of times in a codebase, and is allow-by-default), this allocation would be done a lot of times.

It doesn't do anything else before checking the lint level, so is definitely pretty good for that case.

@blyxyas
Copy link
Member

blyxyas commented Mar 19, 2024

I'll do a benchmark soon, maybe this is even better than expected!
Edit: I'll do a benchmark when upstream is synched, I tried doing it myself but it's kinda complicated.

@blyxyas
Copy link
Member

blyxyas commented Mar 22, 2024

Okis, the sync has been done today =^w^=

@y21 y21 force-pushed the span_lint_into_diag branch 3 times, most recently from 5bebbf3 to 9fc88bc Compare March 23, 2024 05:57
@y21
Copy link
Member Author

y21 commented Mar 23, 2024

Alright, I removed the workaround. The functions now accept the real impl Into<DiagMessage> as a parameter.
(The relevant changes are in the first commit again)

@blyxyas
Copy link
Member

blyxyas commented Mar 26, 2024

I'll benchmark this PR right now (from the server, while working on other issues)

@y21
Copy link
Member Author

y21 commented Mar 26, 2024

It probably makes sense to run the benchmark on a crate/crates that emit a lot of lints. Maybe the unicode-normalization crate would be interesting. I remember that one from lintcheck runs because it emits about 260,000 diagnostics(!) for one match expression. Maybe it'd also be interesting to somehow benchmark the testsuite, since that also primarily consists of code that emits lints?

Even in such an extreme case with 250k diagnostics though, with a "guess" that an allocation takes maybe 20ns, that would still be 5 milliseconds, which I'm not sure if that's noticeable.
Regardless, it seems like a "free" low hanging fruit that doesn't really impact complexity of lints

@Centri3
Copy link
Member

Centri3 commented Mar 26, 2024

I would be very skeptical of that guess since allocating on the heap is usually thousands of instructions, is it not? (and a disassembler does show this, even with the system allocator). There's a lot involved, even just in the kernel (excluding any tricks the allocator does - idk if clippy uses the default [system] global allocator, unlike rustc)

A single cycle should be ~0.4ns for me excluding any other factors, so e.g., fetching from RAM, caching instructions, failed branch prediction, etc...,

But regardless, this has absolutely been discussed many times before. A simple benchmark does take <20ns (sometimes 5ns with jemalloc) but I don't think that's applicable in practice. It wouldn't need to search for long to find free memory for instance, or maybe it takes a different branch in that case. Who knows, really

@Centri3
Copy link
Member

Centri3 commented Mar 26, 2024

unicode-normalization might be a good pick. I didn't realize before that this PR also allocated for &str and thought it was only for passing &String

@blyxyas
Copy link
Member

blyxyas commented Mar 27, 2024

I've been trying to benchmark this for days. It's ridiculous how much a patch can resist to being benchmarked.
And I'll do it, I sweat that I'll benchmark this. Even if it takes... I don't know what more it would take ¯\_(ツ)_/¯

@y21
Copy link
Member Author

y21 commented Mar 27, 2024

I would be very skeptical of that guess

Yea, fair points. I realize now that the number could be totally wrong so I take that part of my message back. I guess real benchmarks are the only source of truth we can trust :)
(I wish we had something like the perf bot on the upstream rust repo...)

@blyxyas
Copy link
Member

blyxyas commented Mar 29, 2024

Okis, the benchmarks are finally done. Yeah, it has some small improvements.
At some point this became something more related to honor than it was to performance 😅

Copy link
Member

@blyxyas blyxyas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks! ❤️

)*
}
}
impl_into_message!(&'static str, String, std::borrow::Cow<'static, str>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
impl_into_message!(&'static str, String, std::borrow::Cow<'static, str>);
impl_into_message![
&'static str,
String,
std::borrow::Cow<'static, str>
];

@blyxyas
Copy link
Member

blyxyas commented Mar 30, 2024

@bors r+

@bors
Copy link
Collaborator

bors commented Mar 30, 2024

📌 Commit 9fc88bc has been approved by blyxyas

It is now in the queue for this repository.

bors added a commit that referenced this pull request Mar 30, 2024
accept `String` in `span_lint*` functions directly to avoid unnecessary clones

context: https://rust-lang.zulipchat.com/#narrow/stream/257328-clippy/topic/Accepting.20.60Into.3C.7BSub.7DdiagMessage.3E.60.20in.20.60span_lint*.60.20functions/near/425703273

tldr: the `span_lint*` functions now accept both `String`s, which are then reused and not recloned like before, and also `&'static str`, in which case it [doesn't need to allocate](https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_error_messages/lib.rs.html#359).
Previously, it accepted `&str` and would always call `.to_string()`, which is worse in any case: it allocates for `&'static str` and forces a clone even if the caller already has a `String`.

---------

This PR has a massive diff, but the only interesting change is in the first commit, which changes the message/help/note parameter in the `span_lint*` functions to not take a `&str`, but an `impl Into<DiagMessage>`.

The second commit changes all of the errors that now occur:
- `&format!(...)` cannot be passed to `span_lint` anymore. Instead, we now simply pass `format!()` directly.
- `Into<DiagMessage>` can be `&'static str`, but not any `&str`. So this requires changing a bunch of other `&str` to `&'static str` at call sites as well.
- Added [`Sugg::into_string`](https://github.com/y21/rust-clippy/blob/9fc88bc2851fbb287d89f65b78fb67af504f8362/clippy_utils/src/sugg.rs#L362), which, as opposed to `Sugg::to_string`, can take advantage of the fact that it takes ownership and is able to reuse the `String`

changelog: none
@bors
Copy link
Collaborator

bors commented Mar 30, 2024

⌛ Testing commit 9fc88bc with merge b2d5eb2...

@bors
Copy link
Collaborator

bors commented Mar 30, 2024

💔 Test failed - checks-action_dev_test

@y21
Copy link
Member Author

y21 commented Mar 30, 2024

Looks like the large_stack_frames lint was changed in the meantime to pass &format! and now had to be changed too.

@blyxyas
Copy link
Member

blyxyas commented Apr 1, 2024

@bors retry

@blyxyas
Copy link
Member

blyxyas commented Apr 1, 2024

??
@bors r+

@bors
Copy link
Collaborator

bors commented Apr 1, 2024

📌 Commit a6881b0 has been approved by blyxyas

It is now in the queue for this repository.

@bors
Copy link
Collaborator

bors commented Apr 1, 2024

⌛ Testing commit a6881b0 with merge f678d26...

bors added a commit that referenced this pull request Apr 1, 2024
accept `String` in `span_lint*` functions directly to avoid unnecessary clones

context: https://rust-lang.zulipchat.com/#narrow/stream/257328-clippy/topic/Accepting.20.60Into.3C.7BSub.7DdiagMessage.3E.60.20in.20.60span_lint*.60.20functions/near/425703273

tldr: the `span_lint*` functions now accept both `String`s, which are then reused and not recloned like before, and also `&'static str`, in which case it [doesn't need to allocate](https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_error_messages/lib.rs.html#359).
Previously, it accepted `&str` and would always call `.to_string()`, which is worse in any case: it allocates for `&'static str` and forces a clone even if the caller already has a `String`.

---------

This PR has a massive diff, but the only interesting change is in the first commit, which changes the message/help/note parameter in the `span_lint*` functions to not take a `&str`, but an `impl Into<DiagMessage>`.

The second commit changes all of the errors that now occur:
- `&format!(...)` cannot be passed to `span_lint` anymore. Instead, we now simply pass `format!()` directly.
- `Into<DiagMessage>` can be `&'static str`, but not any `&str`. So this requires changing a bunch of other `&str` to `&'static str` at call sites as well.
- Added [`Sugg::into_string`](https://github.com/y21/rust-clippy/blob/9fc88bc2851fbb287d89f65b78fb67af504f8362/clippy_utils/src/sugg.rs#L362), which, as opposed to `Sugg::to_string`, can take advantage of the fact that it takes ownership and is able to reuse the `String`

changelog: none
@bors
Copy link
Collaborator

bors commented Apr 1, 2024

💔 Test failed - checks-action_dev_test

@blyxyas
Copy link
Member

blyxyas commented Apr 1, 2024

Oh I thought you fixed it in the latest push 😅
DM me on Zulip when you have it :3

@y21
Copy link
Member Author

y21 commented Apr 1, 2024

oh well, another lint was changed to use &format!() in the meantime. I did fix the other one. But yeah, I can fix it locally and we can coordinate on zulip when I should push so this doesnt keep happening

@blyxyas
Copy link
Member

blyxyas commented Apr 1, 2024

@bors r+

@bors
Copy link
Collaborator

bors commented Apr 1, 2024

📌 Commit 91f514c has been approved by blyxyas

It is now in the queue for this repository.

@bors
Copy link
Collaborator

bors commented Apr 1, 2024

⌛ Testing commit 91f514c with merge c2681f2...

@bors
Copy link
Collaborator

bors commented Apr 1, 2024

☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test
Approved by: blyxyas
Pushing c2681f2 to master...

@bors bors merged commit c2681f2 into rust-lang:master Apr 1, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants