Skip to content

Commit b9b85fa

Browse files
Unchecked arithmetic proofs and lemmas (#571)
The proofs for unchecked arithmetic (from AWS initiative challenge 11) require additional lemmas to confirm that no truncation is happening. These lemmas are added here, also removing one lemma that we had twice (in the lemmas file and in `numbers.md` where `truncate` is defined). Also checking in the Rust code over which we run the proofs, and will set up a convention for which functions to run in `prove-rs` (suggestion: one-line comment containing a list of `start-symbol` parameters at the top of the file). --------- Co-authored-by: devops <[email protected]>
1 parent f46fd8f commit b9b85fa

File tree

8 files changed

+182
-14
lines changed

8 files changed

+182
-14
lines changed

kmir/pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "kmir"
7-
version = "0.3.151"
7+
version = "0.3.152"
88
description = ""
99
requires-python = "~=3.10"
1010
dependencies = [

kmir/src/kmir/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from typing import Final
22

3-
VERSION: Final = '0.3.151'
3+
VERSION: Final = '0.3.152'

kmir/src/kmir/kdist/mir-semantics/lemmas/kmir-lemmas.md

+32
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,38 @@ Therefore, its value range should be simplified for symbolic input asserted to b
5050
[simplification]
5151
```
5252

53+
However, `truncate` gets evaluated and is therefore not present any more for this simplification.
54+
The following simplification rules operate on the expression created by evaluating `truncate` when
55+
`WIDTH` is 8, 16, 32, 64, or 128 and the mode is `Unsigned`. The simplification would hold for any
56+
power of two but the semantics will always operate with these particular ones.
57+
58+
```k
59+
rule VAL &Int MASK => VAL
60+
requires 0 <=Int VAL
61+
andBool VAL <=Int MASK
62+
andBool ( MASK ==Int bitmask8
63+
orBool MASK ==Int bitmask16
64+
orBool MASK ==Int bitmask32
65+
orBool MASK ==Int bitmask64
66+
orBool MASK ==Int bitmask128
67+
)
68+
[simplification, preserves-definedness]
69+
70+
syntax Int ::= "bitmask8" [macro]
71+
| "bitmask16" [macro]
72+
| "bitmask32" [macro]
73+
| "bitmask64" [macro]
74+
| "bitmask128" [macro]
75+
76+
rule bitmask8 => ( 1 <<Int 8 ) -Int 1
77+
rule bitmask16 => ( 1 <<Int 16 ) -Int 1
78+
rule bitmask32 => ( 1 <<Int 32 ) -Int 1
79+
rule bitmask64 => ( 1 <<Int 64 ) -Int 1
80+
rule bitmask128 => ( 1 <<Int 128) -Int 1
81+
82+
```
83+
84+
5385
```k
5486
endmodule
5587
```

kmir/src/kmir/kdist/mir-semantics/rt/numbers.md

+1-4
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,12 @@ This truncation function is instrumental in the implementation of Integer arithm
7474
requires WIDTH <=Int 0
7575
// unsigned values can be truncated using a simple bitmask
7676
// NB if VAL is negative (underflow), the truncation will yield a positive number
77+
7778
rule truncate(VAL, WIDTH, Unsigned)
7879
=> // mask with relevant bits
7980
VAL &Int ((1 <<Int WIDTH) -Int 1)
8081
requires 0 <Int WIDTH
8182
[preserves-definedness]
82-
rule truncate(VAL, WIDTH, Unsigned)
83-
=> VAL // shortcut when there is nothing to do
84-
requires 0 <Int WIDTH andBool VAL <Int 1 <<Int WIDTH
85-
[simplification, preserves-definedness]
8683
8784
// for signed values we need to preserve/restore the sign
8885
rule truncate(VAL, WIDTH, Signed)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// @kmir prove-rs: unchecked_add_i32 unchecked_sub_usize unchecked_mul_isize
2+
3+
fn main() {
4+
unchecked_add_i32(1,2);
5+
unchecked_sub_usize(1, 2);
6+
unchecked_mul_isize(1, 2);
7+
}
8+
9+
/// If the precondition is not met, the program is not executed (exits cleanly, ex falso quodlibet)
10+
macro_rules! precondition {
11+
($pre:expr, $block:expr) => {
12+
if $pre { $block }
13+
};
14+
}
15+
16+
fn unchecked_add_i32(a: i32, b: i32) {
17+
18+
precondition!(
19+
((a as i64 + b as i64) <= i32::MAX as i64) &&
20+
( a as i64 + b as i64 >= i32::MIN as i64),
21+
// =========================================
22+
unsafe {
23+
let result = a.unchecked_add(b);
24+
assert!(result as i64 == a as i64 + b as i64)
25+
}
26+
);
27+
}
28+
29+
30+
macro_rules! unchecked_add_claim_for {
31+
($name:ident, $ty:ident) => {
32+
#[allow(unused)]
33+
fn $name(a: $ty, b: $ty)
34+
{
35+
use std::$ty;
36+
precondition!(
37+
((a as i128 + b as i128) <= $ty::MAX as i128) &&
38+
( a as i128 + b as i128 >= $ty::MIN as i128),
39+
// =========================================
40+
unsafe {
41+
let result = a.unchecked_add(b);
42+
assert!(result as i128 == a as i128 + b as i128)
43+
}
44+
);
45+
}
46+
}
47+
}
48+
49+
unchecked_add_claim_for!(unchecked_add_i8 , i8 );
50+
unchecked_add_claim_for!(unchecked_add_i16 , i16 );
51+
// unchecked_add_claim_for!(unchecked_add_i32 , i32 ); // already above
52+
unchecked_add_claim_for!(unchecked_add_i64 , i64 );
53+
unchecked_add_claim_for!(unchecked_add_isize, isize);
54+
unchecked_add_claim_for!(unchecked_add_u8 , u8 );
55+
unchecked_add_claim_for!(unchecked_add_u16 , u16 );
56+
unchecked_add_claim_for!(unchecked_add_u32 , u32 );
57+
unchecked_add_claim_for!(unchecked_add_u64 , u64 );
58+
unchecked_add_claim_for!(unchecked_add_usize, usize);
59+
60+
macro_rules! unchecked_sub_claim_for {
61+
($name:ident, $ty:ident) => {
62+
#[allow(unused)]
63+
fn $name(a: $ty, b: $ty)
64+
{
65+
use std::$ty;
66+
precondition!(
67+
((a as i128 - b as i128) <= $ty::MAX as i128) &&
68+
( a as i128 - b as i128 >= $ty::MIN as i128),
69+
// =========================================
70+
unsafe {
71+
let result = a.unchecked_sub(b);
72+
assert!(result as i128 == a as i128 - b as i128)
73+
}
74+
);
75+
}
76+
}
77+
}
78+
79+
unchecked_sub_claim_for!(unchecked_sub_i8 , i8 );
80+
unchecked_sub_claim_for!(unchecked_sub_i16 , i16 );
81+
unchecked_sub_claim_for!(unchecked_sub_i32 , i32 );
82+
unchecked_sub_claim_for!(unchecked_sub_i64 , i64 );
83+
unchecked_sub_claim_for!(unchecked_sub_isize, isize);
84+
unchecked_sub_claim_for!(unchecked_sub_u8 , u8 );
85+
unchecked_sub_claim_for!(unchecked_sub_u16 , u16 );
86+
unchecked_sub_claim_for!(unchecked_sub_u32 , u32 );
87+
unchecked_sub_claim_for!(unchecked_sub_u64 , u64 );
88+
unchecked_sub_claim_for!(unchecked_sub_usize, usize);
89+
90+
macro_rules! unchecked_mul_claim_for {
91+
($name:ident, $ty:ident) => {
92+
#[allow(unused)]
93+
fn $name(a: $ty, b: $ty)
94+
{
95+
use std::$ty;
96+
precondition!(
97+
((a as i128 * b as i128) <= $ty::MAX as i128) &&
98+
( a as i128 * b as i128 >= $ty::MIN as i128),
99+
// =========================================
100+
unsafe {
101+
let result = a.unchecked_mul(b);
102+
assert!(result as i128 == a as i128 * b as i128)
103+
}
104+
);
105+
}
106+
}
107+
}
108+
109+
unchecked_mul_claim_for!(unchecked_mul_i8 , i8 );
110+
unchecked_mul_claim_for!(unchecked_mul_i16 , i16 );
111+
unchecked_mul_claim_for!(unchecked_mul_i32 , i32 );
112+
unchecked_mul_claim_for!(unchecked_mul_i64 , i64 );
113+
unchecked_mul_claim_for!(unchecked_mul_isize, isize);
114+
unchecked_mul_claim_for!(unchecked_mul_u8 , u8 );
115+
unchecked_mul_claim_for!(unchecked_mul_u16 , u16 );
116+
unchecked_mul_claim_for!(unchecked_mul_u32 , u32 );
117+
// insufficient bit size for precondition
118+
// unchecked_mul_claim_for!(unchecked_mul_u64 , u64 );
119+
// unchecked_mul_claim_for!(unchecked_mul_usize, usize);

kmir/src/tests/integration/test_integration.py

+26-6
Original file line numberDiff line numberDiff line change
@@ -451,16 +451,36 @@ def test_prove_rs(rs_file: Path, kmir: KMIR, update_expected_output: bool) -> No
451451
should_show = rs_file.stem in PROVE_RS_SHOW_SPECS
452452

453453
prove_rs_opts = ProveRSOpts(rs_file)
454-
apr_proof = kmir.prove_rs(prove_rs_opts)
455-
456-
if not should_fail:
457-
assert apr_proof.passed
458-
else:
459-
assert apr_proof.failed
460454

461455
if should_show:
456+
# always run `main` when kmir show is tested
457+
apr_proof = kmir.prove_rs(prove_rs_opts)
458+
459+
if not should_fail:
460+
assert apr_proof.passed
461+
else:
462+
assert apr_proof.failed
463+
462464
shower = APRProofShow(kmir, node_printer=KMIRAPRNodePrinter(kmir, apr_proof, full_printer=False))
463465
show_res = '\n'.join(shower.show(apr_proof))
464466
assert_or_update_show_output(
465467
show_res, PROVING_DIR / f'show/{rs_file.stem}.expected', update=update_expected_output
466468
)
469+
else:
470+
# read start symbol(s) from the first line (default: [main] otherwise)
471+
start_sym_prefix = '// @kmir prove-rs:'
472+
with open(rs_file) as f:
473+
headline = f.readline().strip('\n')
474+
475+
if headline.startswith(start_sym_prefix):
476+
start_symbols = headline.removeprefix(start_sym_prefix).split()
477+
else:
478+
start_symbols = ['main']
479+
480+
for start_symbol in start_symbols:
481+
prove_rs_opts.start_symbol = start_symbol
482+
apr_proof = kmir.prove_rs(prove_rs_opts)
483+
if not should_fail:
484+
assert apr_proof.passed
485+
else:
486+
assert apr_proof.failed

kmir/uv.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package/version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.151
1+
0.3.152

0 commit comments

Comments
 (0)