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

Automatically make trivial types noop-traversable #117726

Conversation

eggyal
Copy link
Contributor

@eggyal eggyal commented Nov 8, 2023

This is a second reincarnation of #108214 (or at least, what that PR ultimately became), the previous reincarnation in #117620 having been closed accidentally. It may be easier to review by commit, albeit there are quite a few of them.

Terminology

I refer to:

  • folds and visits as "traversals"
  • the types that are folded or visited as "traversables"
  • the folders and visitors as "traversers"
  • traversables on which traversers can act as "interesting" (there are currently only five: binders, types, consts, regions and predicates) and all others as "trivial" (traversals of which are no-ops)
  • the TypeFoldable and TypeVisitable traits (and only these traits) as the "traversable traits"

Content

This PR:

  • introduces a macro, rustc_type_ir::noop_if_trivially_traversable that uses auto-deref specialisation to:
    • perform a no-op traversal on values of type T if the interner implements rustc_type_ir::TriviallyTraverses<T> (thereby obviating all need for most trivial types to implement the traversable traits—and indeed such implementations are removed) or
    • delegate to the relevant traversable trait implementation on T otherwise;
  • introduces an auto-trait, rustc_middle::ty::TriviallyTraversable, that is then "unimplemented" on the interesting types (and thereby remains implemented only on, but on all, trivial types);
  • implements rustc_type_ir::TriviallyTraverses<T> on the TyCtxt<'tcx> interner for all T: rustc_middle::ty::TriviallyTraversable (thus ensuring that, for that interner at least, trivial types do not require implementations of the traversable traits);
  • updates the traversable traits' derive macros to:
    • skip fields that reference neither any generic type parameters nor, if present, the 'tcx lifetime parameter
    • use the above specialisation macro when traversing fields
    • if the traversable is not parameterised by a 'tcx lifetime, generate implementations that are generic over the interner;
  • replaces those derive macros' distinct helper attributes (introduced in Use derive attributes for uninteresting traversals #108040) with a unified #[skip_traversal] helper attribute that requires a justifying reason to be provided (this does mean that the different types of traversal can no longer be independently skipped, but nowhere is that currently required or ever expected to be);
    • the derive macros by default refuse to be implemented on trivial types as specialisation usually negates any need—but this can be overridden with a #[skip_traversal(impl_despite_trivial_because = "<reason>")] attribute;
  • uses those derive macros in place of remaining usages of the TrivialTypeTraversal macros and some explicit implementations; and
  • a few other minor relevant refactorings.

Commentary

One limitation of auto-deref specialisation is that it cannot work for unconstrained generic types. Therefore, absent real specialisation, implementations of the traversable traits must constrain their generics:

  • by default, the derive macros constrain every field type T that references one or more generic type parameters to implementors of the relevant traversable trait—however this can be modified on a field (or variant) basis with a #[skip_traversal(because_trivial)] attribute so that instead the interner is constrained to implement TriviallyTraverses<T> (and the field is not traversed)
    • the constraints are applied to the field types rather than the generic type parameters to achieve a "perfect derive" that not only avoids having to propagate #[skip_traversal] annotations into wrapping types but also works with associated types and other esoteric situations—however this could result in trait solver cycles if interesting generic field types are one day involved in recursive type definitions;
    • for exceptionally rare cases where traversal of an item/variant/field should be a no-op despite it being (potentially, if generic) interesting, it can be annotated with the explicit and deliberately cumbersome #[skip_traversal(despite_potential_miscompilation_because = "<reason>")] (which produces a no-op without adding any constraint).
  • (the few remaining) explicit implementations of the traversable traits over generic types are similarly constrained
    • indeed, for generic tuples all element types must implement the trait—however, since (most) trivial types no longer do, this unfortunately means that the implementations are not applicable to tuples with trivial elements and hence some such tuples have been replaced with more concrete newtypes that have appropriate constraints: c5e9447 is a particularly hefty example of this (where tuples containing Span are replaced with Spanned<T> instead).

r? @lcnr
cc @oli-obk

@rustbot label A-query-system T-compiler WG-trait-system-refactor

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver) labels Nov 8, 2023
@rustbot
Copy link
Collaborator

rustbot commented Nov 8, 2023

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

This PR changes MIR

cc @oli-obk, @RalfJung, @JakobDegen, @davidtwco, @celinval, @vakaras

Some changes occurred in engine.rs, potentially modifying the public API of ObligationCtxt.

cc @lcnr, @compiler-errors

Some changes occurred in abstract_const.rs

cc @BoxyUwU

These commits modify compiler targets.
(See the Target Tier Policy.)

Some changes occurred to the CTFE / Miri engine

cc @rust-lang/miri

