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

Implement tainted_by_errors in MIR borrowck, use it to skip CTFE #93691

Merged
merged 7 commits into from
Feb 12, 2022

Conversation

compiler-errors
Copy link
Member

Putting this up for initial review. The issue that I found is when we're evaluating a const, we're doing borrowck, but doing nothing with the fact that borrowck fails.

This implements a tainted_by_errors field for MIR borrowck like we have in infcx, so we can use that information to return an Err during const eval if our const fails to borrowck.

This PR needs some cleaning up. I should probably just use Result in more places, instead of .expecting in the places I am, but I just wanted it to compile so I could see if it worked!

Fixes #93646

r? @oli-obk
feel free to reassign

@rustbot rustbot added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Feb 6, 2022
@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Feb 6, 2022
@compiler-errors
Copy link
Member Author

I wonder what other places we could use this tainted field that we generate during MIR borrowck...

@rust-log-analyzer

This comment has been minimized.

Copy link
Contributor

@oli-obk oli-obk left a comment

Choose a reason for hiding this comment

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

I think cbec6f8 is too invasive and does not fit how most queries handle errors. Maybe we should just add a tainted_by_errors field to mir::Body, too?

What do you think?


pub fn buffer_error(&mut self, t: DiagnosticBuilder<'_>) {
self.tainted_by_errors = true;
t.buffer(&mut self.errors_buffer);
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems a bit fragile to keep exposing the buffer list. Maybe nest it in some structure that contains the tainted field and the errors? Or at least leave a FIXME for it ^^

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure what you mean by this.

This method on MirBorrowckCtxt is just convenience to both buffer the diagnostic (which we are already doing before this diff) and set tainted_by_errors to true, since I didn't want to have to duplicate setting that boolean (or calling a method to set that boolean) in like the 20 spots I introduced this helper.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry. What I mean is that the previous way is still possible. So people may add invocations of .buffer on the diagnostic and thus keep forgetting to set the tainted flag. If we hide the buffer as a private field of a struct we can avoid people accessing it directly.

It's more of a concern for careless impls in the future than a problem with the PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

As an alternative we could check the sites where the buffer is consumed. If it is not empty, taint the borrowck result

Copy link
Member Author

Choose a reason for hiding this comment

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

oh yeah, good point, I can hide buffer in a harder-to-access field to prevent people accidentally not setting tainted with it too.

I can't just set tainted when we drain the buffer later because DiagnosticBuilder::buffer sometimes just emits the error directly instead of buffering it depending on some settings.

@compiler-errors
Copy link
Member Author

@oli-obk, so I thought about putting it into Body, but it looks like borrowck actually treats Body as immutable in favor of returning a different BorrowCheckResult struct, and I didn't want to do something with interior mutability or reallocating a new Body...

@oli-obk
Copy link
Contributor

oli-obk commented Feb 6, 2022

I was thinking of doing it where you now return Err, as we definitely have a mutable body available. But at that point the field on the body becomes fairly useless.

We could instead just check borrowck (and probably typeck, too) in load_mir. Then we can remove the redundant check in the const eval queries. There are other checks in those queries that may be good to move to load_mir, too

@compiler-errors
Copy link
Member Author

compiler-errors commented Feb 6, 2022

We could instead just check borrowck (and probably typeck, too) in load_mir.

Not opposed to this necessarily, but even if we do borrowck again in load_mir, don't we still need to expose some field like the tainted_by_errors I added that reflects the fact an error occurred during that check?

@oli-obk
Copy link
Contributor

oli-obk commented Feb 6, 2022

We could instead just check borrowck (and probably typeck, too) in load_mir.

Not opposed to this necessarily, but even if we do borrowck again in load_mir, don't we still need to expose some field like the tainted_by_errors I added that reflects the fact an error occurred during that check?

Oh yes, we need all your borrowck changes , only now the mir queries don't change ^^

@compiler-errors
Copy link
Member Author

oh sure, let me see if i can do that then.

@compiler-errors compiler-errors force-pushed the mir-tainted-by-errors branch 2 times, most recently from 2afbfaf to bc9ea71 Compare February 6, 2022 20:17
@compiler-errors compiler-errors changed the title [WIP] Implement tainted_by_errors in MIR borrowck, use it to skip CTFE Implement tainted_by_errors in MIR borrowck, use it to skip CTFE Feb 6, 2022
@compiler-errors
Copy link
Member Author

@oli-obk: Yeah, I didn't need to Result-ify all those MIR queries actually. This is much simpler.

There are two places I do a borrowck call (eval_to_allocation_raw_provider and InterpCx::load_mir), but those are also two places we're currently also checking for typeck errors. Not sure if it's significant to have both of them, and I can try to deduplicate those if you want.

@rust-log-analyzer

This comment has been minimized.

@@ -833,13 +833,14 @@ rustc_queries! {
/// additional requirements that the closure's creator must verify.
query mir_borrowck(key: LocalDefId) -> &'tcx mir::BorrowCheckResult<'tcx> {
desc { |tcx| "borrow-checking `{}`", tcx.def_path_str(key.to_def_id()) }
cache_on_disk_if(tcx) { tcx.is_typeck_child(key.to_def_id()) }
cache_on_disk_if { true }
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this necessary?

I'm going to do a perf run,? but maybe we just need to check the tainted flag here, too

Copy link
Member Author

Choose a reason for hiding this comment

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

I was getting ICEs in incremental tests complaining that the MIR body for a fn was stolen, since we're now calling borrowck which depends on unoptimized MIR which is probably gone if we do const eval late in the compilation process...

Not sure if only caching if the body if it is tainted works, since we're unconditionally calling borrowck before const eval.

Copy link
Contributor

Choose a reason for hiding this comment

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

heh, yea, this is what @BoxyUwU ran into, too, and we never got as far as to figuring it out.

I guess it makes sense that if the cache rules differ between two queries that work on stealing things, that said caching breaks, but it's really opaque to me. Well... let's see what perf says

Copy link
Contributor

Choose a reason for hiding this comment

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

Perf does show the additional query result serialization, so we should try to do something here.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's probably not worth it, but we could try adding a query that returns just an Option<ErrorReported> and does all the processing of typeck, borrowck and const qualifs. Then we can only cache that on disk and it should rarely get recomputed. But: new query so probably overkill, but worth an experiment?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I'm not exactly sure what we can do to limit cache_on_disk just to when it's needed, since it seems like we need to cache it for any body that has a promoted const, see the failing query stack trace:

query stack during panic:
#0 [mir_borrowck] borrow-checking `lit_test::lit_test`
#1 [eval_to_allocation_raw] const-evaluating + checking `lit_test::lit_test::promoted[1]`
#2 [eval_to_allocation_raw] const-evaluating + checking `lit_test::lit_test::promoted[1]`
#3 [optimized_mir] optimizing MIR for `lit_test::lit_test`
#4 [collect_and_partition_mono_items] collect_and_partition_mono_items

... but we don't know if a body has any promoted consts until after borrowck.

so I might try the query experiment you suggested. I'll ping you for a perf run when I put it up, since I don't have super powers.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, we do know it at borrowck time, so we could encode that info in the borrowck result.

But maybe we're going about this the wrong way. When we force mir borrowck and then steal the mir, we own the mir, so we can mutate it and change its tainted field. This is essentially the same idea as the merging of the tainted flags into one query only we're putting it right in the data we have to load anyway

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, we do know it at borrowck time, so we could encode that info in the borrowck result.

Oh lol, I thought we did borrowck on mir_built, whoopsy.

When we force mir borrowck and then steal the mir, we own the mir, so we can mutate it and change its tainted field.

So you're suggesting we do add the tainted field to mir::Body then, instead of BorrowCheckerResult?

Copy link
Contributor

Choose a reason for hiding this comment

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

You need it on both, as (as you reminded me) you can't mutate the mir from borrowck. But at the query that steals the mir after forcing the borrowck query, you can just call borrowck instead of forcing it and copy the tainted field over to the mir body you just stole

@@ -214,6 +214,7 @@ pub struct BorrowCheckResult<'tcx> {
pub concrete_opaque_types: VecMap<OpaqueTypeKey<'tcx>, Ty<'tcx>>,
pub closure_requirements: Option<ClosureRegionRequirements<'tcx>>,
pub used_mut_upvars: SmallVec<[Field; 8]>,
pub tainted_by_errors: bool,
Copy link
Contributor

Choose a reason for hiding this comment

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

Please make it an option like in the typeck results .this will make it easier for the in-flight PRs that make our error handling more solid

@@ -287,6 +287,9 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
if let Some(error_reported) = tcx.typeck_opt_const_arg(def).tainted_by_errors {
Copy link
Contributor

Choose a reason for hiding this comment

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

A bit more than planned for this PR, but if you want to try doing this cleanup in this PR, I'm not going to complain ;)

Basically remove the entire logic in the is_local if block and move whatever is missing in load_mir to it.

We can also guard the checks in load_mir with the is_local check, as any mir we load from dependencies must be error free

Copy link
Contributor

Choose a reason for hiding this comment

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

That said, feel free not to do this in this PR .we can merge it soon in any case

@oli-obk
Copy link
Contributor

oli-obk commented Feb 7, 2022

@bors try @rust-timer queue

@rust-timer
Copy link
Collaborator

Awaiting bors try build completion.

@rustbot label: +S-waiting-on-perf

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Feb 7, 2022
@bors
Copy link
Contributor

bors commented Feb 7, 2022

⌛ Trying commit eb42e801ea203377536cf31ad51caae5cdcd1449 with merge 86a277ecc46223ab296a8b964b834266a79a9d1f...

@bors
Copy link
Contributor

bors commented Feb 7, 2022

💥 Test timed out

@oli-obk
Copy link
Contributor

oli-obk commented Feb 7, 2022

@bors try @rust-timer queue

@rust-timer
Copy link
Collaborator

Awaiting bors try build completion.

@rustbot label: +S-waiting-on-perf

@bors
Copy link
Contributor

bors commented Feb 7, 2022

⌛ Trying commit eb42e801ea203377536cf31ad51caae5cdcd1449 with merge d106320594d82e721bfc144b18d7f1db27135b68...

@bors
Copy link
Contributor

bors commented Feb 7, 2022

☀️ Try build successful - checks-actions
Build commit: d106320594d82e721bfc144b18d7f1db27135b68 (d106320594d82e721bfc144b18d7f1db27135b68)

@compiler-errors
Copy link
Member Author

rebased, an error message went away but it was definitely due to opaque types PR being reverted.

@rustbot ready

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

oli-obk commented Feb 11, 2022

@bors r+

@bors
Copy link
Contributor

bors commented Feb 11, 2022

📌 Commit 67ad0ff has been approved by oli-obk

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 11, 2022
@bors
Copy link
Contributor

bors commented Feb 12, 2022

⌛ Testing commit 67ad0ff with merge 9cdefd7...

@bors
Copy link
Contributor

bors commented Feb 12, 2022

☀️ Test successful - checks-actions
Approved by: oli-obk
Pushing 9cdefd7 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Feb 12, 2022
@bors bors merged commit 9cdefd7 into rust-lang:master Feb 12, 2022
@rustbot rustbot added this to the 1.60.0 milestone Feb 12, 2022
@rust-timer
Copy link
Collaborator

Finished benchmarking commit (9cdefd7): comparison url.

Summary: This benchmark run shows 5 relevant improvements 🎉 but 3 relevant regressions 😿 to instruction counts.

  • Average relevant regression: 1.7%
  • Average relevant improvement: -0.4%
  • Largest improvement in instruction counts: -0.6% on incr-unchanged builds of keccak check
  • Largest regression in instruction counts: 2.4% on incr-patched: u8 3072 builds of issue-46449 debug

If you disagree with this performance assessment, please file an issue in rust-lang/rustc-perf.

Next Steps: If you can justify the regressions found in this perf run, please indicate this with @rustbot label: +perf-regression-triaged along with sufficient written justification. If you cannot justify the regressions please open an issue or create a new PR that fixes the regressions, add a comment linking to the newly created issue or PR, and then add the perf-regression-triaged label to this PR.

@rustbot label: +perf-regression

@oli-obk
Copy link
Contributor

oli-obk commented Feb 12, 2022

@rustbot label: +perf-regression-triaged

This fixes a bunch of compiler crashes that occurred because we weren't checking for borrowck errors before doing const eval. The additional work is entirely expected and other avenues did not pan out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. perf-regression Performance regression. perf-regression-triaged The performance regression has been triaged. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Const generic default ICE: The type checker should prevent reading from a never-written local
9 participants