-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Constify is_aligned
via align_offset
#102795
Constify is_aligned
via align_offset
#102795
Conversation
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 has picked a reviewer for you, use r? to override) |
This comment has been minimized.
This comment has been minimized.
d6732ea
to
51474dc
Compare
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.
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.
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
5b35859
to
e261e55
Compare
The Miri subtree was changed cc @rust-lang/miri |
6f1df27
to
c2605f6
Compare
library/core/src/ptr/const_ptr.rs
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a small performance penalty: Goldbolt link
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.
Ah, please leave a comment to that regard
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 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems inlined to me in https://rust.godbolt.org/z/b7n4MPGdf... But the difference is quite staggering:
example::is_aligned_to_old_unchecked:
dec rsi
test rsi, rdi
sete al
ret
example::is_aligned_to_new_unchecked:
lea r8, [rsi - 1]
test sil, 3
je .LBB1_1
bsf rax, rsi
cmp rax, 2
mov ecx, 2
cmovb rcx, rax
mov edx, -1
shl edx, cl
not edx
mov rax, -1
test edx, edi
je .LBB1_4
.LBB1_10:
test rax, rax
sete al
ret
.LBB1_1:
mov rax, -1
test dil, 3
jne .LBB1_10
add r8, rdi
neg rsi
and rsi, r8
sub rsi, rdi
shr rsi, 2
mov rax, rsi
test rax, rax
sete al
ret
.LBB1_4:
shr rsi, cl
mov r10d, r8d
and r10d, 4
shr r10, cl
and rdi, r8
shr rdi, cl
lea r8, [rsi - 1]
mov r9, rsi
sub r9, rdi
mov rax, r10
shr rax
lea rdi, [rip + .L__unnamed_1]
movzx edi, byte ptr [rax + rdi]
cmp rsi, 17
jae .LBB1_6
mov rax, rdi
jmp .LBB1_9
.LBB1_6:
mov rcx, r10
imul rcx, rdi
mov eax, 2
sub rax, rcx
imul rax, rdi
cmp rsi, 257
jb .LBB1_9
mov edi, 256
.LBB1_8:
imul rdi, rdi
mov rcx, rax
imul rcx, r10
mov edx, 2
sub rdx, rcx
imul rax, rdx
cmp rdi, rsi
jb .LBB1_8
.LBB1_9:
and rax, r8
imul rax, r9
and rax, r8
test rax, rax
sete al
ret
.L__unnamed_1:
.ascii "\001\013\r\007\t\003\005\017"
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
library/core/src/ptr/const_ptr.rs
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There doesn't seem to be a (significant) difference to me, but I've changed it back for now. (Goldbolt diff)
be86396
to
166fb94
Compare
I've updated it now to never actually call Also I added docs and a bunch of examples to |
8d90187
to
005f92d
Compare
This reverts commit f3a577bfae376c0222e934911865ed14cddd1539.
Co-authored-by: Ralf Jung <[email protected]>
* fix allocation alignment for 16bit platforms * add edge case where `stride % align != 0` on pointers with provenance
f862443
to
c9c017d
Compare
Rebased and dropped 7e1481997b8bdf94e11a59236a17100eeca5633e since #103378 got merged. |
@bors r+ |
☀️ Test successful - checks-actions |
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly this caused a regression in Miri: rust-lang/miri#2682
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
/// ``` | |
/// #![feature(pointer_is_aligned)] | |
/// #![feature(pointer_byte_offsets)] | |
/// | |
/// // On some platforms, the alignment of i32 is less than 4. | |
/// #[repr(align(4))] | |
/// struct AlignedI32(i32); | |
/// | |
/// let data = AlignedI32(42); | |
/// let ptr = &data as *const AlignedI32; | |
/// | |
/// assert!(ptr.is_aligned_to(1)); | |
/// assert!(ptr.is_aligned_to(2)); | |
/// assert!(ptr.is_aligned_to(4)); | |
/// | |
/// assert!(ptr.wrapping_byte_add(2).is_aligned_to(2)); | |
/// assert!(!ptr.wrapping_byte_add(2).is_aligned_to(4)); | |
/// | |
/// assert_ne!(ptr.is_aligned_to(8), ptr.wrapping_add(1).is_aligned_to(8)); | |
/// ``` |
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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-check
makes 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_to
to match the compiletime behavior. For example,align_to
only fills the middle slice when the allocation guarantees sufficient alignment.)
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.
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_offset
work in const eval (and not always returnusize::MAX
) and then use that to constifyis_aligned{_to}
.Tracking Issue: #104203