Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
224 changes: 141 additions & 83 deletions cranelift/codegen/src/ir/pcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,36 +128,52 @@ pub enum PccError {
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum Fact {
/// A bitslice of a value (up to a bitwidth) is less than or equal
/// to a given maximum value.
/// A bitslice of a value (up to a bitwidth) is within the given
/// integer range.
///
/// The slicing behavior is needed because this fact can describe
/// both an SSA `Value`, whose entire value is well-defined, and a
/// `VReg` in VCode, whose bits beyond the type stored in that
/// register are don't-care (undefined).
ValueMax {
Range {
/// The bitwidth of bits we care about, from the LSB upward.
bit_width: u16,
/// The minimum value that the bitslice can take
/// (inclusive). The range is unsigned: the specified bits of
/// the actual value will be greater than or equal to this
/// value, as evaluated by an unsigned integer comparison.
min: u64,
/// The maximum value that the bitslice can take
/// (inclusive). The range is unsigned: the bits of the value
/// will be within the range `0..=max`.
/// (inclusive). The range is unsigned: the specified bits of
/// the actual value will be less than or equal to this value,
/// as evaluated by an unsigned integer comparison.
max: u64,
},

/// A pointer to a memory type.
Mem {
/// The memory type.
ty: ir::MemoryType,
/// The offset into the memory type.
offset: i64,
/// The minimum offset into the memory type, inclusive.
min_offset: u64,
/// The maximum offset into the memory type, inclusive.
max_offset: u64,
},
}

impl fmt::Display for Fact {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Fact::ValueMax { bit_width, max } => write!(f, "max({}, {:#x})", bit_width, max),
Fact::Mem { ty, offset } => write!(f, "mem({}, {:#x})", ty, offset),
Fact::Range {
bit_width,
min,
max,
} => write!(f, "range({}, {:#x}, {:#x})", bit_width, min, max),
Fact::Mem {
ty,
min_offset,
max_offset,
} => write!(f, "mem({}, {:#x}, {:#x})", ty, min_offset, max_offset),
}
}
}
Expand All @@ -166,20 +182,24 @@ impl Fact {
/// Try to infer a minimal fact for a value of the given IR type.
pub fn infer_from_type(ty: ir::Type) -> Option<&'static Self> {
static FACTS: [Fact; 4] = [
Fact::ValueMax {
Fact::Range {
bit_width: 8,
min: 0,
max: u8::MAX as u64,
},
Fact::ValueMax {
Fact::Range {
bit_width: 16,
min: 0,
max: u16::MAX as u64,
},
Fact::ValueMax {
Fact::Range {
bit_width: 32,
min: 0,
max: u32::MAX as u64,
},
Fact::ValueMax {
Fact::Range {
bit_width: 64,
min: 0,
max: u64::MAX,
},
];
Expand Down Expand Up @@ -231,24 +251,25 @@ impl<'a> FactContext<'a> {
(l, r) if l == r => true,

(
Fact::ValueMax {
Fact::Range {
bit_width: bw_lhs,
min: min_lhs,
max: max_lhs,
},
Fact::ValueMax {
Fact::Range {
bit_width: bw_rhs,
min: min_rhs,
max: max_rhs,
},
) => {
// If the bitwidths we're claiming facts about are the
// same, and if the value is less than or equal to
// `max_lhs`, and if `max_rhs` is less than `max_lhs`,
// then it is certainly less than or equal to
// `max_rhs`.
// same, and if the right-hand-side range is larger
// than the left-hand-side range, than the LHS
// subsumes the RHS.
//
// In other words, we can always expand the claimed
// possible value range.
bw_lhs == bw_rhs && max_lhs <= max_rhs
bw_lhs == bw_rhs && max_lhs <= max_rhs && min_lhs >= min_rhs
}

_ => false,
Expand All @@ -275,39 +296,58 @@ impl<'a> FactContext<'a> {
pub fn add(&self, lhs: &Fact, rhs: &Fact, add_width: u16) -> Option<Fact> {
match (lhs, rhs) {
(
Fact::ValueMax {
Fact::Range {
bit_width: bw_lhs,
max: lhs,
min: min_lhs,
max: max_lhs,
},
Fact::ValueMax {
Fact::Range {
bit_width: bw_rhs,
max: rhs,
min: min_rhs,
max: max_rhs,
},
) if bw_lhs == bw_rhs && add_width >= *bw_lhs => {
let computed_max = lhs.checked_add(*rhs)?;
let computed_min = min_lhs.checked_add(*min_rhs)?;
let computed_max = max_lhs.checked_add(*max_rhs)?;
let computed_max = std::cmp::min(max_value_for_width(add_width), computed_max);
Some(Fact::ValueMax {
Some(Fact::Range {
bit_width: *bw_lhs,
min: computed_min,
max: computed_max,
})
}

(
Fact::ValueMax {
Fact::Range {
bit_width: bw_max,
min,
max,
},
Fact::Mem { ty, offset },
Fact::Mem {
ty,
min_offset,
max_offset,
},
)
| (
Fact::Mem { ty, offset },
Fact::ValueMax {
Fact::Mem {
ty,
min_offset,
max_offset,
},
Fact::Range {
bit_width: bw_max,
min,
max,
},
) if *bw_max >= self.pointer_width && add_width >= *bw_max => {
let offset = offset.checked_add(i64::try_from(*max).ok()?)?;
Some(Fact::Mem { ty: *ty, offset })
let min_offset = min_offset.checked_add(*min)?;
let max_offset = max_offset.checked_add(*max)?;
Some(Fact::Mem {
ty: *ty,
min_offset,
max_offset,
})
}

_ => None,
Expand All @@ -322,14 +362,20 @@ impl<'a> FactContext<'a> {
// bit_width and from_bits are exactly contiguous, then we
// have defined values in 0..to_bits (and because this is
// a zero-extend, the max value is the same).
Fact::ValueMax { bit_width, max } if *bit_width == from_width => Some(Fact::ValueMax {
Fact::Range {
bit_width,
min,
max,
} if *bit_width == from_width => Some(Fact::Range {
bit_width: to_width,
min: *min,
max: *max,
}),
// Otherwise, we can at least claim that the value is
// within the range of `to_width`.
Fact::ValueMax { .. } => Some(Fact::ValueMax {
Fact::Range { .. } => Some(Fact::Range {
bit_width: to_width,
min: 0,
max: max_value_for_width(to_width),
}),
_ => None,
Expand All @@ -342,9 +388,13 @@ impl<'a> FactContext<'a> {
// If we have a defined value in bits 0..bit_width, and
// the MSB w.r.t. `from_width` is *not* set, then we can
// do the same as `uextend`.
Fact::ValueMax { bit_width, max }
if *bit_width == from_width && (*max & (1 << (*bit_width - 1)) == 0) =>
{
Fact::Range {
bit_width,
// We can ignore `min`: it is always <= max in
// unsigned terms, and we check max's LSB below.
min: _,
max,
} if *bit_width == from_width && (*max & (1 << (*bit_width - 1)) == 0) => {
self.uextend(fact, from_width, to_width)
}
_ => None,
Expand All @@ -353,23 +403,20 @@ impl<'a> FactContext<'a> {

/// Scales a value with a fact by a known constant.
pub fn scale(&self, fact: &Fact, width: u16, factor: u32) -> Option<Fact> {
// The minimal (loosest) fact we can claim: the value will be
// within the range implied by its bitwidth.
let minimal_fact = Fact::ValueMax {
bit_width: width,
max: max_value_for_width(width),
};
match fact {
Fact::ValueMax { bit_width, max } if *bit_width == width => {
let max = match max.checked_mul(u64::from(factor)) {
Some(max) => max,
None => return Some(minimal_fact),
};
Fact::Range {
bit_width,
min,
max,
} if *bit_width == width => {
let min = min.checked_mul(u64::from(factor))?;
let max = max.checked_mul(u64::from(factor))?;
if *bit_width < 64 && max > max_value_for_width(width) {
return Some(minimal_fact);
return None;
}
Some(Fact::ValueMax {
Some(Fact::Range {
bit_width: *bit_width,
min,
max,
})
}
Expand All @@ -388,36 +435,38 @@ impl<'a> FactContext<'a> {

/// Offsets a value with a fact by a known amount.
pub fn offset(&self, fact: &Fact, width: u16, offset: i64) -> Option<Fact> {
match fact {
Fact::ValueMax { bit_width, max } if *bit_width == width => {
// If we eventually support two-sided ranges, we can
// represent (0..n) + m -> ((0+m)..(n+m)). However,
// right now, all ranges start with zero, so any
// negative offset could underflow, and removes all
// claims of constrained range.
let offset = u64::try_from(offset).ok()?;

let max = match max.checked_add(offset) {
Some(max) => max,
None => {
return Some(Fact::ValueMax {
bit_width: width,
max: max_value_for_width(width),
})
}
};
// Any negative offset could underflow, and removes
// all claims of constrained range, so for now we only
// support positive offsets.
let offset = u64::try_from(offset).ok()?;

Some(Fact::ValueMax {
match fact {
Fact::Range {
bit_width,
min,
max,
} if *bit_width == width => {
let min = min.checked_add(offset)?;
let max = max.checked_add(offset)?;

Some(Fact::Range {
bit_width: *bit_width,
min,
max,
})
}
Fact::Mem {
ty,
offset: mem_offset,
min_offset: mem_min_offset,
max_offset: mem_max_offset,
} => {
let offset = mem_offset.checked_sub(offset)?;
Some(Fact::Mem { ty: *ty, offset })
let min_offset = mem_min_offset.checked_sub(offset)?;
let max_offset = mem_max_offset.checked_sub(offset)?;
Some(Fact::Mem {
ty: *ty,
min_offset,
max_offset,
})
}
_ => None,
}
Expand All @@ -427,23 +476,31 @@ impl<'a> FactContext<'a> {
/// a memory access of the given size, is valid.
///
/// If valid, returns the memory type and offset into that type
/// that this address accesses.
fn check_address(&self, fact: &Fact, size: u32) -> PccResult<(ir::MemoryType, i64)> {
/// that this address accesses, if known, or `None` if the range
/// doesn't constrain the access to exactly one location.
fn check_address(&self, fact: &Fact, size: u32) -> PccResult<Option<(ir::MemoryType, u64)>> {
match fact {
Fact::Mem { ty, offset } => {
let end_offset: i64 = offset
.checked_add(i64::from(size))
Fact::Mem {
ty,
min_offset,
max_offset,
} => {
let end_offset: u64 = max_offset
.checked_add(u64::from(size))
.ok_or(PccError::Overflow)?;
let end_offset: u64 =
u64::try_from(end_offset).map_err(|_| PccError::OutOfBounds)?;
match &self.function.memory_types[*ty] {
ir::MemoryTypeData::Struct { size, .. }
| ir::MemoryTypeData::Memory { size } => {
ensure!(end_offset <= *size, OutOfBounds)
}
ir::MemoryTypeData::Empty => bail!(OutOfBounds),
}
Ok((*ty, *offset))
let specific_ty_and_offset = if min_offset == max_offset {
Some((*ty, *min_offset))
} else {
None
};
Ok(specific_ty_and_offset)
}
_ => bail!(OutOfBounds),
}
Expand All @@ -456,9 +513,10 @@ impl<'a> FactContext<'a> {
fact: &Fact,
access_ty: ir::Type,
) -> PccResult<Option<&'b ir::MemoryTypeField>> {
let (ty, offset) = self.check_address(fact, access_ty.bytes())?;
let offset =
u64::try_from(offset).expect("valid access address cannot have a negative offset");
let (ty, offset) = match self.check_address(fact, access_ty.bytes())? {
Some((ty, offset)) => (ty, offset),
None => return Ok(None),
};

if let ir::MemoryTypeData::Struct { fields, .. } = &self.function.memory_types[ty] {
let field = fields
Expand Down
Loading