Some changes occurred to the core trait solver

cc @rust-lang/initiative-trait-system-refactor

The Miri subtree was changed

cc @rust-lang/miri

Some changes occurred to the CTFE / Miri engine

cc @rust-lang/miri

These commits modify the Cargo.lock file. Unintentional changes to Cargo.lock can be introduced when switching branches and rebasing PRs.

If this was unintentional then you should revert the changes before this PR is merged.
Otherwise, you can ignore this comment.

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

@rust-log-analyzer

This comment has been minimized.

@eggyal eggyal force-pushed the automatically_make_trivial_types_noop-traversable branch from 6d4e208 to 037ac16 Compare November 8, 2023 22:20
@eggyal eggyal force-pushed the automatically_make_trivial_types_noop-traversable branch from 037ac16 to 8051389 Compare November 9, 2023 07:16
[Auto-deref specialisation] is introduced to type library traversals,
the public API of which is via the `TriviallyTraverses` trait and the
`noop_if_trivially_traversable` macro.  Traversals invoked via the macro
will then be no-ops if the interner "trivially traverses" the type being
traversed *without requiring that type to implement the relevant
traversable trait*.

A further trait, `rustc_middle::ty::TriviallyTraversable`, is then
auto-implemented for types that do not contain anything that may be of
interest to traversers.  A generic implementation of the former trait is
then provided for the `TyCtxt` interner to indicate that it "trivially
traverses" all such `TriviallyTraversable` types.  This indirection is
necessary because auto-traits are unstable, whereas the type library is
intended to be stabilised.

[Auto-deref specialisation]: http://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html
BindingForm has been a no-op traversable ever since it was first added
in cac6126, but it was modified in 0193d1f to (optionally) contain a
Place without its implementations being updated to fold/visit such.

By using the derive macro to implement the visitable traits, we ensure
that all contained types of interest are folded/visited.
Ever since folding was first added in c5754f3, obligations have not
folded or visited their causes the ObligationCauseCode potentially
containing types that may be of interest to a folder or visitor.

By using the derive macro to implement the visitable traits, we ensure
that all contained types of interest are folded/visited.
Rather than being concerned with internals of ExternalConstraintsData
and PredefinedOpaquesData, we derive traversable impls for those types
to which we then delegate.
UnevaluatedConst is not a type of any interest to folders or visitors,
and therefore should not be "super visitable" at all.
Previously only enforced via comment, now via negative impls.
@eggyal eggyal force-pushed the automatically_make_trivial_types_noop-traversable branch from 8051389 to 37bab7a Compare November 9, 2023 07:20
@lcnr
Copy link
Contributor

lcnr commented Nov 9, 2023

if there are conflicts, feel free to only rebase once I've reviewed it

@eggyal
Copy link
Contributor Author

eggyal commented Nov 9, 2023

Sure. Will wait until you've reviewed before pushing anything else. You may want to try that perf run now before any conflicts arise?

@lcnr
Copy link
Contributor

lcnr commented Nov 9, 2023

@bors try @rust-timer queue

@bors
Copy link
Contributor

bors commented Nov 9, 2023

⌛ Trying commit 37bab7a with merge 793a8bf...

bors added a commit to rust-lang-ci/rust that referenced this pull request Nov 9, 2023
…pes_noop-traversable, r=<try>

Automatically make trivial types noop-traversable

This is a second reincarnation of rust-lang#108214 (or at least, what that PR ultimately became), the previous reincarnation in rust-lang#117620 having been closed accidentally.  It may be easier to review by commit, albeit there are quite a few of them.

## Terminology

I refer to:
* folds and visits as "traversals"
* the types that are folded or visited as "traversables"
* the folders and visitors as "traversers"
* traversables on which traversers can act as "interesting" (there are currently only five: binders, types, consts, regions and predicates) and all others as "trivial" (traversals of which are no-ops)
* the `TypeFoldable` and `TypeVisitable` traits (and only these traits) as the "traversable traits"

## Content

This PR:

