Skip to content

Find cycles in rewrite constraints without performing the full exponential expansion of the RHS #5673

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

Open
wants to merge 2 commits into
base: trunk
Choose a base branch
from

Conversation

danakj
Copy link
Contributor

@danakj danakj commented Jun 16, 2025

Substitute one associated constant reference at a time on the RHS. If there's a cycle in the rewrite constraints, this allows it to be found without expanding the entire RHS, which can involve exponential type expansion, and repeating this growth a number of times equal to the number of steps in the cycle.

Fixes #5672

@github-actions github-actions bot requested a review from dwblaikie June 16, 2025 15:21
@danakj
Copy link
Contributor Author

danakj commented Jun 16, 2025

I couldn't come up with a counter example yet that would still be exponential with a power of the size of the cycle, before finding said cycle, under the solution proposed here.

@danakj danakj requested a review from zygoloid June 16, 2025 15:22
T:! Z where
.T1 = (.T2, .T2, .T2) and
.T2 = (.T3, .T3, .T3) and
.T3 = (.T4, .T4, .T4) and
.T4 = (.T5, .T5, .T5) and
.T5 = (.T6, .T6, .T6) and
.T6 = (.T7, .T7, .T7) and
.T7 = (.T1, .T1, .T1)
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't we also get exponential runtime if we tweaked this example to not have a cycle? Eg

Suggested change
T:! Z where
.T1 = (.T2, .T2, .T2) and
.T2 = (.T3, .T3, .T3) and
.T3 = (.T4, .T4, .T4) and
.T4 = (.T5, .T5, .T5) and
.T5 = (.T6, .T6, .T6) and
.T6 = (.T7, .T7, .T7) and
.T7 = (.T1, .T1, .T1)
);
T:! Z where
.T1 = (.T2, .T2, .T2) and
.T2 = (.T3, .T3, .T3) and
.T3 = (.T4, .T4, .T4) and
.T4 = (.T5, .T5, .T5) and
.T5 = (.T6, .T6, .T6) and
.T6 = (.T7, .T7, .T7) and
.T7 = (i32, i32, i32)
);

If we want that case to also be sub-exponential, I think we'd want a different strategy. A couple of options come to mind:

  • Cache the type that each rewrite substitutes into and reuse it rather than rebuilding it.
  • Precompute the dependencies between rewrites and substitute bottom-up. (But we'd need to handle the .A = .B.A and .B = .Self case somehow.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noting here that with #5692 and #5693 this particular example becomes much faster (17ms). But it's still exponential, so we just need to add more to make it slow.

This version of it, which doesn't have a cycle, and is thus not impacted by those changes, takes minutes (at least) to complete:

fn F(
    T:! Z where
      .T0 = (.T1, .T1, .T1, .T1, .T1, .T1, .T1, .T1, .T1, .T1, .T1, .T1) and
      .T1 = (.T2, .T2, .T2, .T2, .T2, .T2, .T2, .T2, .T2, .T2, .T2, .T2) and
      .T2 = (.T3, .T3, .T3, .T3, .T3, .T3, .T3, .T3, .T3, .T3, .T3, .T3) and
      .T3 = (.T4, .T4, .T4, .T4, .T4, .T4, .T4, .T4, .T4, .T4, .T4, .T4) and
      .T4 = (.T5, .T5, .T5, .T5, .T5, .T5, .T5, .T5, .T5, .T5, .T5, .T5) and
      .T5 = (.T6, .T6, .T6, .T6, .T6, .T6, .T6, .T6, .T6, .T6, .T6, .T6) and
      .T6 = (.T7, .T7, .T7, .T7, .T7, .T7, .T7, .T7, .T7, .T7, .T7, .T7) and
      .T7 = (.T8, .T8, .T8, .T8, .T8, .T8, .T8, .T8, .T8, .T8, .T8, .T8) and
      .T8 = (.T9, .T9, .T9, .T9, .T9, .T9, .T9, .T9, .T9, .T9, .T9, .T9) and
      .T9 = ()
    );```

@dwblaikie dwblaikie removed their request for review June 17, 2025 20:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Rewrite constraint resolution fixed-point search can be exponential
2 participants