Skip to content

Rewrite pointer::with_addr implementation #110318

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

Closed
wants to merge 1 commit into from

Conversation

WaffleLapkin
Copy link
Member

This commit changes the impl from (essentially)

ptr.wrapping_byte_offset(new_addr - self.addr())

to

ptr.wrapping_byte_sub(self.addr()).wrapping_byte_add(new_addr)

While being essentially the same (a-a+b vs a+(b-a)) new implementation generation 2 GEPs, instead of an arithmetic + 1 GEP, which turns out to be easier for LLVM to optimize and reason about.
(ptradd instead of getelementptr when .-.)


Idea by @scottmcm, should fix the problem I've found while working on #110243, which I documented in the LLVM issue: llvm/llvm-project#62093 (TL;DR: the code for .map_addr(|a| a << 2) does not get optimized properly).

This commit changes the impl from (essentially)
```rust
ptr.wrapping_byte_offset(new_addr - self.addr())
```
to
```rust
ptr.wrapping_byte_sub(self.addr()).wrapping_byte_add(new_addr)
```

While being essentially the same (`a-a+b` vs `a+(b-a)`) new implementation
generation 2 GEPs, instead of an arithmetic + 1 GEP, which turns out to be
easier for LLVM to optimize and reason about.

(`ptradd` instead of `getelementptr` when .-.)
@WaffleLapkin WaffleLapkin added T-libs Relevant to the library team, which will review and decide on the PR/issue. A-raw-pointers Area: raw pointers, MaybeUninit, NonNull A-strict-provenance Area: Strict provenance for raw pointers labels Apr 14, 2023
@rustbot
Copy link
Collaborator

rustbot commented Apr 14, 2023

r? @m-ou-se

(rustbot has picked a reviewer for you, use r? to override)

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Apr 14, 2023
@rustbot

This comment was marked as resolved.

@nikic
Copy link
Contributor

nikic commented Apr 14, 2023

Upstream patch: https://reviews.llvm.org/D148341

I'm not sure this is a good idea -- I expect that this will optimize worse in cases where you aren't doing something extremely weird.

Also worth noting that this formulation is not CHERI compatible.

// Note that we are using two offsets, instead of precomputing offset
// with `addr - self.addr()`. This is because it is easier for LLVM to
// optimize such code.
self.wrapping_byte_sub(self.addr()).wrapping_byte_add(addr)
Copy link
Member

Choose a reason for hiding this comment

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

As I was playing yesterday, I noticed another possible implementation:

Suggested change
self.wrapping_byte_sub(self.addr()).wrapping_byte_add(addr)
self.mask(0).wrapping_byte_add(addr)

That's again just two IR instructions (https://rust.godbolt.org/z/EeraoxKnh), and it has the bonus of never doing a ptrtoint.

Any thoughts on whether that's reasonable or horrible, @nikic?

Copy link
Contributor

Choose a reason for hiding this comment

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

That's very horrible. ptrmask has about zero optimization support.

@WaffleLapkin
Copy link
Member Author

@nikic why is it not CHERI compatible, does it not allow to temporary create invalid pointers? 🤔

@nikic
Copy link
Contributor

nikic commented Apr 14, 2023

@nikic why is it not CHERI compatible, does it not allow to temporary create invalid pointers? thinking

I don't think so, but probably @jrtc27 can clarify.

@jrtc27
Copy link

jrtc27 commented Apr 14, 2023

This will take the pointer temporarily way out of bounds, which is not supported on CHERI due to the bounds compression scheme in use. Depending on what IR this maps to I would expect LLVM to be able to perform the transformation you want on non-CHERI architectures for you.

@WaffleLapkin
Copy link
Member Author

This will take the pointer temporarily way out of bounds, which is not supported on CHERI due to the bounds compression scheme in use.

Well, TIL. Does this also mean that no pointer tagging is ever possible on CHERI?

Anyway, I'm not particularly attached to this change. IMO it is a bit nicer code-wise and looks more logical to me. But if it adds problems for CHERI and even LLVM, we can just close this (especially given the candidate patch which should probably fix the original issue).

@jrtc27
Copy link

jrtc27 commented Apr 14, 2023

This will take the pointer temporarily way out of bounds, which is not supported on CHERI due to the bounds compression scheme in use.

Well, TIL. Does this also mean that no pointer tagging is ever possible on CHERI?

Anyway, I'm not particularly attached to this change. IMO it is a bit nicer code-wise and looks more logical to me. But if it adds problems for CHERI and even LLVM, we can just close this (especially given the candidate patch which should probably fix the original issue).

Pointer tagging works provided you use low bits. Some implementations may also provide a way to set high bits without affecting bounds (e.g. as seen in Morello) but that is not portable across CHERI implementations. If you want to start performing arbitrary transformations on addresses and expect to be able to recover a usable pointer then CHERI cannot do that in its current form due to the compression scheme employed (we used to have an uncompressed variant that could, but quadrupling the pointer size is rather less acceptable than merely doubling).

@WaffleLapkin
Copy link
Member Author

Pointer tagging works provided you use low bits.

Can't this also move the pointer out of the allocation? Or is the change of value in low bits small enough that it doesn't break compression?

@jrtc27
Copy link

jrtc27 commented Apr 14, 2023

Pointer tagging works provided you use low bits.

Can't this also move the pointer out of the allocation?

Yes.

Or is the change of value in low bits small enough that it doesn't break compression?

Yes. Note I said way out of bounds before, not just out of bounds. The architecture gives you 1/8th range either side (one direction is in fact 1/4, though I forget if above or below), with a minimum for small allocations that varies based on the number of bits of precision used (think mantissa width for floating point), but is on the order of KiB for all current 64-bit implementations.

(I’m ignoring Microsoft’s CHERIoT here which is far stricter and thus more hostile to code that likes to play games with pointers, but can get away with it due to being used in an embedded context where you have much greater control over the code being compiled and people are more willing to adapt to weird and wonderful architectures)

@WaffleLapkin
Copy link
Member Author

@jrtc27 I see, thanks for the explanation! <3

@WaffleLapkin
Copy link
Member Author

Closing as per the concerns above, given that LLVM issue was fixed.

@WaffleLapkin WaffleLapkin deleted the with_dumber_addr branch April 17, 2023 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-raw-pointers Area: raw pointers, MaybeUninit, NonNull A-strict-provenance Area: Strict provenance for raw pointers S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. 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.

6 participants