-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Miri engine: stronger type-based sanity check for assignments #70532
Conversation
@@ -869,10 +881,10 @@ where | |||
// We do NOT compare the types for equality, because well-typed code can | |||
// actually "transmute" `&mut T` to `&T` in an assignment without a cast. | |||
assert!( | |||
src.layout.layout == dest.layout.layout, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there remaining layout.layout
in miri? Since that was what we were looking at.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's one:
rust/src/librustc_mir/interpret/operand.rs
Line 219 in 8045865
layout.layout, layout2.layout, |
That is verifying that the given layout matches the one we would have computed, so I think here comparing layouts for equality actually makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like that's all "destination layout" type stuff? i.e. where you might have a layout from knowing where a value might be written?
I think it should be better named/described as such, and should also use a type check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be better named/described as such
I mean the function is called from_known_layout
and documented as
// Use the existing layout if given (but sanity check in debug mode),
// or compute the layout.
That seems pretty clear?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and should also use a type check.
In fact I wonder why this does not just compare the types... let me try that (but it might have the same potential mismatches as assignments).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"existing layout" doesn't explain that it's a destination layout, whereas the one that might be computed or compared against is a source one.
If we end up doing subtyping checks, the direction matters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the callers here uniformly make one source and one destination.
The check we do here should be symmetric. And indeed the actual relation we care about is not subtyping, it is layout compatibility -- and that relation is symmetric. Subtyping is just an approximation of that (and it needs more properties, which makes it asymmetric).
I would find it highly suspicious if mir_assign_valid_types
ended up being asymmetric.
That CI failure is interesting, it requires actually building the testcase, not just checking it... then we get the following error:
Looks like we need to also blanket-accept functions with different signatures? All function pointers have the same layout, so that should be fine. |
Oh, dear, higher-ranked subtyping. I forgot we do that (cc @nikomatsakis). Keep in mind you can wrap that I wonder if there's a way to erase bound lifetimes like that, deeply, which is generally unsound (e.g. they might be inside projections, which would change the resulting type), but it would make comparison without |
This comment has been minimized.
This comment has been minimized.
Tests seem to pass when I just add Maybe comparing the layout was not such a silly idea after all?^^ |
There's presumably no tests for those same types wrapped in tuples,
Just because it's much much shallower, so it's barely a sanity check at all. |
So you are saying this could happen with arbitrarily complex types, not just |
Yes, assignment (or passing values to functions) is asymmetric in Rust, there's a subtyping relationship, it implicitly "converts" types, deeply (as deep as variance lets it). I wish this was an explicit coercion in MIR, but then again you see what happened with |
For passing values to functions, Miri is fine -- it uses a transmute-safe copy there, because caller and callee signature might differ. Re: subtyping, I agree the type system check should be asymmetric like it is. But the run-time check in Miri has no good reason to be asymmetric, IMO. At type checking time we are converting types aka sets of values; at run-time we are converting an individual value; the former have an asymmetric relation but the latter a symmetric one. |
// This does not affect reference layout, so that is fine. | ||
src_pointee == dest_pointee | ||
} | ||
(ty::FnPtr(_), ty::FnPtr(_)) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could erase regions and try comparing the result again.
Also I do wonder about what @eddyb said, so we may end up in the false
arm if the assignment is between two instances of struct Foo<T>(T);
where T
is used for two function pointers that only differ between their HKL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't trivially erase lifetimes bound by higher-ranked types (I don't think HKL means anything, there is no 'Foo<'a, 'b>
).
I should come up with an example showing lifetime depending on the shape of bound lifetimes.
Oh that was trivial, this example shows different sizes based on lifetimes (playground): trait Trait {
type Assoc;
}
struct Foo<T: Trait>(T::Assoc);
impl Trait for fn(&'static ()) {
type Assoc = u8;
}
impl Trait for for<'a> fn(&'a ()) {
type Assoc = u16;
}
fn main() {
use std::mem::size_of;
dbg!(size_of::<Foo<fn(&'static ())>>());
dbg!(size_of::<Foo<for<'b> fn(&'b ())>>());
} |
This comment has been minimized.
This comment has been minimized.
Well yeah, with associated types that is not too surprising. The one place where this layout equality check was not just a sanity check, but crucial, is So, as far as I can tell, the old code her isn't wrong, even though the sanity check (for the cases where it really just is a sanity check) was pretty weak. The new sanity check, as you say, is wrong (but for However, given this function type problem, is there even a reasonable type-based way to correctly implement the |
You could do a deep erasure and gate it on variance. Because if there's anything involving associated types, the relevant parameters are marked as invariant (e.g.
EDIT: @nikomatsakis has just informed me that we already anonymize late-bound regions when erasing all the other ones:
Most of the tools are already there, there's just no single Actually, I wonder if codegen (and const-eval and layout, all of which use the regular lifetime-erasing functionality) should do something similar, might solve the problems we've seen and papered over (it would require though that nothing that could potentially reach the trait system is fully erased, only anonymized). cc @rust-lang/compiler |
This comment has been minimized.
This comment has been minimized.
sigh these import changes are a huge pain to rebase (and rustfmt makes it worse and it makes the diffs bigger). This is the second time in 4 days. Since it doesn't seem like we are actually going to solve this problem here, I am going to turn this PR into a mere refactoring without functional changes. |
Thanks, the If we end up doing subtyping-aware erasure, we should do it uniformly so codegen benefits as well. @bors r+ |
📌 Commit 343b3f0 has been approved by |
Rollup of 5 pull requests Successful merges: - rust-lang#68334 (AArch64 bare-metal targets: Build rust-std) - rust-lang#70224 (Clean up rustdoc js testers) - rust-lang#70532 (Miri engine: stronger type-based sanity check for assignments) - rust-lang#70698 (bootstrap: add `--json-output` for rust-analyzer) - rust-lang#70715 (Fix typo in operands section) Failed merges: r? @ghost
r? @oli-obk @eddyb
Fixes #70405
That issue says
I decided not to do that because I see no good reason to do it. The engine does not care either way, the assignment will happen correctly.