Skip to content

Rework next_power_of_two to always be 1 << … - #161209

Merged
rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
scottmcm:redo-npot
Sep 1, 2026
Merged

Rework next_power_of_two to always be 1 << …#161209
rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
scottmcm:redo-npot

Conversation

@scottmcm

@scottmcm scottmcm commented Aug 17, 2026

Copy link
Copy Markdown
Member

View all comments

r? @clarfonthey
Who accidentally nerd-sniped me by mentioning #161069

This obviates that PR by reworking the (checked_)next_power_of_two logic to calculate the necessary exponent e, then return 2ᵉ, so it's structurally obvious from the IR -- the shl nuw 1, … instruction -- that it's always a power of two.

As always, the reason this is tricky is because shifts don't work for << Self::BITS. The previous code handled that by checking self <= 1 first, and thus doing a -1 >> n where n < BITS. This code instead flips the check: it looks up-front for a value that will wrap (an input above 1 << (BITS - 1)) and thus by excluding those cases the calculated 1 << n always has n < BITS. And the codegen tests show that, without needing any llvm.assumes, LLVM can take advantage of it to do things like rewriting % to masking. Plus the overflow check optimizes out for constrained inputs like slice lengths.


No LLM used.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Aug 17, 2026
Comment on lines +42 to +49
// Slices (of non-ZSTs) are short enough that the power-of-two always fits
#[unsafe(no_mangle)]
pub fn slice_length_npot(slice: &[u8]) -> usize {
// CHECK-LABEL: @slice_length_npot(
// CHECK: [[POT:%.+]] = shl nuw i64 1,
// CHECK: ret i64 [[POT]]
slice.len().next_power_of_two()
}

@scottmcm scottmcm Aug 17, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Previously on x86 this had a branch for len < 2: https://rust.godbolt.org/z/TdE5GjvKa

In the new one it has no branches (just a cmov for the saturating_sub).

View changes since the review

@rust-log-analyzer

This comment has been minimized.

