Skip to content

Add core::ptr::assume_moved #3700

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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

mrkajetanp
Copy link

@mrkajetanp mrkajetanp commented Sep 26, 2024

Add a helper for primitive pointer types to facilitate modifying the address of a pointer. This
mechanism is intended to enable the use of architecture features such as AArch64 Top-Byte Ignore
(TBI) to facilitate use-cases such as high-bit pointer tagging. An example application of this
mechanism would be writing a tagging memory allocator.

Rendered

@ChayimFriedman2
Copy link

Is there a reason we can not just say changing the upper bits has no impact on a pointer if an appropriate tagging scheme is available, without need for additional methods?

@mrkajetanp
Copy link
Author

Yes, the reason is that even if the hardware understands a particular tagging scheme, the memory model in Rust and LLVM does not. Setting a tag on a pointer, even though it has no impact on the hardware side, makes the memory model think the pointer has now been offset outside of its original allocation and thus any access to it is Undefined Behaviour.

To be able to do this we need a helper method that simulates a "realloc" from the untagged address to the tagged address to make the memory model happy.
Specifically, we need the helper method to return a pointer that LLVM IR will annotate as noalias.
Relevant section from the linked LLVM doc:

On function return values, the noalias attribute indicates that the function acts like a system memory allocation function, returning a pointer to allocated storage disjoint from the storage for any other object accessible to the caller.

@RalfJung
Copy link
Member

Cc @rust-lang/opsem

@lolbinarycat
Copy link
Contributor

these should probably be associated functions, not methods.

also, this seems to ignore another type of pointer tagging, often used by interpreters, where the bottom bits (otherwise always zero because of alignment) are used to tag the type of the object.

@RalfJung
Copy link
Member

Is there a reason we can not just say changing the upper bits has no impact on a pointer if an appropriate tagging scheme is available, without need for additional methods?

This question should indeed be answered in the RFC text, not just in the discussion thread.

(This RFC could have benefited from a pre-RFC phase, posting it on the forum to get some feedback to ensure that it has all the expected details.)

Copy link
Member

@RalfJung RalfJung left a comment

Choose a reason for hiding this comment

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

Thanks for posting the RFC! However, I think it wasn't quite ready yet. Here's some first feedback. This RFC is still lacking most of the relevant details, so I didn't proceed beyond the reference-level section.

# Summary
[summary]: #summary

Add helper methods on primitive pointer types to facilitate getting and setting the tag of a pointer.
Copy link
Member

Choose a reason for hiding this comment

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

The term "tag of a pointer" means a lot of different things, and often it means something different from how you are using the term here. So the RFC should clarify the terminology it uses. This is, AFAIK, not meant to be a general pointer tagging mechanism. No RFC is even needed for that. It is specific for working with hardware that ignores certain bits of a pointer.

Copy link
Author

Choose a reason for hiding this comment

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

The thinking is that hardware that ignores certain bits of a pointer is only relevant if you're setting those bits to some values, i.e. in the context of pointer tagging. Thus, the way to add support for such hardware is through a mechanism for pointer tagging. Does that reasoning make sense?

This is still a bit of an open question, there are several directions we could go with this. The tricky part is that different architectures have support for something similar, but the details (which exact bits are ignored) may vary.
On that account I'm not sure whether it'd be better to try and make a general high-bit pointer tagging mechanism that could be used across architectures, or whether it'd be better to just upstream e.g. aarch64-specific functions into core_arch and maybe then think about a generic one that just calls into those depending on the platform.
I don't really have very strong opinions here.

Copy link
Member

@RalfJung RalfJung Oct 10, 2024

Choose a reason for hiding this comment

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

I'm just pointing out the RFC is written in a confusing way. I would suggest to make it very targeted specifically for "supporting hardware that ignores some bits in a pointer", rather than general pointer tagging. This should become clear already in the summary and the first paragraph of the motivation.

