-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Closed
Labels
A-const-evalArea: Constant evaluation, covers all const contexts (static, const fn, ...)Area: Constant evaluation, covers all const contexts (static, const fn, ...)E-easyCall for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue.Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue.E-needs-testCall for participation: An issue has been fixed and does not reproduce, but no test has been added.Call for participation: An issue has been fixed and does not reproduce, but no test has been added.
Description
Some people were of the opinion that this shouldn't work, but it currently does. playground:
#![feature(atomic_integers, const_fn)]
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
const unsafe fn transmute(x: *mut u8) -> usize {
union T {
a: *mut u8,
b: usize
}
T { a: x }.b
}
const BAR: *mut u8 = ((|| 3) as fn() -> i32) as *mut u8;
const FOO: AtomicUsize = AtomicUsize::new(unsafe { transmute(BAR) });
// static FOO: AtomicUsize = AtomicUsize::new(unsafe { transmute(BAR) }); // ALSO OK
// const BAZ: AtomicUsize = AtomicUsize::new(BAR as usize); ERRORs
fn main() {
let l = FOO.load(Ordering::Relaxed);
let l: fn() -> i32 = unsafe { std::mem::transmute(l) };
assert_eq!(l(), 3);
}
Metadata
Metadata
Assignees
Labels
A-const-evalArea: Constant evaluation, covers all const contexts (static, const fn, ...)Area: Constant evaluation, covers all const contexts (static, const fn, ...)E-easyCall for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue.Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue.E-needs-testCall for participation: An issue has been fixed and does not reproduce, but no test has been added.Call for participation: An issue has been fixed and does not reproduce, but no test has been added.
Type
Projects
Milestone
Relationships
Development
Select code repository
Activity
[-]Storing a function pointer in a static usize works on nightly[/-][+]Storing a function pointer in a const usize works on nightly[/+]gnzlbg commentedon Jun 14, 2018
cc @RalfJung
oli-obk commentedon Jun 14, 2018
It should work,it's just super scary.EDIT: it should not and does not work. If we allowed this, various surprising things can happen, especially around implicit promotion. If you want to store pointers and integers, you can use a raw pointer.
scottmcm commentedon Jun 15, 2018
For my own education, can you elaborate on why function pointers are ok? I'd thought that the runtime addresses of things -- including functions -- shouldn't be accessible at const-time since we don't know where they'll live at runtime. Could I make a const array that usize long?
oli-obk commentedon Jun 15, 2018
When compiling to LLVM, the real function handle or pointer address is obtained. If you try doing anything weird with pointers at compile time, you will quickly notice that you are not at runtime. For example, you cannot divide such a usize by anything. You can add or subtract integers, because that's just pointer offsetting, but you can't inspect the address in any way.
This also means that no, you cannot use this usize for array lengths or enum discriminants. This is due to the fact that miri pointers are not just addresses, but abstract pointers that are in a separate layer from the bytes of normal memory like the one of integers.
scottmcm commentedon Jun 15, 2018
Thanks for the explanation.
I guess that means that such a
usize
couldn't be passed to a const generic either?gnzlbg commentedon Jun 15, 2018
The thing is const generics don't know where the usize comes from, so they could try to do something with it that is not allowed if it is just a pointer address. Until monomorphization we can't know if there is an issue.
Note that converting a pointer to an usize and back just to be able to put it in const, statics, or use it with const generics, is a real pain. We would be better off in this case with an
AtomicPtr<T>
type, such that I can writeAtomicPtr<fn()->i32>
and avoid the conversion to usize.For const generics, C++ accepts function pointers at the type level and that works just fine with clang, so I expect
fn bar<const PTR: fn()->i32>() {}
to work fine as well.hanna-kruppe commentedon Jun 15, 2018
How would
AtomicPtr<T>
work?AtomicPtr<i32>
disallowed butAtomicPtr<*const i32>
andAtomicPtr<fn(&str) -> &str>
work? that seems very weird and special cased (especially for fn types that involve higher rank lifetimes, as in my example)AtomicPtr<T>
~=*mut T
? thenAtomicPtr<fn()>
is a double indirection and to use it you need to worry about managing the memory it points to (which contains thefn()
)gnzlbg commentedon Jun 15, 2018
I haven't thought this through beyond having used the atomic pointers in the C++ standard library: http://en.cppreference.com/w/cpp/atomic/atomic
One way to implement this could be
AtomicPtr<T> where T: AtomicPtrConstraint
where we provide blanket impls for&'a
,&'a mut
,*const
,*mut
,fn() -> T
,fn(T) -> U
,fn(T, U) -> V
, ... or use some compiler magic to achieve a similar effect...eddyb commentedon Jun 15, 2018
This is a bit offtopic, but regarding
AtomicPtr
, @nikomatsakis mentioned at least once the possibility of introducing some new type, to makefn(A) -> B
sugar for&ThatType<(A,), B>
.I guess it can't entirely be "sugar", because of existing impls, but at least both could work the same.
RalfJung commentedon Jun 19, 2018
Hehe, this is cute. :) And I agree it is working as intended. (Nice that the translation to LLVM actually gets this right, should there be a testcase to make sure it stays that way? :D )
oli-obk commentedon Jun 20, 2018
So... other than testing this, all we need is a PR that adds
*const T
->usize
casts in constants under a feature gate (and makes those casts unsafe in constants andconst fn
)15 remaining items