Reduce bloat from wgsl::Error::as_parse_error - #9944
Conversation
|
The current output size is 4302 lines of IR, 18.3KiB of output. Note that this is larger that what I claimed in the linked issue, since I was testing with 29.0.3 there, and this is aimed at main. In addition to the changes discussed in the issue, this also reorders some of the fields when constructing ParseError {
// This creates a Cow::Borrowed
message: "unexpected components".into(),
// vec! can panic if allocating fails, so it needs to generate code to drop everything in scope.
// It generates Cow::drop for message, even though it's not needed
labels: vec![(bad_span, "unexpected components".into())],
notes: vec![],
}A better version: ParseError {
// This can still panic, but now it doesn't have to drop message
labels: vec![(bad_span, "unexpected components".into())],
message: "unexpected components".into(),
notes: vec![],
} |
|
I have been able to get it down to 3485 lines and 16.3KiB locally by using my own implementation of The reason I did this was to find out the maximum size that ould be saved by I'm going to test to see if it's better with smallvec Edit: I've got it to 3389 and 16.0KiB by using |
| Token::Attribute => "@".to_string(), | ||
| Token::Number(_) => "number".to_string(), | ||
| Token::Word(s) => s.to_string(), |
There was a problem hiding this comment.
Does making expected_str a Cow so it doesn’t have many separate to_string() calls help?
In general, when trying to reduce binary size, I’ve found that it’s useful to push as many things as possible into being literals instead of repeated function calls, so I think expressing these as Cow::Borrowed("@") is worth trying (or even const { Cow::Borrowed("@") }, if necessary).
There was a problem hiding this comment.
For some reason when I tried that here it actually made the output bigger. I'll try using const {}.
There was a problem hiding this comment.
Using const {} helped, but the output is still bigger than without Cow :(
|
I think I've gotten it as low as I can (3775, 17.4KiB). If I'm allowed to update The latest commit separates part of the function out and merges some of the It also merges some duplicate Notes in case anyone wants to try to squeeze it further:
|
|
Possibly related to #9052 and #9075: since you sometimes can't precompile shaders (if you don't know what API you might be using at compile time) would it be worth adding a feature to strip down things like this (i.e. in this case, the feature could cause |
kpreid
left a comment
There was a problem hiding this comment.
Overall feedback: this needs more comments. Any time one writes code weirdly for the sake of a performance characteristic, one should document the reason for the weirdness and how the code should be maintained under this constraint.
| } | ||
|
|
||
| impl ExpectedToken<'_> { | ||
| fn as_string(&self) -> String { |
There was a problem hiding this comment.
Since this constructs a string, it should be named to_string(). But relatedly, have you tried implementing Display instead? I wouldn’t be surprised if that’s worse, but it’s worth checking (and commenting).
There was a problem hiding this comment.
I originally considered implementing Display, but opted not to. I named it as_string to avoid the clippy lint telling me to implement Display.
I've implemented Display now, and the output is smaller now that I can avoid calling to_string.
| FormatChar(&'static str, char, &'static str), | ||
| FormatStr([&'a str; 3]), | ||
| } | ||
| match match self { |
There was a problem hiding this comment.
Does this have to be a match match? I would expect that adding a let doesn’t significantly affect the output, and will be clearer.
There was a problem hiding this comment.
I've moved out one of the matched into a variable.
| message: format!("invalid field accessor `{}`", &source[accessor_span],), | ||
| labels: vec![(accessor_span, "invalid accessor".into())], | ||
| notes: vec![], | ||
| // Other variants with a static message, single label and no notes |
There was a problem hiding this comment.
This comment is inaccurate — not all of the messages are static.
There was a problem hiding this comment.
Whoops, I think I meant 'Other variants with a single static label and no notes'
| } | ||
|
|
||
| impl ExpectedToken<'_> { | ||
| fn as_string(&self) -> String { |
There was a problem hiding this comment.
This function needs a comment (not a doc-comment but an // comment) explaining why it is written so non-straightforwardly, and perhaps how to validate future changes to it.
There was a problem hiding this comment.
I've added comments here and to as_parse_error, but I'm not sure how well they explain things.
| message: format!("no definition in scope for identifier: `{ident}`"), | ||
| labels: vec![(ident_span, "unknown identifier".into())], | ||
| Error::BadNumber(bad_span, ref err) => ParseError { | ||
| message: format!("{}: `{}`", err, &source[*bad_span]).into(), |
There was a problem hiding this comment.
Personally, I would write Cow::Borrowed (or Borrowed with use Cow::Borrowed;) instead of using .into() for these conversions — it’s more explicit (doesn’t leave the reader wondering “why are we making a string and immediately converting it?") and might be faster to compile (I'm just guessing, but it doesn’t require a trait implementation lookup).
But this is likely not a big deal either way.
There was a problem hiding this comment.
I used .into() because the original code was using it, I think having Cow::Borrowed might be a bit verbose.
Connections
#9941
Description
wgsl::Error::as_parse_errorcauses a lot of bloat in binary size and LLVM IR. This PR makes some changes to reduce the amount of bloat.More info in #9941
Testing
I've checked that the LLVM IR and binary outputs are lower with llvm-lines and cargo-bloat
TODO: run all the tests
Squash or Rebase?
Squash (this is a draft)
Checklist
wgpumay be affected behaviorally.CHANGELOG.mdentries for the user-facing effects of this change are present.