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

slice.get(i) should use a slice projection in MIR, like slice[i] does #139118

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

scottmcm
Copy link
Member

slice[i] is built-in magic, so ends up being quite different from slice.get(i) in MIR, even though they're both doing nearly identical operations -- checking the length of the slice then getting a ref/ptr to the element if it's in-bounds.

This PR adds a slice_get_unchecked intrinsic for impl SliceIndex for usize to use to fix that, so it no longer needs to do a bunch of lines of pointer math and instead just gets the obvious single statement. (This is not used for the range versions, since slice[i..] and slice[..k] can't use the mir Slice projection as they're using fenceposts, not indices.)

I originally tried to do this with some kind of GVN pattern, but realized that I'm pretty sure it's not legal to optimize BinOp::Offset to PlaceElem::Index without an extremely complicated condition. Basically, the problem is that the Index projection on a dereferenced slice pointer cares about the metadata, since it's UB to PlaceElem::Index outside the range described by the metadata. But then you cast the fat pointer to a thin pointer then offset it, that ignores the slice length metadata, so it's possible to write things that are legal with Offset but would be UB if translated in the obvious way to Index. Checking (or even determining) the necessary conditions for that would be complicated and error-prone, whereas this intrinsic-based approach is quite straight-forward.

Zero backend changes, because it just lowers to MIR, so it's already supported naturally by CTFE/Miri/cg_llvm/cg_clif.

@rustbot
Copy link
Collaborator

rustbot commented Mar 29, 2025

r? @workingjubilee

rustbot has assigned @workingjubilee.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@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 Mar 29, 2025
@rustbot
Copy link
Collaborator

rustbot commented Mar 29, 2025

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

Some changes occurred to the intrinsics. Make sure the CTFE / Miri interpreter
gets adapted for the changes, if necessary.

cc @rust-lang/miri, @RalfJung, @oli-obk, @lcnr

Comment on lines 30 to 36
bb2: {
StorageDead(_3);
StorageLive(_7);
StorageLive(_5);
_5 = &raw mut (*_1);
StorageLive(_6);
_6 = copy _5 as *mut u32 (PtrToPtr);
_7 = Offset(copy _6, copy _2);
StorageDead(_6);
StorageDead(_5);
_8 = &mut (*_7);
_0 = Option::<&mut u32>::Some(copy _8);
StorageDead(_7);
_5 = &mut (*_1)[_2];
_0 = Option::<&mut u32>::Some(copy _5);
goto -> bb3;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

annot: this pre-codegen test is the clearest demonstration of what the PR does.

index: usize,
) -> ItemPtr;

pub trait SliceGetUnchecked<T> {}
Copy link
Member

Choose a reason for hiding this comment

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

A brief doc comment would be good, it is a public trait after all. (I am surprised CI does not complain.)

Comment on lines 1763 to 1764
/// Equivalent to `{&, &mut, &raw const, &raw mut} (*slice_ptr)[index]`,
/// depending on the types involved.
Copy link
Member

Choose a reason for hiding this comment

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

We can't say it is "equivalent" but then also say it has UB. Those two statements contradict each other, if it's equivalent it must be the same safety requirements.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm confused -- the MIR does have that UB, thus the safety requirements.

Demo: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=f3287f133d3a12718ba3391bbd3e184c

#![feature(core_intrinsics)]
#![feature(custom_mir)]
use std::intrinsics::mir::*;

#[custom_mir(dialect = "runtime", phase = "optimized")]
pub unsafe fn get_unchecked(p: *const [i32], i: usize) -> *const i32 {
    mir! {
        {
            RET = &raw const (*p)[i];
            Return()
        }
    }
}

fn main() {
    let a = [1_i32; 10];
    let p = a.as_ptr();
    let slice_ptr = std::ptr::slice_from_raw_parts(p, 1);
    unsafe {
        get_unchecked(slice_ptr, 1);
    }
}

Miri:

error: Undefined Behavior: indexing out of bounds: the len is 1 but the index is 1
  --> src/main.rs:9:13
   |
9  |             RET = &raw const (*p)[i];
   |             ^^^^^^^^^^^^^^^^^^^^^^^^ indexing out of bounds: the len is 1 but the index is 1
   |
   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
   = note: BACKTRACE:
   = note: inside `get_unchecked` at src/main.rs:9:13: 9:37
note: inside `main`
  --> src/main.rs:20:9
   |
20 |         get_unchecked(slice_ptr, 1);
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^

Copy link
Member

Choose a reason for hiding this comment

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

I'm confused -- the MIR does have that UB, thus the safety requirements.

Oh, when you say {&, &mut, &raw const, &raw mut} (*slice_ptr)[index] you mean that as a MIR expression, not as a Rust expression? That's not clear at all.^^

This is an intrinsic, and I think it should be described in terms of the Rust surface language, not in terms of very unstable implementation details like MIR.

Copy link
Member Author

Choose a reason for hiding this comment

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

To me intrinsics are "very unstable implementation details" :P

I'll update the description to be clearer, though.

@rustbot author

Copy link
Member

Choose a reason for hiding this comment

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

To me intrinsics are "very unstable implementation details" :P

Absolutely. But they also form language primitives that need clear documentation so that everyone using and implementing them knows exactly what they are dealing with.

So, to elaborate, there are two separate points:

  • I'm not sure how anyone was supposed to know from your original comment that this was meant to be MIR, not surface Rust. The default assumption will be surface Rust, given that this file is nowhere near the MIR infrastructure.
  • When writing docs in files far away from the MIR infrastructure, even docs for unstable implementation details, I'd rather refer to the surface language than MIR. MIR can easily change and then one can easily forget updating all the docs that point to it. I'm also not aware of us documenting any other intrinsic in terms of MIR, and we have consumers of these docs that do not use MIR (mrustc, gcc-rs).

