Skip to content
Closed
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
24 changes: 24 additions & 0 deletions prusti-encoder/src/encoders/builtin/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ impl TaskEncoder for MirBuiltinCastEnc {
let name = match kind {
mir::CastKind::PointerCoercion(ty::adjustment::PointerCoercion::Unsize, ..) => "unsize",
mir::CastKind::IntToInt => "i2i",
mir::CastKind::IntToFloat => "i2f",
other => todo!("cast kind {other:?}"),
};
// The unsize cast/methods don't depend on the pointee type (the methods
Expand Down Expand Up @@ -165,6 +166,29 @@ impl TaskEncoder for MirBuiltinCastEnc {
MirBuiltinCastOutput::Simple(fn_idn),
)
}
mir::CastKind::IntToFloat => {
let e_op_ty = op_ty.expect_primitive();
let e_res_ty = res_ty.expect_float();

let real_op = vcx.mk_bin_op_expr(
vir::BinOpKind::FractionalPerm,
e_op_ty.snap_to_prim(arg_ex),
vcx.mk_const_expr(vir::ConstData::Int(1)),
);
Comment on lines +173 to +177

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

On Zulip you said:

Surely, this is not the best way [...]

What would the alternatives be? What happens if we cast integers that can't be represented exactly by a float, and is the behaviour consistent with Rust? For example:

fn main() {
    let x = 9007199254740993i64;
    println!("{}", x);
    println!("{}", x as f64);
}

Since x is beyond the maximum safe integer for f64, this prints:

9007199254740993
9007199254740992

@ThomasMayerl ThomasMayerl Jul 17, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

What would the alternatives be?

I just realized during adding support for float->int that it makes more sense to use the SMT function to convert from int to real instead of doing the division. I'll probably close this PR and open a new PR for float->int and int->float

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

And yes the case you mentioned works

let expr = (e_res_ty.from_real)(real_op.downcast_ty());

// A value-level (thin) cast: no generic parameters, no precondition.
let fn_idn = FunctionIdn::new(name, op_ty_snap, res_ty_snap);
let function = vcx.mk_function(fn_idn, (arg_decl,), &[], &[], None, Some(expr));
(
MirBuiltinCastLocal {
cast: function,
unsize: None,
undo: None,
},
MirBuiltinCastOutput::Simple(fn_idn),
)
}
mir::CastKind::PointerCoercion(ty::adjustment::PointerCoercion::Unsize, _) => {
// Unsizing preserves the (wide) reference snapshot type and only
// rewrites the pointer metadata for the new referent. The cast
Expand Down
14 changes: 14 additions & 0 deletions prusti-encoder/src/encoders/ty/interpretation/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub struct FloatDomainData<'vir> {
pub prim_to_snap: FunctionIdn<'vir, vir::Prim, vir::CSnap>,
#[allow(unused)]
pub from_bv: FunctionIdn<'vir, vir::CSnap, vir::CSnap>,
pub from_real: FunctionIdn<'vir, vir::Perm, vir::CSnap>,
pub fp_eq: FunctionIdn<'vir, (vir::CSnap, vir::CSnap), vir::Bool>,
pub fp_add: FunctionIdn<'vir, (vir::CSnap, vir::CSnap), vir::CSnap>,
pub fp_sub: FunctionIdn<'vir, (vir::CSnap, vir::CSnap), vir::CSnap>,
Expand Down Expand Up @@ -191,6 +192,18 @@ pub(crate) fn ty_pure_float<'vir>(
ty::FloatTy::F128 => "(_ to_fp 15 113)",
};
let from_bv = builder.backend_func("from_bv", (bit_vec.domain)(), builder.self_type(), Some(i));
let from_real_name: &str = match float {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

format!("{backend_type} RNE")?

ty::FloatTy::F16 => "(_ to_fp 5 11) RNE",
ty::FloatTy::F32 => "(_ to_fp 8 24) RNE",
ty::FloatTy::F64 => "(_ to_fp 11 53) RNE",
ty::FloatTy::F128 => "(_ to_fp 15 113) RNE",
};
let from_real = builder.backend_func(
"from_real",
vir::TYPE_PERM,
builder.self_type(),
Some(from_real_name),
);

let fp_to_real = builder.backend_func(
"to_real",
Expand All @@ -206,6 +219,7 @@ pub(crate) fn ty_pure_float<'vir>(
Ok(FloatDomainData {
prim_to_snap,
from_bv,
from_real,
fp_eq,
fp_add,
fp_sub,
Expand Down
19 changes: 19 additions & 0 deletions prusti-tests/tests/verify/pass/casts/int_to_float.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// This currently works with CVC5 but not with Z3..

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This is not a terribly useful comment (nor the right style). Why does it not work with Z3? Do any of the assertions here work? Can we add assertions to make it work?

Either way maybe this should not be in the testsuite. (Yet? Ever?) We could have some tests which are CVC5-specific, in which case we should configure them as such using comments that provide the requisite flags to Prusti (and indirectly Silicon) to make it use CVC5.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Z3 just somehow fails to reason about it. Even cases like 1 -> 1.0 fail. This seems to be more like a Z3 issue. Therefore, I added the test case since we can now create a proper encoding for this cast without crashing. Even if the resulting Viper file doesn't verify with Z3

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Interestingly, Z3 only struggles with int -> float. float -> int (which is more complicated because of the bounds checking) is not a problem


fn main() {
let x = 100;
let y = x as f32;
assert!(y == 100.0);

let x = u128::MAX;
let y = x as f32;
assert!(y == f32::INFINITY);

let x = u8::MAX;
let y = x as f64;
assert!(y == 255.0);

let x = -5;
let y = x as f32;
assert!(y == -5.0);
}
1 change: 1 addition & 0 deletions prusti-viper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ impl<'vir, 'v> ToViper<'vir, 'v> for vir::BinOp<'vir> {
vir::BinOpKind::PermAdd => ctx.ast.perm_add_with_pos(lhs, rhs, pos),
vir::BinOpKind::PermSub => ctx.ast.perm_sub_with_pos(lhs, rhs, pos),
vir::BinOpKind::PermMul => ctx.ast.perm_mul_with_pos(lhs, rhs, pos),
vir::BinOpKind::FractionalPerm => ctx.ast.fractional_perm_with_pos(lhs, rhs, pos),
vir::BinOpKind::PermPermDiv => ctx.ast.perm_perm_div_with_pos(lhs, rhs, pos),
vir::BinOpKind::Mod => ctx.ast.mod_with_pos(lhs, rhs, pos),
vir::BinOpKind::Implies => ctx.ast.implies_with_pos(lhs, rhs, pos),
Expand Down
11 changes: 11 additions & 0 deletions viper/src/ast_factory/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,17 @@ impl<'a> AstFactory<'a> {
)
}

pub fn fractional_perm_with_pos(&self, left: Expr, right: Expr, pos: Position) -> Expr<'a> {
build_ast_node_with_pos!(
self,
Expr,
ast::FractionalPerm,
left.to_jobject(),
right.to_jobject(),
pos.to_jobject()
)
}

pub fn perm_perm_div_with_pos(&self, left: Expr, right: Expr, pos: Position) -> Expr<'a> {
build_ast_node_with_pos!(
self,
Expand Down
1 change: 1 addition & 0 deletions vir/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub enum BinOpKind {
PermSub,
PermMul,
PermPermDiv,
FractionalPerm,
Comment thread
Aurel300 marked this conversation as resolved.
Mod,
// Set ops
SetUnion,
Expand Down
1 change: 1 addition & 0 deletions vir/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ impl<'vir, Curr, Next> Debug for BinOpGenData<'vir, Curr, Next> {
BinOpKind::PermSub => "-",
BinOpKind::PermMul => "*",
BinOpKind::PermPermDiv => "/",
BinOpKind::FractionalPerm => "/",
BinOpKind::Mod => "%",
BinOpKind::SetUnion => "union",
BinOpKind::SetIn => "in",
Expand Down
1 change: 1 addition & 0 deletions vir/src/gendata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl<'vir, Curr, Next> BinOpGenData<'vir, Curr, Next> {
| BinOpKind::PermSub
| BinOpKind::PermMul
| BinOpKind::PermPermDiv => crate::TYPE_PERM.upcast_ty(),
BinOpKind::FractionalPerm => crate::TYPE_PERM.upcast_ty(),
Comment on lines 46 to +47

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Suggested change
| BinOpKind::PermPermDiv => crate::TYPE_PERM.upcast_ty(),
BinOpKind::FractionalPerm => crate::TYPE_PERM.upcast_ty(),
| BinOpKind::PermPermDiv
| BinOpKind::FractionalPerm => crate::TYPE_PERM.upcast_ty(),


BinOpKind::SetUnion => return self.lhs.ty(),
};
Expand Down
Loading