the lower 48 bits, leaving higher bits unused. The remaining bits are for the most part used to
distinguish userspace pointers (0x00) from kernelspace pointers (0xff).
Certain architectures provide extensions, such as TBI on AArch64, that allow programs to make use of
those unused bits to insert custom metadata into the pointer.
Copy link
Member

Choose a reason for hiding this comment

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

To be clear, userspace can already do that without hardware support, trivially. By masking out those bots for each load. The hardware feature just makes this slightly more efficient.

I find the introduction to be a bit confusing due to this.

Comment on lines 23 to 25
Currently, Rust does not acknowledge TBI and related architecture extensions that enable the use of
tagged pointers. This could potentially cause issues in cases such as working with TBI-enabled C/C++
components over FFI, or when writing a tagging memory allocator.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Currently, Rust does not acknowledge TBI and related architecture extensions that enable the use of
tagged pointers. This could potentially cause issues in cases such as working with TBI-enabled C/C++
components over FFI, or when writing a tagging memory allocator.
Currently, Rust does not support directly using TBI and related architecture extensions that simplify the use of
tagged pointers. This could potentially cause issues in cases such as working with TBI-enabled C/C++
components over FFI, or when writing a tagging memory allocator.

Certain architectures provide extensions, such as TBI on AArch64, that allow programs to make use of
those unused bits to insert custom metadata into the pointer.

Currently, Rust does not acknowledge TBI and related architecture extensions that enable the use of
Copy link
Member

Choose a reason for hiding this comment

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

The RFC should explain what the problem with the use of these extensions in Rust is.

Also, it is very odd to see interop with C/C++ as the motivation here, given that those languages do not support TBI either (which is another relevant fact the RFC should mention).

Copy link
Author

Choose a reason for hiding this comment

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

Well they don't support it "fully" on a language level, as in you can't just tag arbitrary pointers with no issues, but they sort of do in that there are currently C/C++ components running in production that operate on TBI-enabled pointers.
I want to make it possible to, say, write a custom allocator which internally just calls malloc, puts some tag in the pointer it got from malloc and then returns that pointer to the user.

Copy link
Member

@RalfJung RalfJung Oct 10, 2024

Choose a reason for hiding this comment

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

I don't doubt there's C/C++ code in production that causes UB and works anyway, but since we are going for a UB-free approach here as we always do, it is not a fair comparison to claim that C/C++ already supports this in a way Rust wouldn't.

