-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Figure out the interaction of GVN and function/vtable pointers #123670
Comments
|
So there's no impact on Apart from unnamed_addr we also have our own multiple-codegen-unit issue, where the same function can have more than one address. That could be considered as making a choice at fn-item-to-fn-ptr coercion time. The possible concern here is having the AllocId we generate there move into different codegen units or crates so they don't end up being the same address. But that's already the case I think? |
Cc @oli-obk for the last question |
The icmp may evaluate to either true or false, but it will do so consistently. That is, for unnamed_addr symbols, LLVM will not fold "icmp eq s1, s2" to false in cases it normally could, in case s1 and s2 end up being merged later. |
it is evaluated per-crate. For constants we only cache within the current crate.
yea, that's why it doesn't matter, each crate generates its own
what integer values? |
@nikic thanks!
If we have But OTOH
The underlying address, after compilation. So probably the GVN issue is about something like const C: fn() = myfunc;
let x = C;
let y = x; // definitely the same address as x becoming const C: fn() = myfunc;
let x = C;
let y = C; // maybe a different address than x? I think we want to be sure that |
It's already true if LLVM makes it true. We swap the alloc id for its Instance in codegen. So whatever happens there is relevant. To also deduplicate AllocIds, we need to remove
|
@RalfJung this is more than a concern, I saw it surface as an actual bug from // crate A, optimized
fn foo() {}
static MARKER: fn() = foo;
#[inline]
fn bar(x: fn()) -> bool {
x == MARKER
}
// crate B, no optimization
fn baz() -> bool {
bar(A::MARKER)
} The original MIR is something like: // CGU-local copy, for LLVM to inline
fn bar(_1: fn()) -> bool {
_2 = const { alloc0 as &fn() }; // the address for the static MARKER
_3 = *_2; // the fn pointer inside MARKER
_0 = Eq(_1, _3)
}
fn baz() -> bool {
_2 = const { alloc0 as &fn() }; // stable cross-crate, so it's ok
_3 = *_2;
_0 = bar(_3)
} All is well. Now, coming to the issue. If we propagate values that contain provenance, we may get to codegen this MIR: // CGU-local copy, for LLVM to inline
fn bar() -> bool {
_3 = const { foo as fn() }; //-> point to a CGU-local copy
_0 = Eq(_1, _3)
}
fn baz() -> bool {
_2 = const { alloc0 as &fn() }; // the address for the static MARKER
_3 = *_2; //-> points to the imported instance from crate A
_0 = bar(_1, _3)
} We have the bug: we now compare the addresses of 2 different copies of |
So the problem is that the same const value results in different final bit patterns when used in different CGUs. These values aren't actually values, there is further non-determinism when they get evaluated to their final form. That's pretty bad. I am surprised this only becomes a problem with optimizations. FWIW one reasonable alternative here may be to say that GVN should never read from a static. With |
When would doing that not be sufficient? const C: fn() = ...;
let x = C;
return (x, x); Now if this is turned into However it seems like we already don't guarantee that this function will return a constant usize: const C: fn() = ...;
fn make() -> usize { C as *const () as usize } So... I don't see how whatever happens in MIR optimizations can make it any worse. Therefore it seems to me the best way forward is to establish a specific guarantee for Or we find a way to fix the insanity that is const values actually not being values... that should be possible; after all const values can only refer to fully monomorphic items so we "just" have to make sure that all uses of the const value refer to the same instantiation of that monomorphic item. For |
Another way to put this is that #116564 is incomplete. @oli-obk made it so that we store the "value" of the static rather than its MIR, so that we compute a single canonical value. The problem is that it turns out "values" aren't actually values, this is not canonical enough -- codegen will re-monomorphize what the "value" needs each time the value shows up in a CGU. Instead static should store a "value" that is truly the final value, something that always becomes the same final bits, something where all the functions (and vtables) mentioned in the Is that possible? |
IMO that code is buggy, it should use a Functions that can be inlined simply don't have a stable address, even if you store them in a |
Turns out we have an issue for that, at least for the case of function pointers: #84581. |
Add test for fn pointer duplication. I managed to make it fail when removing provenance checks in GVN. cc rust-lang#123670 r? `@oli-obk`
Add test for fn pointer duplication. I managed to make it fail when removing provenance checks in GVN. cc rust-lang#123670 r? ``@oli-obk``
Add test for fn pointer duplication. I managed to make it fail when removing provenance checks in GVN. cc rust-lang#123670 r? ```@oli-obk```
Add test for fn pointer duplication. I managed to make it fail when removing provenance checks in GVN. cc rust-lang#123670 r? ````@oli-obk````
Add test for fn pointer duplication. I managed to make it fail when removing provenance checks in GVN. cc rust-lang#123670 r? `````@oli-obk`````
Add test for fn pointer duplication. I managed to make it fail when removing provenance checks in GVN. cc rust-lang#123670 r? ``````@oli-obk``````
Rollup merge of rust-lang#123714 - cjgillot:static-fnptr, r=wesleywiser Add test for fn pointer duplication. I managed to make it fail when removing provenance checks in GVN. cc rust-lang#123670 r? ``````@oli-obk``````
Add test for fn pointer duplication. I managed to make it fail when removing provenance checks in GVN. cc rust-lang/rust#123670 r? ``````@oli-obk``````
I think we should fix that, which might just resolve all the GVN issues. I opened #128775 to discuss the possible options here. |
GVN has special treatment for function/vtable pointers. I haven't fully understood why.
Function pointers have unstable comparison. We set the
unnamed_addr
flag in LLVM. I am not entirely sure how far that attribute goes, but I think it means that==
involving such functions is basically non-deterministic? @nikic @bjorn3 maybe you can help here -- can the result of comparing function pointers depend on whether or not optimizations are applied? Or how exactlyunnamed_addr
affect the generated code?@cjgillot could you give an example for the code you are concerned about, where GVN would go wrong if it did not special-case function pointers? I think you gave an example a while ago but I can't find it right now.
The text was updated successfully, but these errors were encountered: