Skip to content

Exhaustive integer matching #50912

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

Merged
merged 47 commits into from
Aug 22, 2018
Merged
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
e3357d9
Implement interval checking
varkor May 19, 2018
b3d2baf
Give correct suggestions
varkor May 19, 2018
384db4f
Add support for all integer types
varkor May 19, 2018
ed5a4d5
Add feature gate and refactor
varkor May 19, 2018
b8702a0
Add feature gate test
varkor May 19, 2018
121fa8d
Fix handling of signed integers
varkor May 20, 2018
7476ba4
Add semi-exhaustive tests for exhaustiveness
varkor May 20, 2018
9778a81
Improve macros with reduced repetition
varkor May 20, 2018
a20cb10
Require just the Unicode Scalar Values to be matched for a char
varkor May 21, 2018
7f72030
Fix range splitting
varkor May 21, 2018
c00fd8f
Refactor interval conditions
varkor May 21, 2018
7695bd0
Use bit operators for min_max_ty
varkor May 21, 2018
a553fa7
Fix integer overflow
varkor May 22, 2018
8389972
Add singleton patterns to test
varkor May 22, 2018
f4af3b0
Refactor to remove explicit integer type matching
varkor May 22, 2018
a9f2c5a
Fix sign conversion arithmetic errors
varkor May 23, 2018
effb3d0
Improve the comments
varkor May 23, 2018
c388c11
Special-case (RangeEnd::Included, Ordering::Equal) in lower_pattern_u…
varkor May 23, 2018
97a032e
Simplify bitwise operations
varkor May 24, 2018
be12b24
Fix print_miri_value for signed integers
varkor May 24, 2018
72cc4bd
Inline encode and decode methods
varkor May 24, 2018
1aa7494
Introduce signed_bias method
varkor May 24, 2018
732d638
Replace ... with ..= in suggestions
varkor May 25, 2018
6c21a03
Refactor after miri api changes
varkor May 25, 2018
d27c21c
Refactor for less allocation
varkor May 28, 2018
07064de
No longer return value_constructors for all_constructors
varkor May 29, 2018
25ba911
Add guarded arms to tests
varkor Jun 2, 2018
af366b0
Refactor condition
varkor Jun 22, 2018
bfc0807
Add some comments
varkor Aug 12, 2018
4aa929c
Move witnesses inside push_wild_constructor
varkor Aug 12, 2018
5959a35
Move logic from push_wild_constructor to apply_constructor
varkor Aug 12, 2018
bfc8ce3
Add a test for integer products
varkor Aug 12, 2018
99754ad
Some reformatting
varkor Aug 12, 2018
400cb14
Add a summary of the algorithm to the file
varkor Aug 13, 2018
9e9e023
More formatting improvements
varkor Aug 13, 2018
527cccb
Add some more compound exhaustiveness tests
varkor Aug 14, 2018
e9c8361
Add equivalence class splitting for range constructors
varkor Aug 14, 2018
1dbc781
Handle equivalence classes of length-1 ranges
varkor Aug 14, 2018
0383539
Fix handling of floating-point ranges
varkor Aug 14, 2018
798b9ff
Tweak comments
varkor Aug 19, 2018
87463c3
Improve some comments
varkor Aug 20, 2018
6e8a625
Remove pattern consideration from split_grouped_constructors
varkor Aug 20, 2018
c421af9
Add assertion to constructor_intersects_pattern
varkor Aug 20, 2018
61b6363
Add more detail to the split_grouped_constructors comment
varkor Aug 20, 2018
6a957e1
Add a test case for u128::MAX - 1
varkor Aug 21, 2018
dec5563
Use a boundary method instead of an endpoint method for split_grouped…
varkor Aug 21, 2018
6971c5d
Add some extra edge case tests
varkor Aug 21, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Simplify bitwise operations
  • Loading branch information
varkor committed Aug 16, 2018
commit 97a032ebb4f87bb5a0a7478528d4dfb6d2c63ddd
69 changes: 42 additions & 27 deletions src/librustc_mir/hair/pattern/_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,11 +483,11 @@ fn all_constructors<'a, 'tcx: 'a>(cx: &mut MatchCheckCtxt<'a, 'tcx>,
ty::TyUint(_) if exhaustive_integer_patterns => {
// FIXME(49937): refactor these bit manipulations into interpret.
let bits = cx.tcx.layout_of(ty::ParamEnv::reveal_all().and(pcx.ty))
.unwrap().size.bits() as u32;
let max = (!0u128).wrapping_shr(128 - bits);
.unwrap().size.bits() as u128;
let max = !0u128 >> (128 - bits);
value_constructors = true;
vec![ConstantRange(ty::Const::from_bits(cx.tcx, 0u128, pcx.ty),
ty::Const::from_bits(cx.tcx, max as u128, pcx.ty),
vec![ConstantRange(ty::Const::from_bits(cx.tcx, 0, pcx.ty),
ty::Const::from_bits(cx.tcx, max, pcx.ty),
RangeEnd::Included)]
}
_ => {
Expand Down Expand Up @@ -604,21 +604,21 @@ fn max_slice_length<'p, 'a: 'p, 'tcx: 'a, I>(
}

/// An inclusive interval, used for precise integer exhaustiveness checking.
/// `Interval`s always store a contiguous range of integers. This means that
/// signed values are encoded by offsetting them such that `0` represents the
/// minimum value for the integer, regardless of sign.
/// For example, the range `-128...127` is encoded as `0...255`.
/// `IntRange`s always store a contiguous range. This means that values are
/// encoded such that `0` encodes the minimum value for the integer,
/// regardless of the signedness.
/// For example, the pattern `-128...127i8` is encoded as `0..=255`.
/// This makes comparisons and arithmetic on interval endpoints much more
/// straightforward. See `offset_sign` for the conversion technique.
struct Interval<'tcx> {
/// straightforward. See `encode` and `decode` for details.
struct IntRange<'tcx> {
pub range: RangeInclusive<u128>,
pub ty: Ty<'tcx>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use ty::layout::Integer here or similar.

}

impl<'tcx> Interval<'tcx> {
impl<'tcx> IntRange<'tcx> {
fn from_ctor(tcx: TyCtxt<'_, 'tcx, 'tcx>,
ctor: &Constructor<'tcx>)
-> Option<Interval<'tcx>> {
-> Option<IntRange<'tcx>> {
match ctor {
ConstantRange(lo, hi, end) => {
assert_eq!(lo.ty, hi.ty);
Expand All @@ -627,13 +627,13 @@ impl<'tcx> Interval<'tcx> {
if let Some(hi) = hi.assert_bits(ty) {
// Perform a shift if the underlying types are signed,
// which makes the interval arithmetic simpler.
let (lo, hi) = Self::offset_sign(tcx, ty, lo..=hi, true);
let (lo, hi) = Self::encode(tcx, ty, lo..=hi);
// Make sure the interval is well-formed.
return if lo > hi || lo == hi && *end == RangeEnd::Excluded {
None
} else {
let offset = (*end == RangeEnd::Excluded) as u128;
Some(Interval { range: lo..=(hi - offset), ty })
Some(IntRange { range: lo..=(hi - offset), ty })
};
}
}
Expand All @@ -642,8 +642,8 @@ impl<'tcx> Interval<'tcx> {
ConstantValue(val) => {
let ty = val.ty;
if let Some(val) = val.assert_bits(ty) {
let (lo, hi) = Self::offset_sign(tcx, ty, val..=val, true);
Some(Interval { range: lo..=hi, ty })
let (lo, hi) = Self::encode(tcx, ty, val..=val);
Some(IntRange { range: lo..=hi, ty })
} else {
None
}
Expand All @@ -654,11 +654,11 @@ impl<'tcx> Interval<'tcx> {
}
}

fn offset_sign(tcx: TyCtxt<'_, 'tcx, 'tcx>,
ty: Ty<'tcx>,
range: RangeInclusive<u128>,
encode: bool)
-> (u128, u128) {
fn convert(tcx: TyCtxt<'_, 'tcx, 'tcx>,
ty: Ty<'tcx>,
range: RangeInclusive<u128>,
encode: bool)
-> (u128, u128) {
// We ensure that all integer values are contiguous: that is, that their
// minimum value is represented by 0, so that comparisons and increments/
// decrements on interval endpoints work consistently whether the endpoints
Expand All @@ -670,13 +670,14 @@ impl<'tcx> Interval<'tcx> {
let bits = tcx.layout_of(ty::ParamEnv::reveal_all().and(ty))
.unwrap().size.bits() as u128;
let min = 1u128 << (bits - 1);
let shift = 1u128.overflowing_shl(bits as u32);
let mask = shift.0.wrapping_sub(1 + (shift.1 as u128));
let mask = !0u128 >> (128 - bits);
if encode {
let offset = |x: u128| x.wrapping_sub(min) & mask;
(offset(lo), offset(hi))
} else {
let offset = |x: u128| {
// FIXME: this shouldn't be necessary once `print_miri_value`
// sign-extends `TyInt`.
interpret::sign_extend(tcx, x.wrapping_add(min) & mask, ty)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't want a sign_extend, bit values are kept truncated in miri.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a FIXME. This shouldn't be necessary once print_miri_value does the sign extension, but it currently doesn't and it's much easier to do it here where we have a TyCtxt.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW this produces "wrong" values, that could result in weird behavior in miri.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'm fixing it.

.expect("layout error for TyInt")
};
Expand All @@ -686,10 +687,24 @@ impl<'tcx> Interval<'tcx> {
ty::TyUint(_) | ty::TyChar => {
(lo, hi)
}
_ => bug!("`Interval` should only contain integer types")
_ => bug!("`IntRange` should only contain integer types")
}
}

fn encode(tcx: TyCtxt<'_, 'tcx, 'tcx>,
ty: Ty<'tcx>,
range: RangeInclusive<u128>)
-> (u128, u128) {
Self::convert(tcx, ty, range, true)
}

fn decode(tcx: TyCtxt<'_, 'tcx, 'tcx>,
ty: Ty<'tcx>,
range: RangeInclusive<u128>)
-> (u128, u128) {
Self::convert(tcx, ty, range, false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice to have two separate signatures (i.e. encode can produce Self and decode take self) and maybe not a convert method.

}

fn into_inner(self) -> (u128, u128) {
self.range.into_inner()
}
Expand All @@ -702,10 +717,10 @@ fn ranges_subtract_pattern<'a, 'tcx>(cx: &mut MatchCheckCtxt<'a, 'tcx>,
pat_ctor: &Constructor<'tcx>,
ranges: Vec<Constructor<'tcx>>)
-> Vec<Constructor<'tcx>> {
if let Some(pat_interval) = Interval::from_ctor(cx.tcx, pat_ctor) {
if let Some(pat_interval) = IntRange::from_ctor(cx.tcx, pat_ctor) {
let mut remaining_ranges = vec![];
let mut ranges: Vec<_> = ranges.into_iter().filter_map(|r| {
Interval::from_ctor(cx.tcx, &r).map(|i| i.into_inner())
IntRange::from_ctor(cx.tcx, &r).map(|i| i.into_inner())
}).collect();
let ty = pat_interval.ty;
let (pat_interval_lo, pat_interval_hi) = pat_interval.into_inner();
Expand All @@ -729,7 +744,7 @@ fn ranges_subtract_pattern<'a, 'tcx>(cx: &mut MatchCheckCtxt<'a, 'tcx>,
}
// Convert the remaining ranges from pairs to inclusive `ConstantRange`s.
remaining_ranges.into_iter().map(|r| {
let (lo, hi) = Interval::offset_sign(cx.tcx, ty, r, false);
let (lo, hi) = IntRange::decode(cx.tcx, ty, r);
ConstantRange(ty::Const::from_bits(cx.tcx, lo, ty),
ty::Const::from_bits(cx.tcx, hi, ty),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should ConstantRange use ty::Const? Seems expensive, and useless unless the ty::Const is Bits (cc @oli-obk).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should definitely be using ConstValue instead of ty::Const. I put it on my TODO list

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConstantRange can only hold integers, so ConstValue shouldn't be needed, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably. My TODO already says "ConstValue or smaller" ;). That should not block this PR though, since it's a preexisting issue.

RangeEnd::Included)
Expand Down