```
assert!(ptr.tag() == 0);
let tagged_ptr = unsafe { ptr.with_tag(63) };
assert!(tagged_ptr.tag() == 63);
Copy link
Member

Choose a reason for hiding this comment

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

The guide-level explanation should give a guide for how to think about these functions and what they do.

This is where you have to explain that tag is basically realloc. Currently, realloc suddenly appears in "drawbacks", which nobody reading the RFC will understand.

Remember that an RFC is supposed to be a self-contained document such that everyone who is reasonably well-versed in Rust but knows nothing about the particular problem domain can understand what problem the RFC is trying to solve, and how it is trying to solve it. Your RFC is missing a lot of background and explanation to make it satisfy this requirement.

[reference-level-explanation]: #reference-level-explanation

Within Rust's memory model, modifying the high bits offsets the pointer outside of the bounds of
its original allocation, making any use of it Undefined Behaviour.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
its original allocation, making any use of it Undefined Behaviour.
its original allocation, making any load/store with that pointer Undefined Behaviour.

"any use" is not correct, e.g. wrapping_offset or == on such a pointer are completely fine.

caller.

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation
Copy link
Member

@RalfJung RalfJung Sep 29, 2024

Choose a reason for hiding this comment

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

This section should contain the full signature of the newly proposed methods, and their doc comments (see the existing pointer methods for how our doc comments look like), so that we have an exact description of what the proposal even is. You can't just propose two function names and leave the details for later, when the entire reason that an RFC is even needed is that those details are far from simple.

Copy link
Author

Choose a reason for hiding this comment

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

Yes for sure, the reason they're not there yet is that I wanted to ask for outside opinions before settling in on what those signatures should be and what the exact methods should actually look like. My bad that it didn't come across as intended.
The concrete part of the proposal is that we should have a function/method to set the high-bit tag of a pointer & one to retrieve it, the other details such as the name, where it should live or what it should entail are still in flux.

[drawbacks]: #drawbacks

Because the memory model we currently have is not fully compatible with memory tagging and
tagged pointers, setting the high bits of a pointer must be done with great care in order to
Copy link
Member

Choose a reason for hiding this comment

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

That's not correct. Pointer tagging works perfectly fine and is a commonly used technique in Rust. The memory model is just not compatible with loading from a tagged pointer without first removing the tag.

Copy link
Author

Choose a reason for hiding this comment

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

That's why I wrote "not fully compatible" - you can make it work, just not as smoothly as one would like given what the hardware can support.
Besides, if you remove the tag, would that not make the result a different pointer? They won't compare equal after all.
Either way, I can clarify for sure if it's not self evident.

@Diggsey
Copy link
Contributor

Diggsey commented Sep 29, 2024

Why can't this be done by the backend?

ie. I write my code as *(ptr & mask), and then the backend optimizes that to *ptr if it's known that the CPU automatically ignores the bits that were masked out? (This has the obvious benefit that the code is automatically portable to other architectures without that feature...)

@matthieu-m
Copy link

To be able to do this we need a helper method that simulates a "realloc" from the untagged address to the tagged address to make the memory model happy.

Is simulating a realloc correct, though?

Consider the following code:

void *original = /*...*/;

void *copy = original;

void *tagged = realloc(original, ...);

Now, according to the semantics of realloc, it is guaranteed that tagged has no alias, and indeed this is the reason why at LLVM level the noalias attribute is specified.

However, if my understanding of TBI is correct, this is not what happens here.

Specifically, copy and tagged are alias of each others! And if codegen assumes that update through copy do not modify what tagged points to (or vice-versa), we'll have Undefined Behavior.

Am I misunderstanding TBI or noalias?

@programmerjake
Copy link
Member

To be able to do this we need a helper method that simulates a "realloc" from the untagged address to the tagged address to make the memory model happy.

Is simulating a realloc correct, though?

Consider the following code:

void *original = /*...*/;

void *copy = original;

void *tagged = realloc(original, ...);

Now, according to the semantics of realloc, it is guaranteed that tagged has no alias, and indeed this is the reason why at LLVM level the noalias attribute is specified.

However, if my understanding of TBI is correct, this is not what happens here.

Specifically, copy and tagged are alias of each others! And if codegen assumes that update through copy do not modify what tagged points to (or vice-versa), we'll have Undefined Behavior.

you have UB if you try to do any accesses through original or anything derived from it, a realloc essentially marks original as deallocated memory inside the compiler. so it is still noalias since after the realloc, the only valid pointer is tagged, even though you're just changing the pointer tag.

@RalfJung
Copy link
Member

RalfJung commented Oct 5, 2024

@matthieu-m this model definitely makes some code UB that would be correct when using TBI in an assembly program. However, we have to impose some restrictions to make TBI compatible with higher-level language models such as Rust (and the same goes for C and C++). realloc is the best plan we came up with so far -- and yes, this means that after choosing a new tag, all previous pointers to this memory are now invalid. Including the ones that used the same tag! This operation returns a fresh provenance, and all future accesses must be done with pointers that are derived from the pointer returned by with_tag.

@matthieu-m
Copy link

The discrepancy caused by LLVM (and Rust) not understanding the concept of TBI is fairly unfortunate.

I think it should be noted in Future Possibilities that the choice of using a realloc-like method for now is future-compatible with LLVM and Rust gaining an understanding that only the bottom 56 bits of the pointer matter, and that when they do the constraints could be relaxed -- if we so wish -- to allow original & copy to still be valid (and aliased).

That is, while overly restrictive today, the drawback of the selected model is not painting us into a corner as far as I can see.

@Diggsey
Copy link
Contributor

Diggsey commented Oct 5, 2024

realloc is the best plan we came up with so far

Why is this better than explicitly masking off the bits and then having that mask be optimized away?

@RalfJung
Copy link
Member

RalfJung commented Oct 5, 2024

gaining an understanding that only the bottom 56 bits of the pointer matter,

Well, sometimes they get ignored, and sometimes all bits matter. This seems highly non-trivial, but I am not an expert on the relevant LLVM passes.

Why is this better than explicitly masking off the bits and then having that mask be optimized away?

That also sounds like an option, if LLVM supports it.

@Diggsey
Copy link
Contributor

Diggsey commented Oct 5, 2024

That also sounds like an option, if LLVM supports it.

It seems like LLVM knows about it, but doesn't currently have a pass that optimizes for it:
https://github.com/search?q=repo%3Allvm%2Fllvm-project%20UseAddressTopByteIgnored&type=code

Seems like it would make more sense to add this functionality to LLVM rather than Rust though.

@mrkajetanp mrkajetanp marked this pull request as draft October 10, 2024 11:19
@mrkajetanp
Copy link
Author

(This RFC could have benefited from a pre-RFC phase, posting it on the forum to get some feedback to ensure that it has all the expected details.)

Indeed, I should have at least marked it as draft from the get-go, or started with the forum as you suggest. This was intended as a conversation starter, it's by no means a ready proposal. I'm fully expecting to re-write this with more information, just want to get some outside opinions and fresh eyes on the direction first.

@mrkajetanp
Copy link
Author

Why can't this be done by the backend?

ie. I write my code as *(ptr & mask), and then the backend optimizes that to *ptr if it's known that the CPU automatically ignores the bits that were masked out? (This has the obvious benefit that the code is automatically portable to other architectures without that feature...)

That does sound like something that could be a useful LLVM pass, especially for compatibility with different platforms.
But I think that's a different aspect from the use-case that this PR is meant to support. What we want here is a "Rust-way" to do the following:

let addr = &value as *const _ as usize;
let tag = 60;
let tagged_addr = addr | (tag << 56);
let ptr = tagged_addr as *const i32;
let val = unsafe { *ptr };

The snippet above will currently compile & work "fine" on a TBI system, except that Miri will rightly complain that the code has UB. The end goal of this proposal is to create an interface for top-byte tagging that does not break the memory model. This is separate from making those always safe to dereference, for which the LLVM pass would be helpful.

@Diggsey
Copy link
Contributor

Diggsey commented Oct 10, 2024

@mrkajetanp

But I think that's a different aspect from the use-case that this PR is meant to support.

It's not different. Methods already exist to do what you are trying to do:

fn mask_addr(addr: usize) -> usize {
    addr & 0xFFFFFFFFFFFFFF
}
let tag = 60;
let tagged_ptr = (&value as *const _).map_addr(|addr| addr | (tag << 56));
let val = unsafe { *tagged_ptr.map_addr(mask_addr) };

This is completely sound under MIRI and doesn't require any extensions to the Rust abstract machine. The only bit that's missing is a compiler optimization that erases the .map_addr(mask_addr) on platforms where that is a no-op.

@mrkajetanp
Copy link
Author

This is completely sound under MIRI and doesn't require any extensions to the Rust abstract machine

If I'm understanding what you're suggesting correctly, under this model the users would need to write out ptr.map_addr(mask_addr) for every single pointer access, no? Because without that even on a platform that ignores those bits Miri will complain as within the memory model ptr is currently pointing outside of its allocation.
If this was done inside some allocator wrapper (as it is currently being used in Android for instance) then every single pointer returned to the user would be UB to access unless the user explicitly masked those bits out. Surely that can't be a good approach?

@mrkajetanp
Copy link
Author

@RalfJung Should I then re-write this based on the already received comments and then post on Rust Internals?

@Diggsey
Copy link
Contributor

Diggsey commented Oct 10, 2024

If this was done inside some allocator wrapper (as it is currently being used in Android for instance) then every single pointer returned to the user would be UB to access unless the user explicitly masked those bits out.

Fair enough - the proposed API seems a bit too high level for this allocator use-case though? Wouldn't the primitive operation be something like "realloc" but where you specify the target address? And there doesn't need to be an explicit tag() method, since you can always safely access the address of a pointer.

@mrkajetanp
Copy link
Author

Wouldn't the primitive operation be something like "realloc" but where you specify the target address?

It certainly could be if that's the community consensus, I don't have very strong views on what the exact API should look like - my intention when posting this was to get opinions on that exact question. Next time around I'll go through Internals first, I suppose I took the request for comments term a bit too literally for how it's used here :)

And there doesn't need to be an explicit tag() method

True as well, the intent there is just for convenience. Because different architectures can use different bits for the tagging it'd make sense to have a corresponding tag() method just so that the user can set and retrieve tags without having to write code for a specific architecture.
If we just want to support something like realloc(target_addr) but for tagging then the explicit tag() method is not needed as we're leaving it up to the user to work out the specific bits to get and set anyway.

@RalfJung
Copy link
Member

I also think a lower-level API that focuses on the realloc-like operation is better, but I am not a t-libs-api member. I also sadly don't have the capacity to be much further involved in this. I think I gave some good starting points for how the RFC could be improved, and clarified what we generally expect from RFCs. Posting an improved version to IRLO sounds like a good plan. :)

@Diggsey
Copy link
Contributor

Diggsey commented Oct 10, 2024

The realloc approach would also support cases where virtual memory mappings are used for a similar purpose on platforms without hardware support for pointer tagging (ie. where you map the same physical memory to two or more virtual address ranges).

@RalfJung
Copy link
Member

The realloc approach would also support cases where virtual memory mappings are used for a similar purpose on platforms without hardware support for pointer tagging (ie. where you map the same physical memory to two or more virtual address ranges).

I don't think Rust will have standard APIs for manipulating page tables? ;)

I was going to say, I don't think this is ready yet for a portable API. It makes little sense to try and sketch a portable API that has exactly one target implementation. The RFC should focus in providing APIs for platform-specific capabilities, e.g. in core::arch. A portable API can be experimented with as a user crate, since some experimentation will be required before it becomes clear what a good API looks like.

@programmerjake
Copy link
Member

I don't think Rust will have standard APIs for manipulating page tables? ;)

that doesn't matter if you can still use mmap or similar to make two mappings for the same piece of memory -- it would be nice if rust can handle that case.

This is kinda similar to how Rust doesn't have a std thread API on some targets (because they're #![no_std]), but Rust still needs to properly handle running code in different threads that were started by some mechanism outside of the Rust standard library (unless the target is specifically single-threaded only, such as wasm32-unknown-unknown)

@RalfJung
Copy link
Member

that doesn't matter if you can still use mmap or similar to make two mappings for the same piece of memory -- it would be nice if rust can handle that case.

mmap is an opaque operation to Rust. If you use it to relocate an allocation, you can already treat it like a realloc. You just have to make sure that you stop using the old pointer after the realloc, and instead use the one returned by mmap.

@programmerjake
Copy link
Member

programmerjake commented Oct 11, 2024

that doesn't matter if you can still use mmap or similar to make two mappings for the same piece of memory -- it would be nice if rust can handle that case.

mmap is an opaque operation to Rust. If you use it to relocate an allocation, you can already treat it like a realloc. You just have to make sure that you stop using the old pointer after the realloc, and instead use the one returned by mmap.

i meant that you'd mmap the exact same memory to two locations and then use the realloc intrinsic to access both of them without any further mmap calls needed:
https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gist=27b03de9ba18d73a1e60badc1e5b3267

@RalfJung
Copy link
Member

RalfJung commented Jan 7, 2025

From an opsem perspective this is coherent, as far as I can tell. @rust-lang/opsem please chime in if you have any comments.

Nominating for @rust-lang/lang to get some idea of their thoughts on this language extension.

@RalfJung RalfJung added the I-lang-nominated Indicates that an issue has been nominated for prioritizing at the next lang team meeting. label Jan 7, 2025
@traviscross
Copy link
Contributor

Nominating for @rust-lang/lang to get some idea of their thoughts on this language extension.

@rustbot labels -I-lang-nominated +I-lang-radar

We discussed this in the lang call today with 2/5 present (and with @Amanieu). We generally felt positively about this. We understood and agreed with the use case.

On the name, while there was some initial skepticism of "move" being in there, it ended up growing on everyone after considering how this is tied in with making a guarantee about (the lack of) aliasing. Separately, we agreed with RalfJ's reservations about "assume", given the signature.

@rustbot rustbot added I-lang-radar Items that are on lang's radar and will need eventual work or consideration. and removed I-lang-nominated Indicates that an issue has been nominated for prioritizing at the next lang team meeting. labels May 8, 2025
@mrkajetanp
Copy link
Author

We discussed this in the lang call today with 2/5 present (and with @Amanieu). We generally felt positively about this. We understood and agreed with the use case.

Thank you for the update! Is there anything I should do on that account, or are we giving it more time before taking any specific steps?

@traviscross
Copy link
Contributor

We didn't have any specific asks. We'll probably just need to have a closer look, and maybe bikeshed the name a bit, before proposing FCP.

Comment on lines 236 to 241
What is the best way to make this compatible with Strict Provenance? We want to be able to create a
pointer with an arbitrary address, detached from any existing pointers and with a brand-new
provenance. From the LLVM side this can be handled through generating `inttoptr` which does not have
the same aliasing restrictions as `getelementptr` alongside annotating the function return value as
`noalias` which can be done with the aforementioned new built-in attribute. Is this enough for it to
fit within the Strict Provenance framework? If not, how can we make it fit?
Copy link
Contributor

Choose a reason for hiding this comment

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

@RalfJung, what are your thoughts on this? Do we need to leave this as an open question, or do we have any answers here? It would seem a substantial open question to leave.

Copy link
Member

Choose a reason for hiding this comment

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

I can't follow the arguments in the text as all -- what it proposes for LLVM makes little sense to me. I'd like to hear from @nikic what the best way would be to model this for LLVM; my proposal would be an inline asm block.

From the Rust side, we can just say that this is a realloc with a chosen address (and it is UB if that would make the allocation overlap with some other allocation). I don't know which potential problems with strict provenance the RFC refers to.

Copy link
Author

Choose a reason for hiding this comment

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

Which part does not make sense? The implementation needs to do 3 things:

  1. Create the pointer
  2. "Launder" it through a black box of some kind to prevent incorrect optimisations
  3. Make the pointer be treated as if it were returned from an allocator function

For which the solutions I'm suggesting are:

  1. inttoptr, because getelementptr is UB in this scenario
  2. inline asm block
  3. noalias return annotation

We need all 3 of the above for this to actually behave as described. At the very least I've not seen a different way of doing it.

Copy link
Member

Choose a reason for hiding this comment

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

See the other discussions about noalias -- you have fundamentally misunderstood what that attribute does.

Regarding getelementptr, why is that UB? getelementptr inbounds might be UB (strictly speaking: it generates poison), but getelementptr is never UB.

Copy link
Author

Choose a reason for hiding this comment

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

What I'm referring to is this section of the documentation.

Also, GEP carries additional pointer aliasing rules. It’s invalid to take a GEP from one object, address into a different separately allocated object, and dereference it. IR producers (front-ends) must follow this rule, and consumers (optimizers, specifically alias analysis) benefit from being able to rely on it. See the Rules section for more information.

This may just be being overly cautious as well, and using GEP might still work regardless of this. But I see no reason to do so. The thing we're addressing into is the same allocated object on the hardware, but has a different allocation address so maybe it could be seen as a "different separately allocated object". I think it's easier to just use inttoptr and sidestep this entirely.

Copy link
Member

@RalfJung RalfJung Jun 12, 2025

Choose a reason for hiding this comment

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

It’s invalid to take a GEP from one object, address into a different separately allocated object, and dereference it.

Emphasis mine. You're not dereferencing the result of the GEP. You're passing it into the laundromat. There's only two options now:

  • laundry worked, LLVM lost track of the pointer, there's no problem
  • laundry failed, LLVM recognizes this is the old pointer and everything is broken no matter how many inttoptr you add.

I think it's easier to just use inttoptr and sidestep this entirely.

I think it is bad to be cautious without an understanding of what we'd be cautioning against. And once you dig into it and understand what happens here, it's just pointless and confusing.

Furthermore, inttoptr is the topic of so many issues in LLVM, I would argue we are more cautious by not using it.

Copy link
Member

Choose a reason for hiding this comment

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

Generally it seems like this RFC is in need of a pass by someone knowledgeable about LLVM. Sadly I won't have the time to make such a pass any time soon, sorry.

Copy link
Author

Choose a reason for hiding this comment

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

True enough, the asm block should still be able to make it work regardless. Is there some specific reason why we would want to use GEP for this? Even just on the face of what the operation is for, a basic pointer cast seems better suited for this than an operation for indexing into arrays and structures.

Copy link
Member

@RalfJung RalfJung Jun 12, 2025

Choose a reason for hiding this comment

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

@traviscross
Copy link
Contributor

I've now had the opportunity to review this thoroughly. It's well written and well motivated. Thanks both to the author for that and to @RalfJung, who asked a lot of the right questions above to sharpen this up.

As discussed above, I'd like us to pare down the unanswered questions to the degree possible. Personally, I'm happy enough with the name assume_moved as proposed. And to the degree that we could speak more now about the interaction of this with strict provenance, I think that would be good to do in terms of making a complete and self-contained proposal here.

After those points are addressed, I'm planning to propose FCP merge on this RFC.

@traviscross traviscross changed the title Add core::ptr::assume_moved Add core::ptr::assume_moved May 25, 2025
@traviscross
Copy link
Contributor

@rustbot author

@rustbot

This comment was marked as resolved.

@traviscross traviscross added the S-waiting-on-author Status: This is awaiting some action from the author. label May 25, 2025
@mrkajetanp
Copy link
Author

As for the strict provenance questions, I was hoping for input from people involved with that proposal who could say whether this fits into it as-is or needs some specific accommodations. I am not aware of any specific problems, but I've not seen all the discussions relevant to said proposal.

@mrkajetanp
Copy link
Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: This is awaiting some action from the reviewer. and removed S-waiting-on-author Status: This is awaiting some action from the author. labels Jun 12, 2025
@RalfJung
Copy link
Member

I see not much interaction between strict provenance and this operation. If anything, it could be framed as an extension of our strict provenance APIs to also support "switching" between multiple different physical mirror images of the same underlying data.

Remember, strict provenance is just an API. I am not sure what exactly you mean, @traviscross, when you ask about the interaction of this proposal and strict provenance. Do you imagine some way of calling first a strict provenance function and then this new operation that would then somehow lead to issues? Do you have any concrete problematic examples?

We can avoid this issue by assuming that a move from the untagged address to the tagged address has
happened. To do so, we need the helper function to return a pointer with a brand new provenance,
disjoint from the provenance of the original pointer. This can be achieved by the combination of
using `inttoptr` and an inline asm block.
Copy link
Member

Choose a reason for hiding this comment

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

Attaching a comment to this to record the concern that using inttoptr here makes no sense IMO. Also see the discussion here.

Copy link
Member

@RalfJung RalfJung Jun 12, 2025

Choose a reason for hiding this comment

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

That said -- why does the RFC even go into that much detail? The LLVM IR we emit is an implementation detail! The RFC should concern itself with the user-visible behavior and guarantees.

Given the nature of this operation, maybe it should have a section on "implementation considerations" or so. But then it should actually spell out the LLVM IR, the description here is too vague to give me confidence that you and me have the same IR in our minds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
I-lang-radar Items that are on lang's radar and will need eventual work or consideration. S-waiting-on-review Status: This is awaiting some action from the reviewer. T-lang Relevant to the language team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC. T-opsem Relevant to the operational semantics team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.