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 const_swap_nonoverlapping #133668

Open
1 of 4 tasks
RalfJung opened this issue Nov 30, 2024 · 14 comments · May be fixed by #137280
Open
1 of 4 tasks

Tracking Issue for const_swap_nonoverlapping #133668

RalfJung opened this issue Nov 30, 2024 · 14 comments · May be fixed by #137280
Labels
C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC I-lang-easy-decision Issue: The decision needed by the team is conjectured to be easy; this does not imply nomination T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@RalfJung
Copy link
Member

RalfJung commented Nov 30, 2024

Feature gate: #![feature(const_swap_nonoverlapping)]

This is a tracking issue for making swap_nonoverlapping a const fn.

Public API

mod ptr {
    pub const unsafe fn swap_nonoverlapping<T>(x: *mut T, y: *mut T, count: usize);
}

Steps / History

Blocking Issues

ptr::swap_nonoverlapping has a limitation currently where it can fail when the data-to-swap contains pointers that cross the "element boundary" of such a swap (i.e., count > 1 and the pointer straddles the boundary between two T). Here's an example of code that unexpectedly fails:

    const {
        let mut ptr1 = &1;
        let mut ptr2 = &666;

        // Swap ptr1 and ptr2, bytewise.
        unsafe {
            ptr::swap_nonoverlapping(
                ptr::from_mut(&mut ptr1).cast::<u8>(),
                ptr::from_mut(&mut ptr2).cast::<u8>(),
                mem::size_of::<&i32>(),
            );
        }

        // Make sure they still work.
        assert!(*ptr1 == 666);
        assert!(*ptr2 == 1);
    };

The proper way to fix this is to implement rust-lang/const-eval#72.

Footnotes

  1. https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html

@RalfJung RalfJung added C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Nov 30, 2024
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Dec 1, 2024
…lnay

Move some functions out of const_swap feature gate

- `swap_unchecked` is still unstable as a regular fn, so that feature gate can also cover its constness.
- `swap_nonoverlapping` isn't ready to be stabilized yet, so make it a different feature gate.

Part of rust-lang#83163, rust-lang#88539, rust-lang#133668
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Dec 1, 2024
…lnay

Move some functions out of const_swap feature gate

- `swap_unchecked` is still unstable as a regular fn, so that feature gate can also cover its constness.
- `swap_nonoverlapping` isn't ready to be stabilized yet, so make it a different feature gate.

Part of rust-lang#83163, rust-lang#88539, rust-lang#133668
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Dec 1, 2024
Rollup merge of rust-lang#133669 - RalfJung:const_swap_splitup, r=dtolnay

Move some functions out of const_swap feature gate

- `swap_unchecked` is still unstable as a regular fn, so that feature gate can also cover its constness.
- `swap_nonoverlapping` isn't ready to be stabilized yet, so make it a different feature gate.

Part of rust-lang#83163, rust-lang#88539, rust-lang#133668
@RalfJung
Copy link
Member Author

RalfJung commented Dec 23, 2024

I guess the alternative is that we could just error when there is a pointer straddling the boundary of one of the to-be-swapped chunks. That is what happens today, and it doesn't seem entirely unreasonable? This is probably a very rare situation, after all.

@rust-lang/wg-const-eval @rust-lang/lang @rust-lang/opsem do you think it is acceptable for the example code above to error? The context here is that operations that read/write a part of a pointer generally don't work in const-eval. ptr::swap_nonoverlapping is implemented by swapping count many individual chunks of size size_of::<T>() in a loop, so if a pointer straddles the boundary between two such chunks, we get a "trying to read part of a pointer" error.

See here for what the error looks like, and here for a variant of the code that does work since the pointer is entirely contained within a single chunk. (The type used for the chunk does not matter, only its size is relevant.)

