-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Evaluate fixed-length array length expressions lazily. #44275
Conversation
Started a couple crater runs for this PR and my hacky branch enabling |
Failed to link
That thing demangled is: <[u8; Const {
ty. usize,
val. Unevaluated(DefId {
krate. CrateNum(30),
node. DefIndex(2147484848) => rustc_data_structures/a34856b..stable_hasher[0]..{{impl}}[2]..{{initializer}}[0]
}, Slice([]))
}] as rustc_data_structures..stable_hasher..StableHasherResult>::finish::h51cdc1bd689ed229 |
That's funny - I guess I never tried to bootstrap! |
If I understand correctly the symbol is referring to this impl StableHasherResult for [u8; 20] {
fn finish(mut hasher: StableHasher<Self>) -> Self {
...
}
} |
@kennytm Yeah, I just missed a spot - technically that location would already have an issue with associated types but maybe that one is harder to exhibit. EDIT: nevermind, this is not a normalization issue, just a printing one. Fix underway. |
49e8ea5
to
3a15be7
Compare
@nikomatsakis This is horrible, |
I ended up going with a simpler but less informative output for now to side-step the issue. |
Crater report shows no regressions, so this is probably fine for now. |
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.
Feel free to ignore these comments.
} | ||
#[derive(Copy, Clone, Debug, Hash, RustcEncodable, Eq, PartialEq)] | ||
pub struct ByteArray<'tcx> { | ||
pub data: &'tcx [u8], |
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.
Making this a Slice
would seem like a good idea (and other similar cases below) but I guess it can be left for later; I suppose doing that now would possibly require more guarantees on the part of the code?
@@ -58,6 +59,13 @@ impl<'tcx, T: Lift<'tcx>, E: Lift<'tcx>> Lift<'tcx> for Result<T, E> { | |||
} | |||
} | |||
|
|||
impl<'tcx, T: Lift<'tcx>> Lift<'tcx> for Box<T> { |
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 feels suspicious. Why do we have boxes of pointers? Maybe I'm not understanding something...
ConstVal::Aggregate(ConstAggregate::Struct(fields)) => { | ||
let new_fields: Vec<_> = fields.iter().map(|&(name, v)| { | ||
(name, v.fold_with(folder)) | ||
}).collect(); |
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 looks terrible for performance. Could we use an AccumulateVec
here (and in the below code)? Ideally, of course, we wouldn't need to collect
at all when the vectors are equal, but other than adding a boolean to keep track of that as we go there's probably little we can do.
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.
Aggregate
should be rare and unused most of the time - it will be soon replaced by a miri allocation instead.
} | ||
} | ||
hir::ExprType(ref e, _) => cx.eval(e)?, | ||
hir::ExprTup(ref fields) => { | ||
Tuple(fields.iter().map(|e| cx.eval(e)).collect::<Result<_, _>>()?) | ||
let values = fields.iter().map(|e| cx.eval(e)).collect::<Result<Vec<_>, _>>()?; | ||
mk_const(Aggregate(Tuple(tcx.alloc_const_slice(&values)))) |
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.
Shouldn't we be using mk_const_slice
here? That would avoid the Vec
in the common(?) case of having less than eight fields.
There seem to be more cases of this in this file as well.
impl<'a, 'tcx> SpecializedDecoder<ByteArray<'tcx>> for DecodeContext<'a, 'tcx> { | ||
fn specialized_decode(&mut self) -> Result<ByteArray<'tcx>, Self::Error> { | ||
Ok(ByteArray { | ||
data: self.tcx().alloc_byte_array(&Vec::decode(self)?) |
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.
Do we not have a decoder for AcccumulateVec
yet? We should use that I think here if possible...
☔ The latest upstream changes (presumably #44253) made this pull request unmergeable. Please resolve the merge conflicts. |
@@ -23,5 +23,5 @@ const fn f(x: usize) -> usize { | |||
|
|||
#[allow(unused_variables)] | |||
fn main() { | |||
let a : [i32; f(X)]; //~ NOTE for array length here | |||
let a : [i32; f(X)]; //~ NOTE for constant expression 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.
This is a slight decrease in diagnostic quality
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.
With moving to const generics the harcoded messages seem bizarre at best to me - the expression happens to be in an array length, the span is more important that that.
The description could potentially be recovered by walking up the HIR but that only works in the local crate.
@@ -8,11 +8,12 @@ | |||
// option. This file may not be copied, modified, or distributed | |||
// except according to those terms. | |||
|
|||
// error-pattern: unsupported cyclic reference between types/traits detected |
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.
Change it into a ui test?
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, I just realized this is a bit suboptimal. Or rather, the span is lost of some reason.
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 bug with ParamEnvAnd
Key
impl for the query system!
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.
Welp, no, this is the best I get right now:
error[E0391]: unsupported cyclic reference between types/traits detected
--> /home/eddy/Projects/rust-2/src/libcore/mem.rs:192:14
|
192 | unsafe { intrinsics::size_of::<T>() }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ cyclic reference
|
There is a note at the bottom of the cycle which shows the expression that triggered the cycle, so not all is lost!
However, I believe there are two ParamEnv
's with different Reveal
modes on the cycle stack, so the cycle isn't detected early enough... cc @nikomatsakis
@@ -962,16 +960,17 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> { | |||
debug!("trans_constant({:?})", constant); | |||
let ty = self.monomorphize(&constant.ty); | |||
let result = match constant.literal.clone() { | |||
mir::Literal::Item { def_id, substs } => { |
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.
🎉
@bors try |
⌛ Trying commit e873b4f63ea3f93174bd68cc416701f7f276aa35 with merge 2a8d01143216b1b287134b3f2026fa1b871117d9... |
@Mark-Simulacrum ^^ let's see if we can get some perf comparisons from this! |
☀️ Test successful - status-travis |
Looks like a regression on most benchmarks: http://perf.rust-lang.org/compare.html?commit_a=2f1ef9ef1181298d46e79d5dde6bafeb6483926f&commit_b=2a8d01143216b1b287134b3f2026fa1b871117d9&stat=instructions%3Au. |
@Mark-Simulacrum Hmm, regression is expected of course, but I can't tell how bad that is just from looking at it - is there any per-pass timing? |
No, per-pass timing isn't currently available. It seems to be in higher demand than I would expect, so I've filed rust-lang/rustc-perf#147 for discussion. |
It may also be helpful to look at the timing data (though not sure how reliable I'd consider it) for the diff: http://perf.rust-lang.org/compare.html?commit_a=2f1ef9ef1181298d46e79d5dde6bafeb6483926f&commit_b=2a8d01143216b1b287134b3f2026fa1b871117d9&stat=cpu-clock. Not sure if you've already examined that though. |
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.
On the whole, this all makes a lot of sense. I left some questions though.
@@ -493,7 +494,7 @@ impl<'a, 'tcx> TyCtxt<'a, 'tcx, 'tcx> { | |||
let param_env = ty::ParamEnv::empty(Reveal::All); | |||
let value = self.erase_regions(value); | |||
|
|||
if !value.has_projection_types() { | |||
if !value.has_projections() { |
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.
Idle speculation: I wonder if we should call this requires_normalization
, at some point.
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.
needs_normalizing
?
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.
also fine
@@ -540,6 +540,29 @@ fn process_predicate<'a, 'gcx, 'tcx>( | |||
} | |||
} | |||
} | |||
|
|||
ty::Predicate::ConstEvaluatable(def_id, substs) => { | |||
match selcx.tcx().lift_to_global(&obligation.param_env) { |
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.
Hmm, this part seems...a bit suspicious somehow. For now it's probably ok to just pass None
results through, but I think we're going to want to revisit what's happening here. I guess that -- in the end -- my plan to "chalkify" things will mean that all param-env and queries can be converted into a closed (and global) form, so this will be ok.
|
||
fn fold_const(&mut self, constant: &'tcx ty::Const<'tcx>) -> &'tcx ty::Const<'tcx> { | ||
if let ConstVal::Unevaluated(def_id, substs) = constant.val { | ||
if substs.needs_infer() { |
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.
wait -- what is happening here -- can you leave a comment? I think what is happening is that, if inference results are needed, we are taking a kind of "stab" at doing the evaluation with "generic" substitutions, just to see if it cares at all about the results of inference, is that right?
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.
Yes, should I try freshening for this instead?
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.
Hmm. Well, freshening will substitute FreshTy
values, which most of the system is not setup to cope with. I'm not sure I'd go down that road (yet). I do think as part of reworking traits -- and especially implementing higher-ranked trait bounds on types -- we would have the option of substituting skolemized things ...
... I guess plausibly you could freshen, create a fresh inference context, remap those freshened types to fresh variables in there ... this is sort of roughly what chalk does ... but would the const-eval code maybe unify them in any way, or would it just fail to make progress if it encountered an unresolved type variable?
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.
const-eval is global so it'd just error if it can't make a decision.
@@ -687,6 +687,21 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> { | |||
} | |||
} | |||
} | |||
|
|||
ty::Predicate::ConstEvaluatable(def_id, substs) => { | |||
match self.tcx().lift_to_global(&(obligation.param_env, substs)) { |
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.
Not entirely clear to me why this would behave differently from the code above, which seems to have some more "fallback" options...
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.
They should just both use freshening, right?
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 think they should do the same, in any case
src/librustc/ty/relate.rs
Outdated
expected_found(relation, &sz_a_u64, &sz_b_u64))) | ||
} | ||
} | ||
_ => Ok(tcx.types.err) |
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.
So, it's ok to use err
here because we currently expect these to always be evaluatable? Can we add a comment about it? Also, I'd prefer the return type of the closure to be Result<u64, ErrorRepored>
, rather than Option<u64>
, since it makes it clearer in the type what is expected, and we are less likely to make the closure return None
without thinking about it (I personally would also write (Err(ErrorReported), _) | (_, Err(ErrorReported)) => {
instead of _
, as insurance against the type changing...)
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 have a delay_span_bug
so it's impossible to produce tcx.types.err
without a compiler error. We need lazy normalization to really do anything smarter here.
☔ The latest upstream changes (presumably #44142) made this pull request unmergeable. Please resolve the merge conflicts. |
☔ The latest upstream changes (presumably #44316) made this pull request unmergeable. Please resolve the merge conflicts. |
0365c26
to
57ebd28
Compare
@bors r+ OK, so, as per our discussion on IRC, there are various things we can do better here, but what you're doing now -- particularly around the freshening bits -- seems ok (not unsound, anyway). I feel like anything we do going forward should be compatible with it. |
📌 Commit 57ebd28 has been approved by |
Evaluate fixed-length array length expressions lazily. This is in preparation for polymorphic array lengths (aka `[T; T::A]`) and const generics. We need deferred const-evaluation to break cycles when array types show up in positions which require knowing the array type to typeck the array length, e.g. the array type is in a `where` clause. The final step - actually passing bounds in scope to array length expressions from the parent - is not done because it still produces cycles when *normalizing* `ParamEnv`s, and @nikomatsakis' in-progress lazy normalization work is needed to deal with that uniformly. However, the changes here are still useful to unlock work on const generics, which @EpicatSupercell manifested interest in, and I might be mentoring them for that, but we need this baseline first. r? @nikomatsakis cc @oli-obk
☀️ Test successful - status-appveyor, status-travis |
This is in preparation for polymorphic array lengths (aka
[T; T::A]
) and const generics.We need deferred const-evaluation to break cycles when array types show up in positions which require knowing the array type to typeck the array length, e.g. the array type is in a
where
clause.The final step - actually passing bounds in scope to array length expressions from the parent - is not done because it still produces cycles when normalizing
ParamEnv
s, and @nikomatsakis' in-progress lazy normalization work is needed to deal with that uniformly.However, the changes here are still useful to unlock work on const generics, which @EpicatSupercell manifested interest in, and I might be mentoring them for that, but we need this baseline first.
r? @nikomatsakis cc @oli-obk