Skip to content

Commit d548a81

Browse files
authored
Merge pull request rust-lang#19 from Chris-Hawblitzel/mutual-recursion
Add support for mutual recursion
2 parents cc331c0 + a41b209 commit d548a81

File tree

9 files changed

+583
-95
lines changed

9 files changed

+583
-95
lines changed

verify/rust_verify/example/recursion.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,6 @@ fn arith_sum_int(i: int) -> int {
1212
if i <= 0 { 0 } else { i + arith_sum_int(i - 1) }
1313
}
1414

15-
#[spec]
16-
fn arith_sum_nat(i: nat) -> nat {
17-
decreases(i);
18-
19-
if i == 0 { 0 } else { i + arith_sum_nat(i - 1) }
20-
}
21-
2215
#[spec]
2316
#[opaque]
2417
fn arith_sum_u64(i: u64) -> u64 {
@@ -40,13 +33,17 @@ fn arith_sum_int_nonneg(i: nat) {
4033
#[proof]
4134
fn arith_sum_test1() {
4235
assert(arith_sum_int(0) == 0);
43-
assert(arith_sum_int(1) == 1);
36+
// Recursive functions default to 1 fuel, so without the assert above,
37+
// the following assert will fail
38+
assert(arith_sum_int(1) == 1);
4439
assert(arith_sum_int(2) == 3);
4540
assert(arith_sum_int(3) == 6);
4641
}
4742

4843
#[proof]
4944
fn arith_sum_test2() {
45+
// Instead of writing out intermediate assertions,
46+
// we can instead boost the fuel setting
5047
reveal_with_fuel(arith_sum_int, 4);
5148
assert(arith_sum_int(3) == 6);
5249
}
@@ -56,3 +53,4 @@ fn arith_sum_test3() {
5653
reveal_with_fuel(arith_sum_u64, 4);
5754
assert(arith_sum_u64(3) == 6);
5855
}
56+

verify/rust_verify/src/verifier.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,18 @@ impl Verifier {
208208
Self::check_internal_result(air_context.command(&command));
209209
}
210210

211+
// Pre-declare the function symbols, to allow for the possibility of non-sorted function usage
212+
for function in &krate.functions {
213+
let commands = vir::func_to_air::func_name_to_air(&ctx, &function)?;
214+
if commands.len() > 0 {
215+
air_context.blank_line();
216+
air_context.comment(&("Function-PreDecl ".to_string() + &function.x.name));
217+
}
218+
for command in commands.iter() {
219+
self.check_result_validity(compiler, &command, air_context.command(&command));
220+
}
221+
}
222+
211223
for function in &krate.functions {
212224
let commands = vir::func_to_air::func_decl_to_air(&ctx, &function)?;
213225
if commands.len() > 0 {

verify/rust_verify/tests/common/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,10 @@ pub fn assert_one_fails(err: Vec<(Option<ErrorSpan>, Option<ErrorSpan>)>) {
128128
assert_eq!(err.len(), 1);
129129
assert!(err[0].0.as_ref().expect("span").test_span_line.contains("FAILS"));
130130
}
131+
132+
#[allow(dead_code)]
133+
pub fn assert_two_fails(err: Vec<(Option<ErrorSpan>, Option<ErrorSpan>)>) {
134+
assert_eq!(err.len(), 2);
135+
assert!(err[0].0.as_ref().expect("span").test_span_line.contains("FAILS"));
136+
assert!(err[1].0.as_ref().expect("span").test_span_line.contains("FAILS"));
137+
}

verify/rust_verify/tests/recursion.rs

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
#![feature(rustc_private)]
2+
#[macro_use]
3+
mod common;
4+
use common::*;
5+
6+
test_verify_with_pervasive! {
7+
#[test] basic_correctness_expr code! {
8+
#[spec]
9+
fn arith_sum_nat(i: nat) -> nat {
10+
decreases(i);
11+
12+
if i == 0 { 0 } else { i + arith_sum_nat(i - 1) }
13+
}
14+
} => Ok(())
15+
}
16+
17+
test_verify_with_pervasive! {
18+
#[test] basic_correctness_stmt code! {
19+
#[proof]
20+
fn count_down_stmt(i:nat) {
21+
decreases(i);
22+
23+
if i != 0 {
24+
count_down_stmt(i - 1);
25+
}
26+
}
27+
} => Ok(())
28+
}
29+
30+
test_verify_with_pervasive! {
31+
// Basic test of mutually recursive expressions
32+
#[test] mutually_recursive_expressions code! {
33+
#[spec]
34+
fn count_down_a(i:nat) -> nat {
35+
decreases(i);
36+
37+
if i == 0 { 0 } else { 1 + count_down_b(i - 1) }
38+
}
39+
40+
#[spec]
41+
fn count_down_b(i:nat) -> nat {
42+
decreases(i);
43+
44+
if i == 0 { 0 } else { 1 + count_down_a(i - 1) }
45+
}
46+
47+
#[proof]
48+
fn count_down_properties() {
49+
assert(count_down_b(0) == 0);
50+
assert(count_down_a(1) == 1);
51+
}
52+
} => Ok(())
53+
}
54+
55+
test_verify_with_pervasive! {
56+
// Test that fuel only provides one definition unfolding
57+
#[test] mutually_recursive_expressions_insufficient_fuel code! {
58+
#[spec]
59+
fn count_down_a(i:nat) -> nat {
60+
decreases(i);
61+
62+
if i == 0 { 0 } else { 1 + count_down_b(i - 1) }
63+
}
64+
65+
#[spec]
66+
fn count_down_b(i:nat) -> nat {
67+
decreases(i);
68+
69+
if i == 0 { 0 } else { 1 + count_down_a(i - 1) }
70+
}
71+
72+
#[proof]
73+
fn count_down_properties() {
74+
assert(count_down_a(1) == 1); // FAILS
75+
}
76+
} => Err(err) => assert_one_fails(err)
77+
}
78+
79+
test_verify_with_pervasive! {
80+
// Basic test of mutually recursive statements
81+
#[test] mutually_recursive_statements code! {
82+
#[proof]
83+
fn count_down_a_stmt(i:nat) {
84+
decreases(i);
85+
86+
if i != 0 {
87+
count_down_b_stmt(i - 1);
88+
}
89+
}
90+
91+
#[proof]
92+
fn count_down_b_stmt(i:nat) {
93+
decreases(i);
94+
95+
if i != 0 {
96+
count_down_a_stmt(i - 1);
97+
}
98+
}
99+
} => Ok(())
100+
}
101+
102+
test_verify_with_pervasive! {
103+
// Expression that fails to decrease
104+
#[test] expr_decrease_fail_1 code! {
105+
#[spec]
106+
fn arith_sum_int(i: int) -> int {
107+
decreases(i);
108+
109+
if i <= 0 { 0 } else { i + arith_sum_int(i + 1) } // FAILS
110+
}
111+
} => Err(err) => assert_one_fails(err)
112+
}
113+
114+
test_verify_with_pervasive! {
115+
// Statement that fails to decrease
116+
#[test] stmt_decrease_fail code! {
117+
#[proof]
118+
fn count_down_stmt(i:nat) {
119+
decreases(i);
120+
121+
if i != 0 {
122+
count_down_stmt(i + 1); // FAILS
123+
}
124+
}
125+
} => Err(err) => assert_one_fails(err)
126+
}
127+
128+
test_verify_with_pervasive! {
129+
// Expression that decreases, but not based on the decreases clause provided
130+
#[test] expr_wrong_decreases code! {
131+
#[spec]
132+
fn arith_sum_int(i: int) -> int {
133+
decreases(100 - i);
134+
135+
if i <= 0 { 0 } else { i + arith_sum_int(i - 1) } // FAILS
136+
}
137+
} => Err(err) => assert_one_fails(err)
138+
}
139+
140+
test_verify_with_pervasive! {
141+
// Expression that decreases, but not based on the decreases clause provided
142+
#[test] expr_wrong_decreases_2 code! {
143+
#[spec]
144+
fn arith_sum_int(x:nat, i: int) -> int {
145+
decreases(x);
146+
147+
if i <= 0 { 0 } else { i + arith_sum_int(x, i - 1) } // FAILS
148+
}
149+
} => Err(err) => assert_one_fails(err)
150+
}
151+
152+
test_verify_with_pervasive! {
153+
// Expression that decreases, but not based on the decreases clause provided
154+
#[test] expr_wrong_decreases_3 code! {
155+
#[spec]
156+
fn arith_sum_int(i: int) -> int {
157+
decreases(5);
158+
159+
if i <= 0 { 0 } else { i + arith_sum_int(4) } // FAILS
160+
}
161+
} => Err(err) => assert_one_fails(err)
162+
}
163+
164+
test_verify_with_pervasive! {
165+
// Expression that doesn't decrease due to extra clause
166+
#[test] expr_decrease_fail_2 code! {
167+
#[spec]
168+
fn arith_sum_int(x:nat, y:nat, i: int) -> int {
169+
decreases(i);
170+
171+
if i <= 0 && x < y { 0 } else { i + arith_sum_int(x, y, i - 1) } // FAILS
172+
}
173+
} => Err(err) => assert_one_fails(err)
174+
}
175+
176+
test_verify_with_pervasive! {
177+
// Expression that fails to decrease
178+
#[test] expr_decrease_fail_3 code! {
179+
#[spec]
180+
fn arith_sum_int(i: int) -> int {
181+
decreases(i);
182+
183+
if i <= 0 { 0 } else { i + arith_sum_int(i) } // FAILS
184+
}
185+
} => Err(err) => assert_one_fails(err)
186+
}
187+
188+
test_verify_with_pervasive! {
189+
// Mutually recursive expressions fail to decrease
190+
#[test] mutual_expr_decrease_fail code! {
191+
#[spec]
192+
fn count_down_a(i:nat) -> nat {
193+
decreases(i);
194+
195+
if i == 0 { 0 } else { 1 + count_down_b(i - 1) } // FAILS
196+
}
197+
198+
#[spec]
199+
fn count_down_b(i:nat) -> nat {
200+
decreases(5 - i);
201+
202+
if i >= 5 { 0 } else { 1 + count_down_a(i + 1) } // FAILS
203+
}
204+
} => Err(err) => assert_two_fails(err)
205+
}
206+
207+
test_verify_with_pervasive! {
208+
// Mutually recursive statements fail to decrease
209+
#[test] mutual_stmt_decrease_fail code! {
210+
#[proof]
211+
fn count_down_a_stmt(i:nat) {
212+
decreases(i);
213+
214+
if i != 0 {
215+
count_down_b_stmt(i + 1); // FAILS
216+
}
217+
}
218+
219+
#[proof]
220+
fn count_down_b_stmt(i:nat) {
221+
decreases(i);
222+
223+
if i != 0 {
224+
count_down_a_stmt(i + 1); // FAILS
225+
}
226+
}
227+
} => Err(err) => assert_two_fails(err)
228+
}
229+
230+
// TODO: Expression that fails to decrease in a function returning unit
231+
/*
232+
test_verify_with_pervasive! {
233+
#[test] unit_expr_decrease_fail code! {
234+
#[spec]
235+
fn count_down_tricky(i:nat) {
236+
decreases(i);
237+
238+
if i != 0 {
239+
count_down_tricky(i + 1)
240+
}
241+
}
242+
} => Err(err) => assert_one_fails(err)
243+
}
244+
*/

