Skip to content

Commit cacc75c

Browse files
committed
Auto merge of rust-lang#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`
2 parents 3bebee7 + bdbf9b2 commit cacc75c

File tree

4 files changed

+87
-115
lines changed

4 files changed

+87
-115
lines changed

Diff for: compiler/rustc_mir_build/src/build/matches/simplify.rs

+12-9
Original file line numberDiff line numberDiff line change
@@ -227,15 +227,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
227227
_ => (None, 0),
228228
};
229229
if let Some((min, max, sz)) = range {
230-
if let (Some(lo), Some(hi)) = (lo.try_to_bits(sz), hi.try_to_bits(sz)) {
231-
// We want to compare ranges numerically, but the order of the bitwise
232-
// representation of signed integers does not match their numeric order.
233-
// Thus, to correct the ordering, we need to shift the range of signed
234-
// integers to correct the comparison. This is achieved by XORing with a
235-
// bias (see pattern/_match.rs for another pertinent example of this
236-
// pattern).
237-
let (lo, hi) = (lo ^ bias, hi ^ bias);
238-
if lo <= min && (hi > max || hi == max && end == RangeEnd::Included) {
230+
// We want to compare ranges numerically, but the order of the bitwise
231+
// representation of signed integers does not match their numeric order. Thus,
232+
// to correct the ordering, we need to shift the range of signed integers to
233+
// correct the comparison. This is achieved by XORing with a bias (see
234+
// pattern/_match.rs for another pertinent example of this pattern).
235+
//
236+
// Also, for performance, it's important to only do the second `try_to_bits` if
237+
// necessary.
238+
let lo = lo.try_to_bits(sz).unwrap() ^ bias;
239+
if lo <= min {
240+
let hi = hi.try_to_bits(sz).unwrap() ^ bias;
241+
if hi > max || hi == max && end == RangeEnd::Included {
239242
// Irrefutable pattern match.
240243
return Ok(());
241244
}

Diff for: compiler/rustc_mir_build/src/build/matches/test.rs

+25-34
Original file line numberDiff line numberDiff line change
@@ -632,39 +632,30 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
632632
}
633633

634634
(&TestKind::Range(test), &PatKind::Range(pat)) => {
635+
use std::cmp::Ordering::*;
636+
635637
if test == pat {
636638
self.candidate_without_match_pair(match_pair_index, candidate);
637639
return Some(0);
638640
}
639641

640-
let no_overlap = (|| {
641-
use rustc_hir::RangeEnd::*;
642-
use std::cmp::Ordering::*;
643-
644-
let tcx = self.tcx;
645-
646-
let test_ty = test.lo.ty();
647-
let lo = compare_const_vals(tcx, test.lo, pat.hi, self.param_env, test_ty)?;
648-
let hi = compare_const_vals(tcx, test.hi, pat.lo, self.param_env, test_ty)?;
649-
650-
match (test.end, pat.end, lo, hi) {
651-
// pat < test
652-
(_, _, Greater, _) |
653-
(_, Excluded, Equal, _) |
654-
// pat > test
655-
(_, _, _, Less) |
656-
(Excluded, _, _, Equal) => Some(true),
657-
_ => Some(false),
658-
}
659-
})();
660-
661-
if let Some(true) = no_overlap {
662-
// Testing range does not overlap with pattern range,
663-
// so the pattern can be matched only if this test fails.
642+
// For performance, it's important to only do the second
643+
// `compare_const_vals` if necessary.
644+
let no_overlap = if matches!(
645+
(compare_const_vals(self.tcx, test.hi, pat.lo, self.param_env)?, test.end),
646+
(Less, _) | (Equal, RangeEnd::Excluded) // test < pat
647+
) || matches!(
648+
(compare_const_vals(self.tcx, test.lo, pat.hi, self.param_env)?, pat.end),
649+
(Greater, _) | (Equal, RangeEnd::Excluded) // test > pat
650+
) {
664651
Some(1)
665652
} else {
666653
None
667-
}
654+
};
655+
656+
// If the testing range does not overlap with pattern range,
657+
// the pattern can be matched only if this test fails.
658+
no_overlap
668659
}
669660

670661
(&TestKind::Range(range), &PatKind::Constant { value }) => {
@@ -768,15 +759,15 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
768759
) -> Option<bool> {
769760
use std::cmp::Ordering::*;
770761

771-
let tcx = self.tcx;
772-
773-
let a = compare_const_vals(tcx, range.lo, value, self.param_env, range.lo.ty())?;
774-
let b = compare_const_vals(tcx, value, range.hi, self.param_env, range.lo.ty())?;
775-
776-
match (b, range.end) {
777-
(Less, _) | (Equal, RangeEnd::Included) if a != Greater => Some(true),
778-
_ => Some(false),
779-
}
762+
// For performance, it's important to only do the second
763+
// `compare_const_vals` if necessary.
764+
Some(
765+
matches!(compare_const_vals(self.tcx, range.lo, value, self.param_env)?, Less | Equal)
766+
&& matches!(
767+
(compare_const_vals(self.tcx, value, range.hi, self.param_env)?, range.end),
768+
(Less, _) | (Equal, RangeEnd::Included)
769+
),
770+
)
780771
}
781772

782773
fn values_not_contained_in_range(

Diff for: compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs

+3-18
Original file line numberDiff line numberDiff line change
@@ -828,14 +828,8 @@ impl<'tcx> Constructor<'tcx> {
828828
FloatRange(other_from, other_to, other_end),
829829
) => {
830830
match (
831-
compare_const_vals(pcx.cx.tcx, *self_to, *other_to, pcx.cx.param_env, pcx.ty),
832-
compare_const_vals(
833-
pcx.cx.tcx,
834-
*self_from,
835-
*other_from,
836-
pcx.cx.param_env,
837-
pcx.ty,
838-
),
831+
compare_const_vals(pcx.cx.tcx, *self_to, *other_to, pcx.cx.param_env),
832+
compare_const_vals(pcx.cx.tcx, *self_from, *other_from, pcx.cx.param_env),
839833
) {
840834
(Some(to), Some(from)) => {
841835
(from == Ordering::Greater || from == Ordering::Equal)
@@ -848,16 +842,7 @@ impl<'tcx> Constructor<'tcx> {
848842
(Str(self_val), Str(other_val)) => {
849843
// FIXME Once valtrees are available we can directly use the bytes
850844
// in the `Str` variant of the valtree for the comparison here.
851-
match compare_const_vals(
852-
pcx.cx.tcx,
853-
*self_val,
854-
*other_val,
855-
pcx.cx.param_env,
856-
pcx.ty,
857-
) {
858-
Some(comparison) => comparison == Ordering::Equal,
859-
None => false,
860-
}
845+
self_val == other_val
861846
}
862847
(Slice(self_slice), Slice(other_slice)) => self_slice.is_covered_by(*other_slice),
863848

Diff for: compiler/rustc_mir_build/src/thir/pattern/mod.rs

+47-54
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ use rustc_hir::def::{CtorOf, DefKind, Res};
1515
use rustc_hir::pat_util::EnumerateAndAdjustIterator;
1616
use rustc_hir::RangeEnd;
1717
use rustc_index::vec::Idx;
18-
use rustc_middle::mir::interpret::{get_slice_bytes, ConstValue};
19-
use rustc_middle::mir::interpret::{ErrorHandled, LitToConstError, LitToConstInput};
18+
use rustc_middle::mir::interpret::{
19+
ConstValue, ErrorHandled, LitToConstError, LitToConstInput, Scalar,
20+
};
2021
use rustc_middle::mir::{self, UserTypeProjection};
2122
use rustc_middle::mir::{BorrowKind, Field, Mutability};
2223
use rustc_middle::thir::{Ascription, BindingMode, FieldPat, LocalVarId, Pat, PatKind, PatRange};
@@ -129,7 +130,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
129130
) -> PatKind<'tcx> {
130131
assert_eq!(lo.ty(), ty);
131132
assert_eq!(hi.ty(), ty);
132-
let cmp = compare_const_vals(self.tcx, lo, hi, self.param_env, ty);
133+
let cmp = compare_const_vals(self.tcx, lo, hi, self.param_env);
133134
match (end, cmp) {
134135
// `x..y` where `x < y`.
135136
// Non-empty because the range includes at least `x`.
@@ -753,57 +754,49 @@ pub(crate) fn compare_const_vals<'tcx>(
753754
a: mir::ConstantKind<'tcx>,
754755
b: mir::ConstantKind<'tcx>,
755756
param_env: ty::ParamEnv<'tcx>,
756-
ty: Ty<'tcx>,
757757
) -> Option<Ordering> {
758-
let from_bool = |v: bool| v.then_some(Ordering::Equal);
759-
760-
let fallback = || from_bool(a == b);
761-
762-
// Use the fallback if any type differs
763-
if a.ty() != b.ty() || a.ty() != ty {
764-
return fallback();
765-
}
766-
767-
if a == b {
768-
return from_bool(true);
769-
}
770-
771-
let a_bits = a.try_eval_bits(tcx, param_env, ty);
772-
let b_bits = b.try_eval_bits(tcx, param_env, ty);
773-
774-
if let (Some(a), Some(b)) = (a_bits, b_bits) {
775-
use rustc_apfloat::Float;
776-
return match *ty.kind() {
777-
ty::Float(ty::FloatTy::F32) => {
778-
let l = rustc_apfloat::ieee::Single::from_bits(a);
779-
let r = rustc_apfloat::ieee::Single::from_bits(b);
780-
l.partial_cmp(&r)
781-
}
782-
ty::Float(ty::FloatTy::F64) => {
783-
let l = rustc_apfloat::ieee::Double::from_bits(a);
784-
let r = rustc_apfloat::ieee::Double::from_bits(b);
785-
l.partial_cmp(&r)
786-
}
787-
ty::Int(ity) => {
788-
use rustc_middle::ty::layout::IntegerExt;
789-
let size = rustc_target::abi::Integer::from_int_ty(&tcx, ity).size();
790-
let a = size.sign_extend(a);
791-
let b = size.sign_extend(b);
792-
Some((a as i128).cmp(&(b as i128)))
793-
}
794-
_ => Some(a.cmp(&b)),
795-
};
796-
}
797-
798-
if let ty::Str = ty.kind() && let (
799-
Some(a_val @ ConstValue::Slice { .. }),
800-
Some(b_val @ ConstValue::Slice { .. }),
801-
) = (a.try_to_value(tcx), b.try_to_value(tcx))
802-
{
803-
let a_bytes = get_slice_bytes(&tcx, a_val);
804-
let b_bytes = get_slice_bytes(&tcx, b_val);
805-
return from_bool(a_bytes == b_bytes);
758+
assert_eq!(a.ty(), b.ty());
759+
760+
let ty = a.ty();
761+
762+
// This code is hot when compiling matches with many ranges. So we
763+
// special-case extraction of evaluated scalars for speed, for types where
764+
// raw data comparisons are appropriate. E.g. `unicode-normalization` has
765+
// many ranges such as '\u{037A}'..='\u{037F}', and chars can be compared
766+
// in this way.
767+
match ty.kind() {
768+
ty::Float(_) | ty::Int(_) => {} // require special handling, see below
769+
_ => match (a, b) {
770+
(
771+
mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(a)), _a_ty),
772+
mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(b)), _b_ty),
773+
) => return Some(a.cmp(&b)),
774+
_ => {}
775+
},
776+
}
777+
778+
let a = a.eval_bits(tcx, param_env, ty);
779+
let b = b.eval_bits(tcx, param_env, ty);
780+
781+
use rustc_apfloat::Float;
782+
match *ty.kind() {
783+
ty::Float(ty::FloatTy::F32) => {
784+
let a = rustc_apfloat::ieee::Single::from_bits(a);
785+
let b = rustc_apfloat::ieee::Single::from_bits(b);
786+
a.partial_cmp(&b)
787+
}
788+
ty::Float(ty::FloatTy::F64) => {
789+
let a = rustc_apfloat::ieee::Double::from_bits(a);
790+
let b = rustc_apfloat::ieee::Double::from_bits(b);
791+
a.partial_cmp(&b)
792+
}
793+
ty::Int(ity) => {
794+
use rustc_middle::ty::layout::IntegerExt;
795+
let size = rustc_target::abi::Integer::from_int_ty(&tcx, ity).size();
796+
let a = size.sign_extend(a);
797+
let b = size.sign_extend(b);
798+
Some((a as i128).cmp(&(b as i128)))
799+
}
800+
_ => Some(a.cmp(&b)),
806801
}
807-
808-
fallback()
809802
}

0 commit comments

Comments
 (0)