Skip to content

Commit ecc115d

Browse files
committed
refactor(rt): move runtime locals into slot store
1 parent a3e3167 commit ecc115d

16 files changed

Lines changed: 582 additions & 613 deletions

File tree

kmir/src/kmir/_prove.py

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from pyk.kast.manip import abstract_term_safely, split_config_from
1212
from pyk.kcfg import KCFG
1313
from pyk.kcfg.explore import KCFGExplore
14-
from pyk.kore.rpc import BoosterServer, KoreClient
14+
from pyk.kore.rpc import BoosterServer, DefaultError, KoreClient
1515
from pyk.proof.proof import parallel_advance_proof
1616
from pyk.proof.reachability import APRProof, APRProver
1717

@@ -42,14 +42,14 @@ def prove(opts: ProveOpts) -> APRProof:
4242

4343
if opts.proof_dir is not None:
4444
target_path = opts.proof_dir / label
45-
return _prove(opts, target_path, label)
45+
return _prove(opts, target_path, label, allow_rpc_recovery=False)
4646

4747
with tempfile.TemporaryDirectory() as tmp_dir:
4848
target_path = Path(tmp_dir)
49-
return _prove(opts, target_path, label)
49+
return _prove(opts, target_path, label, allow_rpc_recovery=True)
5050

5151

52-
def _prove(opts: ProveOpts, target_path: Path, label: str) -> APRProof:
52+
def _prove(opts: ProveOpts, target_path: Path, label: str, *, allow_rpc_recovery: bool) -> APRProof:
5353
if not opts.reload and opts.proof_dir is not None and APRProof.proof_data_exists(label, opts.proof_dir):
5454
_LOGGER.info(f'Reading proof from disc: {opts.proof_dir}, {label}')
5555
proof = APRProof.read_proof_data(opts.proof_dir, label)
@@ -97,12 +97,13 @@ def _prove(opts: ProveOpts, target_path: Path, label: str) -> APRProof:
9797
break_on_function=opts.break_on_function or None,
9898
)
9999

100+
proof_root = opts.proof_dir if opts.proof_dir is not None else target_path
100101
proof = apr_proof_from_smir(
101102
kmir,
102103
label,
103104
smir_info,
104105
start_symbol=opts.start_symbol,
105-
proof_dir=opts.proof_dir,
106+
proof_dir=proof_root,
106107
)
107108
if proof.proof_dir is not None and (proof.proof_dir / label).is_dir():
108109
smir_info.dump(proof.proof_dir / proof.id / 'smir.json')
@@ -127,12 +128,17 @@ def _prove(opts: ProveOpts, target_path: Path, label: str) -> APRProof:
127128
break_every_step=opts.break_every_step,
128129
break_on_function=opts.break_on_function,
129130
)
130-
131-
if opts.max_workers and opts.max_workers > 1:
132-
_prove_parallel(kmir, proof, opts=opts, label=label, cut_point_rules=cut_point_rules)
133-
else:
134-
_prove_sequential(kmir, proof, opts=opts, label=label, cut_point_rules=cut_point_rules)
135-
return proof
131+
try:
132+
if opts.max_workers and opts.max_workers > 1:
133+
_prove_parallel(kmir, proof, opts=opts, label=label, cut_point_rules=cut_point_rules)
134+
else:
135+
_prove_sequential(kmir, proof, opts=opts, label=label, cut_point_rules=cut_point_rules)
136+
return proof
137+
except (DefaultError, RuntimeError) as err:
138+
recovered = _recover_proof_on_rpc_error(proof, err, allow_rpc_recovery=allow_rpc_recovery)
139+
if recovered is not None:
140+
return recovered
141+
raise
136142

137143

138144
def _prove_parallel(
@@ -167,6 +173,7 @@ def create_prover() -> APRProver:
167173
cterm_symbolic = CTermSymbolic(
168174
client,
169175
kmir.definition,
176+
log_succ_rewrites=_record_proof_logs(opts),
170177
)
171178
kcfg_explore = KCFGExplore(
172179
cterm_symbolic,
@@ -197,7 +204,11 @@ def _prove_sequential(
197204
label: str,
198205
cut_point_rules: list[str],
199206
) -> None:
200-
with kmir.kcfg_explore(label, terminate_on_thunk=opts.terminate_on_thunk) as kcfg_explore:
207+
with kmir.kcfg_explore(
208+
label,
209+
terminate_on_thunk=opts.terminate_on_thunk,
210+
log_succ_rewrites=_record_proof_logs(opts),
211+
) as kcfg_explore:
201212
prover = APRProver(
202213
kcfg_explore,
203214
execute_depth=opts.max_depth,
@@ -211,6 +222,30 @@ def _prove_sequential(
211222
)
212223

213224

225+
def _record_proof_logs(opts: ProveOpts) -> bool:
226+
# Persisted proofs may later be sectioned using stored rewrite logs.
227+
# Ephemeral test proofs do not need them, and omitting them avoids huge RPC payloads.
228+
return opts.proof_dir is not None
229+
230+
231+
def _recover_proof_on_rpc_error(proof: APRProof, err: Exception, *, allow_rpc_recovery: bool) -> APRProof | None:
232+
if not allow_rpc_recovery:
233+
return None
234+
235+
if proof.proof_dir is None or not APRProof.proof_data_exists(proof.id, proof.proof_dir):
236+
return None
237+
238+
if isinstance(err, RuntimeError) and str(err) != 'Empty response received':
239+
return None
240+
241+
recovered = APRProof.read_proof_data(proof.proof_dir, proof.id)
242+
if recovered.passed or recovered.failed:
243+
_LOGGER.warning(f'Recovered saved proof after RPC error: {proof.id}')
244+
return recovered
245+
246+
return None
247+
248+
214249
def apr_proof_from_smir(
215250
kmir: KMIR,
216251
id: str,

kmir/src/kmir/kast.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from pyk.kast.inner import KApply, KSequence, KSort, KVariable, Subst, build_cons
99
from pyk.kast.manip import free_vars, split_config_from
10-
from pyk.kast.prelude.collections import list_empty, list_of
10+
from pyk.kast.prelude.collections import list_empty, list_of, map_of
1111
from pyk.kast.prelude.kint import eqInt, intToken, leInt
1212
from pyk.kast.prelude.ml import mlEqualsTrue
1313
from pyk.kast.prelude.utils import token
@@ -19,12 +19,11 @@
1919
BoolValue,
2020
DynamicSize,
2121
IntValue,
22-
Local,
2322
Metadata,
24-
Place,
2523
PtrLocalValue,
2624
RangeValue,
2725
RefValue,
26+
SlotPlace,
2827
StaticSize,
2928
)
3029

@@ -192,12 +191,16 @@ def init_subst() -> dict[str, KInner]:
192191
k_cell,
193192
)
194193

194+
slot_ids = [token(i) for i in range(len(localvars))]
195195
subst = Subst(
196196
{
197197
**init_subst(),
198198
**{
199199
'K_CELL': k_cell,
200-
'LOCALS_CELL': list_of(localvars),
200+
'OWNEDSLOTS_CELL': list_of(slot_ids),
201+
'SLOTSTORE_CELL': map_of(zip(slot_ids, localvars, strict=True)),
202+
'NEXTSLOT_CELL': token(len(slot_ids)),
203+
'GENERATEDCOUNTER_CELL': token(len(slot_ids)),
201204
},
202205
}
203206
)
@@ -215,11 +218,15 @@ def _make_symbolic_call_config(
215218
types: Mapping[Ty, TypeMetadata],
216219
) -> tuple[KInner, list[KInner]]:
217220
locals, constraints = _symbolic_locals(fn_data.args, types)
221+
slot_ids = [token(i) for i in range(len(locals))]
218222
subst = Subst(
219223
{
220224
'K_CELL': fn_data.call_terminator,
221225
'STACK_CELL': list_empty(), # FIXME see #560, problems matching a symbolic stack
222-
'LOCALS_CELL': list_of(locals),
226+
'OWNEDSLOTS_CELL': list_of(slot_ids),
227+
'SLOTSTORE_CELL': map_of(zip(slot_ids, locals, strict=True)),
228+
'NEXTSLOT_CELL': token(len(slot_ids)),
229+
'GENERATEDCOUNTER_CELL': token(len(slot_ids)),
223230
},
224231
)
225232
empty_config = definition.empty_config(KSort('GeneratedTopCell'))
@@ -373,7 +380,11 @@ def _symbolic_value(self, ty: Ty, mutable: bool) -> tuple[KInner, Iterable[KInne
373380
mlEqualsTrue(leInt(variant_var, token(max_variant))),
374381
]
375382
args = self._fresh_var('ENUM_ARGS')
376-
return KApply('Value::Aggregate', (KApply('variantIdx', (variant_var,)), args)), idx_range, None
383+
return (
384+
KApply('Value::Aggregate', (KApply('variantIdx', (variant_var,)), args)),
385+
idx_range + [mlEqualsTrue(KApply('allValues', (args,)))],
386+
None,
387+
)
377388

378389
case StructT(_, _, fields):
379390
field_vars: list[KInner] = []
@@ -390,14 +401,18 @@ def _symbolic_value(self, ty: Ty, mutable: bool) -> tuple[KInner, Iterable[KInne
390401

391402
case UnionT():
392403
args = self._fresh_var('ARG_UNION')
393-
return KApply('Value::Aggregate', (KApply('variantIdx', (token(0),)), args)), [], None
404+
return (
405+
KApply('Value::Aggregate', (KApply('variantIdx', (token(0),)), args)),
406+
[mlEqualsTrue(KApply('allValues', (args,)))],
407+
None,
408+
)
394409

395410
case ArrayT(_, None):
396411
elems = self._fresh_var('ARG_ARRAY')
397412
l = self._fresh_var('ARG_ARRAY_LEN')
398413
return (
399414
KApply('Value::Range', (elems,)),
400-
[mlEqualsTrue(eqInt(KApply('sizeList', (elems,)), l))],
415+
[mlEqualsTrue(eqInt(KApply('sizeList', (elems,)), l)), mlEqualsTrue(KApply('allValues', (elems,)))],
401416
KApply(
402417
'Metadata',
403418
(
@@ -450,8 +465,7 @@ def _symbolic_value(self, ty: Ty, mutable: bool) -> tuple[KInner, Iterable[KInne
450465
KApply(
451466
'Value::Reference',
452467
(
453-
token(0), # Stack OFFSET field
454-
KApply('place', (KApply('local', (token(ref),)), KApply('ProjectionElems::empty', ()))),
468+
KApply('SlotPlace', (token(ref), KApply('ProjectionElems::empty', ()))),
455469
KApply('Mutability::Mut', ()) if mutable else KApply('Mutability::Not', ()),
456470
metadata if metadata is not None else no_metadata,
457471
),
@@ -468,8 +482,7 @@ def _symbolic_value(self, ty: Ty, mutable: bool) -> tuple[KInner, Iterable[KInne
468482
KApply(
469483
'Value::PtrLocal',
470484
(
471-
token(0),
472-
KApply('place', (KApply('local', (token(ref),)), KApply('ProjectionElems::empty', ()))),
485+
KApply('SlotPlace', (token(ref), KApply('ProjectionElems::empty', ()))),
473486
KApply('Mutability::Mut', ()) if mutable else KApply('Mutability::Not', ()),
474487
metadata if metadata is not None else no_metadata,
475488
),
@@ -652,15 +665,13 @@ def _random_ptr_value(self, mut: bool, type_info: PtrT | RefT) -> PtrLocalValue
652665
match type_info:
653666
case PtrT():
654667
return PtrLocalValue(
655-
stack_depth=0,
656-
place=Place(local=Local(ref)),
668+
place=SlotPlace(slot=ref),
657669
mut=mut,
658670
metadata=metadata,
659671
)
660672
case RefT():
661673
return RefValue(
662-
stack_depth=0,
663-
place=Place(local=Local(ref)),
674+
place=SlotPlace(slot=ref),
664675
mut=mut,
665676
metadata=metadata,
666677
)

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

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,11 @@ Execution gets stuck (no matching rule) when operands have different types or un
8989
```k
9090
// Raw eq: dereference operands, extract types, and delegate to typed comparison
9191
rule <k> #execIntrinsic(IntrinsicFunction(symbol("raw_eq")), ARG1:Operand ARG2:Operand .Operands, PLACE, _SPAN)
92-
=> #execRawEqTyped(PLACE, #withDeref(ARG1), #extractOperandType(#withDeref(ARG1), LOCALS),
93-
#withDeref(ARG2), #extractOperandType(#withDeref(ARG2), LOCALS))
92+
=> #execRawEqTyped(PLACE, #withDeref(ARG1), #extractOperandType(#withDeref(ARG1), STORE, SLOTS),
93+
#withDeref(ARG2), #extractOperandType(#withDeref(ARG2), STORE, SLOTS))
9494
... </k>
95-
<locals> LOCALS </locals>
95+
<currentFrame> <locals> SLOTS </locals> ... </currentFrame>
96+
<slotStore> STORE </slotStore>
9697
9798
// Compare values only if types are identical
9899
syntax KItem ::= #execRawEqTyped(Place, Evaluation, MaybeTy, Evaluation, MaybeTy) [seqstrict(2,4)]
@@ -112,17 +113,17 @@ Execution gets stuck (no matching rule) when operands have different types or un
112113
rule #withDeref(OP) => OP [owise]
113114
114115
// Extract type from operands (locals with projections, constants, fallback to unknown)
115-
syntax MaybeTy ::= #extractOperandType(Operand, List) [function, total]
116-
rule #extractOperandType(operandCopy(place(local(I), PROJS)), LOCALS)
117-
=> getTyOf(tyOfLocal({LOCALS[I]}:>TypedLocal), PROJS)
118-
requires 0 <=Int I andBool I <Int size(LOCALS) andBool isTypedLocal(LOCALS[I])
116+
syntax MaybeTy ::= #extractOperandType(Operand, Map, List) [function, total]
117+
rule #extractOperandType(operandCopy(place(local(I), PROJS)), STORE, SLOTS)
118+
=> getTyOf(tyOfLocal(getSlot(STORE, #frameSlotId(SLOTS, I))), PROJS)
119+
requires 0 <=Int I andBool I <Int size(SLOTS) andBool isTypedLocal(getSlot(STORE, #frameSlotId(SLOTS, I)))
119120
[preserves-definedness]
120-
rule #extractOperandType(operandMove(place(local(I), PROJS)), LOCALS)
121-
=> getTyOf(tyOfLocal({LOCALS[I]}:>TypedLocal), PROJS)
122-
requires 0 <=Int I andBool I <Int size(LOCALS) andBool isTypedLocal(LOCALS[I])
121+
rule #extractOperandType(operandMove(place(local(I), PROJS)), STORE, SLOTS)
122+
=> getTyOf(tyOfLocal(getSlot(STORE, #frameSlotId(SLOTS, I))), PROJS)
123+
requires 0 <=Int I andBool I <Int size(SLOTS) andBool isTypedLocal(getSlot(STORE, #frameSlotId(SLOTS, I)))
123124
[preserves-definedness]
124-
rule #extractOperandType(operandConstant(constOperand(_, _, mirConst(_, TY, _))), _) => TY
125-
rule #extractOperandType(_, _) => TyUnknown [owise]
125+
rule #extractOperandType(operandConstant(constOperand(_, _, mirConst(_, TY, _))), _, _) => TY
126+
rule #extractOperandType(_, _, _) => TyUnknown [owise]
126127
```
127128

128129
#### Volatile Store (`std::intrinsics::volatile_store`, `std::ptr::write_volatile`)
@@ -191,8 +192,8 @@ the second argument, so the returned difference is always positive.
191192
192193
rule <k>
193194
#ptrOffsetDiff(
194-
PtrLocal(HEIGHT, PLACE, _, metadata( _ , OFF1, _)),
195-
PtrLocal(HEIGHT, PLACE, _, metadata( _ , OFF2, _)),
195+
PtrLocal(PLACE, _, metadata( _ , OFF1, _)),
196+
PtrLocal(PLACE, _, metadata( _ , OFF2, _)),
196197
SIGNED_FLAG,
197198
DEST
198199
) => #setLocalValue(DEST, Integer(OFF1 -Int OFF2, 64, SIGNED_FLAG))
@@ -202,8 +203,8 @@ the second argument, so the returned difference is always positive.
202203
203204
rule <k>
204205
#ptrOffsetDiff(
205-
PtrLocal(_, _, _, _) #as PTR1,
206-
PtrLocal(_, _, _, _) #as PTR2,
206+
PtrLocal(_, _, _) #as PTR1,
207+
PtrLocal(_, _, _) #as PTR2,
207208
SIGNED_FLAG,
208209
_DEST
209210
) => #UBErrorPtrOffsetDiff(PTR1, PTR2, SIGNED_FLAG)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,7 @@ module KMIR-AST
3333
3434
syntax TypeMappings ::= List{TypeMapping, ""} [group(mir-list), symbol(TypeMappings::append), terminator-symbol(TypeMappings::empty)]
3535
36+
syntax Bool ::= allValues ( List ) [function, total, symbol(allValues)]
37+
3638
endmodule
3739
```

0 commit comments

Comments
 (0)