verify/vir/src/context.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::ast::{Function, Ident, Krate, Mode, Path, Variants, VirErr};
22
use crate::def::FUEL_ID;
3+
use crate::scc::Graph;
34
use air::ast::{Command, CommandX, Commands, DeclX, MultiOp, Span};
45
use air::ast_util::str_typ;
56
use std::collections::HashMap;
@@ -9,6 +10,7 @@ pub struct Ctx {
910
pub(crate) datatypes: HashMap<Path, Variants>,
1011
pub(crate) functions: Vec<Function>,
1112
pub(crate) func_map: HashMap<Ident, Function>,
13+
pub(crate) func_call_graph: Graph<Ident>,
1214
pub(crate) chosen_triggers: std::cell::RefCell<Vec<(Span, Vec<Vec<String>>)>>, // diagnostics
1315
}
1416

@@ -21,14 +23,16 @@ impl Ctx {
2123
.collect::<HashMap<_, _>>();
2224
let mut functions: Vec<Function> = Vec::new();
2325
let mut func_map: HashMap<Ident, Function> = HashMap::new();
26+
let mut func_call_graph: Graph<Ident> = Graph::new();
2427
for function in krate.functions.iter() {
2528
func_map.insert(function.x.name.clone(), function.clone());
26-
crate::recursion::check_no_mutual_recursion(&func_map, function)?;
29+
crate::recursion::expand_call_graph(&mut func_call_graph, function)?;
2730
functions.push(function.clone());
2831
}
32+
func_call_graph.compute_sccs();
2933
let chosen_triggers: std::cell::RefCell<Vec<(Span, Vec<Vec<String>>)>> =
3034
std::cell::RefCell::new(Vec::new());
31-
Ok(Ctx { datatypes, functions, func_map, chosen_triggers })
35+
Ok(Ctx { datatypes, functions, func_map, func_call_graph, chosen_triggers })
3236
}
3337

3438
pub fn prelude(&self) -> Commands {

0 commit comments

Comments
 (0)