-
Notifications
You must be signed in to change notification settings - Fork 13k
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
allow constant evaluation of function calls #26848
Conversation
r? @pnkfelix (rust_highfive has picked a reviewer for you, use r? to override) |
ef3593b
to
6d1e92a
Compare
@@ -271,6 +271,7 @@ pub enum ConstVal { | |||
Bool(bool), | |||
Struct(ast::NodeId), | |||
Tuple(ast::NodeId), | |||
Function(ast::DefId), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not evaluate const fn
calls eagerly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was just following the Struct/Tuple definition. And it made things easier with Expr::Call
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, this is a path to a function, not a call. Don't mind me!
🗽 This is pretty neat! |
} | ||
}, | ||
Some(def::DefFn(id, _)) => return Ok(Function(id)), | ||
// FIXME: implement const methods? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should at least check that the function is const fn
- check_const
runs long after type collection - and type collection is what triggers the evaluation of array length expressions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that is unfortunate... but for all practical purposes irrelevant, since the function is simply interpreted as const fn and if it hits a non-const value anywhere const_eval bails out anyway. I'd rather keep const eval for all functions, so regular statements calling non-const-fn-but-could-be-const-fn functions with const arguments can also be const evaluated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does your PR do if it attempts to eval a call to a non const fn
whose body is more complex than a single expression?
From my initial reading of the PR, it seems like it is assuming that it is always given a const fn
and thus can assume that the block.expr.as_ref().unwrap()
is all it needs to look at in the body, in particular it is not (I think) double-checking that the block has no statements...
Update: oh wait, let me double check, but now I'm thinking that all such functions should have already been marked as NOT_CONST
from the check_const
run.
Update 2: But wait, as eddyb already said, check_const
runs long after type collection ... so it still seems like you could mistakenly think you are evaluating a non-const fn
correctly but in fact should have flagged the fn
as non-const on-the-fly...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea, I misunderstood. I will force const-fn here. This should also un-break all the unit tests :D
Maybe it might be a good idea to introduce a |
Is there any plan to fix rustc's usage of const_eval so it doesn't try to evaluate expressions that haven't gone through type-checking first? Fixing that would massively simplify const_eval, and make changes like this a lot easier to reason about. (See also #26683.) For the tests, probably should just implement "id" as a store+load from a static mut variable, which is basically guaranteed to be impossible to const-evaluate. |
yea, I had similar thoughts, but then got too lazy to introduce a PR before this one, but if preferred I can do that once @eefriedman gets his hint PR through |
☔ The latest upstream changes (presumably #26935) made this pull request unmergeable. Please resolve the merge conflicts. |
6d1e92a
to
0e2388d
Compare
☔ The latest upstream changes (presumably #26683) made this pull request unmergeable. Please resolve the merge conflicts. |
What's the status on this? Just needs review? Blocked on @eefriedman's PR? |
e41da4c
to
2e750e6
Compare
unsafety, | ||
abi, | ||
block, | ||
) = match try!(eval_const_expr_partial(tcx, callee, UncheckedExprNoHint, fn_args)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eefriedman this eval_const_expr_partial
gets me the DefId
of the function I'm calling. I'm not sure if anything but UncheckedExprNoHint
makes sense here.
I'll look at this more carefully tomorrow, but the way hints work inside of eval_const_expr_partial is that if the argument is ExprTypeChecked, all recursive calls should generally also be ExprTypeChecked. This limits the use of weird hinting semantics to places where it's absolutely necessary. |
Am I understanding correctly that this doesn't magically turn every |
Looking over this more carefully, I'm pretty sure this approach is going to exacerbate the existing problems with overflow in constant evaluation in type-checking. You simply can't correctly evaluate a const fn correctly in general without knowing the declared argument and result types. For example: const fn bmax() -> u8 { !0 }
const fn smax() -> u16 { !0 }
let c: [u8; bmax() as usize] = [0; 255];
let c: [u8; smax() as usize] = [0; 65535]; |
I will add that and similar functions with function arguments as test cases. For now I will raise an error in case the type is not yet in the ast_ty_to_ty map. I wonder how this plays with generics... |
☔ The latest upstream changes (presumably #27234) made this pull request unmergeable. Please resolve the merge conflicts. |
can you elaborate on what you mean here? Is this specifically because you were treating arbitrary fns as const fns? |
@oli-obk wrote:
@nikomatsakis asked:
Niko and I then reviewed the situation further, and determined the following:
If you follow that policy (of emitting code to panic at runtime -- which I guess you could emulate by just not const-evaluating the overflowing cases), then the hypothesis no longer holds: This would no longer be a breaking-change, and it would re-enforce a precedent that improvements to the const-evaluator should not be breaking changes. |
yes, but it applies also to my other branch about constant evaluation of indexing operations or any further operations or constant values that are added (e.g. Ranges).
I'll create an RFC to make this the standard procedure for compiler-improvements that detect an error at compile-time, but insert an unconditional runtime-panic to not be a breaking change.
This would require 2 things
|
RFC: rust-lang/rfcs#1229 |
This RFC was merged, any chance the PR can get rebased? |
I implemented the RFC, there'll be a PR for it soon, after that, I'll rebase this PR over the RFC-PR and add the appropriate regression tests. |
Great! On Fri, Oct 2, 2015 at 5:08 AM, Oliver Schneider [email protected]
|
0c978cb
to
084c171
Compare
done and rebased depends on PR #28845 (rfc 1229) |
☔ The latest upstream changes (presumably #28845) made this pull request unmergeable. Please resolve the merge conflicts. |
084c171
to
72f42f1
Compare
now that #28845 (RFC 1229) has landed, we can merge this without causing breaking changes |
So we cannot assume that the function call was marked NOT_CONST by check_const.
}, | ||
_ => signal!(e, NonConstPath), | ||
}; | ||
if let ExprTypeChecked = ty_hint { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eddyb's comment is addressed here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you for pointing this out.
Despite my misgivings about the change to the I would nonetheless be interested in hearing @oli-obk 's opinion on whether that change to the test should stand, or if a followup PR should perhaps return it to its former state (or something similar to that). |
@bors r+ |
📌 Commit 2b000fe has been approved by |
this has the funky side-effect of also allowing constant evaluation of function calls to functions that are not `const fn` as long as `check_const` didn't mark that function `NOT_CONST` It's still not possible to call a normal function from a `const fn`, but let statements' initialization value can get const evaluated (this caused the fallout in the overflowing tests) we can now do this: ```rust const fn add(x: usize, y: usize) -> usize { x + y } const ARR: [i32; add(1, 2)] = [5, 6, 7]; ``` also added a test for destructuring in const fn args ```rust const fn i((a, b): (u32, u32)) -> u32 { a + b } //~ ERROR: E0022 ``` This is a **[breaking change]**, since it turns some runtime panics into compile-time errors. This statement is true for ANY improvement to the const evaluator.
} else { | ||
// we don't know much about the function, so we force it to be a const fn | ||
// so compilation will fail later in case the const fn's body is not const | ||
assert_eq!(constness, hir::Constness::Const) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm getting a ICE here with the current nightly on playpen:
thread 'rustc' panicked at 'assertion failed: `(left == right)` (left: `NotConst`, right: `Const`)', ../src/librustc/middle/const_eval.rs:106
in this simple test program:
fn f(x: usize) -> usize {
x
}
const Y: usize = f(2);
fn main() {
let z = [0; X];
}
Curiously if you move the const Y...
line to the top, then it at least doesn't ICE, although I'd expect to get E0015 as an error, rather than E0307 (you can then get the E0015 by removing the let z = ...
line)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't the right place to file a bug report; try https://github.com/rust-lang/rust/issues/new . (You can link to the PR from there if you think it's useful.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just been playing on my phone on playpen on the bus, and won't have a laptop for at least a week, so was just hoping to put this somewhere that someone would spot it, and they can triage it (otherwise I'll just forget)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filed #29587
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thanks
On Wed, 4 Nov 2015 at 20:46 eefriedman [email protected] wrote:
In src/librustc/middle/const_eval.rs
#26848 (comment):
},
_ => signal!(e, NonConstPath),
}
} else {
signal!(e, NonConstPath)
},
_ => signal!(e, NonConstPath),
};
if let ExprTypeChecked = ty_hint {
// no need to check for constness... either check_const
// already forbids this or we const eval over whatever
// we want
} else {
// we don't know much about the function, so we force it to be a const fn
// so compilation will fail later in case the const fn's body is not const
assert_eq!(constness, hir::Constness::Const)
—
Reply to this email directly or view it on GitHub
https://github.com/rust-lang/rust/pull/26848/files#r43936838.
Won't this introduce a backcompat hazard? Functions which are const but not marked as such may become non-const in the future, breaking libs which depend on this. |
@Manishearth it's impossible to use non-const functions in a const environment |
I'm talking about
|
yes, normal code can be const evaluated. But in case the const evaluation fails, it falls back to normal code generation anyway. I had an RFC for that and implemented it a while back. |
as you can see in #29587, there's actually an assertion preventing the use of non-const functions in a true const environment. |
this has the funky side-effect of also allowing constant evaluation of function calls to functions that are not
const fn
as long ascheck_const
didn't mark that functionNOT_CONST
It's still not possible to call a normal function from a
const fn
, but let statements' initialization value can get const evaluated (this caused the fallout in the overflowing tests)we can now do this:
also added a test for destructuring in const fn args
This is a [breaking change], since it turns some runtime panics into compile-time errors. This statement is true for ANY improvement to the const evaluator.