Skip to content
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

Check substs of ConstKind::Unevaluated in generalize #72351

Closed
wants to merge 4 commits into from

Conversation

lcnr
Copy link
Contributor

@lcnr lcnr commented May 19, 2020

This prevents the stack overflow mentioned in #72129 (comment).

While I think this change makes sense, I am not completely certain and there may be a better solution I am missing.

The stack overflow was caused by unifying a type inference variable with an unevaluated const which had exactly this inference variable in it's substitutions. Meaning something has probably already gone wrong when reaching this.

i.e: we previously ended up with

TyVar(_#1t) := [
    ();
    Const { ty: usize, val: Unevaluated(DefId(0:7 ~ issue_69654_run_pass[317d]::{{impl}}[0]::{{constant}}[0]), [TyVar(_#1t)], None) }
]

in the TypeVariableTable.

r? @nikomatsakis @varkor

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 19, 2020
@lcnr lcnr added A-const-generics Area: const generics (parameters and arguments) A-inference Area: Type inference A-lazy-normalization Area: Lazy normalization (tracking issue: #60471) F-const_generics `#![feature(const_generics)]` labels May 19, 2020
@lcnr lcnr force-pushed the stacky-mc-stack-stack branch from fea4757 to 82ac8c6 Compare May 19, 2020 14:47
@lcnr
Copy link
Contributor Author

lcnr commented May 19, 2020

This is not the correct fix here, currently discussing a better solution on zulip: https://rust-lang.zulipchat.com/#narrow/stream/182449-t-compiler.2Fhelp/topic/substs.20of.20Unevaluated.20consts

@lcnr lcnr closed this May 19, 2020
@lcnr lcnr reopened this Jun 23, 2020
@lcnr
Copy link
Contributor Author

lcnr commented Jun 23, 2020

Well, seems like it is at least partially the correct approach 😆

@lcnr lcnr force-pushed the stacky-mc-stack-stack branch from c2b7c97 to 7807f86 Compare June 23, 2020 21:40
@lcnr lcnr force-pushed the stacky-mc-stack-stack branch from 7807f86 to c035299 Compare June 23, 2020 21:41
@lcnr lcnr requested a review from varkor June 23, 2020 21:43
// We have to generalize inference variables used in the generic substitutions,
// as unevaluated consts may otherwise contain invalid inference variables.
let new_substs =
self.relate_with_variance(ty::Variance::Invariant, &substs, &substs)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an existing pattern? It feels like a hack, because relating something to itself should be a no-op. Why does this work?

Copy link
Contributor Author

@lcnr lcnr Jun 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an existing pattern, we already (ab)use type relations in a lot of places where want to traverse a given type/const/whatever:

assert_eq!(t, t2); // we are abusing TypeRelation here; both LHS and RHS ought to be ==

or

impl<D> TypeRelation<'tcx> for TypeGeneralizer<'me, 'tcx, D>

Copy link
Member

@varkor varkor Jun 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you be able to give a small example of what's going wrong as part of a comment in the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

later this week 😆 I have to think of a good example where this check is required and not just
a stopgap because we have unused substs in ConstKind::Unevaluated.

Copy link
Contributor

@nikomatsakis nikomatsakis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some thoughts and comments, checking my understanding here.

let variable_table = &mut inner.const_unification_table();
let var_value = variable_table.probe_value(vid);
let var_value =
self.infcx.inner.borrow_mut().const_unification_table().probe_value(vid);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, so, actually, what does subtyping even mean with respect to const kind parameters? I assume that T<C1> <: T<C2> only if C1 == C2, right? In other words, if we have something like ?X <: Foo<?Y, ?C>, we probably want to instantiate Foo with Foo<?Z, ?C> -- i.e., the constant still has to be equal..?

But I guess the point is that it only has to be equal after evaluation, and hence we do want a fresh variable ?C2 and the requirement that ?C1 = ?C2, which really means that the evaluate to the same value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite get why subtyping is relevant here 🤔

Currently looking into getting a potentially more helpful example, the minimized const generics example is

#![feature(const_generics)]
trait Bar<const M: usize> {}
impl<const N: usize> Bar<N> for A<{ 6 + 1 }> {}

struct A<const N: usize>
where
    A<N>: Bar<N>;

fn main() {
    let _ = A;
}

Copy link
Contributor Author

@lcnr lcnr Jul 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the same problem for ty vars

#![feature(const_generics)]

// The goal is is to get an unevaluated const `ct` with a `Ty::Infer(TyVar(_#1t)` subst.
//
// If we are then able to infer `ty::Infer(TyVar(_#1t) := Ty<ct>`, we should be able
// to get a stack overflow.

struct Foo<const N: usize>;

trait Bind<T> {
    fn bind() -> (T, Self);
}

// `N` has to be `ConstKind::Unevaluated`.
impl<T> Bind<T> for Foo<{ 6 + 1 }> {
    fn bind() -> (T, Self) {
        (panic!(), Foo)
    }
}

fn main() {
    let (mut t, foo) = Foo::bind();
    // `t` is `ty::Infer(TyVar(_#1t))`,
    // `foo` contains `ty::Infer(TyVar(_#1t))` in its substs
    t = foo; // ! MISSED OCCURS CHECK => STACKOVERFLOW !
}

Copy link
Contributor Author

@lcnr lcnr Jul 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further minimized, we don't even need explicit const params

#![feature(const_generics)]

// The goal is is to get an unevaluated const `ct` with a `Ty::Infer(TyVar(_#1t)` subst.
//
// If we are then able to infer `ty::Infer(TyVar(_#1t) := Ty<ct>`, we should be able
// to get a stack overflow.
fn bind<T>() -> (T, [u8; 6 + 1]) {
    todo!()
} 

fn main() {
    let (mut t, foo) = bind();
    // `t` is `ty::Infer(TyVar(_#1t))`,
    // `foo` contains `ty::Infer(TyVar(_#1t))` in its substs
    t = foo; // ! MISSED OCCURS CHECK => STACKOVERFLOW !
}

if self.tcx().lazy_normalization() =>
{
// We have to generalize inference variables used in the generic substitutions,
// as unevaluated consts may otherwise contain invalid inference variables.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is kind of uninformative, yeah. It'd be nice to have an example. But I guess it comes down to the point I was raising earlier, right? That two 'unevaluated' constants can be equal so long as they are equal after evaluation, and we don't know yet which of the substitutions will be important?

@lcnr lcnr marked this pull request as draft July 2, 2020 18:35
@lcnr lcnr force-pushed the stacky-mc-stack-stack branch from 14e1c15 to 831c0df Compare July 2, 2020 19:17
@lcnr
Copy link
Contributor Author

lcnr commented Jul 2, 2020

I think I found the underlying problem causing the stack overflows, as this PR is currently not enough.

Afaict we need some kind of occurs check in

(ty::ConstKind::Infer(InferConst::Var(vid)), _) => {
return self.unify_const_variable(a_is_expected, vid, b);
}
(_, ty::ConstKind::Infer(InferConst::Var(vid))) => {
return self.unify_const_variable(!a_is_expected, vid, a);
}

We currently unify ConstKind::Infer(InferConst::Var(_cvar)) with ConstKind::Unevaluated(def, substs = [ConstKind::Infer(InferConst::Var(_cvar))]) which looking at the substs in generalize is not enough (as we only generalize
for type vars).

We probably need something similar to sub_root_var to correctly detect cycles.

Does this seem correct?

@lcnr lcnr marked this pull request as ready for review July 2, 2020 20:54
@lcnr lcnr marked this pull request as draft July 2, 2020 20:55
@lcnr
Copy link
Contributor Author

lcnr commented Jul 4, 2020

an actual fix is in #74040

@lcnr lcnr closed this Jul 4, 2020
@lcnr lcnr deleted the stacky-mc-stack-stack branch July 5, 2020 06:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-const-generics Area: const generics (parameters and arguments) A-inference Area: Type inference A-lazy-normalization Area: Lazy normalization (tracking issue: #60471) F-const_generics `#![feature(const_generics)]` S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants