Skip to content
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

Tracking issue for RFC 2043: Add align_offset intrinsic (formerly: and [T]::align_to function) #44488

Closed
1 of 4 tasks
aturon opened this issue Sep 11, 2017 · 48 comments · Fixed by #60303
Closed
1 of 4 tasks
Labels
B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@aturon
Copy link
Member

aturon commented Sep 11, 2017

This is a tracking issue for the RFC "Add align_offset intrinsic and [T]::align_to function " (rust-lang/rfcs#2043). align_to is stable, so this tracks just align_offset now.

Steps:

Unresolved questions:

  • "produce a lint in case sizeof<T>() % sizeof<U>() != 0 and in case the expansion is not part of a monomorphisation, since in that case align_to is statically known to never be effective
@aturon aturon added B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Sep 11, 2017
frewsxcv added a commit to frewsxcv/rust that referenced this issue Sep 16, 2017
@TimNN TimNN added the C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC label Sep 17, 2017
alexcrichton added a commit to alexcrichton/rust that referenced this issue Sep 18, 2017
alexcrichton added a commit to alexcrichton/rust that referenced this issue Sep 18, 2017
GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this issue Dec 18, 2017
…trochenkov

Fix the wrong subtraction in align_offset intrinsic.

Given how the stage0 implementation in rust-lang#43903 is written, as well as that in the RFC, I suppose the current implementation has a typo.

cc rust-lang#44488, cc @oli-obk.
@SimonSapin
Copy link
Contributor

@oli-obk Is it useful to stabilize align_offset before align_to is implemented?

@oli-obk
Copy link
Contributor

oli-obk commented Mar 17, 2018

Generally yes, because that's the key feature. Align_to is just convenience

@SimonSapin
Copy link
Contributor

Alright, then. Let’s stabilize align_offset and leave this issue open for tracking align_to.

@rfcbot fcp merge

@rfcbot
Copy link

rfcbot commented Mar 17, 2018

Team member @SimonSapin has proposed to merge this. The next step is review by the rest of the tagged team members:

Concerns:

Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added the proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. label Mar 17, 2018
@alexcrichton
Copy link
Member

@rfcbot concern motivation-and-ergonomics

The original motivation for these functions (unless it's changed, which it may have!) I believe was for miri and const evaluating various functions. Is that still the main motivation for these functions? If so do we expect that more things to be available on stable once these are stabilized?

I'm a little unclear about how and when such an intrinsic would be used so I'm mostly looking to straighten up my own thinking! In particular the clause "If it is not possible to align the pointer, the implementation returns usize::max_value()." I'm having trouble wrapping my head around in how it'd be expected to get used.

Is the long term plan to make this a const fn but otherwise disallow operations like *mut T as usize in a const fn?

In terms of ergonomics I'd also naively expect a signature like:

pub fn align_to(self, align: usize) -> Option<*mut Self>;

but would that create the same problems it's trying to avoid?

@oli-obk
Copy link
Contributor

oli-obk commented Mar 19, 2018

Is that still the main motivation for these functions?

yes

If so do we expect that more things to be available on stable once these are stabilized?

This will allow making it const fn, which is a separate topic in the future

I'm having trouble wrapping my head around in how it'd be expected to get used.

No matter which computation you use to figure out the number of bytes that you need to offset your pointer in order to make it aligned, you will need to check whether it still points into your buffer. If you have a [u8; 3] and have a pointer to the first element, how many bytes to do you need to offset in order to get a 8 byte aligned pointer? that might be beyond the range. So you always need to check. Returning max_value means that you can't align it in any buffer.

Is the long term plan to make this a const fn but otherwise disallow operations like *mut T as usize in a const fn?

No, these operations are fine. The issue is that no matter how many bytes you offset a *mut u8 in miri, it will never become aligned to anything with a higher alignment. This function exists to abstract away such manual pointer alignments.

In terms of ergonomics I'd also naively expect a signature like:

You could do that, but then you'd also need an argument for the allocation size. Now that I think about it, that doesn't seem too bad and since you need to check anyway... As long as the intrinsic doesn't have to return an Option (the intrinsic an impl detail anyway).

@alexcrichton
Copy link
Member

Ok, thanks for the info! It sounds like this function doesn't necessarily unlock anything in the near term as const-things are still pretty unstable? If that's the case I'm personally tempted to leave these as unstable until we've got the const story more fleshed out to see how they fit into the grand scheme of things

@nagisa
Copy link
Member

nagisa commented Mar 26, 2018

Oh dear, it has been almost a year since the RFC and it went totally outside my radar, until I really, really needed [T]::align_to for my current use case of… aligning a byte buffer :)

Thinking about possible implementation approaches, I realised that to implement [T]::align_to in some cases it would be necessary to resort to calling the intrinsic multiple times in a manner similar to this:

loop {
    x = intrinsics::align_offset(ptr, align);
    if (x is aligned to object start) { return }
    ptr = x.offset(1);
}

So with @oli-obk we decided that it would probably be most prudent to simply change the intrinsic to do such calculation by itself, changing its signature from the current one to align_offset<T>(*mut T, usize) -> *mut T, that aligns the pointer to the first aligned object, not the first aligned pointer value, like it does currently.

@nagisa
Copy link
Member

nagisa commented Apr 28, 2018

I’m almost done with the improved implementation of the align_offset intrinsic, and I have noticed, that we currently specify that offset returned by the code is in bytes. This seems suboptimal in multiple ways.

  1. Since implementation works strictly in units of "elements", a conversion step is always necessary for when the stride of T does not equal 1;
  2. Furthermore, to implement align_to, conversion from bytes back to elements is necessary again;
  3. Cannot be used directly with ptr.offset()/ptr.offset_to without converting bytes back into "elements" again;

I propose that we change align_offset to return offset in number of elements.

@oli-obk
Copy link
Contributor

oli-obk commented Apr 28, 2018

I'm assuming we are talking about elements of the source slice and not the aligned slice. Sgtm

@nagisa
Copy link
Member

nagisa commented Apr 28, 2018

I guess I should’ve said in “number of Ts” where <*const T>::align_offset(...), but yeah.

@scottmcm
Copy link
Member

IIRC this was in bytes in the intrinsic because the pointer to align could have a ZST pointee, and always using bytes sidesteps the questions of what to do there. (Not that I'm sure why you'd want to align a *const [i32; 0] to *const [u64; 0], but you could.) The library could plausibly have a better API, though...

@nagisa
Copy link
Member

nagisa commented Apr 30, 2018

FYI implementation for align_to and changes to align_offset are at #50319.

bors added a commit that referenced this issue May 18, 2018
Implement [T]::align_to

Note that this PR deviates from what is accepted by RFC slightly by making `align_offset` to return an offset in elements, rather than bytes. This is necessary to sanely support `[T]::align_to` and also simply makes more sense™. The caveat is that trying to align a pointer of ZST is now an equivalent to `is_aligned` check, rather than anything else (as no number of ZST elements will align a misaligned ZST pointer).

It also implements the `align_to` slightly differently than proposed in the RFC to properly handle cases where size of T and U aren’t co-prime.

Furthermore, a promise is made that the slice containing `U`s will be as large as possible (contrary to the RFC) – otherwise the function is quite useless.

The implementation uses quite a few underhanded tricks and takes advantage of the fact that alignment is a power-of-two quite heavily to optimise the machine code down to something that results in as few known-expensive instructions as possible. Currently calling `ptr.align_offset` with an unknown-at-compile-time `align` results in code that has just a single "expensive" modulo operation; the rest is "cheap" arithmetic and bitwise ops.

cc #44488 @oli-obk

As mentioned in the commit message for align_offset, many thanks go to Chris McDonald.
@RalfJung RalfJung changed the title Tracking issue for RFC 2043: Add align_offset intrinsic and [T]::align_to function Tracking issue for RFC 2043: Add align_offset intrinsic (formerly: and [T]::align_to function) Feb 13, 2019
@alexcrichton
Copy link
Member

@rfcbot resolve motivation-and-ergonomics

I don't want to personally be on the hook for blocking this any more

@rfcbot rfcbot added the final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. label Feb 20, 2019
@rfcbot
Copy link

rfcbot commented Feb 20, 2019

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot removed the proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. label Feb 20, 2019
@rfcbot
Copy link

rfcbot commented Mar 2, 2019

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

The RFC will be merged soon.

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Mar 2, 2019
@CryZe
Copy link
Contributor

CryZe commented Apr 26, 2019

What's the status of this?

@RalfJung
Copy link
Member

In terms of process, this FCP includes align_offset from what I can tell. So I guess all this needs is a stabilization PR?

@CryZe
Copy link
Contributor

CryZe commented Apr 26, 2019

Cool, I'll look into creating that then.

CryZe added a commit to CryZe/rust that referenced this issue Apr 26, 2019
bors added a commit that referenced this issue Apr 26, 2019
Stabilize pointer::align_offset

Closes #44488
@jethrogb
Copy link
Contributor

It is permissible for the implementation to always return usize::max_value(). Only your algorithm's performance can depend on getting a usable offset here, not its correctness.

What is the point of this function? I'm dealing with hardware where if the pointer is not aligned, the instruction will fault. So I need to implement this function myself because this function is not guaranteed to be useful?

@oli-obk
Copy link
Contributor

oli-obk commented Oct 11, 2019

The point of this function that if it returns an offset that is out of bounds of your allocation, you should run your backup logic that does not depend on alignment. Imagine having a *mut [u8; 2] that you try to align to be usable as *mut u16, if you get an offset of 1 (because the original pointer was not aligned), you can't use the resulting pointer. In this case using your operation would be UB because you'd be working outside of the allocation.

Platforms like miri returning usize::max_value() is essentially just a way of signaling that you need to run the backup logic that you have to have anyway in case the alignment moves beyond your buffer

@jethrogb
Copy link
Contributor

run your backup logic that does not depend on alignment.

What backup logic? As stated, in my use case it is not possible to use an unaligned value.

@oli-obk
Copy link
Contributor

oli-obk commented Oct 11, 2019

In that case, what happens if you get an aligned value but it's outside of your allocation? You can't make a memory location aligned, you can only offset a pointer until it is aligned. But offsetting a pointer may go beyond the allocation your pointer points to

EDIT: maybe you need to make this decision at a higher level? e.g. by using the corresponding slice method (https://doc.rust-lang.org/std/primitive.slice.html#method.align_to) ?

@RalfJung
Copy link
Member

RalfJung commented Oct 11, 2019

This function is for cases where e.g. you have a &mut [u8] that could be large, and you want to get the "aligned center" of that slice as &mut [u64] for more efficient processing. Maybe your case is different, in which case maybe this function is not for you.

But as @oli-obk said, you need backup logic anyway because the pointer you are getting might not be sufficiently aligned, and the buffer might not be large enough to find something aligned "in the middle". What do you want to do in that case? I think we need a bit more context here.

@jethrogb
Copy link
Contributor

This function is for cases where e.g. you have a &mut [u8] that could be large, and you want to get the "aligned center" of that slice as &mut [u64] for more efficient processing. Maybe your case is different, in which case maybe this function is not for you.

No this is pretty much exactly the case for me. But I know that my input buffer is guaranteed to be large enough to hold the "aligned center".

@RalfJung
Copy link
Member

RalfJung commented Oct 11, 2019

Well, that is a subtly different use-case -- not what this function was designed for, and also one that Miri currently does not support. (There are trade-offs here: supporting that means being less good at detecting misaligned memory accesses.)

In particular, what do you do with the unaligned prefix and suffix? Usually they also need processing, and that's why you need fall-back code.

You could always use align_to and then assert that the center part is non-empty. Or you just do the usual "round ptr up to next multiple of alignment"; you don't actually need all the complexity in align_to (which is mostly caused by returning a result measured in units of size_of::<T>()).

@jethrogb
Copy link
Contributor

jethrogb commented Oct 11, 2019

Or you just do the usual "round ptr up to next multiple of alignment"; you don't actually need all the complexity in align_to

I'm talking about ptr::align_offset, which, in my mind, is exactly supposed to do the rounding up. The documentation I quoted earlier is from that function.

In particular, what do you do with the unaligned prefix and suffix? Usually they also need processing, and that's why you need fall-back code.

I'm using mmap to try to allocate naturally-aligned memory by asking for twice the size I need, computing the right range, and then munmapping the rest.

@RalfJung
Copy link
Member

I'm using mmap to try to allocate naturally-aligned memory by asking for twice the size I need, computing the right range, and then munmapping the rest.

Yeah that's not at all the use-case for which this was designed. If you want to implement an aligned allocator based on an unaligned one, this function currently won't help you. libstd in fact also contains such code because Windows.

I'm talking about ptr::align_offset, which, in my mind, is exactly supposed to do the rounding up.

Oh but it is doing way more than that. Quoting from the docs:

The offset is expressed in number of T elements, and not bytes.

That's what makes this function complicated. It actually solves an integer modulo congruence.

@jethrogb
Copy link
Contributor

jethrogb commented Oct 11, 2019

@RalfJung T is u8 for me.

It seems weird to me that ptr::align_offset should not be used to align pointer values except in some special cases. It's not clear at all from the documentation that you shouldn't use this function (except for the warning that the function might not give a useful output).

@RalfJung
Copy link
Member

RalfJung commented Oct 11, 2019

I guessed as much. So? All I said is, the function is more general than your use-case, making it way more complicated than "rounding up", and its reason to exist is not what you seem to think it is (to remove some simple rounding-up code from allocators -- an extremely rare thing to write).

You are looking at a very complicated tool with subtle trade-offs, wanting to use it in its simplest possible form (ignoring 99% of its functionality) and then complain that it doesn't exactly do what you want it to do. Well, I am afraid the tradeoffs that informed its design are different than yours. Probably this is not the tool you are looking for.

Assuming allocation is expensive, it also shouldn't be too much of a problem to call align_offset and then assert that your result is < align.

It's not clear at all from the documentation that you shouldn't use this function

You (like, the general public) should use it. You (@jethrogb) are just misinterpreting what it should be used for.

@RalfJung
Copy link
Member

In fact the docs say quite clearly:

Only your algorithm's performance can depend on getting a usable offset here, not its correctness.

So, if that's not a constraint you can live with, that's okay -- just don't use this function.

@jethrogb
Copy link
Contributor

Assuming allocation is expensive, it also shouldn't be too much of a problem to call align_offset and then assert that your result is < align.

Except that won't work, because:

It is permissible for the implementation to always return usize::max_value().

So when I do the assertion (which I'd be fine with), the implementation is free to unconditionally insert an assertion failure. Unless you think that that's not the case, in which case this is a documentation issue.

Only your algorithm's performance can depend on getting a usable offset here, not its correctness.

I definitely can't live with this, because now I still need to implement a "round up" function in case this function failed. Then I might as well just not use this function.

(to remove some simple rounding-up code from allocators -- an extremely rare thing to write)

I don't think pointer alignment manipulation is at all rare when dealing with low-level code, something Rust is supposed to be good at. Definitely not limited to allocators. I will not be the first person to come across this function when looking at the ptr docs thinking it would be useful.

@RalfJung
Copy link
Member

RalfJung commented Oct 11, 2019

So when I do the assertion (which I'd be fine with), the implementation is free to unconditionally insert an assertion failure.

Correct.

The current situation is that the libstd/rustc implementation will always succeed to align if size_of::<T>() == 1. However, the Miri implementation will not. The doc has to cover all implementations, but you could decide to rely on a specific one.

I don't think pointer alignment manipulation is at all rare when dealing with low-level code, something Rust is supposed to be good at. Definitely not limited to allocators. I will not be the first person to come across this function when looking at the ptr docs thinking it would be useful.

So far, allocators are the only use-case I am aware of that would need proper alignment for correctness, not just performance. Most of the time you are fed in data you have to work with; allocation is the only time when you are asked to produce memory with a certain alignment. But I am mostly unfamiliar with embedded programming and many other ways of using Rust, so if anyone reading along also has a use-case that is affected by this, please chime in. :)

For your use-case, a "please round up this pointer/integer to alignment X" function seems to be much more useful though than something that just computes the offset?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.