1
Fork 0

Abstract over PatRange boundary value

This commit is contained in:
Nadrieril 2023-10-12 23:29:16 +02:00
parent 11268b48a1
commit 8a77b3248f
7 changed files with 299 additions and 215 deletions

View file

@ -1027,7 +1027,7 @@ enum TestKind<'tcx> {
ty: Ty<'tcx>,
},
/// Test whether the value falls within an inclusive or exclusive range
/// Test whether the value falls within an inclusive or exclusive range.
Range(Box<PatRange<'tcx>>),
/// Test that the length of the slice is equal to `len`.

View file

@ -15,11 +15,7 @@
use crate::build::expr::as_place::PlaceBuilder;
use crate::build::matches::{Ascription, Binding, Candidate, MatchPair};
use crate::build::Builder;
use rustc_hir::RangeEnd;
use rustc_middle::thir::{self, *};
use rustc_middle::ty;
use rustc_middle::ty::layout::IntegerExt;
use rustc_target::abi::{Integer, Size};
use std::mem;
@ -148,7 +144,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
match_pair: MatchPair<'pat, 'tcx>,
candidate: &mut Candidate<'pat, 'tcx>,
) -> Result<(), MatchPair<'pat, 'tcx>> {
let tcx = self.tcx;
match match_pair.pattern.kind {
PatKind::AscribeUserType {
ref subpattern,
@ -210,41 +205,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
Ok(())
}
PatKind::Range(box PatRange { lo, hi, end }) => {
let (range, bias) = match *lo.ty().kind() {
ty::Char => {
(Some(('\u{0000}' as u128, '\u{10FFFF}' as u128, Size::from_bits(32))), 0)
}
ty::Int(ity) => {
let size = Integer::from_int_ty(&tcx, ity).size();
let max = size.truncate(u128::MAX);
let bias = 1u128 << (size.bits() - 1);
(Some((0, max, size)), bias)
}
ty::Uint(uty) => {
let size = Integer::from_uint_ty(&tcx, uty).size();
let max = size.truncate(u128::MAX);
(Some((0, max, size)), 0)
}
_ => (None, 0),
};
if let Some((min, max, sz)) = range {
// We want to compare ranges numerically, but the order of the bitwise
// representation of signed integers does not match their numeric order. Thus,
// to correct the ordering, we need to shift the range of signed integers to
// correct the comparison. This is achieved by XORing with a 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 necessary.
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.
return Ok(());
}
}
PatKind::Range(ref range) => {
if let Some(true) = range.is_full_range(self.tcx) {
// Irrefutable pattern match.
return Ok(());
}
Err(match_pair)
}

View file

@ -8,7 +8,6 @@
use crate::build::expr::as_place::PlaceBuilder;
use crate::build::matches::{Candidate, MatchPair, Test, TestKind};
use crate::build::Builder;
use crate::thir::pattern::compare_const_vals;
use rustc_data_structures::fx::FxIndexMap;
use rustc_hir::{LangItem, RangeEnd};
use rustc_index::bit_set::BitSet;
@ -59,8 +58,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
},
PatKind::Range(ref range) => {
assert_eq!(range.lo.ty(), match_pair.pattern.ty);
assert_eq!(range.hi.ty(), match_pair.pattern.ty);
assert_eq!(range.ty, match_pair.pattern.ty);
Test { span: match_pair.pattern.span, kind: TestKind::Range(range.clone()) }
}
@ -309,11 +307,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
}
TestKind::Range(box PatRange { lo, hi, ref end }) => {
TestKind::Range(ref range) => {
let lower_bound_success = self.cfg.start_new_block();
let target_blocks = make_target_blocks(self);
// Test `val` by computing `lo <= val && val <= hi`, using primitive comparisons.
// FIXME: skip useless comparison when the range is half-open.
let lo = range.lo.to_const(range.ty, self.tcx);
let hi = range.hi.to_const(range.ty, self.tcx);
let lo = self.literal_operand(test.span, lo);
let hi = self.literal_operand(test.span, hi);
let val = Operand::Copy(place);
@ -330,7 +331,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
lo,
val.clone(),
);
let op = match *end {
let op = match range.end {
RangeEnd::Included => BinOp::Le,
RangeEnd::Excluded => BinOp::Lt,
};
@ -698,34 +699,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
(TestKind::Range(test), PatKind::Range(pat)) => {
use std::cmp::Ordering::*;
if test == pat {
self.candidate_without_match_pair(match_pair_index, candidate);
return Some(0);
}
// For performance, it's important to only do the second
// `compare_const_vals` if necessary.
let no_overlap = if matches!(
(compare_const_vals(self.tcx, test.hi, pat.lo, self.param_env)?, test.end),
(Less, _) | (Equal, RangeEnd::Excluded) // test < pat
) || matches!(
(compare_const_vals(self.tcx, test.lo, pat.hi, self.param_env)?, pat.end),
(Greater, _) | (Equal, RangeEnd::Excluded) // test > pat
) {
Some(1)
} else {
None
};
// If the testing range does not overlap with pattern range,
// the pattern can be matched only if this test fails.
no_overlap
if !test.overlaps(pat, self.tcx, self.param_env)? { Some(1) } else { None }
}
(TestKind::Range(range), &PatKind::Constant { value }) => {
if let Some(false) = self.const_range_contains(&*range, value) {
if !range.contains(value, self.tcx, self.param_env)? {
// `value` is not contained in the testing range,
// so `value` can be matched only if this test fails.
Some(1)
@ -817,27 +802,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
span_bug!(match_pair.pattern.span, "simplifiable pattern found: {:?}", match_pair.pattern)
}
fn const_range_contains(&self, range: &PatRange<'tcx>, value: Const<'tcx>) -> Option<bool> {
use std::cmp::Ordering::*;
// For performance, it's important to only do the second
// `compare_const_vals` if necessary.
Some(
matches!(compare_const_vals(self.tcx, range.lo, value, self.param_env)?, Less | Equal)
&& matches!(
(compare_const_vals(self.tcx, value, range.hi, self.param_env)?, range.end),
(Less, _) | (Equal, RangeEnd::Included)
),
)
}
fn values_not_contained_in_range(
&self,
range: &PatRange<'tcx>,
options: &FxIndexMap<Const<'tcx>, u128>,
) -> Option<bool> {
for &val in options.keys() {
if self.const_range_contains(range, val)? {
if range.contains(val, self.tcx, self.param_env)? {
return Some(false);
}
}

View file

@ -57,7 +57,7 @@ use rustc_hir::RangeEnd;
use rustc_index::Idx;
use rustc_middle::middle::stability::EvalResult;
use rustc_middle::mir;
use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange};
use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange, PatRangeBoundary};
use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef};
use rustc_span::{Span, DUMMY_SP};
@ -278,19 +278,20 @@ impl IntRange {
let (lo, hi) = self.boundaries();
let bias = IntRange::signed_bias(tcx, ty);
let (lo, hi) = (lo ^ bias, hi ^ bias);
let (lo_bits, hi_bits) = (lo ^ bias, hi ^ bias);
let env = ty::ParamEnv::empty().and(ty);
let lo_const = mir::Const::from_bits(tcx, lo, env);
let hi_const = mir::Const::from_bits(tcx, hi, env);
let lo_const = mir::Const::from_bits(tcx, lo_bits, env);
let hi_const = mir::Const::from_bits(tcx, hi_bits, env);
let kind = if lo == hi {
let kind = if lo_bits == hi_bits {
PatKind::Constant { value: lo_const }
} else {
PatKind::Range(Box::new(PatRange {
lo: lo_const,
hi: hi_const,
lo: PatRangeBoundary::Finite(lo_const),
hi: PatRangeBoundary::Finite(hi_const),
end: RangeEnd::Included,
ty,
}))
};
@ -1387,11 +1388,12 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> {
}
}
}
PatKind::Range(box PatRange { lo, hi, end }) => {
PatKind::Range(box PatRange { lo, hi, end, .. }) => {
use rustc_apfloat::Float;
let ty = lo.ty();
let lo = lo.try_eval_bits(cx.tcx, cx.param_env).unwrap();
let hi = hi.try_eval_bits(cx.tcx, cx.param_env).unwrap();
let ty = pat.ty;
// FIXME: handle half-open ranges
let lo = lo.eval_bits(ty, cx.tcx, cx.param_env);
let hi = hi.eval_bits(ty, cx.tcx, cx.param_env);
ctor = match ty.kind() {
ty::Char | ty::Int(_) | ty::Uint(_) => {
IntRange(IntRange::from_range(cx.tcx, lo, hi, ty, *end))

View file

@ -17,11 +17,11 @@ use rustc_hir::def::{CtorOf, DefKind, Res};
use rustc_hir::pat_util::EnumerateAndAdjustIterator;
use rustc_hir::RangeEnd;
use rustc_index::Idx;
use rustc_middle::mir::interpret::{
ErrorHandled, GlobalId, LitToConstError, LitToConstInput, Scalar,
};
use rustc_middle::mir::interpret::{ErrorHandled, GlobalId, LitToConstError, LitToConstInput};
use rustc_middle::mir::{self, BorrowKind, Const, Mutability, UserTypeProjection};
use rustc_middle::thir::{Ascription, BindingMode, FieldPat, LocalVarId, Pat, PatKind, PatRange};
use rustc_middle::thir::{
Ascription, BindingMode, FieldPat, LocalVarId, Pat, PatKind, PatRange, PatRangeBoundary,
};
use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::{
self, AdtDef, CanonicalUserTypeAnnotation, GenericArg, GenericArgsRef, Region, Ty, TyCtxt,
@ -90,7 +90,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
&mut self,
expr: Option<&'tcx hir::Expr<'tcx>>,
) -> Result<
(Option<mir::Const<'tcx>>, Option<Ascription<'tcx>>, Option<LocalDefId>),
(Option<PatRangeBoundary<'tcx>>, Option<Ascription<'tcx>>, Option<LocalDefId>),
ErrorGuaranteed,
> {
match expr {
@ -113,7 +113,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
);
return Err(self.tcx.sess.delay_span_bug(expr.span, msg));
};
Ok((Some(value), ascr, inline_const))
Ok((Some(PatRangeBoundary::Finite(value)), ascr, inline_const))
}
}
}
@ -187,31 +187,23 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
let (lo, lo_ascr, lo_inline) = self.lower_pattern_range_endpoint(lo_expr)?;
let (hi, hi_ascr, hi_inline) = self.lower_pattern_range_endpoint(hi_expr)?;
let lo = lo.unwrap_or_else(|| {
// Unwrap is ok because the type is known to be numeric.
let lo = ty.numeric_min_val(self.tcx).unwrap();
mir::Const::from_ty_const(lo, self.tcx)
});
let hi = hi.unwrap_or_else(|| {
// Unwrap is ok because the type is known to be numeric.
let hi = ty.numeric_max_val(self.tcx).unwrap();
mir::Const::from_ty_const(hi, self.tcx)
});
assert_eq!(lo.ty(), ty);
assert_eq!(hi.ty(), ty);
let lo = lo.unwrap_or_else(|| PatRangeBoundary::lower_bound(ty, self.tcx));
let hi = hi.unwrap_or_else(|| PatRangeBoundary::upper_bound(ty, self.tcx));
let cmp = compare_const_vals(self.tcx, lo, hi, self.param_env);
let cmp = lo.compare_with(hi, ty, self.tcx, self.param_env);
let mut kind = match (end, cmp) {
// `x..y` where `x < y`.
// Non-empty because the range includes at least `x`.
(RangeEnd::Excluded, Some(Ordering::Less)) => {
PatKind::Range(Box::new(PatRange { lo, hi, end }))
PatKind::Range(Box::new(PatRange { lo, hi, end, ty }))
}
// `x..=y` where `x == y`.
(RangeEnd::Included, Some(Ordering::Equal)) => PatKind::Constant { value: lo },
(RangeEnd::Included, Some(Ordering::Equal)) => {
PatKind::Constant { value: lo.to_const(ty, self.tcx) }
}
// `x..=y` where `x < y`.
(RangeEnd::Included, Some(Ordering::Less)) => {
PatKind::Range(Box::new(PatRange { lo, hi, end }))
PatKind::Range(Box::new(PatRange { lo, hi, end, ty }))
}
// `x..y` where `x >= y`, or `x..=y` where `x > y`. The range is empty => error.
_ => {
@ -851,59 +843,3 @@ impl<'tcx> PatternFoldable<'tcx> for PatKind<'tcx> {
}
}
}
#[instrument(skip(tcx), level = "debug")]
pub(crate) fn compare_const_vals<'tcx>(
tcx: TyCtxt<'tcx>,
a: mir::Const<'tcx>,
b: mir::Const<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> Option<Ordering> {
assert_eq!(a.ty(), b.ty());
let ty = a.ty();
// This code is hot when compiling matches with many ranges. So we
// special-case extraction of evaluated scalars for speed, for types where
// 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::Const::Val(mir::ConstValue::Scalar(Scalar::Int(a)), _a_ty),
mir::Const::Val(mir::ConstValue::Scalar(Scalar::Int(b)), _b_ty),
) => return Some(a.cmp(&b)),
(mir::Const::Ty(a), mir::Const::Ty(b)) => {
return Some(a.kind().cmp(&b.kind()));
}
_ => {}
},
}
let a = a.eval_bits(tcx, param_env);
let b = b.eval_bits(tcx, param_env);
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)),
}
}