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

Different behavior when changing opt-level #126866

Closed
tgeng opened this issue Jun 23, 2024 · 4 comments
Closed

Different behavior when changing opt-level #126866

tgeng opened this issue Jun 23, 2024 · 4 comments
Labels
C-gub Category: the reverse of a compiler bug is generally UB

Comments

@tgeng
Copy link

tgeng commented Jun 23, 2024

I tried this code:

use std::ops::Deref;

struct TaggedPointer<T>(T);

impl<'a, T> Deref for TaggedPointer<&'a T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        unsafe {
            let ptr = self.0 as *const T as usize;
            let ptr = ptr & !0b11;
            &*(ptr as *const T)
        }
    }
}

fn tag_pointer<T>(ptr: &T) -> &T {
    unsafe {
        let ptr = ptr as *const T as usize;
        &*((ptr | 0b01) as *const T)
    }
}


fn main() {
    let i = 123;
    let tagged_pointer = TaggedPointer(tag_pointer(&i));
    assert_eq!(*tagged_pointer, 123);
}

I expected to see this happen:
Regardless of opt-level in Cargo.toml, the assertion should succeed since the deref impl would undo the pointer tag.

Instead, this happened:
With opt-level = 0, I see the expected behavior: assertion succeeds. But with opt-level = 1 or above, the assertion fails.

Meta

> rustc --version --verbose
rustc 1.81.0-nightly (3cb521a43 2024-06-22)
binary: rustc
commit-hash: 3cb521a4344f0b556b81c55eec8facddeb1ead83
commit-date: 2024-06-22
host: aarch64-apple-darwin
release: 1.81.0-nightly
LLVM version: 18.1.7
Backtrace

> RUST_BACKTRACE=1 cargo build
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.17s

@tgeng tgeng added the C-bug Category: This is a bug. label Jun 23, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Jun 23, 2024
@asquared31415
Copy link
Contributor

The tag_pointer operation is undefined behavior. When you &* on the pointer you've created, there's a rule that the pointer must be in bounds of an allocation, but in your case there isn't. When in opt-level 1, the optimizer is likely using its knowledge of the rules around pointers and causing your code to behave differently than you expected.

@rustbot label -needs-triage -C-bug +C-discussion

@rustbot rustbot added C-discussion Category: Discussion or questions that doesn't represent real issues. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. C-bug Category: This is a bug. labels Jun 23, 2024
@asquared31415
Copy link
Contributor

As a fix, I would suggest having TaggedPointer store a *const T or some other raw pointer, and only doing the * operation after masking it back to the correct value. This means you lose the lifetime checks, but it may be possible to have a PhantomData<&'a T> to keep the lifetime around.

@CraftSpider
Copy link
Contributor

If writing unsafe code, you may want to use miri, the UB checker. For this code it emits the following error:

Miri Output
error: Undefined Behavior: out-of-bounds pointer use: alloc1225 has size 4, so pointer to 4 bytes starting at offset 1 is out-of-bounds
  --> src/main.rs:20:9
   |
20 |         &*((ptr | 0b01) as *const T)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: alloc1225 has size 4, so pointer to 4 bytes starting at offset 1 is out-of-bounds
   |
   = 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
help: alloc1225 was allocated here:
  --> src/main.rs:26:9
   |
26 |     let i = 123;
   |         ^
   = note: BACKTRACE (of the first span):
   = note: inside `tag_pointer::<i32>` at src/main.rs:20:9: 20:37
note: inside `main`
  --> src/main.rs:27:40
   |
27 |     let tagged_pointer = TaggedPointer(tag_pointer(&i));
   |                                        ^^^^^^^^^^^^^^^

@tgeng
Copy link
Author

tgeng commented Jun 23, 2024

Ah thanks for the explanation and tips! I will check out miri!

@tgeng tgeng closed this as completed Jun 23, 2024
@workingjubilee workingjubilee added C-gub Category: the reverse of a compiler bug is generally UB and removed C-discussion Category: Discussion or questions that doesn't represent real issues. labels Jun 23, 2024
tgeng added a commit to tgeng/archon-vm that referenced this issue Jun 24, 2024
Casting a T pointer to a usize and misalign it causes undefined
behavior. See rust-lang/rust#126866
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-gub Category: the reverse of a compiler bug is generally UB
Projects
None yet
Development

No branches or pull requests

5 participants