@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 Apr 1, 2025
@rustbot
Copy link
Collaborator

rustbot commented Apr 1, 2025

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@scottmcm scottmcm force-pushed the slice-get-unchecked-intrinsic branch from 4cf8aa1 to bb5dd9b Compare April 6, 2025 04:10
@scottmcm
Copy link
Member Author

scottmcm commented Apr 6, 2025

Ok, updated:

  1. Redid the comment to talk about the projection primarily, just mentioning MIR as an aside that backends don't need to implement it.

  2. Moved the trait to a private module to emphasize that it's not for public use, gave it some comments, and redid it to be more general so it can also replace the AggregateRawPtr trait.

@rustbot ready

@rustbot rustbot 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 Apr 6, 2025
@rust-log-analyzer

This comment has been minimized.

@scottmcm scottmcm force-pushed the slice-get-unchecked-intrinsic branch from bb5dd9b to 5d9cc7a Compare April 6, 2025 04:20
@rust-log-analyzer

This comment has been minimized.

@scottmcm scottmcm closed this Apr 6, 2025
@scottmcm scottmcm reopened this Apr 6, 2025
@rust-log-analyzer

This comment has been minimized.

@scottmcm scottmcm force-pushed the slice-get-unchecked-intrinsic branch from 5d9cc7a to 938fe84 Compare April 6, 2025 07:19
@rust-log-analyzer
Copy link
Collaborator

The job x86_64-gnu-tools failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)
tests/pass-dep/concurrency/linux-futex.rs ... FAILED
tests/pass-dep/libc/mmap.rs ... ok

FAILED TEST: tests/pass-dep/concurrency/linux-futex.rs
command: MIRI_ENV_VAR_TEST="0" MIRI_TEMP="/tmp/miri-uitest-uqQWFy" RUST_BACKTRACE="1" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/miri" "--error-format=json" "--sysroot=/checkout/obj/build/x86_64-unknown-linux-gnu/miri-sysroot" "-Dwarnings" "-Dunused" "-Ainternal_features" "-O" "-Zmir-opt-level=4" "-Cdebug-assertions=yes" "-Zui-testing" "--out-dir" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/tests/pass-dep/concurrency" "tests/pass-dep/concurrency/linux-futex.rs" "-Zmiri-disable-isolation" "--extern" "cfg_if=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libcfg_if-c59a4916bb0f0878.rlib" "--extern" "cfg_if=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libcfg_if-c59a4916bb0f0878.rmeta" "--extern" "getrandom_01=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libgetrandom-e63509764dc6ebd4.rlib" "--extern" "getrandom_01=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libgetrandom-e63509764dc6ebd4.rmeta" "--extern" "getrandom_02=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libgetrandom-77233cd1afe9e250.rlib" "--extern" "getrandom_02=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libgetrandom-77233cd1afe9e250.rmeta" "--extern" "getrandom_03=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libgetrandom-9f66fc7652f80580.rlib" "--extern" "getrandom_03=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libgetrandom-9f66fc7652f80580.rmeta" "--extern" "libc=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/liblibc-dec160b54690387b.rlib" "--extern" "libc=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/liblibc-dec160b54690387b.rmeta" "--extern" "num_cpus=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libnum_cpus-468997db13f4a162.rlib" "--extern" "num_cpus=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libnum_cpus-468997db13f4a162.rmeta" "--extern" "page_size=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libpage_size-4db56407b9cc5a5c.rlib" "--extern" "page_size=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libpage_size-4db56407b9cc5a5c.rmeta" "--extern" "tempfile=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libtempfile-a130439ce9f3f13d.rlib" "--extern" "tempfile=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libtempfile-a130439ce9f3f13d.rmeta" "--extern" "tokio=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libtokio-efb9f88f6e02a9fd.rlib" "--extern" "tokio=/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps/libtokio-efb9f88f6e02a9fd.rmeta" "-L" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/debug/deps" "-L" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/miri_ui/0/miri/x86_64-unknown-linux-gnu/debug/deps" "--edition" "2021"

error: test got exit status: 101, but expected 0
 = note: the compiler panicked

full stderr:
warning: Miri does not support optimizations: the opt-level is ignored. The only effect of selecting a Cargo profile that enables optimizations (such as --release) is to apply its remaining settings, such as whether debug assertions and overflow checks are enabled.

warning: You have explicitly enabled MIR optimizations, overriding Miri's default which is to completely disable them. Any optimizations may hide UB that Miri would otherwise detect, and it is not necessarily possible to predict what kind of UB will be missed. If you are enabling optimizations to make Miri run faster, we advise using cfg(miri) to shrink your workload instead. The performance benefit of enabling MIR optimizations is usually marginal at best.


thread 'main' panicked at tests/pass-dep/concurrency/linux-futex.rs:286:5:
assertion failed: woken > 0 && woken < rounds
stack backtrace:
   0: std::panicking::begin_panic_handler
             at /checkout/library/std/src/panicking.rs:697:5
   1: std::rt::panic_fmt
             at /checkout/library/core/src/panicking.rs:75:14
---

Location:
   /cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ui_test-0.29.2/src/lib.rs:369

Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.
Run with RUST_BACKTRACE=full to include source snippets.
error: test failed, to rerun pass `--test ui`

Caused by:
  process didn't exit successfully: `/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/x86_64-unknown-linux-gnu/release/deps/ui-8399c055961d030a tests/pass tests/panic` (exit status: 1)
Command has failed. Rerun with -v to see more details.
Build completed unsuccessfully in 0:04:53
  local time: Sun Apr  6 07:51:48 UTC 2025
  network time: Sun, 06 Apr 2025 07:51:48 GMT
##[error]Process completed with exit code 1.
Post job cleanup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants