-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Long-running const-eval (loops) unusable in practice. #93481
Comments
I moved your comment from the other issue, as it is unrelated. Fixing the other issue will not make your problem worse. Do you have more information about your failure? E.g. the entire error? Your code should definitely work, and we're gonna make it work. What makes nightly different for your case? |
Thanks a lot! This was the issue linked to by the compiler so I thought it belongs there. What I'm trying to do is to fill some arrays with pre-computed values in compile-time. The computation shouldn't take more than few seconds and is mostly bearable when I put it somewhere, e.g. in "independent" (of the project code) build.rs file. However, having it in the build script slows down The computation on its own is not too "heavy": it fills an array of up to ~ Error messageWhen trying to compile the compile-time evaluation version of the computation, I get two errors:
and
I don't know exactly why these two are pointed out by the compiler but it seems that in both cases the variables in question are really large (those are bitmasks, so I'm moving 1s close to the Most Significant Bit of Unstable RustSo, the thing that forces me to use Nightly Rust is that there I can do
After I do that and switch to ContextAlso, apologies for the code being super dense and maybe hard to understand. Please let me know if you want me to extract (and possibly minimize) the entire snippet to make it independent of the project I'm building (it's quite easy to do so) or just make it cleaner & easier to follow. If you need more information, please let me know. |
Thanks for the details, yea, this is an unfortunate situation. The only recommendation I have at this time for stable code is to split the computation into multiple constants and merge it in another constant. A minimal repro is const _: () = {
let mut i = 333_333;
while i > 0 {
i -= 1;
}
}; If the initial value of It is very suboptimal that stable code mentions Opinions @rust-lang/wg-const-eval |
Thanks! Splitting is... I would say very hard: the code is really tightly coupled, I don't think I can decompose. Stabilizing |
Yea... it's hard to come up with a good limit. The problem is that we also want a quick response when you end up in an infinite loop. Can you check what limit will allow your example to run so we have some numbers? |
@oli-obk Is there somewhere to read more detail on this? It seems to me that const eval shouldn't necessarily be special compared to just running a program -- i.e., an infinite loop leads to hanging, which we probably 'always' want to diagnose (unlikely to be intended unless written literally as In general a limit seems pretty annoying, since presumably then users will constantly need to be bumping it or setting it "infinitely" high if they're e.g. expecting to deserialize configuration data at compile-time which can be somewhat arbitrarily large. That seems like a pretty terrible experience, and comparatively the value of "detecting stalls" feels lower. Ideally we'd have a nice way to debug those -- maybe we can hook things up such that rustc will provide and/or dump a const backtrace in some way when stopped via gdb/lldb or something like that? Those are typically the tools I at least reach for when trying to debug a stalled loop in a normal program, and it's not clear that it's terrible for const to be similar. |
@Mark-Simulacrum I would liken this more to the recursion limit that we have for trait resolution. Both are really just different forms of user-defined computation happening at compile time. The compiler cannot reliably tell apart infinite loops from long computations, so any heuristic that diagnoses infinite loops will be wrong sometimes. |
Sure, they're similar/equivalent at a theoretical level, but I suspect that many users will not view them as such. Notably, it seems easy to write long-running const-eval code (it's just regular Rust code!) whereas the number of folks writing traits and clauses that cause 'sufficiently deep' recursion seems pretty likely to be low and/or at least more indicative of unintentional behavior. Maybe it just means we should bump the default limit up really high (e.g., so that it takes minutes to get there on an average computer), or make an assumption that most users aren't using const-eval to compute anything serious, but it feels like a materially different case to me compared to trait resolution based on how users typically interact with these things. My suggestion of basically removing the limit entirely and working on good interfaces to debugging const eval (e.g., making gdb/lldb or similar tooling work well with const eval) sort of goes towards that direction -- it leaves the user to determine whether they've created an infinite loop or not, and then provides tools to fix it, rather than heuristically guessing at an arbitrary point that a loop has been hit. |
A quick binary search over |
So that's 30-40 times the current default limit. |
|
FWIW I'm also coming up against this. Thankfully I can work around by splitting the expression, but I'd definitely be grateful for stabilising |
Sorry I'm coming in late. It's not clear to me exactly if there are any blockers with regards to stabilizing |
I'm on board with just scrapping the limit entirely and adding some way for users to output Having a print mechanism doesn't need to be a blocker for just removing the limit. |
Wrt automatically detecting loops, we've had that before and it was messy logic. We can detect plain |
I think this all sounds good, sounds like the next steps are (posting explicitly here to make sure everyone's okay with it, and I'm reading @oli-obk 's responses correctly):
|
Miri has an option to print a warning every N basic blocks of execution. We could have something similar for CTFE, maybe, if we have a way of emitting diagnostics during CTFE without halting evaluation. (Having that would also be useful for #99923. )
|
You should be able to emit arbitrary diagnostics from at least any part of the compiler that has access to Though I am a bit confused since I thought miri already emitted lint warnings and whatnot? However, I suppose that extremely speculative interpretation could be an issue. |
Do you mean the core interpreter, or Miri-the-tool? Miri-the-tool has some support for non-halting diagnostics. But the core interpreter never emits a diagnostic, it just raises errors and then it is up to whoever created the The interpreter runs inside a query so I have no idea what happens when we start emitting diagnostics in it. If you have lots of constants you might also get lots of diagnostics... and it's unclear how |
All diagnostics involving HIR/MIR/type-system/trait-system/etc. come from inside queries, so that's all good. This is an example of emitting a MIR lint (with significant metadata from ( (just realized that the second part of this is quite long, so I'm posting this smaller comment first) |
For "running too long", we can at least narrow the "loopy frames" if we:
So my thought process here is that for something like this: const fn root() {
leaf_1();
loopy();
leaf_2();
}
const fn loopy() {
leaf_3();
for _ in ... {
leaf_4();
}
leaf_5();
}
const fn leaf_{1,2,...}() {
// Does some work but relatively quickly.
} (with potentially more frames above/below An even cooler thing (but more expensive so probably not a good idea?) is to have in each frame
In a sense, this is an approximation of the (BB-level) execution trace for that frame. When entering a block
Even ignoring loops, conditions like these can be used to categorize a block
Though you also need to track "total elapsed while in That only helps you with the Well that's enough ranting, I doubt most of this is implementable anyway. However, if done isolated enough to not leak implementation details into the rest of miri (esp. if this can be done through machine hooks, per-frame machine-specific data etc.), it might be a decent compromise of "well-bounded" (it would be fun to end up with a miri-based coverage/heatmap system, that merges the per-frame data into something more global/per-instance, every time a frame is popped - for the existing instrumentation-based coverage system, miri could even output a compatible file with existing tooling, and all of the above shenanigans aren't even relevant, since miri-the-too could literally be interpreting the coverage counter increment instructions, into a global set of coverage counters, heh) |
Oh another last-minute idea (should maybe get own issue?): so right now Ctrl+C just kills I guess it would have to go on the "cache miss, time to run provider" codepath... oh yeah we could make the provider But for miri specifically, we can have it check some And after the Ctrl+C thread has tried to get the message across to miri and/or the query system, it can put itself to sleep for long enough (100ms?) to give everything a chance to react, but when it wakes up it either:
rustfilt --help > /dev/null || cargo install --force rustfilt
echo 'fn main() {}' | rustc - -C linker="$(
bash -c '\
PROXY="$(mktemp --tmpdir proxy.XXXXXXXX)"; \
echo "#!/usr/bin/env bash" > "$PROXY" && \
echo "$0" >> "$PROXY" && \
chmod +x "$PROXY" && \
echo "$PROXY" \
' \
'gdb -q --pid=$PPID -ex bt | grep "^#" | rustfilt; false'
)"
Oh yeah that works, cool! cc @wesleywiser |
We are encountering this limit in practice evaluating constants in a bignum arithmetic library: RustCrypto/crypto-bigint#130 (comment) Namely we're trying to add support for modular arithmetic where the moduli are represented as (associated) constants, and encountering this limit while trying to compute the constant values using This is a fairly trivial computation compared to what we would like to eventually do with |
Bumping up priority so this issue will get more eyeballs during T-compiler meeting @rustbot -P-medium +P-high |
Discussed at 2023 Q1 P-high review Is this resolved by PR #106227? Or is there more work we need to do here beyond that? |
No, that just made us not regress it. We would still need to get rid of the limit entirely and report some sort of progress messages. Ideally we'd also catch Ctrl+C and print a const eval backtrace before exiting rustc. |
Something like #103877 would have helped though, right? |
After further discussion in P-high review meeting linked above, decided to downgrade this to a P-medium feature request @rustbot label: -P-high +P-medium |
I've recently hit the problem of the hard error in the case when there is no infinite loop: my code generates PEXT Bitboards for a chess engine and it requires few seconds to finish but it's certainly not an infinite loop. The problem is that it forces me into either computing in the build time and going the code generation route (which is not pleasant) or having to only allow Rust Nightly which is maybe even less pleasant.
Originally posted by @kirillbobyrev in #71800 (comment)
The text was updated successfully, but these errors were encountered: