-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Optimize fold_ty
#107627
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
Optimize fold_ty
#107627
Changes from all commits
bac7628
f08a337
c2cf3f7
fb8e681
4aec134
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -140,79 +140,21 @@ impl<'a, 'tcx> TypeFolder<'tcx> for TypeFreshener<'a, 'tcx> { | |
} | ||
} | ||
|
||
#[inline] | ||
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { | ||
if !t.needs_infer() && !t.has_erasable_regions() { | ||
return t; | ||
} | ||
|
||
let tcx = self.infcx.tcx; | ||
|
||
match *t.kind() { | ||
ty::Infer(ty::TyVar(v)) => { | ||
let opt_ty = self.infcx.inner.borrow_mut().type_variables().probe(v).known(); | ||
self.freshen_ty(opt_ty, ty::TyVar(v), ty::FreshTy) | ||
} | ||
t | ||
} else { | ||
match *t.kind() { | ||
ty::Infer(v) => self.fold_infer_ty(v).unwrap_or(t), | ||
|
||
ty::Infer(ty::IntVar(v)) => self.freshen_ty( | ||
self.infcx | ||
.inner | ||
.borrow_mut() | ||
.int_unification_table() | ||
.probe_value(v) | ||
.map(|v| v.to_type(tcx)), | ||
ty::IntVar(v), | ||
ty::FreshIntTy, | ||
), | ||
// This code is hot enough that a non-debug assertion here makes a noticeable | ||
// difference on benchmarks like `wg-grammar`. | ||
#[cfg(debug_assertions)] | ||
ty::Placeholder(..) | ty::Bound(..) => bug!("unexpected type {:?}", t), | ||
|
||
ty::Infer(ty::FloatVar(v)) => self.freshen_ty( | ||
self.infcx | ||
.inner | ||
.borrow_mut() | ||
.float_unification_table() | ||
.probe_value(v) | ||
.map(|v| v.to_type(tcx)), | ||
ty::FloatVar(v), | ||
ty::FreshFloatTy, | ||
), | ||
|
||
ty::Infer(ty::FreshTy(ct) | ty::FreshIntTy(ct) | ty::FreshFloatTy(ct)) => { | ||
if ct >= self.ty_freshen_count { | ||
bug!( | ||
"Encountered a freshend type with id {} \ | ||
but our counter is only at {}", | ||
ct, | ||
self.ty_freshen_count | ||
); | ||
} | ||
t | ||
_ => t.super_fold_with(self), | ||
} | ||
|
||
ty::Generator(..) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Additional question: Does the wildcard have a perf difference over the exhaustive match? Otherwise, I kinda prefer the exhaustive match. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It probably does. Transforming exhaustive matches to a wildcard in the code generation may be a good idea if so. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It depends on the use case. If the match can be converted to a table lookup, the exhaustive match will have one less branch in LLVM, but have a bigger lookup table: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c7b21a7f9d032aea5aa261953a85d735 For actual branching logic, it doesn't really matter. There may be a larger lookup table in LLVM IR, but that will become the same thing at the assembly level There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the debug assertion for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (It makes me sad that we discover the need to do micro-optimizations like re-encoding a big or-pattern arm as a wildcard; I, like @compiler-errors, find value in the exhaustive match from the view point of maintenance. Are we keeping track of efforts, if any, to put such a transformation into rustc itself?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (I could even imagine an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem here is not "wildcard is faster than manually listing the alternatives". The problem is the assertion on If that assertion wasn't necessary, then you can do an exhaustive match that is the same speed as a wildcard match. (I just tried it out; same speed.) Though I would argue that an exhaustive match probably isn't appropriate when |
||
| ty::Bool | ||
| ty::Char | ||
| ty::Int(..) | ||
| ty::Uint(..) | ||
| ty::Float(..) | ||
| ty::Adt(..) | ||
| ty::Str | ||
| ty::Error(_) | ||
| ty::Array(..) | ||
| ty::Slice(..) | ||
| ty::RawPtr(..) | ||
| ty::Ref(..) | ||
| ty::FnDef(..) | ||
| ty::FnPtr(_) | ||
| ty::Dynamic(..) | ||
| ty::Never | ||
| ty::Tuple(..) | ||
| ty::Alias(..) | ||
| ty::Foreign(..) | ||
| ty::Param(..) | ||
| ty::Closure(..) | ||
| ty::GeneratorWitnessMIR(..) | ||
| ty::GeneratorWitness(..) => t.super_fold_with(self), | ||
|
||
ty::Placeholder(..) | ty::Bound(..) => bug!("unexpected type {:?}", t), | ||
} | ||
} | ||
|
||
|
@@ -253,3 +195,54 @@ impl<'a, 'tcx> TypeFolder<'tcx> for TypeFreshener<'a, 'tcx> { | |
} | ||
} | ||
} | ||
|
||
impl<'a, 'tcx> TypeFreshener<'a, 'tcx> { | ||
// This is separate from `fold_ty` to keep that method small and inlinable. | ||
#[inline(never)] | ||
fn fold_infer_ty(&mut self, v: ty::InferTy) -> Option<Ty<'tcx>> { | ||
nnethercote marked this conversation as resolved.
Show resolved
Hide resolved
|
||
match v { | ||
ty::TyVar(v) => { | ||
let opt_ty = self.infcx.inner.borrow_mut().type_variables().probe(v).known(); | ||
Some(self.freshen_ty(opt_ty, ty::TyVar(v), ty::FreshTy)) | ||
} | ||
|
||
ty::IntVar(v) => Some( | ||
self.freshen_ty( | ||
self.infcx | ||
.inner | ||
.borrow_mut() | ||
.int_unification_table() | ||
.probe_value(v) | ||
.map(|v| v.to_type(self.infcx.tcx)), | ||
ty::IntVar(v), | ||
ty::FreshIntTy, | ||
), | ||
), | ||
|
||
ty::FloatVar(v) => Some( | ||
self.freshen_ty( | ||
self.infcx | ||
.inner | ||
.borrow_mut() | ||
.float_unification_table() | ||
.probe_value(v) | ||
.map(|v| v.to_type(self.infcx.tcx)), | ||
ty::FloatVar(v), | ||
ty::FreshFloatTy, | ||
), | ||
), | ||
|
||
ty::FreshTy(ct) | ty::FreshIntTy(ct) | ty::FreshFloatTy(ct) => { | ||
if ct >= self.ty_freshen_count { | ||
bug!( | ||
"Encountered a freshend type with id {} \ | ||
but our counter is only at {}", | ||
ct, | ||
self.ty_freshen_count | ||
); | ||
} | ||
None | ||
} | ||
} | ||
} | ||
} |
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.
Personally think this should stay an "always" assertion
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.
agreed. To avoid any performance issues, this could call a
#[cold]
function with thebug!
inside instead of having the formatting inside the main functionThere 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 tried changing it back to an always assertion, and it had a noticeable perf impact, e.g. the instruction count for
wg-grammar
increased by 0.7%. I then tried the#[cold]
function and it made a small improvement, but was still 0.5% worse.So I will leave this as is, but I will add a comment about it.