We could beef up const-eval so that it can support this kind of code (see rust-lang/const-eval#72), but doing so is non-trivial. ptr::swap_nonoverlapping is quite useful even with this limitation, and this approach is forward-compatible with lifting the limitation later.

@RalfJung RalfJung added the I-lang-nominated Nominated for discussion during a lang team meeting. label Dec 23, 2024
Zalathar added a commit to Zalathar/rust that referenced this issue Dec 24, 2024
core: fix const ptr::swap_nonoverlapping when there are pointers at odd offsets

Ensure that the pointer gets swapped correctly even if it is not stored at an aligned offset. This rules out implementations that copy things in a `usize` loop -- so our implementation needs to be adjusted to avoid such a loop when running in const context.

Part of rust-lang#133668
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Dec 24, 2024
Rollup merge of rust-lang#134689 - RalfJung:ptr-swap-test, r=oli-obk

core: fix const ptr::swap_nonoverlapping when there are pointers at odd offsets

Ensure that the pointer gets swapped correctly even if it is not stored at an aligned offset. This rules out implementations that copy things in a `usize` loop -- so our implementation needs to be adjusted to avoid such a loop when running in const context.

Part of rust-lang#133668
@lcnr
Copy link
Contributor

lcnr commented Jan 7, 2025

I guess the alternative is that we could just error when there is a pointer straddling the boundary of one of the to-be-swapped chunks. That is what happens today, and it doesn't seem entirely unreasonable? This is probably a very rare situation, after all.

I personally think that as these errors are unrecoverable (i.e. I would be hesitant if ctfe were able to silently fail during candidate selection with const generics) then erroring in this case for now seems totally acceptable for me, especially if it unblocks an otherwise useful function. Adding pointer fragments in the future would be backwards compatible afaict

@RalfJung
Copy link
Member Author

RalfJung commented Jan 7, 2025

Yeah this behaves like other dynamically detected "const cannot do this" errors, such as is_null on a pointer where we don't know whether it is null or not. And indeed it should be backwards comaptible to make previously failing const expressions work; we generally rely on that for other things like #133700 as well.

@RalfJung
Copy link
Member Author

@rust-lang/lang any comment on this? This is the last raw pointer function that is #[stable] but #[rustc_const_unstable], would be nice to stabilize it and thus finish the task of making all basic raw pointer ops that we can support available in const. :)

@oli-obk oli-obk added the I-lang-easy-decision Issue: The decision needed by the team is conjectured to be easy; this does not imply nomination label Feb 14, 2025
@joshtriplett
Copy link
Member

joshtriplett commented Feb 14, 2025

@RalfJung Wearing a lang hat but not speaking for the rest of the lang team:

  • I do think this should work eventually, and would not be in support of treating this as a permanent limitation.
  • No objections to temporarily having such a limitation, and documenting it (in the comments of such functions and possibly in some central documentation of const eval limitations) as "this limitation may be lifted in the future". This is not a one-way door, code is not going to rely on this limitation, and working in many-but-not-all cases is better than working in zero cases. (That's particularly true for const evaluation, in which any error will arise at compile time; such limitations can be more surprising if they occur at runtime.)

@traviscross traviscross added the T-lang Relevant to the language team, which will review and decide on the PR/issue. label Feb 14, 2025
@traviscross
Copy link
Contributor

...ptr::swap_nonoverlapping is quite useful even with this limitation, and this approach is forward-compatible with lifting the limitation later.

Generally things that take this shape get a warm reception. It sounds fine to me. We'll try to get to this one in the meeting next week.

@nikomatsakis
Copy link
Contributor

If this is a 2-way door (sounds like yes) then I have no objection to stabilizing. I admit I don't 100% understand the details of the question @RalfJung that you are asking though.

@traviscross
Copy link
Contributor

@rustbot labels -I-lang-nominated

We talked about this in the meeting today and had positive vibes. Please nominate for us the stabilization PR and we'll FCP that.

@rustbot rustbot removed the I-lang-nominated Nominated for discussion during a lang team meeting. label Feb 19, 2025
@RalfJung
Copy link
Member Author

RalfJung commented Feb 19, 2025

@nikomatsakis

Stabilization is never a 2-way door, we cannot un-do a stabilization... so I am confused by the first part of your message.

For the second part, basically I am asking whether it is okay to stabilize a function that sometimes leads to a const-eval failure in a situation where the code doesn't have UB or anything like that -- it's just a situation that is hard to support in const-eval, and our current implementation does not support it. Users can already trigger the same const-eval failure themselves with unsafe code, but this would be the first time that failure can be triggered from safe code. That felt to me like it should get explicit t-lang approval. :)

@joshtriplett
Copy link
Member

joshtriplett commented Feb 19, 2025

@RalfJung For the first part, the conclusion was "this doesn't commit us to continuing to fail in this case, we can start handling it in the future".

@nikomatsakis I think you wanted "not closing a one-way door" rather than "is a two-way door". :)

@nikomatsakis
Copy link
Contributor

Let me restate my summary of what is happening to check my understanding:

If I understand, the impl of swap_nonoverlapping can spuriously fail when the two pointers happen to be adjacent in memory (i.e., in the example, there are two local variables and the first array ends at the same address where the second begins), That is not a soundness problem because const in general is allowed to be incomplete with respect to runtime dynamics, but it could cause a bad user experience. It seems like you are basically asking "is it ok to stabilize giving this known bug".

@RalfJung the two-way door I was referring to is: we can fix this impl bug in the future.

@RalfJung
Copy link
Member Author

If I understand, the impl of swap_nonoverlapping can spuriously fail when the two pointers happen to be adjacent in memory

No, it has nothing to do with that. It is always UB to do any kind of access on adjacent allocations.

The swap_nonoverlapping call in the example swaps the contents of ptr1 and ptr2. I don't know which "arrays" you are talking about.

@RalfJung RalfJung linked a pull request Feb 19, 2025 that will close this issue
@RalfJung
Copy link
Member Author

RalfJung commented Feb 19, 2025

I came up with a new version of the example, maybe that makes it more clear. :)
See the comments in the code:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=6c390452379fb593e109b8f8ee854d2a

Stabilization PR is up at #137280.

@oli-obk
Copy link
Contributor

oli-obk commented Feb 19, 2025

The issue is that doing a bytewise copy of memory containing pointers will fail as we cannot represent a single byte of a pointer right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC I-lang-easy-decision Issue: The decision needed by the team is conjectured to be easy; this does not imply nomination T-lang Relevant to the language team, which will review and decide on the 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.

7 participants