Auto merge of #97936 - nnethercote:compile-unicode_normalization-faster, r=oli-obk
Compile `unicode-normalization` faster Various optimizations and cleanups aimed at improving compilation of `unicode-normalization`, which is notable for having several very large `match`es with many char ranges. Best reviewed one commit at a time. r? `@oli-obk`
This commit is contained in:
commit
cacc75c82e
|
@ -227,15 +227,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
_ => (None, 0),
|
_ => (None, 0),
|
||||||
};
|
};
|
||||||
if let Some((min, max, sz)) = range {
|
if let Some((min, max, sz)) = range {
|
||||||
if let (Some(lo), Some(hi)) = (lo.try_to_bits(sz), hi.try_to_bits(sz)) {
|
// We want to compare ranges numerically, but the order of the bitwise
|
||||||
// We want to compare ranges numerically, but the order of the bitwise
|
// representation of signed integers does not match their numeric order. Thus,
|
||||||
// representation of signed integers does not match their numeric order.
|
// to correct the ordering, we need to shift the range of signed integers to
|
||||||
// Thus, to correct the ordering, we need to shift the range of signed
|
// correct the comparison. This is achieved by XORing with a bias (see
|
||||||
// integers to correct the comparison. This is achieved by XORing with a
|
// pattern/_match.rs for another pertinent example of this pattern).
|
||||||
// bias (see pattern/_match.rs for another pertinent example of this
|
//
|
||||||
// pattern).
|
// Also, for performance, it's important to only do the second `try_to_bits` if
|
||||||
let (lo, hi) = (lo ^ bias, hi ^ bias);
|
// necessary.
|
||||||
if lo <= min && (hi > max || hi == max && end == RangeEnd::Included) {
|
let lo = lo.try_to_bits(sz).unwrap() ^ bias;
|
||||||
|
if lo <= min {
|
||||||
|
let hi = hi.try_to_bits(sz).unwrap() ^ bias;
|
||||||
|
if hi > max || hi == max && end == RangeEnd::Included {
|
||||||
// Irrefutable pattern match.
|
// Irrefutable pattern match.
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
|
@ -632,39 +632,30 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
(&TestKind::Range(test), &PatKind::Range(pat)) => {
|
(&TestKind::Range(test), &PatKind::Range(pat)) => {
|
||||||
|
use std::cmp::Ordering::*;
|
||||||
|
|
||||||
if test == pat {
|
if test == pat {
|
||||||
self.candidate_without_match_pair(match_pair_index, candidate);
|
self.candidate_without_match_pair(match_pair_index, candidate);
|
||||||
return Some(0);
|
return Some(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let no_overlap = (|| {
|
// For performance, it's important to only do the second
|
||||||
use rustc_hir::RangeEnd::*;
|
// `compare_const_vals` if necessary.
|
||||||
use std::cmp::Ordering::*;
|
let no_overlap = if matches!(
|
||||||
|
(compare_const_vals(self.tcx, test.hi, pat.lo, self.param_env)?, test.end),
|
||||||
let tcx = self.tcx;
|
(Less, _) | (Equal, RangeEnd::Excluded) // test < pat
|
||||||
|
) || matches!(
|
||||||
let test_ty = test.lo.ty();
|
(compare_const_vals(self.tcx, test.lo, pat.hi, self.param_env)?, pat.end),
|
||||||
let lo = compare_const_vals(tcx, test.lo, pat.hi, self.param_env, test_ty)?;
|
(Greater, _) | (Equal, RangeEnd::Excluded) // test > pat
|
||||||
let hi = compare_const_vals(tcx, test.hi, pat.lo, self.param_env, test_ty)?;
|
) {
|
||||||
|
|
||||||
match (test.end, pat.end, lo, hi) {
|
|
||||||
// pat < test
|
|
||||||
(_, _, Greater, _) |
|
|
||||||
(_, Excluded, Equal, _) |
|
|
||||||
// pat > test
|
|
||||||
(_, _, _, Less) |
|
|
||||||
(Excluded, _, _, Equal) => Some(true),
|
|
||||||
_ => Some(false),
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
if let Some(true) = no_overlap {
|
|
||||||
// Testing range does not overlap with pattern range,
|
|
||||||
// so the pattern can be matched only if this test fails.
|
|
||||||
Some(1)
|
Some(1)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// If the testing range does not overlap with pattern range,
|
||||||
|
// the pattern can be matched only if this test fails.
|
||||||
|
no_overlap
|
||||||
}
|
}
|
||||||
|
|
||||||
(&TestKind::Range(range), &PatKind::Constant { value }) => {
|
(&TestKind::Range(range), &PatKind::Constant { value }) => {
|
||||||
|
@ -768,15 +759,15 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
) -> Option<bool> {
|
) -> Option<bool> {
|
||||||
use std::cmp::Ordering::*;
|
use std::cmp::Ordering::*;
|
||||||
|
|
||||||
let tcx = self.tcx;
|
// For performance, it's important to only do the second
|
||||||
|
// `compare_const_vals` if necessary.
|
||||||
let a = compare_const_vals(tcx, range.lo, value, self.param_env, range.lo.ty())?;
|
Some(
|
||||||
let b = compare_const_vals(tcx, value, range.hi, self.param_env, range.lo.ty())?;
|
matches!(compare_const_vals(self.tcx, range.lo, value, self.param_env)?, Less | Equal)
|
||||||
|
&& matches!(
|
||||||
match (b, range.end) {
|
(compare_const_vals(self.tcx, value, range.hi, self.param_env)?, range.end),
|
||||||
(Less, _) | (Equal, RangeEnd::Included) if a != Greater => Some(true),
|
(Less, _) | (Equal, RangeEnd::Included)
|
||||||
_ => Some(false),
|
),
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn values_not_contained_in_range(
|
fn values_not_contained_in_range(
|
||||||
|
|
|
@ -828,14 +828,8 @@ impl<'tcx> Constructor<'tcx> {
|
||||||
FloatRange(other_from, other_to, other_end),
|
FloatRange(other_from, other_to, other_end),
|
||||||
) => {
|
) => {
|
||||||
match (
|
match (
|
||||||
compare_const_vals(pcx.cx.tcx, *self_to, *other_to, pcx.cx.param_env, pcx.ty),
|
compare_const_vals(pcx.cx.tcx, *self_to, *other_to, pcx.cx.param_env),
|
||||||
compare_const_vals(
|
compare_const_vals(pcx.cx.tcx, *self_from, *other_from, pcx.cx.param_env),
|
||||||
pcx.cx.tcx,
|
|
||||||
*self_from,
|
|
||||||
*other_from,
|
|
||||||
pcx.cx.param_env,
|
|
||||||
pcx.ty,
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
(Some(to), Some(from)) => {
|
(Some(to), Some(from)) => {
|
||||||
(from == Ordering::Greater || from == Ordering::Equal)
|
(from == Ordering::Greater || from == Ordering::Equal)
|
||||||
|
@ -848,16 +842,7 @@ impl<'tcx> Constructor<'tcx> {
|
||||||
(Str(self_val), Str(other_val)) => {
|
(Str(self_val), Str(other_val)) => {
|
||||||
// FIXME Once valtrees are available we can directly use the bytes
|
// FIXME Once valtrees are available we can directly use the bytes
|
||||||
// in the `Str` variant of the valtree for the comparison here.
|
// in the `Str` variant of the valtree for the comparison here.
|
||||||
match compare_const_vals(
|
self_val == other_val
|
||||||
pcx.cx.tcx,
|
|
||||||
*self_val,
|
|
||||||
*other_val,
|
|
||||||
pcx.cx.param_env,
|
|
||||||
pcx.ty,
|
|
||||||
) {
|
|
||||||
Some(comparison) => comparison == Ordering::Equal,
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(Slice(self_slice), Slice(other_slice)) => self_slice.is_covered_by(*other_slice),
|
(Slice(self_slice), Slice(other_slice)) => self_slice.is_covered_by(*other_slice),
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,9 @@ use rustc_hir::def::{CtorOf, DefKind, Res};
|
||||||
use rustc_hir::pat_util::EnumerateAndAdjustIterator;
|
use rustc_hir::pat_util::EnumerateAndAdjustIterator;
|
||||||
use rustc_hir::RangeEnd;
|
use rustc_hir::RangeEnd;
|
||||||
use rustc_index::vec::Idx;
|
use rustc_index::vec::Idx;
|
||||||
use rustc_middle::mir::interpret::{get_slice_bytes, ConstValue};
|
use rustc_middle::mir::interpret::{
|
||||||
use rustc_middle::mir::interpret::{ErrorHandled, LitToConstError, LitToConstInput};
|
ConstValue, ErrorHandled, LitToConstError, LitToConstInput, Scalar,
|
||||||
|
};
|
||||||
use rustc_middle::mir::{self, UserTypeProjection};
|
use rustc_middle::mir::{self, UserTypeProjection};
|
||||||
use rustc_middle::mir::{BorrowKind, Field, Mutability};
|
use rustc_middle::mir::{BorrowKind, Field, Mutability};
|
||||||
use rustc_middle::thir::{Ascription, BindingMode, FieldPat, LocalVarId, Pat, PatKind, PatRange};
|
use rustc_middle::thir::{Ascription, BindingMode, FieldPat, LocalVarId, Pat, PatKind, PatRange};
|
||||||
|
@ -129,7 +130,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
|
||||||
) -> PatKind<'tcx> {
|
) -> PatKind<'tcx> {
|
||||||
assert_eq!(lo.ty(), ty);
|
assert_eq!(lo.ty(), ty);
|
||||||
assert_eq!(hi.ty(), ty);
|
assert_eq!(hi.ty(), ty);
|
||||||
let cmp = compare_const_vals(self.tcx, lo, hi, self.param_env, ty);
|
let cmp = compare_const_vals(self.tcx, lo, hi, self.param_env);
|
||||||
match (end, cmp) {
|
match (end, cmp) {
|
||||||
// `x..y` where `x < y`.
|
// `x..y` where `x < y`.
|
||||||
// Non-empty because the range includes at least `x`.
|
// Non-empty because the range includes at least `x`.
|
||||||
|
@ -753,57 +754,49 @@ pub(crate) fn compare_const_vals<'tcx>(
|
||||||
a: mir::ConstantKind<'tcx>,
|
a: mir::ConstantKind<'tcx>,
|
||||||
b: mir::ConstantKind<'tcx>,
|
b: mir::ConstantKind<'tcx>,
|
||||||
param_env: ty::ParamEnv<'tcx>,
|
param_env: ty::ParamEnv<'tcx>,
|
||||||
ty: Ty<'tcx>,
|
|
||||||
) -> Option<Ordering> {
|
) -> Option<Ordering> {
|
||||||
let from_bool = |v: bool| v.then_some(Ordering::Equal);
|
assert_eq!(a.ty(), b.ty());
|
||||||
|
|
||||||
let fallback = || from_bool(a == b);
|
let ty = a.ty();
|
||||||
|
|
||||||
// Use the fallback if any type differs
|
// This code is hot when compiling matches with many ranges. So we
|
||||||
if a.ty() != b.ty() || a.ty() != ty {
|
// special-case extraction of evaluated scalars for speed, for types where
|
||||||
return fallback();
|
// raw data comparisons are appropriate. E.g. `unicode-normalization` has
|
||||||
|
// many ranges such as '\u{037A}'..='\u{037F}', and chars can be compared
|
||||||
|
// in this way.
|
||||||
|
match ty.kind() {
|
||||||
|
ty::Float(_) | ty::Int(_) => {} // require special handling, see below
|
||||||
|
_ => match (a, b) {
|
||||||
|
(
|
||||||
|
mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(a)), _a_ty),
|
||||||
|
mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(b)), _b_ty),
|
||||||
|
) => return Some(a.cmp(&b)),
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if a == b {
|
let a = a.eval_bits(tcx, param_env, ty);
|
||||||
return from_bool(true);
|
let b = b.eval_bits(tcx, param_env, ty);
|
||||||
|
|
||||||
|
use rustc_apfloat::Float;
|
||||||
|
match *ty.kind() {
|
||||||
|
ty::Float(ty::FloatTy::F32) => {
|
||||||
|
let a = rustc_apfloat::ieee::Single::from_bits(a);
|
||||||
|
let b = rustc_apfloat::ieee::Single::from_bits(b);
|
||||||
|
a.partial_cmp(&b)
|
||||||
|
}
|
||||||
|
ty::Float(ty::FloatTy::F64) => {
|
||||||
|
let a = rustc_apfloat::ieee::Double::from_bits(a);
|
||||||
|
let b = rustc_apfloat::ieee::Double::from_bits(b);
|
||||||
|
a.partial_cmp(&b)
|
||||||
|
}
|
||||||
|
ty::Int(ity) => {
|
||||||
|
use rustc_middle::ty::layout::IntegerExt;
|
||||||
|
let size = rustc_target::abi::Integer::from_int_ty(&tcx, ity).size();
|
||||||
|
let a = size.sign_extend(a);
|
||||||
|
let b = size.sign_extend(b);
|
||||||
|
Some((a as i128).cmp(&(b as i128)))
|
||||||
|
}
|
||||||
|
_ => Some(a.cmp(&b)),
|
||||||
}
|
}
|
||||||
|
|
||||||
let a_bits = a.try_eval_bits(tcx, param_env, ty);
|
|
||||||
let b_bits = b.try_eval_bits(tcx, param_env, ty);
|
|
||||||
|
|
||||||
if let (Some(a), Some(b)) = (a_bits, b_bits) {
|
|
||||||
use rustc_apfloat::Float;
|
|
||||||
return match *ty.kind() {
|
|
||||||
ty::Float(ty::FloatTy::F32) => {
|
|
||||||
let l = rustc_apfloat::ieee::Single::from_bits(a);
|
|
||||||
let r = rustc_apfloat::ieee::Single::from_bits(b);
|
|
||||||
l.partial_cmp(&r)
|
|
||||||
}
|
|
||||||
ty::Float(ty::FloatTy::F64) => {
|
|
||||||
let l = rustc_apfloat::ieee::Double::from_bits(a);
|
|
||||||
let r = rustc_apfloat::ieee::Double::from_bits(b);
|
|
||||||
l.partial_cmp(&r)
|
|
||||||
}
|
|
||||||
ty::Int(ity) => {
|
|
||||||
use rustc_middle::ty::layout::IntegerExt;
|
|
||||||
let size = rustc_target::abi::Integer::from_int_ty(&tcx, ity).size();
|
|
||||||
let a = size.sign_extend(a);
|
|
||||||
let b = size.sign_extend(b);
|
|
||||||
Some((a as i128).cmp(&(b as i128)))
|
|
||||||
}
|
|
||||||
_ => Some(a.cmp(&b)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if let ty::Str = ty.kind() && let (
|
|
||||||
Some(a_val @ ConstValue::Slice { .. }),
|
|
||||||
Some(b_val @ ConstValue::Slice { .. }),
|
|
||||||
) = (a.try_to_value(tcx), b.try_to_value(tcx))
|
|
||||||
{
|
|
||||||
let a_bytes = get_slice_bytes(&tcx, a_val);
|
|
||||||
let b_bytes = get_slice_bytes(&tcx, b_val);
|
|
||||||
return from_bool(a_bytes == b_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
fallback()
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue