Skip to content
Draft
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
176 changes: 125 additions & 51 deletions src/analyze/basic_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,27 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
Rvalue::UnaryOp(op, operand) => {
let operand_ty = self.operand_type(operand);

// PtrMetadata extracts the length from a fat-pointer slice (&[T]).
// In the model, &[T] is &immut TupleType([Box<Array<Int,T>>, Box<Int>]),
// so the length is (*operand).1.
if op == mir::UnOp::PtrMetadata {
if let rty::Type::Pointer(ref ptr) = operand_ty.ty {
if matches!(ptr.kind, rty::PointerKind::Ref(rty::RefKind::Immut))
&& operand_ty
.ty
.as_pointer()
.unwrap()
.elem
.ty
.as_tuple()
.is_some()
{
return operand_ty.deref().tuple_proj(1).deref();
}
}
unimplemented!("PtrMetadata on {:?}", operand_ty.ty);
}

let mut builder = PlaceTypeBuilder::default();
let (operand_ty, operand_term) = builder.subsume(operand_ty);
match (&operand_ty, op) {
Expand Down Expand Up @@ -535,63 +556,97 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
builder.build(rty::PointerType::immut_to(ty).into(), chc::Term::box_(term))
}
Rvalue::Aggregate(kind, fields) => {
// elaboration: all fields are boxed
let field_tys: Vec<_> = fields
.into_iter()
.map(|operand| self.operand_type(operand).boxed())
.collect();
match *kind {
mir::AggregateKind::Adt(did, variant_idx, args, _, _)
if self.tcx.def_kind(did) == DefKind::Enum =>
{
let enum_def = self.ctx.get_or_register_enum_def(did);
let variant_def = &enum_def.variants[variant_idx];
let variant_rtys = variant_def
.field_tys
.clone()
mir::AggregateKind::Array(mir_elem_ty) => {
// Build a Seq<T> = (Array<Int,T>, len) from the literal elements.
let elem_ptys: Vec<_> = fields
.into_iter()
.map(|ty| rty::RefinedType::unrefined(ty.vacuous()));

let rty_args: IndexVec<_, _> = args
.types()
.map(|ty| {
self.type_builder
.for_template(&mut self.ctx)
.with_scope(&self.env)
.build_refined(ty)
})
.map(|operand| self.operand_type(operand))
.collect();
for (field_pty, mut variant_rty) in
field_tys.clone().into_iter().zip(variant_rtys)
{
variant_rty.instantiate_ty_params(rty_args.clone());
let cs = self
.env
.relate_sub_refined_type(&field_pty.into(), &variant_rty.boxed());
self.ctx.extend_clauses(cs);
}

let sort_args: Vec<_> =
rty_args.iter().map(|rty| rty.ty.to_sort()).collect();
let ty = rty::EnumType::new(enum_def.name.clone(), rty_args).into();

let n = elem_ptys.len();
let mut builder = PlaceTypeBuilder::default();
let mut field_terms = Vec::new();
for field_ty in field_tys {
let (_, field_term) = builder.subsume(field_ty);
field_terms.push(field_term);
let elem_ty = elem_ptys.first().map_or_else(
|| self.type_builder.build(mir_elem_ty).vacuous(),
|p| p.ty.clone(),
);
let base_idx = builder.push_existential(chc::Sort::array(
chc::Sort::int(),
elem_ty.to_sort(),
));
let mut arr_term: chc::Term<PlaceTypeVar> = chc::Term::var(base_idx.into());
for (i, pty) in elem_ptys.into_iter().enumerate() {
let (_, elem_term) = builder.subsume(pty);
arr_term = arr_term.store(chc::Term::int(i as i64), elem_term);
}
let arr_pty = builder.build(
rty::ArrayType::new(rty::Type::int(), elem_ty).into(),
arr_term,
);
let n_pty =
PlaceType::with_ty_and_term(rty::Type::int(), chc::Term::int(n as i64));
PlaceType::tuple(vec![arr_pty.boxed(), n_pty.boxed()])
}
other => {
// elaboration: all fields are boxed
let field_tys: Vec<_> = fields
.into_iter()
.map(|operand| self.operand_type(operand).boxed())
.collect();
match other {
mir::AggregateKind::Adt(did, variant_idx, args, _, _)
if self.tcx.def_kind(did) == DefKind::Enum =>
{
let enum_def = self.ctx.get_or_register_enum_def(did);
let variant_def = &enum_def.variants[variant_idx];
let variant_rtys = variant_def
.field_tys
.clone()
.into_iter()
.map(|ty| rty::RefinedType::unrefined(ty.vacuous()));

let rty_args: IndexVec<_, _> = args
.types()
.map(|ty| {
self.type_builder
.for_template(&mut self.ctx)
.with_scope(&self.env)
.build_refined(ty)
})
.collect();
for (field_pty, mut variant_rty) in
field_tys.clone().into_iter().zip(variant_rtys)
{
variant_rty.instantiate_ty_params(rty_args.clone());
let cs = self.env.relate_sub_refined_type(
&field_pty.into(),
&variant_rty.boxed(),
);
self.ctx.extend_clauses(cs);
}

let sort_args: Vec<_> =
rty_args.iter().map(|rty| rty.ty.to_sort()).collect();
let ty = rty::EnumType::new(enum_def.name.clone(), rty_args).into();

let mut builder = PlaceTypeBuilder::default();
let mut field_terms = Vec::new();
for field_ty in field_tys {
let (_, field_term) = builder.subsume(field_ty);
field_terms.push(field_term);
}
builder.build(
ty,
chc::Term::datatype_ctor(
enum_def.name,
sort_args,
variant_def.name.clone(),
field_terms,
),
)
}
_ => PlaceType::tuple(field_tys),
}
builder.build(
ty,
chc::Term::datatype_ctor(
enum_def.name,
sort_args,
variant_def.name.clone(),
field_terms,
),
)
}
_ => PlaceType::tuple(field_tys),
}
}
Rvalue::Cast(
Expand All @@ -608,6 +663,14 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
};
PlaceType::with_ty_and_term(func_ty.vacuous(), chc::Term::null())
}
Rvalue::Cast(
mir::CastKind::PointerCoercion(mir_ty::adjustment::PointerCoercion::Unsize, _),
operand,
_ty,
) => {
// &[T; N] → &[T]: both are modeled as &Seq<T>, so pass through directly.
self.operand_type(operand)
}
Rvalue::Discriminant(place) => {
let place = self.elaborate_place(&place);
let ty = self.env.place_type(place);
Expand All @@ -622,6 +685,17 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
let (_, term) = builder.subsume(ty);
builder.build(rty::Type::Int, chc::Term::datatype_discr(sym, term))
}
Rvalue::Len(place) => {
let model_place = self.elaborate_place(&place);
let place_ty = self.env.place_type(model_place);
match &place_ty.ty {
rty::Type::Tuple(_) => {
// Seq<T> = (Array<Int,T>, Int): length is field 1 (boxed Int).
place_ty.tuple_proj(1).deref()
}
_ => unimplemented!("Rvalue::Len for model type: {:?}", place_ty.ty),
}
}
_ => unimplemented!(
"rvalue={:?} ({:?})",
rvalue,
Expand Down
22 changes: 22 additions & 0 deletions src/refine/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,7 @@ enum Path {
Deref(Box<Path>),
TupleProj(Box<Path>, usize),
Downcast(Box<Path>, VariantIdx, FieldIdx),
Index(Box<Path>, Local),
}

impl<'tcx> From<Place<'tcx>> for Path {
Expand All @@ -1067,6 +1068,7 @@ impl<'tcx> From<Place<'tcx>> for Path {
};
Path::Downcast(Box::new(path), variant_idx, field_idx)
}
Some(PlaceElem::Index(local)) => Path::Index(Box::new(path), local),
None => break,
_ => unimplemented!(),
};
Expand Down Expand Up @@ -1099,6 +1101,26 @@ where
self.path_type(path)
.downcast(*variant_idx, *field_idx, &self.enum_defs)
}
Path::Index(path, idx_local) => {
let inner_pty = self.path_type(path);
let idx_pty = self.local_type(*idx_local);
// Seq<T> = (Box<Array<Int,T>>, Box<Int>): get field 0 then deref the box.
let arr_pty = match inner_pty.ty.clone() {
rty::Type::Tuple(_) => inner_pty.tuple_proj(0).deref(),
ty => unimplemented!("PlaceElem::Index on non-Seq type: {:?}", ty),
};
let rty::Type::Array(arr_ty) = arr_pty.ty.clone() else {
unreachable!("expected Array type after index normalization")
};
let elem_refined_ty = *arr_ty.elem;
let mut builder = refine::PlaceTypeBuilder::default();
let (_, arr_term) = builder.subsume(arr_pty);
let (_, idx_term) = builder.subsume(idx_pty);
let (elem_ty, value_var_ex) = builder.subsume_rty(elem_refined_ty);
let elem_term = crate::chc::Term::var(value_var_ex.into());
builder.push_formula(elem_term.clone().equal_to(arr_term.select(idx_term)));
builder.build(elem_ty, elem_term)
}
}
}

Expand Down
48 changes: 48 additions & 0 deletions std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,14 @@ mod thrust_models {
type Ty = model::Seq<<T as Model>::Ty>;
}

impl<T: Model> Model for [T] {
type Ty = model::Seq<<T as Model>::Ty>;
}

impl<T: Model, const N: usize> Model for [T; N] {
type Ty = model::Seq<<T as Model>::Ty>;
}

impl<T> Model for Option<T> where T: Model {
type Ty = Option<<T as Model>::Ty>;
}
Expand Down Expand Up @@ -825,3 +833,43 @@ fn _extern_spec_partialord_gt<T>(x: &T, y: &T) -> bool
{
PartialOrd::gt(x, y)
}

#[thrust::extern_spec_fn]
#[thrust_macros::requires(true)]
#[thrust_macros::ensures(result == slice.1)]
fn _extern_spec_slice_len<T>(slice: &[T]) -> usize
where T: thrust_models::Model, T::Ty: PartialEq
{
<[T]>::len(slice)
}

#[thrust::extern_spec_fn]
#[thrust_macros::requires(index < slice.1)]
#[thrust_macros::ensures(*result == slice.0[index])]
fn _extern_spec_slice_index<T>(slice: &[T], index: usize) -> &T
where T: thrust_models::Model, T::Ty: PartialEq
{
<[T] as std::ops::Index<usize>>::index(slice, index)
}

#[thrust::extern_spec_fn]
#[thrust_macros::requires(index < (*slice).1)]
#[thrust_macros::ensures(
*result == (*slice).0[index] &&
!result == (!slice).0[index] &&
!slice == thrust_models::model::Seq((*slice).0.store(index, !result), (*slice).1)
)]
fn _extern_spec_slice_index_mut<T>(slice: &mut [T], index: usize) -> &mut T
where T: thrust_models::Model, T::Ty: PartialEq
{
<[T] as std::ops::IndexMut<usize>>::index_mut(slice, index)
}

#[thrust::extern_spec_fn]
#[thrust_macros::requires(true)]
#[thrust_macros::ensures(result == (slice.1 == 0))]
fn _extern_spec_slice_is_empty<T>(slice: &[T]) -> bool
where T: thrust_models::Model, T::Ty: PartialEq
{
<[T]>::is_empty(slice)
}
9 changes: 9 additions & 0 deletions tests/ui/fail/array_literal_1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//@error-in-other-file: Unsat
//@compile-flags: -C debug-assertions=off

fn main() {
let arr = [1i32, 2, 3];
let s: &[i32] = &arr;
let v = s[0];
assert!(v == 99);
}
8 changes: 8 additions & 0 deletions tests/ui/fail/array_literal_2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//@error-in-other-file: Unsat
//@compile-flags: -C debug-assertions=off

fn main() {
let arr = [0i32, 0, 0, 0];
let s: &[i32] = &arr;
let _ = s[4];
}
14 changes: 14 additions & 0 deletions tests/ui/fail/slice_1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//@error-in-other-file: Unsat
//@compile-flags: -C debug-assertions=off

#[thrust::trusted]
#[thrust_macros::requires(true)]
#[thrust_macros::ensures(result.1 == 0)]
fn empty_slice() -> &'static [i32] {
unimplemented!()
}

fn main() {
let s = empty_slice();
let _ = s[0];
}
14 changes: 14 additions & 0 deletions tests/ui/fail/slice_2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//@error-in-other-file: Unsat
//@compile-flags: -C debug-assertions=off

#[thrust::trusted]
#[thrust_macros::requires(true)]
#[thrust_macros::ensures(result.1 >= 2)]
fn two_elem_slice() -> &'static [i32] {
unimplemented!()
}

fn main() {
let s = two_elem_slice();
let _ = s[2];
}
9 changes: 9 additions & 0 deletions tests/ui/pass/array_literal_1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//@check-pass
//@compile-flags: -C debug-assertions=off

fn main() {
let arr = [1i32, 2, 3];
let s: &[i32] = &arr;
let v = s[0];
assert!(v == 1);
}
8 changes: 8 additions & 0 deletions tests/ui/pass/array_literal_2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//@check-pass
//@compile-flags: -C debug-assertions=off

fn main() {
let arr = [0i32, 0, 0, 0];
let s: &[i32] = &arr;
let _ = s[3];
}
15 changes: 15 additions & 0 deletions tests/ui/pass/slice_1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//@check-pass
//@compile-flags: -C debug-assertions=off

#[thrust::trusted]
#[thrust_macros::requires(true)]
#[thrust_macros::ensures(result.1 > 0)]
fn nonempty_slice() -> &'static [i32] {
unimplemented!()
}

fn main() {
let s = nonempty_slice();
assert!(s.len() > 0);
let _ = s[0];
}
Loading