#[unsafe(no_mangle)]
pub unsafe fn restricted_npot(x: u16) -> u16 {
std::hint::assert_unchecked(1 <= x);
std::hint::assert_unchecked(x <= 11111);

@clarfonthey clarfonthey Aug 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This 11111 makes me a bit upset because at first I think it's binary, then I remember it's decimal. I guess this is so you're less than 2^15?

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, that was too arbitrary. Updated to the actual threshold value for the CHECK.

@clarfonthey

Copy link
Copy Markdown
Contributor

Worth adding the test for next_power_of_two() >= self as well; not sure if that one actually gets picked up.

@clarfonthey

Copy link
Copy Markdown
Contributor

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 21, 2026
@scottmcm

Copy link
Copy Markdown
Member Author

@SomeFlyingThing Can you say what you were doing that didn't optimize as expected that had you wanting the assume(pot >= self)?

@SomeFlyingThing

Copy link
Copy Markdown
Contributor

@SomeFlyingThing Can you say what you were doing that didn't optimize as expected that had you wanting the assume(pot >= self)?

i didnt have a specific reason i was just looking to make know invariants more explicit to the optimizer so maybe downstream optimizations could make use of them

@scottmcm

Copy link
Copy Markdown
Member Author

not sure if that one actually gets picked up.

It doesn't.

Thoughts on whether we want to assume it, @clarfonthey ? It doesn't necessarily seem unreasonable to do so, but also from #t-libs/meta > policy on adding assert_unchecked into libs methods them maybe we shouldn't.

@rustbot

rustbot commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@clarfonthey

Copy link
Copy Markdown
Contributor

Maybe npot>=self might help some bounds checks somewhere but idk, seems unlikely. I'm fine calling this good enough.

@scottmcm

scottmcm commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

To me it's the "unsigned smaller" cases that are most useful for bounds checks so that someone calculating something off a length (or range of in-bound indexes) is also known to be in-bounds.

If this was previous power of two then not having a bounds-check in slice.split_at(slice.len().previous_power_of_two()) seems like it could be valuable. But needing x.next_power_of_two() - x seems far less likely.

@scottmcm scottmcm added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 1, 2026
@clarfonthey

Copy link
Copy Markdown
Contributor

@bors r+ rollup

Thank you!

@rust-bors

rust-bors Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📌 Commit f771946 has been approved by clarfonthey

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 1, 2026
@clarfonthey

Copy link
Copy Markdown
Contributor

Actually:

@bors r-

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rust-bors rust-bors Bot added the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Sep 1, 2026
@rust-bors

rust-bors Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

This pull request was unapproved.

View changes since this unapproval

@rust-bors rust-bors Bot removed the S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. label Sep 1, 2026
@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Sep 1, 2026
@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Sep 1, 2026
Rework `next_power_of_two` to always be `1 << …`
@rust-bors

rust-bors Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: 0a90f9f (0a90f9f45a37461fc43d607c3b362ea6c45b2e90)
Base parent: 7022271 (70222712809cd5cc1718ed8995914a1cbacb6b92)

@rust-timer

This comment has been minimized.

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (0a90f9f): comparison URL.

Overall result: ❌✅ regressions and improvements - no action needed

Benchmarking means the PR may be perf-sensitive. Consider adding rollup=never if this change is not fit for rolling up.

@rustbot label: -S-waiting-on-perf -perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
0.3% [0.2%, 0.3%] 2
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-0.3% [-0.4%, -0.2%] 2
All ❌✅ (primary) - - 0

Max RSS (memory usage)

This perf run didn't have relevant results for this metric.

Cycles

This perf run didn't have relevant results for this metric.

Binary size

Results (primary 0.0%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
0.2% [0.0%, 0.3%] 2
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
-0.2% [-0.2%, -0.2%] 1
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 0.0% [-0.2%, 0.3%] 3

Bootstrap: 475.539s -> 474.052s (-0.31%)
Artifact size: 400.59 MiB -> 400.50 MiB (-0.02%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Sep 1, 2026
@clarfonthey

Copy link
Copy Markdown
Contributor

@bors r+

Perf nothing-burger.

@rust-bors

rust-bors Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📌 Commit f771946 has been approved by clarfonthey

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 1, 2026
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Sep 1, 2026
Rework `next_power_of_two` to always be `1 << …`

r? @clarfonthey
Who accidentally nerd-sniped me by mentioning rust-lang#161069

This obviates that PR by reworking the `(checked_)next_power_of_two` logic to calculate the necessary exponent e, then return 2ᵉ, so it's structurally obvious from the IR -- the `shl nuw 1, …` instruction -- that it's always a power of two.

As always, the reason this is tricky is because shifts don't work for `<< Self::BITS`.  The previous code handled that by checking `self <= 1` first, and thus doing a `-1 >> n` where `n < BITS`.  This code instead flips the check: it looks up-front for a value that will wrap (an input above `1 << (BITS - 1)`) and thus by excluding those cases the calculated `1 << n` always has `n < BITS`.  And the codegen tests show that, without needing any `llvm.assume`s, LLVM can take advantage of it to do things like rewriting `%` to masking.  Plus the overflow check optimizes out for constrained inputs like slice lengths.
rust-bors Bot pushed a commit that referenced this pull request Sep 1, 2026
…uwer

Rollup of 11 pull requests

Successful merges:

 - #151618 (rustdoc: add `--print` option)
 - #161287 (Update `icu_list` dependency to 2.3)
 - #161767 (change DEFAULT_STACK_SIZE to be 32MB on s390x)
 - #161968 (Diverse offload fixes)
 - #161971 (Remove -Zsaturating-float-casts flag)
 - #162071 (Fix ICE of getting item name from RPITIT)
 - #161209 (Rework `next_power_of_two` to always be `1 << …`)
 - #162073 (Change some `Infallible` to `!` in std)
 - #162086 (Remove `gate_check` from `AttributeStability::Unstable`)
 - #162102 (`alloc` crate: shrink undocumented `unsafe` blocks)
 - #162110 (make it clear that Range cannot represent arbitrary ranges)
@rust-bors
rust-bors Bot merged commit 4679857 into rust-lang:main Sep 1, 2026
14 checks passed
@rustbot rustbot added this to the 1.100.0 milestone Sep 1, 2026
rust-bors Bot pushed a commit that referenced this pull request Sep 1, 2026
Rollup merge of #161209 - scottmcm:redo-npot, r=clarfonthey

Rework `next_power_of_two` to always be `1 << …`

r? @clarfonthey
Who accidentally nerd-sniped me by mentioning #161069

This obviates that PR by reworking the `(checked_)next_power_of_two` logic to calculate the necessary exponent e, then return 2ᵉ, so it's structurally obvious from the IR -- the `shl nuw 1, …` instruction -- that it's always a power of two.

As always, the reason this is tricky is because shifts don't work for `<< Self::BITS`.  The previous code handled that by checking `self <= 1` first, and thus doing a `-1 >> n` where `n < BITS`.  This code instead flips the check: it looks up-front for a value that will wrap (an input above `1 << (BITS - 1)`) and thus by excluding those cases the calculated `1 << n` always has `n < BITS`.  And the codegen tests show that, without needing any `llvm.assume`s, LLVM can take advantage of it to do things like rewriting `%` to masking.  Plus the overflow check optimizes out for constrained inputs like slice lengths.
@scottmcm
scottmcm deleted the redo-npot branch September 2, 2026 01:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants