-
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
change handling of subtyping to be less fragile after analysis #112651
Comments
When implementing this, also handle all the |
@rustbot claim |
Make subtyping explicit in MIR This adds new mir-opt that pushes new `ProjectionElem` called `ProjectionElem::Subtype(T)` to `Rvalue` of a subtyped assignment so we can unsoundness issues like rust-lang#107205 Addresses rust-lang#112651 r? `@lcnr`
Make subtyping explicit in MIR This adds new mir-opt that pushes new `ProjectionElem` called `ProjectionElem::Subtype(T)` to `Rvalue` of a subtyped assignment so we can unsoundness issues like rust-lang#107205 Addresses rust-lang#112651 r? `@lcnr`
Make subtyping explicit in MIR This adds new mir-opt that pushes new `ProjectionElem` called `ProjectionElem::Subtype(T)` to `Rvalue` of a subtyped assignment so we can unsoundness issues like rust-lang/rust#107205 Addresses rust-lang/rust#112651 r? `@lcnr`
Make subtyping explicit in MIR This adds new mir-opt that pushes new `ProjectionElem` called `ProjectionElem::Subtype(T)` to `Rvalue` of a subtyped assignment so we can unsoundness issues like rust-lang/rust#107205 Addresses rust-lang/rust#112651 r? `@lcnr`
Make subtyping explicit in MIR This adds new mir-opt that pushes new `ProjectionElem` called `ProjectionElem::Subtype(T)` to `Rvalue` of a subtyped assignment so we can unsoundness issues like rust-lang/rust#107205 Addresses rust-lang/rust#112651 r? `@lcnr`
Should this be closed now that #115025 has been merged? edit: I guess there are still some FIXMEs referencing this issue |
we encountered an issue with function pointers: fn main() {
let x: fn(fn(&'static ())) = some_function;
let y: fn(for<'a> fn(&'a ())) = x;
y(|&()| ());
} here we can make subtyping inside of main explicit, resulting in something like the following fn main() {
let x: fn(fn(&'static ())) = some_function;
let y: fn(for<'a> fn(&'a ())) = x as fn(fn(&'static ()));
y(|&()| ());
} however, when calling As this means that subtyping has to still be implicitly handled somewhere, I believe that it's safer to always implicitly handle this. I believe that it's otherwise easier to accidentally rely on subtyping being explicit, resulting in bugs which are even harder to diagnose. |
What is the issue with that? That subtyping only arises dynamically; as far as the type system is concerned, we don't know what |
yes, this is totally fine and even expected. However, we wanted to make subtyping explicit to prevent bugs like #107205 going forward: While subtyping does not change the layout of a type, it does change trait selection. This broke stuff in two ways: Optimizations, e.g. const prop, but also inlining, can incorrectly change let x: subtype = whatever;
let y: supertype = x;
y.requires_trait_selection(); // e.g. by casting it to a trait object to let x: subtype;
x.requires_trait_selection(); We also currently use typed values both in the MIR, e.g. https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/consts/enum.Const.html#variant.Val and during codegen https://doc.rust-lang.org/nightly/nightly-rustc/rustc_codegen_ssa/mir/operand/struct.OperandRef.html (this uses
It can be known by optimizations/ctfe, which can be unsound if they accidentally use the type of the value passed into the function to do trait selection. I think ideally we would not have values which are typed, especially not in codegen, where the actual unsoundness happened. However, it was very difficult to change |
Uh, okay, that sounds bad.
CTFE considers function argument passing to be a transmute. The caller and callee might have completely different but ABI-compatible types. Many things would go wrong loudly if we used the caller type ever inside the callee. Also, in CTFE values are only ephemerally typed, during the execution of a single machine step ( Likewise, The only optimization where type information from the caller could leak to the callee is inlining, I think? |
👍 that's good to know, so for ctfe method calls are not an issue. Happy that CTFE has the imo right way of dealing with values and their types, codegen currently does not '^^
yeah, inlining should be the only one, though if we actually make the "transmute" of the arguments explicit when inlining it could be alright? though ofc now you have to remember to always explicitly transmute when looking into nested functions, which is also easy to mess up 😅 alternatively we could try to change any op which relies on type info to store the type info by itself, without relying on the type of the affected local? |
yeah that'd be like wasm a bit, having each operation carry the types it needs and basically making locals untyped. Not sure if that would be a good fit for MIR though, it seems highly redundant. As you say, codegen just uses a bad structure here. Though you also had to fix some optimizations and I am less sure what you be done structurally to help them avoid this pitfall... But overall if we say that inlining is a transmutation site then the explicit projections you had planned should work, right? And inlining has to be a transmutation site in general when function pointers are involved because we allow some type mismatches as long as things are ABI-compatible.
What do you mean? We don't "look into" nested functions, do we? We just paste the MIR of the callee into the caller. And then if this is a call through a function pointer we need to add transmute. We already need to do that, the caller might use type When the call is a direct call of a fn item, then I don't think there is a possible issue here? Currently we only inline direct calls AFAIK, so the current inliner should be fine. Overall I don't yet see why your plan of making subtyping explicit should be problematic. |
hmm, I am undecided on what to do again 😅 🤔
I think to avoid this pitfall in optimizations we either need subtyping to be explicit or move the type of the op into the op itself 🤔
Quickly looking over the MIR, the typed operations are
I think so The possible solutions here are: explicitly handle subtyping after borrowck:
change ops to not rely on the type of locals:
|
Now I am confused. I thought we'd need types basically everywhere a local shows up. Why only these two, what is special about them? The type of a local is relevant in a lot more places after all. That doesn't sound so bad then.
Right, inlining of function pointers without transmute is already wrong, so this changes nothing here. I wonder what other MIR consumers think. Cc @rust-lang/wg-mir-opt |
subtyping can't change layout, it's only when interacting with the trait system that it makes a difference. Most MIR ops don't call the trait solver, so subtyping doesn't impact them |
Okay, so those two operations are the only ones that involve traits?
"Having more subtyping locations" seems more likely than "having more things depend on traits". And generally it seems preferable to have analysis MIR and optimization/runtime MIR be as close to each other as possible. So I guess I am now also leaning slightly (very slightly) towards implicit subtyping with explicit types where it matters. |
The only time that subtyping can change trait solver results is higher ranked subtyping (e.g. |
jup ✨ |
edit: no longer up-to date. the current plan is to add the explicitly add the source type to MIR casts instead
We should add a MIR pass after analysis (and before any optimizations) which makes subtyping explicit in the MIR. This is a robust way to fix #107205, preventing similar issues in the future. I don't quite know how to do that, probably by adding an explicit
ProjectionElem::Cast
(probably reusingOpaqueCast
for that).The text was updated successfully, but these errors were encountered: