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

Try to find cyclic data dependencies during const-checking #71526

Closed

Conversation

ecstatic-morse
Copy link
Contributor

@ecstatic-morse ecstatic-morse commented Apr 24, 2020

#71078 contained an example of a cyclic data dependency in a static initializer being optimized away before reaching const-eval. #71140 solved this by trying to trigger the cycle while performing the optimization that may remove it.

I think we should try to catch as many cycles as we can during const-checking, which will always run before optimizations are applied. Unlike const-eval, however, const-checking is done pre-monomorphization, so I don't think it's possible to detect cyclic dependencies with perfect precision if they involve associated consts. I could be wrong though.

This PR is based on #71330, which only runs const qualification when type-based qualification fails, meaning that without this PR, const-checking will find even less cycles than before. Only the last two commits are new.

It causes an error on #71078 even without #71140 applied. Like #71140, this is a breaking change, and will need a crater run. Unlike #71140, it may result in false positives.

r? @oli-obk

This was needed by an early version of dataflow-based const
qualification where `QualifCursor` needed to return a full `BitSet` with
the current state.
@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Apr 24, 2020
@rust-highfive
Copy link
Collaborator

Your PR failed (pretty log, raw log). Through arcane magic we have determined that the following fragments from the build log may contain information about the problem.

Click to expand the log.
2020-04-24T19:06:05.4491387Z ========================== Starting Command Output ===========================
2020-04-24T19:06:05.4493573Z [command]/bin/bash --noprofile --norc /home/vsts/work/_temp/4a647c4e-6547-482b-92cf-8b80bfd37813.sh
2020-04-24T19:06:05.4493804Z 
2020-04-24T19:06:05.4497193Z ##[section]Finishing: Disable git automatic line ending conversion
2020-04-24T19:06:05.4514101Z ##[section]Starting: Checkout rust-lang/rust@refs/pull/71526/merge to s
2020-04-24T19:06:05.4517201Z Task         : Get sources
2020-04-24T19:06:05.4517477Z Description  : Get sources from a repository. Supports Git, TfsVC, and SVN repositories.
2020-04-24T19:06:05.4517764Z Version      : 1.0.0
2020-04-24T19:06:05.4517947Z Author       : Microsoft
---
2020-04-24T19:06:06.4427403Z ##[command]git remote add origin https://github.com/rust-lang/rust
2020-04-24T19:06:06.4432829Z ##[command]git config gc.auto 0
2020-04-24T19:06:06.4436364Z ##[command]git config --get-all http.https://github.com/rust-lang/rust.extraheader
2020-04-24T19:06:06.4439620Z ##[command]git config --get-all http.proxy
2020-04-24T19:06:06.4445646Z ##[command]git -c http.extraheader="AUTHORIZATION: basic ***" fetch --force --tags --prune --progress --no-recurse-submodules --depth=2 origin +refs/heads/*:refs/remotes/origin/* +refs/pull/71526/merge:refs/remotes/pull/71526/merge
---
2020-04-24T19:08:23.2453345Z  ---> f7353ccad5b1
2020-04-24T19:08:23.2453892Z Step 6/7 : ENV RUN_CHECK_WITH_PARALLEL_QUERIES 1
2020-04-24T19:08:23.2455814Z  ---> Using cache
2020-04-24T19:08:23.2456653Z  ---> ed38efbaa060
2020-04-24T19:08:23.2458493Z Step 7/7 : ENV SCRIPT python3 ../x.py test src/tools/expand-yaml-anchors &&            python3 ../x.py check --target=i686-pc-windows-gnu --host=i686-pc-windows-gnu &&            python3 ../x.py build --stage 0 src/tools/build-manifest &&            python3 ../x.py test --stage 0 src/tools/compiletest &&            python3 ../x.py test src/tools/tidy &&            /scripts/validate-toolstate.sh
2020-04-24T19:08:23.2461934Z  ---> c5008ef7ae8e
2020-04-24T19:08:23.2516956Z Successfully built c5008ef7ae8e
2020-04-24T19:08:23.2543586Z Successfully tagged rust-ci:latest
2020-04-24T19:08:23.2822297Z Built container sha256:c5008ef7ae8e94d7ef502e3cef26e61208e14ebdb36913f3a8bb86291bd6430b
2020-04-24T19:08:23.2822297Z Built container sha256:c5008ef7ae8e94d7ef502e3cef26e61208e14ebdb36913f3a8bb86291bd6430b
2020-04-24T19:08:23.2838073Z Looks like docker image is the same as before, not uploading
2020-04-24T19:08:31.2544984Z [CI_JOB_NAME=mingw-check]
2020-04-24T19:08:31.2747239Z [CI_JOB_NAME=mingw-check]
2020-04-24T19:08:31.2779970Z == clock drift check ==
2020-04-24T19:08:31.2790529Z   local time: Fri Apr 24 19:08:31 UTC 2020
2020-04-24T19:08:31.5690743Z   network time: Fri, 24 Apr 2020 19:08:31 GMT
2020-04-24T19:08:31.5727723Z Starting sccache server...
2020-04-24T19:08:31.6797550Z configure: processing command line
2020-04-24T19:08:31.6797793Z configure: 
2020-04-24T19:08:31.6798669Z configure: rust.parallel-compiler := True
---
2020-04-24T19:12:03.2158690Z     Checking rustc_feature v0.0.0 (/checkout/src/librustc_feature)
2020-04-24T19:12:03.4442812Z     Checking fmt_macros v0.0.0 (/checkout/src/libfmt_macros)
2020-04-24T19:12:03.5769474Z     Checking rustc_ast_pretty v0.0.0 (/checkout/src/librustc_ast_pretty)
2020-04-24T19:12:03.6285932Z     Checking rustc_hir v0.0.0 (/checkout/src/librustc_hir)
2020-04-24T19:12:04.1161517Z     Checking rustc_query_system v0.0.0 (/checkout/src/librustc_query_system)
2020-04-24T19:12:06.1718887Z     Checking rustc_attr v0.0.0 (/checkout/src/librustc_attr)
2020-04-24T19:12:06.6065176Z     Checking rustc_parse v0.0.0 (/checkout/src/librustc_parse)
2020-04-24T19:12:08.4034005Z     Checking rustc_hir_pretty v0.0.0 (/checkout/src/librustc_hir_pretty)
2020-04-24T19:12:08.7949684Z     Checking rustc_ast_lowering v0.0.0 (/checkout/src/librustc_ast_lowering)
---
2020-04-24T19:12:45.0323771Z     Checking rustc_privacy v0.0.0 (/checkout/src/librustc_privacy)
2020-04-24T19:12:45.8363668Z error[E0308]: `if` and `else` have incompatible types
2020-04-24T19:12:45.8365148Z    --> src/librustc_mir/transform/check_consts/validation.rs:423:17
2020-04-24T19:12:45.8366084Z     |
2020-04-24T19:12:45.8367258Z 416 |               } else if let ty::ConstKind::Unevaluated(def_id, _, promoted) = c.literal.val {
2020-04-24T19:12:45.8369415Z 417 | |                 assert!(promoted.is_none(), "Const-checking should run before promotion");
2020-04-24T19:12:45.8370626Z 418 | |
2020-04-24T19:12:45.8371792Z 419 | |                 // FIXME: This means we don't look for cycles involving associated constants, but
2020-04-24T19:12:45.8387151Z 420 | |                 // we should handle fully monomorphized ones here at least.
2020-04-24T19:12:45.8387151Z 420 | |                 // we should handle fully monomorphized ones here at least.
2020-04-24T19:12:45.8398036Z 421 | |                 self.tcx.trait_of_item(def_id).is_none().then_some(|| def_id);
2020-04-24T19:12:45.8400033Z     | |                 |                                                            |
2020-04-24T19:12:45.8401246Z     | |                 |                                                            help: consider removing this semicolon
2020-04-24T19:12:45.8402257Z     | |                 expected because of this
2020-04-24T19:12:45.8403001Z 422 | |             } else {
---
2020-04-24T19:12:46.6937433Z For more information about this error, try `rustc --explain E0308`.
2020-04-24T19:12:46.7128307Z error: could not compile `rustc_mir`.
2020-04-24T19:12:46.7128618Z 
2020-04-24T19:12:46.7129029Z To learn more, run the command again with --verbose.
2020-04-24T19:12:46.7194656Z command did not execute successfully: "/checkout/obj/build/x86_64-unknown-linux-gnu/stage0/bin/cargo" "check" "--target" "x86_64-unknown-linux-gnu" "-Zbinary-dep-depinfo" "-j" "2" "--release" "--color" "always" "--features" " llvm" "--manifest-path" "/checkout/src/rustc/Cargo.toml" "--message-format" "json-render-diagnostics"
2020-04-24T19:12:46.7204395Z failed to run: /checkout/obj/build/bootstrap/debug/bootstrap check
2020-04-24T19:12:46.7204759Z Build completed unsuccessfully in 0:04:14
2020-04-24T19:12:46.7310134Z == clock drift check ==
2020-04-24T19:12:46.7310134Z == clock drift check ==
2020-04-24T19:12:46.7334364Z   local time: Fri Apr 24 19:12:46 UTC 2020
2020-04-24T19:12:47.0215083Z   network time: Fri, 24 Apr 2020 19:12:47 GMT
2020-04-24T19:12:47.7508250Z 
2020-04-24T19:12:47.7508250Z 
2020-04-24T19:12:47.7569888Z ##[error]Bash exited with code '1'.
2020-04-24T19:12:47.7583199Z ##[section]Finishing: Run build
2020-04-24T19:12:47.7631242Z ##[section]Starting: Checkout rust-lang/rust@refs/pull/71526/merge to s
2020-04-24T19:12:47.7636017Z Task         : Get sources
2020-04-24T19:12:47.7636345Z Description  : Get sources from a repository. Supports Git, TfsVC, and SVN repositories.
2020-04-24T19:12:47.7636631Z Version      : 1.0.0
2020-04-24T19:12:47.7636836Z Author       : Microsoft
2020-04-24T19:12:47.7636836Z Author       : Microsoft
2020-04-24T19:12:47.7637176Z Help         : [More Information](https://go.microsoft.com/fwlink/?LinkId=798199)
2020-04-24T19:12:47.7637551Z ==============================================================================
2020-04-24T19:12:48.0785112Z Cleaning any cached credential from repository: rust-lang/rust (GitHub)
2020-04-24T19:12:48.0834583Z ##[section]Finishing: Checkout rust-lang/rust@refs/pull/71526/merge to s
2020-04-24T19:12:48.0930516Z Cleaning up task key
2020-04-24T19:12:48.0931686Z Start cleaning up orphan processes.
2020-04-24T19:12:48.1109802Z Terminate orphan process: pid (3671) (python)
2020-04-24T19:12:48.1235713Z ##[section]Finishing: Finalize Job

I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact @rust-lang/infra. (Feature Requests)

@ecstatic-morse
Copy link
Contributor Author

ecstatic-morse commented Apr 24, 2020

This causes a new error for the case in #62189. I guess we should ignore statics in the initial version of this. Obviously, this would allow cycles like the following:

static A: i32 = {
    let p = &A;
    *p
};

@RalfJung
Copy link
Member

#71140 solved this by trying to trigger the cycle while performing the optimization that may remove it.

I don't think that's accurate. That PR solves the problem by making zero-sized accesses actually access the pointed-to data. It has nothing to do with optimizations, and IMO that is exactly the right fix for the problem: it makes it so that the dependency of a static on another is recorded in the query graph even if it is a ZST dependency.

@ecstatic-morse
Copy link
Contributor Author

ecstatic-morse commented Apr 24, 2020

That's true. For context, this is an attempt to address #71330 (comment). I guess it wasn't "optimizing away" in the MIR transform sense that caused #71330, but an optimization in the interpreter.


// If a cyclic data dependency exists within a const initializer, try to find
// it during const-checking. This is important because MIR optimizations could
// eliminate a cycle before const-eval runs. See #71078 for an example of this.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

#71078 isn't actually an example of this, as Ralf notes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Huh? It isn't? Or are you talking about a comment by Ralf from elsewhere but this PR?

@bors
Copy link
Contributor

bors commented Apr 25, 2020

☔ The latest upstream changes (presumably #71539) made this pull request unmergeable. Please resolve the merge conflicts.

//
// FIXME: This means we don't look for cycles involving associated constants, but we
// should handle fully monomorphized ones here at least.
if self.tcx.trait_of_item(def_id).is_none() {
Copy link
Contributor

Choose a reason for hiding this comment

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

You can invoke Instance::resolve here, which will either bail out or give you the correct assoc constant if it can be resolved with the information at hand.

@RalfJung
Copy link
Member

For context, this is an attempt to address #71330 (comment). I guess it wasn't "optimizing away" in the MIR transform sense that caused #71330, but an optimization in the interpreter.

Indeed, and thanks for the background link.

Which optimizations do we run on MIR before const-evalauting it? Maybe it would be better to just not optimize that MIR.

Also, what would be an example for an optimization turning a const-unsound item it a const-sound one? I can imagine those exist, but this discussion would be easier with an example. My hypothesis is that those are not actually soundness issues -- if the optimization can "mask" the failure, then I think it is actually perfectly fine to use that const (e.g., the user could have written the const in the optimized way to begin with, and that must be sound, too).

@RalfJung
Copy link
Member

The first example coming to my mind is a const reading from a mutable static, and discarding the result. Optimizations could eliminate the read. There is no bug here though, this is fine.

If this fixes a hole in the dynamic checks, there should be a testcase -- and ideally a miri.unleashed test case to make sure that indeed there is no hole any more.

@oli-obk
Copy link
Contributor

oli-obk commented Apr 26, 2020

Which optimizations do we run on MIR before const-evalauting it? Maybe it would be better to just not optimize that MIR.

All of them. We can't do this any other way except by duplicating all MIR, even in metadata. The query to obtain earlier MIR without optimizations has a Steal return value which the optimized_mir query steals in order to not have to clone the MIR.

@RalfJung
Copy link
Member

All of them. We can't do this any other way except by duplicating all MIR, even in metadata. The query to obtain earlier MIR without optimizations has a Steal return value which the optimized_mir query steals in order to not have to clone the MIR.

Okay, makes sense.

My other point remains though: the MIR optimizations I can think of, since they preserve behavior, do not actually make our dynamic checks unsound in any way.

The bug with recursive statics was not a MIR optimizations, it was an oversight in the MIR engine where it failed to properly inform the query system about some of the dependencies (namely dependencies via ZST reads -- which are irrelevant in terms of just the underlying value, but can be relevant in terms of safety invariants).

@oli-obk
Copy link
Contributor

oli-obk commented Apr 26, 2020

My other point remains though: the MIR optimizations I can think of, since they preserve behavior, do not actually make our dynamic checks unsound in any way.

I agree

@ecstatic-morse
Copy link
Contributor Author

ecstatic-morse commented Apr 26, 2020

Consider the following:

const A: i32 = {
    let _ = A;
    4
};

This currently fails to compile on stable but compiles successfully on nightly with #71330 applied. This is because MIR optimizations remove the useless load of A within its initializer; it is only observed by const-checking. This concerns me going forward because it makes removing MIR optimizations a possibly breaking change. You could imagine a more complex optimization pass for removing dead loads that is less battle tested than the current one. Now:

  1. Changing mir-opt-level could affect whether code compiles.
  2. Once that optimization pass is run by default on stable, it cannot be disabled backwards compatibly.

@RalfJung
Copy link
Member

RalfJung commented Apr 26, 2020

This concerns me going forward because it makes removing MIR optimizations a possibly breaking change.

Okay so we are not talking about fixing possible correctness problems any more, but about future-compat concerns. That's a totally different game.

And I agree, this is a future-compat hazard. I am not sure what to do about that though, it seems pretty inherent to the way our dynamic checks work.

We could possibly pretend that mir-opt-level is 0 for all static and const bodies, to at least lessen the impact of this? const fn should still get the full set of optimizations though.

Changing mir-opt-level could affect whether code compiles.

Note that this can only happen for cases where the static checks are missing things that the dynamic checks are properly catching. That helps to find places of possible concern here. Not sure how many such "gaps" we have, besides recursive-static-checking.

We should keep this in mind in the future whenever we consider relaxing the static checks and relying on the dynamic checks instead.

(I thought maybe panics or other dynamic post-monomorphization CTFE failures could be an issue, but it would be incorrect for an optimization to remove those.)

Changing mir-opt-level could affect whether code compiles.

Relatedly, -Cdebug-assertions (and thus -O) can effect whether code compiles, if the const-code hits a debug assertion (e.g., an integer overflow), and that assertion is gone with optimizations enabled. I think that is by design though.

@ecstatic-morse
Copy link
Contributor Author

ecstatic-morse commented Apr 26, 2020

From my POV, the problem is that, while we want to assume that variable accesses are side-effect free during MIR optimization, this assumption is violated while executing initializers. At this stage, simple reads can result in a cycle error and ultimately compilation failure.

We could possibly pretend that mir-opt-level is 0 for all static and const bodies, to at least lessen the impact of this? const fn should still get the full set of optimizations though.

Initializers can call const fn though, so I think we would need to do this everywhere to future-proof the following (although it currently fails to compile on both stable and nightly).

const A: usize = b();
const fn b() -> usize {
    let _: usize = A;
    42
}

@RalfJung
Copy link
Member

From my POV, the problem is that, while we want to assume that variable accesses are side-effect free during MIR optimization, this assumption is violated while executing initializers. At this stage, simple reads can result in a cycle error and ultimately compilation failure.

That sounds like a fair characterization.

Initializers can call const fn though, so I think we would need to do this everywhere to future-proof the following (although it currently fails to compile on both stable and nightly).

Indeed, my comment was meant to point out that that scheme would not be water-proof.

@RalfJung
Copy link
Member

Could you give a high-level description of how this PR solves the problem? The description gives a lot of background but does not explain that part (or I missed it).

@ecstatic-morse
Copy link
Contributor Author

It doesn't solve the problem. It does reduce its surface area by making things that used to cause cycle errors during const-checking prior to #71330 still cause cycle errors. These cycle errors were triggered incidentally while running the dataflow analysis that determines the set of qualifs in each local. We would call mir_const_qualif every time we saw a reference to a const inside a const initializer.

Cyclic data dependencies in initializers that involved statics, associated constants or went through a function call did not trigger a compile error because we use type-based qualification in all of those cases. Otherwise, #71078 would have caused a cycle error during const-checking when we tried to look up the qualifications in FOO while const-checking FOO.

@RalfJung
Copy link
Member

I see. I think this means we should have a miri-unleash test for cyclic ZST statics, to make sure that even if something like this lands, we still test #71140.

@oli-obk
Copy link
Contributor

oli-obk commented Apr 27, 2020

This concerns me going forward because it makes removing MIR optimizations a possibly breaking change.

I remember having talked about this with people during the move to using miri for CTFE. There were two options:

  1. Keep an unoptimized copy of MIR for const fn around for all const fn
  2. Evaluate optimized MIR, with all the problematic side effects that gives us.

The assumption was that any optimization that would cause previously not const evaluable MIR to become const evaluable, would trigger cycles or cause other errors itself.

Specifically for the cycle case shown here, shouldn't this already be happening, even for associated consts, since we now have the list of unevaluated constants in the MIR and evaluate them during codegen? We may have forgotten to evaluate them during ctfe though (cc @spastorino). If a MIR is never codegenned but is used in const eval, we should also evaluate the list of unevaluated constants.

@RalfJung
Copy link
Member

since we now have the list of unevaluated constants in the MIR and evaluate them during codegen? We may have forgotten to evaluate them during ctfe though (cc @spastorino). If a MIR is never codegenned but is used in const eval, we should also evaluate the list of unevaluated constants.

Ah, that's a good point! So maybe push_stack_frame should iterate that list and fire off a const-eval query for all of them? This is also needed for Miri, where whatever codegen does is irrelevant.

@oli-obk
Copy link
Contributor

oli-obk commented Apr 27, 2020

miri should get it automatically when we do it in push_stack_frame

@RalfJung
Copy link
Member

Yes that's what I meant.

We should probably have a shared method for this that is used by Miri and all codegen backends (Cc @bjorn3). Ideally we'd make it so that codegen backends cannot forget to call that method; I am not sure if that is possible.

@bjorn3
Copy link
Member

bjorn3 commented Apr 27, 2020

Ideally we'd make it so that codegen backends cannot forget to call that method; I am not sure if that is possible.

You could make a wrapper around instance_mir that accepts Instance instead of InstanceDef and evaluates all constants.

@ecstatic-morse
Copy link
Contributor Author

ecstatic-morse commented Apr 27, 2020

Do we wanna just close this PR? It can't address the general problem that I described above, although it does reduce the surface area a bit. I think this probably deserves a tracking issue.

I'm not sure how evaluating unused constants before CTFE as opposed to codegen fixes the problem. Is the point that the unoptimized MIR is still available then?

@joelpalmer joelpalmer added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. 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 May 5, 2020
@joelpalmer joelpalmer 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-author Status: This is awaiting some action (such as code changes or more information) from the author. labels May 14, 2020
@crlf0710 crlf0710 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-author Status: This is awaiting some action (such as code changes or more information) from the author. labels May 22, 2020
@crlf0710 crlf0710 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-author Status: This is awaiting some action (such as code changes or more information) from the author. labels May 30, 2020
@JohnCSimon JohnCSimon 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-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jun 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants