Skip to content

Commit 6f06466

Browse files
refactor(deque): clarify proof contracts
1 parent a78f9f9 commit 6f06466

2 files changed

Lines changed: 49 additions & 96 deletions

File tree

deque/index_ops.mbt

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,13 @@
1616
/// The deque representation invariant guarantees these indices are in bounds.
1717
/// Keep these private so callers cannot bypass bounds checks.
1818
#inline
19-
#proof_import("moonbit_builtin_prelude.FixedArray")
2019
fn[T] UninitializedArray::unsafe_get(
2120
self : UninitializedArray[T],
2221
index : Int,
2322
) -> T = "%fixedarray.unsafe_get"
2423

2524
///|
2625
#inline
27-
#proof_import("moonbit_builtin_prelude.FixedArray")
2826
fn[T] UninitializedArray::unsafe_set(
2927
self : UninitializedArray[T],
3028
index : Int,
@@ -40,11 +38,12 @@ fn wrap_index(
4038
offset : Int,
4139
capacity : Int,
4240
) -> Int where {
43-
proof_require: wrap_index_pre(head, offset, capacity),
41+
proof_require: buffer_index_in_bounds(head, capacity),
42+
proof_require: valid_wrap_offset(offset, capacity),
4443
proof_ensure: result => wrap_index_post(head, offset, capacity, result),
45-
proof_ensure: result => wrap_index_mod_equiv(head, offset, capacity, result),
4644
} {
47-
proof_assert single_wrap_range(head, offset, capacity)
45+
proof_assert head + offset >= 0
46+
proof_assert head + offset < capacity * 2
4847
let contiguous = capacity - head
4948
if offset < contiguous {
5049
proof_assert (head + offset) % capacity == head + offset
@@ -62,22 +61,23 @@ fn decrement_index(
6261
index : Int,
6362
capacity : Int,
6463
) -> Int where {
65-
proof_require: decrement_index_pre(index, capacity),
64+
proof_require: buffer_index_in_bounds(index, capacity),
6665
proof_ensure: result => decrement_index_post(index, capacity, result),
67-
proof_ensure: result => decrement_index_mod_equiv(index, capacity, result),
6866
} {
6967
if index == 0 {
68+
proof_assert buffer_index_in_bounds(capacity - 1, capacity)
7069
proof_assert (index + capacity - 1) % capacity == capacity - 1
7170
capacity - 1
7271
} else {
72+
proof_assert buffer_index_in_bounds(index - 1, capacity)
7373
proof_assert (index + capacity - 1) % capacity == index - 1
7474
index - 1
7575
}
7676
}
7777

7878
///|
7979
/// Computes the capacity used by `realloc` after its runtime overflow check.
80-
/// The contract proves the index invariant restored after a successful growth.
80+
/// The contract proves the layout invariant restored after a successful growth.
8181
#warnings("-unused_value")
8282
#inline
8383
fn deque_realloc_capacity(
@@ -101,10 +101,9 @@ fn deque_tail_index(
101101
len : Int,
102102
capacity : Int,
103103
) -> Int where {
104-
proof_require: deque_index_inv(capacity, head, len),
104+
proof_require: deque_layout_inv(capacity, head, len),
105105
proof_require: len > 0,
106106
proof_ensure: result => wrap_index_post(head, len - 1, capacity, result),
107-
proof_ensure: result => buffer_index_in_bounds(result, capacity),
108107
} {
109108
wrap_index(head, len - 1, capacity)
110109
}
@@ -118,11 +117,10 @@ fn deque_element_index(
118117
capacity : Int,
119118
index : Int,
120119
) -> Int where {
121-
proof_require: deque_index_inv(capacity, head, len),
120+
proof_require: deque_layout_inv(capacity, head, len),
122121
proof_require: index >= 0,
123122
proof_require: index < len,
124123
proof_ensure: result => wrap_index_post(head, index, capacity, result),
125-
proof_ensure: result => buffer_index_in_bounds(result, capacity),
126124
} {
127125
wrap_index(head, index, capacity)
128126
}
@@ -134,9 +132,11 @@ fn deque_push_back_core(
134132
len : Int,
135133
capacity : Int,
136134
) -> Int where {
137-
proof_require: deque_index_inv(capacity, head, len),
135+
proof_require: deque_layout_inv(capacity, head, len),
138136
proof_require: len < capacity,
139-
proof_ensure: result => deque_push_back_post(capacity, head, len, result),
137+
proof_ensure: result => {
138+
deque_push_back_index_post(capacity, head, len, result)
139+
},
140140
} {
141141
wrap_index(head, len, capacity)
142142
}
@@ -149,9 +149,11 @@ fn deque_push_front_core(
149149
len : Int,
150150
capacity : Int,
151151
) -> Int where {
152-
proof_require: deque_index_inv(capacity, head, len),
152+
proof_require: deque_layout_inv(capacity, head, len),
153153
proof_require: len < capacity,
154-
proof_ensure: result => deque_push_front_post(capacity, head, len, result),
154+
proof_ensure: result => {
155+
deque_push_front_index_post(capacity, head, len, result)
156+
},
155157
} {
156158
decrement_index(head, capacity)
157159
}
@@ -164,9 +166,11 @@ fn deque_pop_front_core(
164166
len : Int,
165167
capacity : Int,
166168
) -> Int where {
167-
proof_require: deque_index_inv(capacity, head, len),
169+
proof_require: deque_layout_inv(capacity, head, len),
168170
proof_require: len > 0,
169-
proof_ensure: result => deque_pop_front_post(capacity, head, len, result),
171+
proof_ensure: result => {
172+
deque_pop_front_index_post(capacity, head, len, result)
173+
},
170174
} {
171175
wrap_index(head, 1, capacity)
172176
}
@@ -178,9 +182,9 @@ fn deque_pop_back_core(
178182
len : Int,
179183
capacity : Int,
180184
) -> Int where {
181-
proof_require: deque_index_inv(capacity, head, len),
185+
proof_require: deque_layout_inv(capacity, head, len),
182186
proof_require: len > 0,
183-
proof_ensure: result => deque_pop_back_post(capacity, head, len, result),
187+
proof_ensure: result => deque_pop_back_index_post(capacity, head, len, result),
184188
} {
185189
wrap_index(head, len - 1, capacity)
186190
}

deque/index_ops.mbtp

Lines changed: 25 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -13,98 +13,60 @@
1313
// limitations under the License.
1414

1515
///|
16-
/// Contracts for the private circular-buffer index operations.
17-
/// `offset` may reach `capacity` for the next free slot, but never exceeds it.
18-
predicate wrap_index_pre(head : Int, offset : Int, capacity : Int) {
19-
capacity > 0 &&
20-
head >= 0 &&
21-
head < capacity &&
22-
offset >= 0 &&
23-
offset <= capacity
16+
/// A physical index into a buffer of `capacity` elements.
17+
predicate buffer_index_in_bounds(index : Int, capacity : Int) {
18+
index >= 0 && index < capacity
2419
}
2520

2621
///|
27-
/// A valid deque offset can cross the end of the buffer at most once.
28-
predicate single_wrap_range(head : Int, offset : Int, capacity : Int) {
29-
head + offset >= 0 && head + offset < capacity * 2
22+
/// An offset accepted by a single-wrap operation. The upper bound is inclusive
23+
/// because advancing one position in a capacity-one buffer uses `offset == 1`.
24+
predicate valid_wrap_offset(offset : Int, capacity : Int) {
25+
offset >= 0 && offset <= capacity
3026
}
3127

3228
///|
33-
/// The branch form is also the machine-integer safety argument: every
34-
/// subtraction is non-negative, and the addition branch stays below capacity.
29+
/// The result is in bounds, follows the branch implementation, and is equal to
30+
/// the remainder expression in the mathematical-integer proof model.
3531
predicate wrap_index_post(
3632
head : Int,
3733
offset : Int,
3834
capacity : Int,
3935
result : Int,
4036
) {
41-
result >= 0 &&
42-
result < capacity &&
37+
buffer_index_in_bounds(result, capacity) &&
4338
(if offset < capacity - head {
4439
result == head + offset
4540
} else {
4641
result == offset - (capacity - head)
47-
})
48-
}
49-
50-
///|
51-
/// In the mathematical-integer proof model, the branch form is exactly the
52-
/// remainder expression it replaces.
53-
predicate wrap_index_mod_equiv(
54-
head : Int,
55-
offset : Int,
56-
capacity : Int,
57-
result : Int,
58-
) {
42+
}) &&
5943
result == (head + offset) % capacity
6044
}
6145

62-
///|
63-
predicate decrement_index_pre(index : Int, capacity : Int) {
64-
capacity > 0 && index >= 0 && index < capacity
65-
}
66-
6746
///|
6847
predicate decrement_index_post(
6948
index : Int,
7049
capacity : Int,
7150
result : Int,
7251
) {
73-
result >= 0 &&
74-
result < capacity &&
75-
(if index == 0 { result == capacity - 1 } else { result == index - 1 })
76-
}
77-
78-
///|
79-
predicate decrement_index_mod_equiv(
80-
index : Int,
81-
capacity : Int,
82-
result : Int,
83-
) {
52+
buffer_index_in_bounds(result, capacity) &&
53+
(if index == 0 { result == capacity - 1 } else { result == index - 1 }) &&
8454
result == (index + capacity - 1) % capacity
8555
}
8656

8757
///|
8858
/// The arithmetic part of the private `Deque` representation invariant.
89-
predicate deque_index_inv(capacity : Int, head : Int, len : Int) {
59+
predicate deque_layout_inv(capacity : Int, head : Int, len : Int) {
9060
(capacity == 0 && head == 0 && len == 0) ||
91-
(capacity > 0 &&
92-
head >= 0 &&
93-
head < capacity &&
61+
(buffer_index_in_bounds(head, capacity) &&
9462
len >= 0 &&
9563
len <= capacity)
9664
}
9765

98-
///|
99-
predicate buffer_index_in_bounds(index : Int, capacity : Int) {
100-
index >= 0 && index < capacity
101-
}
102-
10366
///|
10467
/// The numerical part of the deque invariant needed by reallocation. Element
10568
/// initialization and logical order are deliberately outside this proof slice.
10669
predicate deque_realloc_pre(capacity : Int, len : Int) {
107-
capacity >= 0 &&
10870
capacity <= 0x3fff_ffff &&
10971
len >= 0 &&
11072
len <= capacity
@@ -121,63 +83,50 @@ predicate deque_realloc_post(
12183
new_capacity > capacity &&
12284
new_capacity <= 0x7fff_ffff &&
12385
len < new_capacity &&
124-
deque_index_inv(new_capacity, 0, len)
86+
deque_layout_inv(new_capacity, 0, len)
12587
}
12688

12789
///|
128-
predicate deque_push_back_post(
90+
predicate deque_push_back_index_post(
12991
capacity : Int,
13092
head : Int,
13193
len : Int,
13294
write_index : Int,
13395
) {
134-
buffer_index_in_bounds(write_index, capacity) &&
135-
deque_index_inv(capacity, head, len + 1)
96+
wrap_index_post(head, len, capacity, write_index) &&
97+
deque_layout_inv(capacity, head, len + 1)
13698
}
13799

138100
///|
139-
predicate deque_push_front_post(
101+
predicate deque_push_front_index_post(
140102
capacity : Int,
141103
head : Int,
142104
len : Int,
143105
new_head : Int,
144106
) {
145107
decrement_index_post(head, capacity, new_head) &&
146-
buffer_index_in_bounds(new_head, capacity) &&
147-
deque_index_inv(capacity, new_head, len + 1)
108+
deque_layout_inv(capacity, new_head, len + 1)
148109
}
149110

150111
///|
151-
predicate deque_pop_front_post(
112+
predicate deque_pop_front_index_post(
152113
capacity : Int,
153114
head : Int,
154115
len : Int,
155116
new_head : Int,
156117
) {
157118
buffer_index_in_bounds(head, capacity) &&
158119
wrap_index_post(head, 1, capacity, new_head) &&
159-
deque_index_inv(capacity, new_head, len - 1)
120+
deque_layout_inv(capacity, new_head, len - 1)
160121
}
161122

162123
///|
163-
predicate deque_pop_back_post(
124+
predicate deque_pop_back_index_post(
164125
capacity : Int,
165126
head : Int,
166127
len : Int,
167128
tail : Int,
168129
) {
169130
wrap_index_post(head, len - 1, capacity, tail) &&
170-
buffer_index_in_bounds(tail, capacity) &&
171-
deque_index_inv(capacity, head, len - 1)
172-
}
173-
174-
///|
175-
lemma wrap_index_pre_has_single_wrap(
176-
head : Int,
177-
offset : Int,
178-
capacity : Int,
179-
) where {
180-
proof_require: wrap_index_pre(head, offset, capacity),
181-
proof_ensure: single_wrap_range(head, offset, capacity),
182-
} {
131+
deque_layout_inv(capacity, head, len - 1)
183132
}

0 commit comments

Comments
 (0)