Tracking issue for #[ffi_pure]
Annotates an extern C function with C pure attribute.
This corresponds to the
readonlyLLVM attribute.Simonas Kazlauskas at 2019-02-09 22:58:21
Is this fully implemented and ready for potential stabilization, or is there any blocker?
Josh Triplett at 2022-06-15 17:35:15
I am not sure but I believe that this and its cousin, #58328, are of interest to the opsem team, as they significantly affect what optimizations the codegen backend is allowed to perform (and declaring them is probably very
unsafe!). cc @rust-lang/opsemJubilee at 2024-05-19 23:36:30
Yes this sounds very unsafe. I assume it works a lot like
asm!annotations? Are the requirements the same as what the Reference says aboutpurethere?Ralf Jung at 2024-05-20 05:52:42
Interestingly, I see LLVM
readonlyis applied to the@llvm.type.checked.load.relativeintrinsic but I don't actually see it defined for functions, only for parameters. I presume it means the same asmemory(read). IIUC, GCC'spureis close to but more permissive than[^1] LLVM'smemory(read, inaccessiblemem: readwrite)and GCC'sconstis close to but more permissive than[^2] LLVM'smemory(none).[^1]: GCC says that “functions declared with the
pureattribute can safely [...] modify the value of objects in a way that does not affect their return value or the observable state of the program.” LLVM'sinaccessiblemem: readwritepermits e.g. encapsulated usage of malloc/free, but I don't know how exactly to interpret GCC's notion of writes that exist but aren't observable.[^2]: GCC says that “functions declared with the [
const] attribute can safely read objects that do not change their return value, such as non-volatile constants.” LLVM doesn't expose a knob for allowing reads of runtime constant memory but not runtime mutable memory.We do already expose a similar feature, in that
asm!blocks can be annotated withoptions(pure, nomem)oroptions(pure, readonly). IIUC, these would correspond to GCCconstandpure, respectively. Even though the purpose of the Rust attribute is to match C headers using__attribute__(), I think it'd be preferable to spell them something like#[pure(nomem)]/#[pure(readonly)]instead, to make the Rust semantic more clear.With the removal of
#[ffi_returns_twice], I don't think we want#[ffi_*]style attributes. If we want this, it should apply just as much without any FFI involved. The one advantage of applying to extern declared functions is that the point ofunsafefor their signature being accurate is calling them. If allowed on fn items, either the attribute would need to similarly defer the safety of the annotation to the caller or be itself unsafe to use.However, I can't come up with a way to define the semantics operationally in a way Miri could check. Even a brute force attempt that retags all globally accessible locations as Frozen and makes the called function use the new tag for global loads isn't sufficient, as a pointer loaded out of a global still has its existing provenance (thus can validly be used for writes).
Well, except for a shim that calls the actual symbol from an
asm!block and uses those options annotations.Crystal Durham at 2024-05-20 06:05:34
hmm, cc @antoyo Do you by any chance have any insight into the question about GCC's semantics for the
pureandconstattributes?Jubilee at 2024-05-20 08:09:34
However, I can't come up with a way to define the semantics operationally in a way Miri could check. Even a brute force attempt that retags all globally accessible locations as Frozen and makes the called function use the new tag for global loads isn't sufficient, as a pointer loaded out of a global still has its existing provenance (thus can validly be used for writes).
I think we can get most of
pure, readonlywith a per-thread flag indicating that this thread is currently in "pure" mode, and rejecting all sorts of operations if the current thread is in "pure" mode -- in particular, writes and fences. I guess we'd have to allow writes to the locals of the stack pure frame itself (and functions it calls), but that should be doable. Something similar could work forpure, nomem.The part that is obviously impossible to check is the requirement that this function must terminate.
Ralf Jung at 2024-05-21 19:26:01
The pure attribute prohibits a function from modifying the state of the program that is observable by means other than inspecting the function’s return value. However, functions declared with the pure attribute can safely read any non-volatile objects, and modify the value of objects in a way that does not affect their return value or the observable state of the program.
Hmm, yeah, I'm also not really sure what that "modify the value of objects in a way that does not affect their return value or the observable state of the program" means, but I think what it's trying to say is "it's still
pureif internally it uses something likeOnceCellto memoize some computations, you're just not allowed to leak that fact".Jubilee at 2024-05-21 20:06:47
What I just said doesn't sound like an easy-to-specify operational semantics, so yeah, "spec what we feel is appropriate and assert it has supremacy over the C attributes, then emit whatever optimization annotations qualify" sounds like the path we have to take if we don't want these to have YOLO opsem.
That does indeed lead to "we probably shouldn't try to force ourselves to reuse their names" conclusions, even if we try to get it very close. It is bad for annotations which can significantly alter program semantics to suffer from the false friend problem.
Jubilee at 2024-05-21 20:22:07