* introduces a macro, `rustc_type_ir::noop_if_trivially_traversable` that uses [auto-deref specialisation](http://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html) to:
    * perform a no-op traversal on values of type `T` if the interner implements `rustc_type_ir::TriviallyTraverses<T>` (thereby obviating all need for most trivial types to implement the traversable traits—and indeed such implementations are removed) or
    * delegate to the relevant traversable trait implementation on `T` otherwise;
* introduces an auto-trait, `rustc_middle::ty::TriviallyTraversable`, that is then "unimplemented" on the interesting types (and thereby remains implemented only on, but on all, trivial types);
* implements `rustc_type_ir::TriviallyTraverses<T>` on the `TyCtxt<'tcx>` interner for all `T: rustc_middle::ty::TriviallyTraversable` (thus ensuring that, for that interner at least, trivial types do not require implementations of the traversable traits);
* updates the traversable traits' derive macros to:
    * skip fields that reference neither any generic type parameters nor, if present, the `'tcx` lifetime parameter
    * use the above specialisation macro when traversing fields
    * if the traversable is not parameterised by a `'tcx` lifetime, generate implementations that are generic over the interner;
* replaces those derive macros' distinct helper attributes (introduced in rust-lang#108040) with a unified `#[skip_traversal]` helper attribute that requires a justifying reason to be provided (this does mean that the different types of traversal can no longer be independently skipped, but nowhere is that currently required or ever expected to be);
    * the derive macros by default refuse to be implemented on trivial types as specialisation usually negates any need—but this can be overridden with a `#[skip_traversal(impl_despite_trivial_because = "<reason>")]` attribute;
* uses those derive macros in place of remaining usages of the `TrivialTypeTraversal` macros and some explicit implementations; and
* a few other minor relevant refactorings.

## Commentary

One limitation of auto-deref specialisation is that it cannot work for unconstrained generic types.  Therefore, absent real specialisation, implementations of the traversable traits must constrain their generics:
* by default, the derive macros constrain every field type `T` that references one or more generic type parameters to implementors of the relevant traversable trait—however this can be modified on a field (or variant) basis with a `#[skip_traversal(because_trivial)]` attribute so that instead the interner is constrained to implement `TriviallyTraverses<T>` (and the field is not traversed)
    * the constraints are applied to the field types rather than the generic type parameters to achieve a "[perfect derive](https://smallcultfollowing.com/babysteps/blog/2022/04/12/implied-bounds-and-perfect-derive/)" that not only avoids having to propagate `#[skip_traversal]` annotations into wrapping types but also works with associated types and other esoteric situations—however this could result in trait solver cycles if interesting generic field types are one day involved in recursive type definitions;
    * for exceptionally rare cases where traversal of an item/variant/field should be a no-op despite it being (potentially, if generic) interesting, it can be annotated with the explicit and deliberately cumbersome `#[skip_traversal(despite_potential_miscompilation_because = "<reason>")]` (which produces a no-op without adding any constraint).
* (the few remaining) explicit implementations of the traversable traits over generic types are similarly constrained
    * indeed, for generic tuples *all element types* must implement the trait—however, since (most) trivial types no longer do, this unfortunately means that the implementations are not applicable to tuples with trivial elements and hence some such tuples have been replaced with more concrete newtypes that have appropriate constraints: c5e9447 is a particularly hefty example of this (where tuples containing `Span` are replaced with `Spanned<T>` instead).

r? `@lcnr`
cc `@oli-obk`

`@rustbot` label A-query-system T-compiler WG-trait-system-refactor
@bors
Copy link
Contributor

bors commented Nov 9, 2023

☀️ Try build successful - checks-actions
Build commit: 793a8bf (793a8bf6d82bf779eead11e22a6dad04873c5342)

@lcnr
Copy link
Contributor

lcnr commented Nov 9, 2023

@rust-timer build 793a8bf

@rust-timer

This comment has been minimized.

@rust-timer
Copy link
Collaborator

Finished benchmarking commit (793a8bf): comparison URL.

Overall result: ❌ regressions - ACTION NEEDED

Benchmarking this pull request likely means that it is perf-sensitive, so we're automatically marking it as not fit for rolling up. While you can manually mark this PR as fit for rollup, we strongly recommend not doing so since this PR may lead to changes in compiler perf.

Next Steps: If you can justify the regressions found in this try perf run, please indicate this with @rustbot label: +perf-regression-triaged along with sufficient written justification. If you cannot justify the regressions please fix the regressions and do another perf run. If the next run shows neutral or positive results, the label will be automatically removed.

@bors rollup=never
@rustbot label: -S-waiting-on-perf +perf-regression

Instruction count

This is a highly reliable metric that was used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
0.3% [0.1%, 0.6%] 14
Regressions ❌
(secondary)
0.6% [0.2%, 1.0%] 15
Improvements ✅
(primary)
-0.4% [-0.4%, -0.4%] 1
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 0.3% [-0.4%, 0.6%] 15

Max RSS (memory usage)

Results

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
0.8% [0.4%, 1.8%] 10
Regressions ❌
(secondary)
4.2% [4.2%, 4.2%] 1
Improvements ✅
(primary)
-1.0% [-1.6%, -0.4%] 3
Improvements ✅
(secondary)
-3.4% [-4.5%, -2.3%] 2
All ❌✅ (primary) 0.4% [-1.6%, 1.8%] 13

Cycles

Results

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
0.5% [0.4%, 0.6%] 5
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
-0.5% [-0.5%, -0.5%] 2
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 0.3% [-0.5%, 0.6%] 7

Binary size

This benchmark run did not return any relevant results for this metric.

Bootstrap: 664.787s -> 664.957s (0.03%)
Artifact size: 308.65 MiB -> 308.80 MiB (0.05%)

@rustbot rustbot added the perf-regression Performance regression. label Nov 9, 2023
@eggyal
Copy link
Contributor Author

eggyal commented Nov 10, 2023

I'll try and look into the perf regression over the weekend.

One thing that I should perhaps highlight is that, in order to support safe derivation of the traversable traits upstream of rustc_middle (therefore over generic interners, absent the TriviallyTraverses impl from the auto-trait), the derive macro itself now makes an initial determination (on syntax alone) that a type is guaranteed to be trivial if it references neither generic type parameters nor a 'tcx lifetime (on the basis that without some handle on the interner, it won't contain anything of interest).

