-
Notifications
You must be signed in to change notification settings - Fork 8
Implement known bits analysis #2004
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
Conversation
|
This is a quick partial review. Broadly speaking, I want to get this in, but I would like it to slightly better match existing conventions in the codebase (even trivial things like naming and comment width). I also think there are some trivial comments that are too long, and some surprising things that aren't documented (e.g. functions with surprising pre/post-conditions). Nothing major, but they'll just make the code easier to live with in the long term. |
Yep I'm still getting a feel of the conventions. Please bear with me on this one, thanks! |
| /// Known-bits analysis. | ||
| pub(super) struct KnownBits { | ||
| known_bits: IndexVec<InstIdx, Option<KnownBitValue>>, | ||
| pending_commit: Option<KnownBitValue>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This attribute needs documenting (I know what it does because I know its existence is because of an incomplete API... which is shameful on my part!).
|
|
||
| fn inst_committed(&mut self, _opt: &CommitInstOpt, _iidx: InstIdx, _inst: &Inst) { | ||
| self.known_bits.push(self.pending_commit.clone()); | ||
| assert_eq!(_iidx.index(), self.known_bits.len() - 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iidx doesn't need the leading _ if it's used in assert. Also, wouldn't this assert be clearer before the push when the - 1 wouldn't be needed?
| }) | ||
| } | ||
|
|
||
| fn set_knownbits(&mut self, bits: KnownBitValue) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be set_pending perhaps?
| // If no new information (set zeroes) was gained, that means this | ||
| // op is useless. | ||
| if rhs_b.all_known() | ||
| && (rhs_b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised cargo fmt hasn't removed the brackets. If it doesn't, we should remove them, as "over bracketing" isn't really needed in Rust like it is in C.
| } | ||
|
|
||
| /// Full credits to the PyPy blog post for some of the functions here: | ||
| /// https://pypy.org/posts/2024/08/toy-knownbits.html#the-knownbits-abstract-domain |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be clearer / less repetitive to clearly say at the top-level docstring (which we almost do currently) This is heavily influenced by the [PyPy blog post](https://...) (using Markdown syntax for URLs) once and not repeat it three times (as is currently the case).
| /// https://pypy.org/posts/2024/08/toy-knownbits.html#the-knownbits-abstract-domain | ||
| /// | ||
| /// In short: | ||
| /// one unknown knownbit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably best to either use Markdown formatting for the table (or, at a push, a code block).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also probably make the first row the ? state since that's the first. Then it becomes clear that one can go from row 1 to either rows 2 or 3, but no other transitions are legal (so we can condense the slightly long lattice comment perhaps?).
| /// https://pypy.org/posts/2024/08/toy-knownbits.html#the-knownbits-abstract-domain | ||
| impl KnownBitValue { | ||
| /// Constructs a KnownBitValue from a constant. | ||
| fn from_constant(num: &ArbBitInt) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we make this from_const(num: ArbBitInt) we force ownership onto the caller which might allow them to sometimes sidestep a clone (i.e. this will, sometimes, be a more efficient API; and it's never less efficient). [Note suggested name from_const which is the conventional j2 shortening of constant.]
|
|
||
| #[test] | ||
| fn opt_and() { | ||
| // any = any & 01 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I must admit these comments confused me because I thought the arrow was pointing to 11, not to the whole line! I tend to think these particular comments are best removed, since the test itself more clearly shows what's going on. That said, I quite like high-level comments as a grouping exercise: that might not be relevant here, yet, though.
| ); | ||
|
|
||
| // any & any | ||
| test_known_bits( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what this is testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops yeah, this test is useless
| } | ||
|
|
||
| #[test] | ||
| fn opt_and_or() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced by this test because scaling it's going to be really hard as we analyse more IR instructions! I'd probably remove it.
|
|
||
| #[test] | ||
| fn with_intermediate() { | ||
| // Test that other instructions stay around |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what these tests are testing.
| } | ||
| } | ||
|
|
||
| /// Return a new [ArbBitInt] that performs bitwise `NEG` on `self`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
arbbitint instructions all need corresponding tests. [Mostly those are proptest tests.]
|
I've now done a thorough review: don't be scared! I take code in the optimiser very seriously, because it's the easiest code to get wrong. Because there are lots of little things, feel free in this (unusual) case to bunch changes up into large commits (you can even do one big commit if you want): I will review the whole PR again afterwards. |
|
@ltratt I tried to address all your reviews in a few commits. I tried to make sure I didn't miss out anything. Please take a look again when you have the time, thanks! |
|
Dumb question: with this PR do all the tests pass and benchmarks run? |
|
I just realised that we probably need an additional test(s) where we prove that the knownbits pass can turn operations into constants. So I assume that we should be able to optimise this sort of thing: into One other (very minor) comment: comments need formatting to 100 chars. [I'm somewhat neutral on comment width, but the current code is formatted to 100 chars so we should follow that unless we make a consistent decision to reformat all comments.] |
9bf545d to
62e578f
Compare
|
FWIW, all the benchmarks pass, only one showed any difference (which I'm also suspicious of). Considering this only implements |
| /// around. `illegal` occurs when both `0` and `1` are set and known, which is impossible in a | ||
| /// valid program. `illegal` indicates a likely bug in the optimizer/IR. | ||
| #[derive(Clone)] | ||
| pub struct KnownBitValue { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, just realised: why are these pub? They shouldn't be known outside this file AFAICS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops yeah this is an artifact leftover form the olden days. Will remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
|
||
| fn bitand(&self, other: &KnownBitValue) -> KnownBitValue { | ||
| let set_ones = self.ones.bitand(&other.ones); | ||
| let set_zeroes = self.zeroes().bitor(&other.zeroes()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is ones a field access and zeroes a method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is due to the bit representation: ones is not computed, while zeroes is.
|
Two minor comments: we're almost there! |
|
Please squash. |
a6e0bd8 to
290c85f
Compare
|
Done! I will run all the tests and benchmarks one more time to be sure nothing crashes. |
|
All tests pass. All benchmarks on |
290c85f to
373d0a3
Compare
|
Oops, the clippy on the buildbot fails but not locally, guess the buildbot has a newer clippy? In any case I force pushed a formatting fix. Please retry, thanks! |
|
buildbot always uses the latest nightly Clippy. I personally find it worth |
No description provided.