use crate::ir;
use crate::ir::types::*;
use crate::isa::TargetIsa;
use crate::machinst::{BlockIndex, LowerBackend, VCode};
use crate::trace;
use core::fmt;
use regalloc2::Function as _;
#[cfg(feature = "enable-serde")]
use serde_derive::{Deserialize, Serialize};
pub type PccResult<T> = core::result::Result<T, PccError>;
#[derive(Debug, Clone)]
pub enum PccError {
Overflow,
MissingFact,
UnsupportedFact,
UnsupportedBlockparam,
OutOfBounds,
UnimplementedBackend,
UnimplementedInst,
InvalidFieldOffset,
BadFieldType,
WriteToReadOnlyField,
InvalidStoredFact,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum Fact {
Range {
bit_width: u16,
min: u64,
max: u64,
},
DynamicRange {
bit_width: u16,
min: Expr,
max: Expr,
},
Mem {
ty: ir::MemoryType,
min_offset: u64,
max_offset: u64,
nullable: bool,
},
DynamicMem {
ty: ir::MemoryType,
min: Expr,
max: Expr,
nullable: bool,
},
Def {
value: ir::Value,
},
Compare {
kind: ir::condcodes::IntCC,
lhs: Expr,
rhs: Expr,
},
Conflict,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct Expr {
pub base: BaseExpr,
pub offset: i64,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum BaseExpr {
None,
GlobalValue(ir::GlobalValue),
Value(ir::Value),
Max,
}
impl BaseExpr {
fn le(lhs: &BaseExpr, rhs: &BaseExpr) -> bool {
lhs == rhs || *lhs == BaseExpr::None || *rhs == BaseExpr::Max
}
fn min(lhs: &BaseExpr, rhs: &BaseExpr) -> BaseExpr {
if lhs == rhs {
lhs.clone()
} else if *lhs == BaseExpr::Max {
rhs.clone()
} else if *rhs == BaseExpr::Max {
lhs.clone()
} else {
BaseExpr::None }
}
fn max(lhs: &BaseExpr, rhs: &BaseExpr) -> BaseExpr {
if lhs == rhs {
lhs.clone()
} else if *lhs == BaseExpr::None {
rhs.clone()
} else if *rhs == BaseExpr::None {
lhs.clone()
} else {
BaseExpr::Max
}
}
}
impl Expr {
pub fn constant(offset: i64) -> Self {
Expr {
base: BaseExpr::None,
offset,
}
}
pub fn value(value: ir::Value) -> Self {
Expr {
base: BaseExpr::Value(value),
offset: 0,
}
}
pub fn global_value(gv: ir::GlobalValue) -> Self {
Expr {
base: BaseExpr::GlobalValue(gv),
offset: 0,
}
}
fn le(lhs: &Expr, rhs: &Expr) -> bool {
if rhs.base == BaseExpr::Max {
true
} else {
BaseExpr::le(&lhs.base, &rhs.base) && lhs.offset <= rhs.offset
}
}
fn min(lhs: &Expr, rhs: &Expr) -> Expr {
if lhs.base == BaseExpr::None && lhs.offset == 0 {
lhs.clone()
} else if rhs.base == BaseExpr::None && rhs.offset == 0 {
rhs.clone()
} else {
Expr {
base: BaseExpr::min(&lhs.base, &rhs.base),
offset: core::cmp::min(lhs.offset, rhs.offset),
}
}
}
fn max(lhs: &Expr, rhs: &Expr) -> Expr {
if lhs.base == BaseExpr::None && lhs.offset == 0 {
rhs.clone()
} else if rhs.base == BaseExpr::None && rhs.offset == 0 {
lhs.clone()
} else {
Expr {
base: BaseExpr::max(&lhs.base, &rhs.base),
offset: core::cmp::max(lhs.offset, rhs.offset),
}
}
}
fn add(lhs: &Expr, rhs: &Expr) -> Option<Expr> {
if lhs.base == rhs.base {
Some(Expr {
base: lhs.base.clone(),
offset: lhs.offset.checked_add(rhs.offset)?,
})
} else if lhs.base == BaseExpr::None {
Some(Expr {
base: rhs.base.clone(),
offset: lhs.offset.checked_add(rhs.offset)?,
})
} else if rhs.base == BaseExpr::None {
Some(Expr {
base: lhs.base.clone(),
offset: lhs.offset.checked_add(rhs.offset)?,
})
} else {
Some(Expr {
base: BaseExpr::Max,
offset: 0,
})
}
}
pub fn offset(lhs: &Expr, rhs: i64) -> Option<Expr> {
let offset = lhs.offset.checked_add(rhs)?;
Some(Expr {
base: lhs.base.clone(),
offset,
})
}
pub fn without_offset(&self) -> Option<&BaseExpr> {
if self.offset == 0 {
Some(&self.base)
} else {
None
}
}
}
impl fmt::Display for BaseExpr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
BaseExpr::None => Ok(()),
BaseExpr::Max => write!(f, "max"),
BaseExpr::GlobalValue(gv) => write!(f, "{gv}"),
BaseExpr::Value(value) => write!(f, "{value}"),
}
}
}
impl BaseExpr {
pub fn is_some(&self) -> bool {
match self {
BaseExpr::None => false,
_ => true,
}
}
}
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.base)?;
match self.offset {
offset if offset > 0 && self.base.is_some() => write!(f, "+{offset:#x}"),
offset if offset > 0 => write!(f, "{offset:#x}"),
offset if offset < 0 => {
let negative_offset = -i128::from(offset); write!(f, "-{negative_offset:#x}")
}
0 if self.base.is_some() => Ok(()),
0 => write!(f, "0"),
_ => unreachable!(),
}
}
}
impl fmt::Display for Fact {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Fact::Range {
bit_width,
min,
max,
} => write!(f, "range({bit_width}, {min:#x}, {max:#x})"),
Fact::DynamicRange {
bit_width,
min,
max,
} => {
write!(f, "dynamic_range({bit_width}, {min}, {max})")
}
Fact::Mem {
ty,
min_offset,
max_offset,
nullable,
} => {
let nullable_flag = if *nullable { ", nullable" } else { "" };
write!(
f,
"mem({ty}, {min_offset:#x}, {max_offset:#x}{nullable_flag})"
)
}
Fact::DynamicMem {
ty,
min,
max,
nullable,
} => {
let nullable_flag = if *nullable { ", nullable" } else { "" };
write!(f, "dynamic_mem({ty}, {min}, {max}{nullable_flag})")
}
Fact::Def { value } => write!(f, "def({value})"),
Fact::Compare { kind, lhs, rhs } => {
write!(f, "compare({kind}, {lhs}, {rhs})")
}
Fact::Conflict => write!(f, "conflict"),
}
}
}
impl Fact {
pub fn constant(bit_width: u16, value: u64) -> Self {
debug_assert!(value <= max_value_for_width(bit_width));
Fact::Range {
bit_width,
min: value,
max: value,
}
}
pub fn dynamic_base_ptr(ty: ir::MemoryType) -> Self {
Fact::DynamicMem {
ty,
min: Expr::constant(0),
max: Expr::constant(0),
nullable: false,
}
}
pub fn value(bit_width: u16, value: ir::Value) -> Self {
Fact::DynamicRange {
bit_width,
min: Expr::value(value),
max: Expr::value(value),
}
}
pub fn value_offset(bit_width: u16, value: ir::Value, offset: i64) -> Self {
Fact::DynamicRange {
bit_width,
min: Expr::offset(&Expr::value(value), offset).unwrap(),
max: Expr::offset(&Expr::value(value), offset).unwrap(),
}
}
pub fn global_value(bit_width: u16, gv: ir::GlobalValue) -> Self {
Fact::DynamicRange {
bit_width,
min: Expr::global_value(gv),
max: Expr::global_value(gv),
}
}
pub fn global_value_offset(bit_width: u16, gv: ir::GlobalValue, offset: i64) -> Self {
Fact::DynamicRange {
bit_width,
min: Expr::offset(&Expr::global_value(gv), offset).unwrap(),
max: Expr::offset(&Expr::global_value(gv), offset).unwrap(),
}
}
pub const fn max_range_for_width(bit_width: u16) -> Self {
match bit_width {
bit_width if bit_width < 64 => Fact::Range {
bit_width,
min: 0,
max: (1u64 << bit_width) - 1,
},
64 => Fact::Range {
bit_width: 64,
min: 0,
max: u64::MAX,
},
_ => panic!("bit width too large!"),
}
}
pub const fn max_range_for_width_extended(from_width: u16, to_width: u16) -> Self {
debug_assert!(from_width <= to_width);
match from_width {
from_width if from_width < 64 => Fact::Range {
bit_width: to_width,
min: 0,
max: (1u64 << from_width) - 1,
},
64 => Fact::Range {
bit_width: to_width,
min: 0,
max: u64::MAX,
},
_ => panic!("bit width too large!"),
}
}
pub fn infer_from_type(ty: ir::Type) -> Option<&'static Self> {
static FACTS: [Fact; 4] = [
Fact::max_range_for_width(8),
Fact::max_range_for_width(16),
Fact::max_range_for_width(32),
Fact::max_range_for_width(64),
];
match ty {
I8 => Some(&FACTS[0]),
I16 => Some(&FACTS[1]),
I32 => Some(&FACTS[2]),
I64 => Some(&FACTS[3]),
_ => None,
}
}
pub fn propagates(&self) -> bool {
match self {
Fact::Mem { .. } => true,
_ => false,
}
}
pub fn as_const(&self, bits: u16) -> Option<u64> {
match self {
Fact::Range {
bit_width,
min,
max,
} if *bit_width == bits && min == max => Some(*min),
_ => None,
}
}
pub fn as_symbol(&self) -> Option<&Expr> {
match self {
Fact::DynamicRange { min, max, .. } if min == max => Some(min),
_ => None,
}
}
pub fn intersect(a: &Fact, b: &Fact) -> Fact {
match (a, b) {
(
Fact::Range {
bit_width: bw_lhs,
min: min_lhs,
max: max_lhs,
},
Fact::Range {
bit_width: bw_rhs,
min: min_rhs,
max: max_rhs,
},
) if bw_lhs == bw_rhs && max_lhs >= min_rhs && max_rhs >= min_lhs => Fact::Range {
bit_width: *bw_lhs,
min: core::cmp::max(*min_lhs, *min_rhs),
max: core::cmp::min(*max_lhs, *max_rhs),
},
(
Fact::DynamicRange {
bit_width: bw_lhs,
min: min_lhs,
max: max_lhs,
},
Fact::DynamicRange {
bit_width: bw_rhs,
min: min_rhs,
max: max_rhs,
},
) if bw_lhs == bw_rhs && Expr::le(min_rhs, max_lhs) && Expr::le(min_lhs, max_rhs) => {
Fact::DynamicRange {
bit_width: *bw_lhs,
min: Expr::max(min_lhs, min_rhs),
max: Expr::min(max_lhs, max_rhs),
}
}
(
Fact::Mem {
ty: ty_lhs,
min_offset: min_offset_lhs,
max_offset: max_offset_lhs,
nullable: nullable_lhs,
},
Fact::Mem {
ty: ty_rhs,
min_offset: min_offset_rhs,
max_offset: max_offset_rhs,
nullable: nullable_rhs,
},
) if ty_lhs == ty_rhs
&& max_offset_lhs >= min_offset_rhs
&& max_offset_rhs >= min_offset_lhs =>
{
Fact::Mem {
ty: *ty_lhs,
min_offset: core::cmp::max(*min_offset_lhs, *min_offset_rhs),
max_offset: core::cmp::min(*max_offset_lhs, *max_offset_rhs),
nullable: *nullable_lhs && *nullable_rhs,
}
}
(
Fact::DynamicMem {
ty: ty_lhs,
min: min_lhs,
max: max_lhs,
nullable: null_lhs,
},
Fact::DynamicMem {
ty: ty_rhs,
min: min_rhs,
max: max_rhs,
nullable: null_rhs,
},
) if ty_lhs == ty_rhs && Expr::le(min_rhs, max_lhs) && Expr::le(min_lhs, max_rhs) => {
Fact::DynamicMem {
ty: *ty_lhs,
min: Expr::max(min_lhs, min_rhs),
max: Expr::min(max_lhs, max_rhs),
nullable: *null_lhs && *null_rhs,
}
}
_ => Fact::Conflict,
}
}
}
macro_rules! ensure {
( $condition:expr, $err:tt $(,)? ) => {
if !$condition {
return Err(PccError::$err);
}
};
}
macro_rules! bail {
( $err:tt ) => {{
return Err(PccError::$err);
}};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InequalityKind {
Strict,
Loose,
}
pub struct FactContext<'a> {
function: &'a ir::Function,
pointer_width: u16,
}
impl<'a> FactContext<'a> {
pub fn new(function: &'a ir::Function, pointer_width: u16) -> Self {
FactContext {
function,
pointer_width,
}
}
pub fn subsumes(&self, lhs: &Fact, rhs: &Fact) -> bool {
match (lhs, rhs) {
(l, r) if l == r => true,
(
Fact::Range {
bit_width: bw_lhs,
min: min_lhs,
max: max_lhs,
},
Fact::Range {
bit_width: bw_rhs,
min: min_rhs,
max: max_rhs,
},
) => {
bw_lhs >= bw_rhs && max_lhs <= max_rhs && min_lhs >= min_rhs
}
(
Fact::DynamicRange {
bit_width: bw_lhs,
min: min_lhs,
max: max_lhs,
},
Fact::DynamicRange {
bit_width: bw_rhs,
min: min_rhs,
max: max_rhs,
},
) => {
bw_lhs == bw_rhs && Expr::le(max_lhs, max_rhs) && Expr::le(min_rhs, min_lhs)
}
(
Fact::Mem {
ty: ty_lhs,
min_offset: min_offset_lhs,
max_offset: max_offset_lhs,
nullable: nullable_lhs,
},
Fact::Mem {
ty: ty_rhs,
min_offset: min_offset_rhs,
max_offset: max_offset_rhs,
nullable: nullable_rhs,
},
) => {
ty_lhs == ty_rhs
&& max_offset_lhs <= max_offset_rhs
&& min_offset_lhs >= min_offset_rhs
&& (*nullable_lhs || !*nullable_rhs)
}
(
Fact::DynamicMem {
ty: ty_lhs,
min: min_lhs,
max: max_lhs,
nullable: nullable_lhs,
},
Fact::DynamicMem {
ty: ty_rhs,
min: min_rhs,
max: max_rhs,
nullable: nullable_rhs,
},
) => {
ty_lhs == ty_rhs
&& Expr::le(max_lhs, max_rhs)
&& Expr::le(min_rhs, min_lhs)
&& (*nullable_lhs || !*nullable_rhs)
}
(
Fact::Range {
bit_width,
min: 0,
max: 0,
},
Fact::DynamicMem { nullable: true, .. },
) if *bit_width == self.pointer_width => true,
(_, Fact::Def { .. }) => true,
_ => false,
}
}
pub fn subsumes_fact_optionals(&self, lhs: Option<&Fact>, rhs: Option<&Fact>) -> bool {
match (lhs, rhs) {
(None, None) => true,
(Some(_), None) => true,
(None, Some(_)) => false,
(Some(lhs), Some(rhs)) => self.subsumes(lhs, rhs),
}
}
pub fn add(&self, lhs: &Fact, rhs: &Fact, add_width: u16) -> Option<Fact> {
let result = match (lhs, rhs) {
(
Fact::Range {
bit_width: bw_lhs,
min: min_lhs,
max: max_lhs,
},
Fact::Range {
bit_width: bw_rhs,
min: min_rhs,
max: max_rhs,
},
) if bw_lhs == bw_rhs && add_width >= *bw_lhs => {
let computed_min = min_lhs.checked_add(*min_rhs)?;
let computed_max = max_lhs.checked_add(*max_rhs)?;
let computed_max = core::cmp::min(max_value_for_width(add_width), computed_max);
Some(Fact::Range {
bit_width: *bw_lhs,
min: computed_min,
max: computed_max,
})
}
(
Fact::Range {
bit_width: bw_max,
min,
max,
},
Fact::Mem {
ty,
min_offset,
max_offset,
nullable,
},
)
| (
Fact::Mem {
ty,
min_offset,
max_offset,
nullable,
},
Fact::Range {
bit_width: bw_max,
min,
max,
},
) if *bw_max >= self.pointer_width
&& add_width >= *bw_max
&& (!*nullable || *max == 0) =>
{
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,
nullable: false,
})
}
(
Fact::Range {
bit_width: bw_static,
min: min_static,
max: max_static,
},
Fact::DynamicRange {
bit_width: bw_dynamic,
min: min_dynamic,
max: max_dynamic,
},
)
| (
Fact::DynamicRange {
bit_width: bw_dynamic,
min: min_dynamic,
max: max_dynamic,
},
Fact::Range {
bit_width: bw_static,
min: min_static,
max: max_static,
},
) if bw_static == bw_dynamic => {
let min = Expr::offset(min_dynamic, i64::try_from(*min_static).ok()?)?;
let max = Expr::offset(max_dynamic, i64::try_from(*max_static).ok()?)?;
Some(Fact::DynamicRange {
bit_width: *bw_dynamic,
min,
max,
})
}
(
Fact::DynamicMem {
ty,
min: min_mem,
max: max_mem,
nullable: false,
},
Fact::DynamicRange {
bit_width,
min: min_range,
max: max_range,
},
)
| (
Fact::DynamicRange {
bit_width,
min: min_range,
max: max_range,
},
Fact::DynamicMem {
ty,
min: min_mem,
max: max_mem,
nullable: false,
},
) if *bit_width == self.pointer_width => {
let min = Expr::add(min_mem, min_range)?;
let max = Expr::add(max_mem, max_range)?;
Some(Fact::DynamicMem {
ty: *ty,
min,
max,
nullable: false,
})
}
(
Fact::Mem {
ty,
min_offset,
max_offset,
nullable: false,
},
Fact::DynamicRange {
bit_width,
min: min_range,
max: max_range,
},
)
| (
Fact::DynamicRange {
bit_width,
min: min_range,
max: max_range,
},
Fact::Mem {
ty,
min_offset,
max_offset,
nullable: false,
},
) if *bit_width == self.pointer_width => {
let min = Expr::offset(min_range, i64::try_from(*min_offset).ok()?)?;
let max = Expr::offset(max_range, i64::try_from(*max_offset).ok()?)?;
Some(Fact::DynamicMem {
ty: *ty,
min,
max,
nullable: false,
})
}
(
Fact::Range {
bit_width: bw_static,
min: min_static,
max: max_static,
},
Fact::DynamicMem {
ty,
min: min_dynamic,
max: max_dynamic,
nullable,
},
)
| (
Fact::DynamicMem {
ty,
min: min_dynamic,
max: max_dynamic,
nullable,
},
Fact::Range {
bit_width: bw_static,
min: min_static,
max: max_static,
},
) if *bw_static == self.pointer_width && (!*nullable || *max_static == 0) => {
let min = Expr::offset(min_dynamic, i64::try_from(*min_static).ok()?)?;
let max = Expr::offset(max_dynamic, i64::try_from(*max_static).ok()?)?;
Some(Fact::DynamicMem {
ty: *ty,
min,
max,
nullable: false,
})
}
_ => None,
};
trace!("add: {lhs:?} + {rhs:?} -> {result:?}");
result
}
pub fn uextend(&self, fact: &Fact, from_width: u16, to_width: u16) -> Option<Fact> {
if from_width == to_width {
return Some(fact.clone());
}
let result = match fact {
Fact::Range {
bit_width,
min,
max,
} if *bit_width >= from_width
&& *min <= max_value_for_width(from_width)
&& *max <= max_value_for_width(from_width) =>
{
Some(Fact::Range {
bit_width: to_width,
min: *min,
max: *max,
})
}
Fact::DynamicRange {
bit_width,
min,
max,
} if *bit_width == from_width => Some(Fact::DynamicRange {
bit_width: to_width,
min: min.clone(),
max: max.clone(),
}),
Fact::Def { value } => Some(Fact::value(to_width, *value)),
Fact::Range { .. } => Some(Fact::max_range_for_width_extended(from_width, to_width)),
_ => None,
};
trace!("uextend: fact {fact:?} from {from_width} to {to_width} -> {result:?}");
result
}
pub fn sextend(&self, fact: &Fact, from_width: u16, to_width: u16) -> Option<Fact> {
match fact {
Fact::Range {
bit_width,
min: _,
max,
} if *bit_width == from_width && (*max & (1 << (*bit_width - 1)) == 0) => {
self.uextend(fact, from_width, to_width)
}
_ => None,
}
}
pub fn truncate(&self, fact: &Fact, from_width: u16, to_width: u16) -> Option<Fact> {
if from_width == to_width {
return Some(fact.clone());
}
trace!(
"truncate: fact {:?} from {} to {}",
fact, from_width, to_width
);
match fact {
Fact::Range {
bit_width,
min,
max,
} if *bit_width == from_width => {
let max_val = (1u64 << to_width) - 1;
if *min <= max_val && *max <= max_val {
Some(Fact::Range {
bit_width: to_width,
min: *min,
max: *max,
})
} else {
Some(Fact::Range {
bit_width: to_width,
min: 0,
max: max_val,
})
}
}
_ => None,
}
}
pub fn scale(&self, fact: &Fact, width: u16, factor: u32) -> Option<Fact> {
let result = match fact {
x if factor == 1 => Some(x.clone()),
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 None;
}
Some(Fact::Range {
bit_width: *bit_width,
min,
max,
})
}
_ => None,
};
trace!("scale: {fact:?} * {factor} at width {width} -> {result:?}");
result
}
pub fn shl(&self, fact: &Fact, width: u16, amount: u16) -> Option<Fact> {
if amount >= 32 {
return None;
}
let factor: u32 = 1 << amount;
self.scale(fact, width, factor)
}
pub fn offset(&self, fact: &Fact, width: u16, offset: i64) -> Option<Fact> {
if offset == 0 {
return Some(fact.clone());
}
let compute_offset = |base: u64| -> Option<u64> {
if offset >= 0 {
base.checked_add(u64::try_from(offset).unwrap())
} else {
base.checked_sub(u64::try_from(-offset).unwrap())
}
};
let result = match fact {
Fact::Range {
bit_width,
min,
max,
} if *bit_width == width => {
let min = compute_offset(*min)?;
let max = compute_offset(*max)?;
Some(Fact::Range {
bit_width: *bit_width,
min,
max,
})
}
Fact::DynamicRange {
bit_width,
min,
max,
} if *bit_width == width => {
let min = Expr::offset(min, offset)?;
let max = Expr::offset(max, offset)?;
Some(Fact::DynamicRange {
bit_width: *bit_width,
min,
max,
})
}
Fact::Mem {
ty,
min_offset: mem_min_offset,
max_offset: mem_max_offset,
nullable: false,
} => {
let min_offset = compute_offset(*mem_min_offset)?;
let max_offset = compute_offset(*mem_max_offset)?;
Some(Fact::Mem {
ty: *ty,
min_offset,
max_offset,
nullable: false,
})
}
Fact::DynamicMem {
ty,
min,
max,
nullable: false,
} => {
let min = Expr::offset(min, offset)?;
let max = Expr::offset(max, offset)?;
Some(Fact::DynamicMem {
ty: *ty,
min,
max,
nullable: false,
})
}
_ => None,
};
trace!("offset: {fact:?} + {offset} in width {width} -> {result:?}");
result
}
fn check_address(&self, fact: &Fact, size: u32) -> PccResult<Option<(ir::MemoryType, u64)>> {
trace!("check_address: fact {:?} size {}", fact, size);
match fact {
Fact::Mem {
ty,
min_offset,
max_offset,
nullable: _,
} => {
let end_offset: u64 = max_offset
.checked_add(u64::from(size))
.ok_or(PccError::Overflow)?;
match &self.function.memory_types[*ty] {
ir::MemoryTypeData::Struct { size, .. }
| ir::MemoryTypeData::Memory { size } => {
ensure!(end_offset <= *size, OutOfBounds)
}
ir::MemoryTypeData::DynamicMemory { .. } => bail!(OutOfBounds),
ir::MemoryTypeData::Empty => bail!(OutOfBounds),
}
let specific_ty_and_offset = if min_offset == max_offset {
Some((*ty, *min_offset))
} else {
None
};
Ok(specific_ty_and_offset)
}
Fact::DynamicMem {
ty,
min: _,
max:
Expr {
base: BaseExpr::GlobalValue(max_gv),
offset: max_offset,
},
nullable: _,
} => match &self.function.memory_types[*ty] {
ir::MemoryTypeData::DynamicMemory {
gv,
size: mem_static_size,
} if gv == max_gv => {
let end_offset = max_offset
.checked_add(i64::from(size))
.ok_or(PccError::Overflow)?;
let mem_static_size =
i64::try_from(*mem_static_size).map_err(|_| PccError::Overflow)?;
ensure!(end_offset <= mem_static_size, OutOfBounds);
Ok(None)
}
_ => bail!(OutOfBounds),
},
_ => bail!(OutOfBounds),
}
}
pub fn struct_field<'b>(
&'b self,
fact: &Fact,
access_ty: ir::Type,
) -> PccResult<Option<&'b ir::MemoryTypeField>> {
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
.iter()
.find(|field| field.offset == offset)
.ok_or(PccError::InvalidFieldOffset)?;
if field.ty != access_ty {
bail!(BadFieldType);
}
Ok(Some(field))
} else {
Ok(None)
}
}
pub fn load<'b>(&'b self, fact: &Fact, access_ty: ir::Type) -> PccResult<Option<&'b Fact>> {
Ok(self
.struct_field(fact, access_ty)?
.and_then(|field| field.fact()))
}
pub fn store(
&self,
fact: &Fact,
access_ty: ir::Type,
data_fact: Option<&Fact>,
) -> PccResult<()> {
if let Some(field) = self.struct_field(fact, access_ty)? {
if field.readonly {
bail!(WriteToReadOnlyField);
}
if !self.subsumes_fact_optionals(data_fact, field.fact()) {
bail!(InvalidStoredFact);
}
}
Ok(())
}
pub fn apply_inequality(
&self,
fact: &Fact,
lhs: &Fact,
rhs: &Fact,
kind: InequalityKind,
) -> Fact {
let result = match (
lhs.as_symbol(),
lhs.as_const(self.pointer_width)
.and_then(|k| i64::try_from(k).ok()),
rhs.as_symbol(),
fact,
) {
(
Some(lhs),
None,
Some(rhs),
Fact::DynamicMem {
ty,
min,
max,
nullable,
},
) if rhs.base == max.base => {
let strict_offset = match kind {
InequalityKind::Strict => 1,
InequalityKind::Loose => 0,
};
if let Some(offset) = max
.offset
.checked_add(lhs.offset)
.and_then(|x| x.checked_sub(rhs.offset))
.and_then(|x| x.checked_sub(strict_offset))
{
let new_max = Expr {
base: lhs.base.clone(),
offset,
};
Fact::DynamicMem {
ty: *ty,
min: min.clone(),
max: new_max,
nullable: *nullable,
}
} else {
fact.clone()
}
}
(
None,
Some(lhs_const),
Some(rhs),
Fact::DynamicMem {
ty,
min: _,
max,
nullable,
},
) if rhs.base == max.base => {
let strict_offset = match kind {
InequalityKind::Strict => 1,
InequalityKind::Loose => 0,
};
if let Some(offset) = max
.offset
.checked_add(lhs_const)
.and_then(|x| x.checked_sub(rhs.offset))
.and_then(|x| x.checked_sub(strict_offset))
{
Fact::Mem {
ty: *ty,
min_offset: 0,
max_offset: u64::try_from(offset).unwrap_or(0),
nullable: *nullable,
}
} else {
fact.clone()
}
}
_ => fact.clone(),
};
trace!("apply_inequality({fact:?}, {lhs:?}, {rhs:?}, {kind:?} -> {result:?}");
result
}
pub fn union(&self, lhs: &Fact, rhs: &Fact) -> Option<Fact> {
let result = match (lhs, rhs) {
(lhs, rhs) if lhs == rhs => Some(lhs.clone()),
(
Fact::DynamicMem {
ty: ty_lhs,
min: min_lhs,
max: max_lhs,
nullable: nullable_lhs,
},
Fact::DynamicMem {
ty: ty_rhs,
min: min_rhs,
max: max_rhs,
nullable: nullable_rhs,
},
) if ty_lhs == ty_rhs => Some(Fact::DynamicMem {
ty: *ty_lhs,
min: Expr::min(min_lhs, min_rhs),
max: Expr::max(max_lhs, max_rhs),
nullable: *nullable_lhs || *nullable_rhs,
}),
(
Fact::Range {
bit_width: bw_const,
min: 0,
max: 0,
},
Fact::DynamicMem {
ty,
min,
max,
nullable: _,
},
)
| (
Fact::DynamicMem {
ty,
min,
max,
nullable: _,
},
Fact::Range {
bit_width: bw_const,
min: 0,
max: 0,
},
) if *bw_const == self.pointer_width => Some(Fact::DynamicMem {
ty: *ty,
min: min.clone(),
max: max.clone(),
nullable: true,
}),
(
Fact::Range {
bit_width: bw_const,
min: 0,
max: 0,
},
Fact::Mem {
ty,
min_offset,
max_offset,
nullable: _,
},
)
| (
Fact::Mem {
ty,
min_offset,
max_offset,
nullable: _,
},
Fact::Range {
bit_width: bw_const,
min: 0,
max: 0,
},
) if *bw_const == self.pointer_width => Some(Fact::Mem {
ty: *ty,
min_offset: *min_offset,
max_offset: *max_offset,
nullable: true,
}),
_ => None,
};
trace!("union({lhs:?}, {rhs:?}) -> {result:?}");
result
}
}
fn max_value_for_width(bits: u16) -> u64 {
assert!(bits <= 64);
if bits == 64 {
u64::MAX
} else {
(1u64 << bits) - 1
}
}
pub fn check_vcode_facts<B: LowerBackend + TargetIsa>(
f: &ir::Function,
vcode: &mut VCode<B::MInst>,
backend: &B,
) -> PccResult<()> {
let ctx = FactContext::new(f, backend.triple().pointer_width().unwrap().bits().into());
for block in 0..vcode.num_blocks() {
let block = BlockIndex::new(block);
let mut flow_state = B::FactFlowState::default();
for inst in vcode.block_insns(block).iter() {
if let Err(e) = backend.check_fact(&ctx, vcode, inst, &mut flow_state) {
log::info!("Error checking instruction: {:?}", vcode[inst]);
return Err(e);
}
if vcode.is_branch(inst) {
for (succ_idx, succ) in vcode.block_succs(block).iter().enumerate() {
for (arg, param) in vcode
.branch_blockparams(block, inst, succ_idx)
.iter()
.zip(vcode.block_params(*succ).iter())
{
let arg_fact = vcode.vreg_fact(*arg);
let param_fact = vcode.vreg_fact(*param);
if !ctx.subsumes_fact_optionals(arg_fact, param_fact) {
return Err(PccError::UnsupportedBlockparam);
}
}
}
}
}
}
Ok(())
}