Uh oh!
There was an error while loading. Please reload this page.
Constify is_aligned via align_offset - #102795
Conversation
rustbot
commented
Oct 7, 2022
Some changes occurred to the CTFE / Miri engine cc @rust-lang/miri Hey! It looks like you've submitted a new PR for the library teams! If this PR contains changes to any Examples of
|
rust-highfive
commented
Oct 7, 2022
(rust-highfive has picked a reviewer for you, use r? to override) |
This comment has been minimized.
This comment has been minimized.
d6732ea to
51474dcCompareoli-obk
commented
Oct 7, 2022
I like this a lot more, thanks for doing it! I'll give it a thorough review next week |
This comment has been minimized.
This comment has been minimized.
RalfJung
left a comment
There was a problem hiding this comment.
not commenting on whether we want this (making align_offset never-const also has some good arguments in its favor), just some feedback on the implementation
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
5b35859 to
e261e55Comparerustbot
commented
Oct 8, 2022
The Miri subtree was changed cc @rust-lang/miri |
Uh oh!
There was an error while loading. Please reload this page.
6f1df27 to
c2605f6Compare| pub fn is_aligned_to(self, align: usize) -> bool { | ||
| if !align.is_power_of_two() { | ||
| panic!("is_aligned_to: align is not a power-of-two"); | ||
| #[rustc_const_unstable(feature = "const_pointer_is_aligned", issue = "none")] | ||
| pub const fn is_aligned_to(self, align: usize) -> bool { | ||
| assert!(align.is_power_of_two(), "is_aligned_to: align is not a power-of-two"); | ||
| #[inline] | ||
| fn runtime(ptr: *const u8, align: usize) -> bool { | ||
| ptr.addr() & (align - 1) == 0 | ||
| } | ||
| const fn comptime(ptr: *const u8, align: usize) -> bool { | ||
| ptr.align_offset(align) == 0 | ||
| } | ||
| // Cast is needed for `T: !Sized` | ||
| self.cast::<u8>().addr() & align - 1 == 0 | ||
| // SAFETY: `ptr.align_offset(align)` returns 0 if and only if the pointer is already aligned. | ||
| unsafe { intrinsics::const_eval_select((self.cast::<u8>(), align), comptime, runtime) } |
There was a problem hiding this comment.
can we always invoke ptr.align_offset(align) == 0 even at runtime? Or is that a performance concern?
There was a problem hiding this comment.
There is a small performance penalty: Goldbolt link
There was a problem hiding this comment.
Ah, please leave a comment to that regard
There was a problem hiding this comment.
I would not call the performance penalty small. *const u8 is easy mode here, you can see the code size explode if you make the pointee type u16, and for u32 and larger align_offset is not even inlined.
There was a problem hiding this comment.
For the purpose of is_aligned we can just cast the pointer to *const u8, because we only care if the offset is zero or not zero. (And we do this cast already anyway to deal with fat pointers.)
There was a problem hiding this comment.
It seems inlined to me in https://rust.godbolt.org/z/b7n4MPGdf... But the difference is quite staggering:
example::is_aligned_to_old_unchecked:decrsitestrsi,rdi sete alretexample::is_aligned_to_new_unchecked:lear8,[rsi-1]test sil,3je .LBB1_1bsfrax,rsicmprax,2movecx,2 cmovb rcx,raxmovedx,-1shledx,clnotedxmovrax,-1testedx,edije .LBB1_4.LBB1_10:testrax,rax sete alret.LBB1_1:movrax,-1test dil,3jne .LBB1_10addr8,rdinegrsiandrsi,r8subrsi,rdishrrsi,2movrax,rsitestrax,rax sete alret.LBB1_4:shrrsi,clmovr10d,r8dandr10d,4shrr10,clandrdi,r8shrrdi,cllear8,[rsi-1]movr9,rsisubr9,rdimovrax,r10shrraxleardi,[rip+ .L__unnamed_1]movzxedi, byte ptr [rax+rdi]cmprsi,17jae .LBB1_6movrax,rdijmp .LBB1_9.LBB1_6:movrcx,r10imulrcx,rdimoveax,2subrax,rcximulrax,rdicmprsi,257jb .LBB1_9movedi,256.LBB1_8:imulrdi,rdimovrcx,raximulrcx,r10movedx,2subrdx,rcximulrax,rdxcmprdi,rsijb .LBB1_8.LBB1_9:andrax,r8imulrax,r9andrax,r8testrax,rax sete alret.L__unnamed_1: .ascii "\001\013\r\007\t\003\005\017"There was a problem hiding this comment.
Note that align_offset is also large enough to never get inlined (even for u8) on -Copt-level=s (and probably z too). And we definitely don't want to #[inline(always)] it due to how much code it can generate in some cases.
There was a problem hiding this comment.
I found out that align_offset == 0 does get optimized to the old is_aligned_to impl with opt-level=1/2/3/s/z if you cast the pointer to *const () and #[inline] the align_offset method on pointers (not the big freestanding function): https://rust.godbolt.org/z/Kd98b9jvM
But the "optimized for size" code is still larger than the optimized for speed one, because it keeps the dead assembly for align_offset around.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| self.eval_fn_call( | ||
| FnVal::Instance(instance), | ||
| (CallAbi::Rust, fn_abi), | ||
| &[addr, align], | ||
| false, | ||
| dest, | ||
| ret, | ||
| StackPopUnwind::NotAllowed, | ||
| )?; | ||
| Ok(ControlFlow::BREAK) |
There was a problem hiding this comment.
That's odd, why does this not just CONTINUE?
I guess it needs to adjust the arguments? But it is rather odd to have such different codepaths here for the two cases we can handle. I think they should be uniform.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| panic!("is_aligned_to: align is not a power-of-two"); | ||
| #[rustc_const_unstable(feature = "const_pointer_is_aligned", issue = "none")] | ||
| pub const fn is_aligned_to(self, align: usize) -> bool { | ||
| assert!(align.is_power_of_two(), "is_aligned_to: align is not a power-of-two"); |
There was a problem hiding this comment.
This will be a slightly uglier panic message than before, since it will also print the stringified expression.
There was a problem hiding this comment.
There doesn't seem to be a (significant) difference to me, but I've changed it back for now. (Goldbolt diff)
be86396 to
166fb94CompareI've updated it now to never actually call Also I added docs and a bunch of examples to |
8d90187 to
005f92dCompareThis reverts commit f3a577bfae376c0222e934911865ed14cddd1539.
Co-authored-by: Ralf Jung <post@ralfj.de>
* fix allocation alignment for 16bit platforms * add edge case where `stride % align != 0` on pointers with provenance
f862443 to
c9c017dComparelukas-code
commented
Nov 19, 2022
Rebased and dropped 7e1481997b8bdf94e11a59236a17100eeca5633e since #103378 got merged. |
oli-obk
commented
Nov 19, 2022
@bors r+ |
bors
commented
Nov 19, 2022
bors
commented
Nov 19, 2022
bors
commented
Nov 19, 2022
☀️ Test successful - checks-actions |
rust-timer
commented
Nov 19, 2022
Finished benchmarking commit (c5d82ed): comparison URL. Overall result: no relevant changes - no action needed@rustbot label: -perf-regression Instruction countThis benchmark run did not return any relevant results for this metric. Max RSS (memory usage)ResultsThis is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
CyclesResultsThis is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
|
| // The cast to `()` is used to | ||
| // 1. deal with fat pointers; and | ||
| // 2. ensure that `align_offset` doesn't actually try to compute an offset. | ||
| self.cast::<()>().align_offset(align) == 0 |
There was a problem hiding this comment.
Sadly this caused a regression in Miri: rust-lang/miri#2682
There was a problem hiding this comment.
While the immediate issue is fixed, it's still somewhat strange that is_aligned would change behavior with Miri's symbolic alignment mode... but maybe it makes sense, it is consistent with align_to, anyway. We'll have to watch out for other similar regressions. If too many bugreports come in we'll have to find another solution.
There was a problem hiding this comment.
The last line of this doc test is also failing with -Zmiri-symbolic-alignment-check
rust/library/core/src/ptr/const_ptr.rs
Lines 1492 to 1511 in c9c017d
Maybe we should partially revert daccb8c to put the const_eval_select back?
It might also make sense to redefine -Zmiri-symbolic-alignment-check as "runtime alignment behaves like const eval alignment", because i think that is what it currently does after this PR and rust-lang/miri#2683.
There was a problem hiding this comment.
We are not running libcore tests with symbolic alignment so I guess I didn't notice this.
It might also make sense to redefine -Zmiri-symbolic-alignment-check as "runtime alignment behaves like const eval alignment", because i think that is what it currently does after this PR and rust-lang/miri#2683.
I guess that makes sense. Are you proposing just a docs change or also an implementation change?
There was a problem hiding this comment.
We could do some deduplication between the ctfe impl and miri impl of align_offset, but the implementation looks functionally identical to me, so this would mostly be a docs change.
Currently, the docs look like this:
-Zmiri-symbolic-alignment-checkmakes the alignment check more strict. By default, alignment is checked by casting the pointer to an integer, and making sure that is a multiple of the alignment. This can lead to cases where a program passes the alignment check by pure chance, because things "happened to be" sufficiently aligned -- there is no UB in this execution but there would be UB in others. To avoid such cases, the symbolic alignment check only takes into account the requested alignment of the relevant allocation, and the offset into that allocation. This avoids missing such bugs, but it also incurs some false positives when the code does manual integer arithmetic to ensure alignment. (The standard library align_to method works fine in both modes; under symbolic alignment it only fills the middle slice when the allocation guarantees sufficient alignment.)
From this it actually seems pretty clear to me that new behavior for is_aligned with -Zmiri-symbolic-alignment-check is correct and the old one was wrong. Also, it seems weird to me that the docs don't mention align_offset at all when literally all this flag does is change the behavior of align_offset.
Maybe we could just change the last sentence in parentheses to something like
(This changes the runtime behavior of alignment-related standard library functions like
is_aligned,align_offset, oralign_toto match the compiletime behavior. For example,align_toonly fills the middle slice when the allocation guarantees sufficient alignment.)
There was a problem hiding this comment.
Also, it seems weird to me that the docs don't mention align_offset at all when literally all this flag does is change the behavior of align_offset.
It does more. It's core feature is to toggle a flag in the interpreter that affects how alignment checking works. Adjusting align_offset is just a side thing that we also do to keep more code working in this mode.
…lign-offset, r=oli-obk Constify `is_aligned` via `align_offset` Alternative to rust-lang#102753 Make `align_offset` work in const eval (and not always return `usize::MAX`) and then use that to constify `is_aligned{_to}`. Tracking Issue: rust-lang#104203
Alternative to #102753
Make
align_offsetwork in const eval (and not always returnusize::MAX) and then use that to constifyis_aligned{_to}.Tracking Issue: #104203