Also, since auto-deref specialisation doesn't work for generic types (generics must be constrained to use either a trivial or a non-trivial traversal), the only time that specialisation is now needed is when traversing a field that does not reference any generic type parameters but that does reference a 'tcx lifetime. It may be worth exploring how many of these are actually trivial, as if there are very few then perhaps the complexity of the specialisation machinery isn't justified? (The auto-trait would nevertheless remain to constrain impls so that #[skip_traversal] attributes cannot be unintentionally misused).

Accordingly, I think this PR could be split into a number of smaller ones; for example:

  • syntactically detect & skip trivial types in the derive macro, use derive macro in trivial cases, remove superfluous impls
  • (if still required, add specialisation and remove further superfluous impls)
  • add auto-trait and #[skip_traversal] attribute, use derive macros in newly-supported cases
  • make derivations generic over interner (where possible) and use upstream of rustc_middle
  • other refactorings

This could make review easier and help to identify the source of the perf regression... let me know if you'd like me to take a crack at it.

@lcnr
Copy link
Contributor

lcnr commented Nov 10, 2023

the derive macro itself now makes an initial determination (on syntax alone) that a type is guaranteed to be trivial if it references neither generic type parameters nor a 'tcx lifetime (on the basis that without some handle on the interner, it won't contain anything of interest).

People sometimes use non 'tcx lifetimes for interned lifetimes, so I would change the rule to simply be "no type or non-static lifetime params".

I think splitting the work over multiple self-contained PRs is helpful if possible. So feel free to go ahead. Will hold off reviewing this PR in that case.

@eggyal
Copy link
Contributor Author

eggyal commented Nov 10, 2023

Presence of a 'tcx lifetime is already used to determine whether the derived impl is generic over the interner or only for TyCtxt<'tcx>. Indeed, I'm pretty sure all cases where a lifetime other than 'tcx is currently used are indeed trivial (as I don't think a previous iteration would have compiled if not)—but I'll check on that.

Will go ahead and split this monster up.

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 10, 2023
@eggyal
Copy link
Contributor Author

eggyal commented Nov 14, 2023

Okay, I have reworked this into 8 upcoming PR's—but since some build on others, I'm not sure how best to post them. Shall I open one at a time, waiting for each to merge before opening the next? Or shall I open them all at once, each based off its predecessor and therefore automatically rolling forward with master but risking confusion/excess review work if considered out of order? Or I could really pull them apart and rebase to resolve the conflicts as they arise...

@lcnr
Copy link
Contributor

lcnr commented Nov 14, 2023

feel free to open them all, assigning each to me, as long as the description clearly states which other PR they depend on

bors added a commit to rust-lang-ci/rust that referenced this pull request Nov 14, 2023
TypeFoldable/TypeVisitable tidy up

I'm breaking rust-lang#117726 into more manageable, logical chunks.  This is the first of those, and just makes some minor/tidying refactors in preparation for the chunks to follow.

r? types
bors added a commit to rust-lang-ci/rust that referenced this pull request Nov 23, 2023
TypeFoldable/TypeVisitable tidy up

I'm breaking rust-lang#117726 into more manageable, logical chunks.  This is the first of those, and just makes some minor/tidying refactors in preparation for the chunks to follow.

r? types
@JohnCSimon
Copy link
Member

Triage:
@eggyal I guess this PR is still active.

@Dylan-DPC
Copy link
Member

Closing this as it is being split into parts with #117896 being the first part

@Dylan-DPC Dylan-DPC closed this Feb 17, 2024
@Dylan-DPC Dylan-DPC removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Feb 17, 2024
@eggyal eggyal deleted the automatically_make_trivial_types_noop-traversable branch April 16, 2024 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
perf-regression Performance regression. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants