Skip to content

Commit 7961500

Browse files
Stevengreclaude
andcommitted
feat(cse): full slotStore CSE pipeline passes end-to-end test
Complete pipeline: callee proof → cover path extraction → summary rule generation via cterm_build_rule → add-module injection → caller reuse. Key implementation details: - Uses cterm_build_rule (same as APRProver dependency rules) for properly structured KRule with full config cell nesting - Rule LHS: #execTerminatorCall ~> CONT with function name constraint - Rule RHS: #setLocalValue(DEST, RET) ~> #continueAt(TARGET) - Summary injected via CTermSymbolic.add_module(), backend applies automatically — no custom_step needed - Test: double(x)=x+x, callee has 1 cover + 1 stuck (overflow), summary rule generated and reuse proof passes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 279ef97 commit 7961500

2 files changed

Lines changed: 90 additions & 46 deletions

File tree

kmir/src/kmir/_cse.py

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
from pyk.cterm import CTerm
2020
from pyk.kast.inner import KApply, KRewrite, KSequence, KSort, KToken, KVariable, Subst
21+
from pyk.kast.att import Atts as AttKeys
22+
from pyk.kast.att import AttEntry, KAtt
2123
from pyk.kast.outer import KFlatModule, KImport, KRule
2224
from pyk.kast.prelude.ml import mlAnd, mlEqualsTrue
2325
from pyk.proof.reachability import APRProof
@@ -277,70 +279,80 @@ def generate_summary_rules(
277279
return rules
278280

279281

282+
_EXEC_TERMINATOR_CALL = '#execTerminatorCall(_,_,_,_,_,_,_)_KMIR-CONTROL-FLOW_KItem_Ty_MonoItemKind_Operands_Place_MaybeBasicBlockIdx_UnwindAction_Span'
283+
_SET_LOCAL_VALUE = '#setLocalValue(_,_)_RT-DATA_KItem_Place_Evaluation'
284+
_CONTINUE_AT = '#continueAt(_)_KMIR-CONTROL-FLOW_KItem_MaybeBasicBlockIdx'
285+
_GET_FUNCTION_NAME = 'getFunctionName(_)_KMIR-CONTROL-FLOW_String_MonoItemKind'
286+
_EQ_STRING = '_==String__STRING-COMMON_Bool_String_String'
287+
288+
280289
def _build_summary_rule(
281290
callee_name: str,
282291
path: CoverPath,
283292
path_idx: int,
284293
init_cterm: CTerm,
285294
) -> KRule | None:
286-
"""Build one K rule for a single execution path."""
295+
"""Build one K rule for a single execution path.
296+
297+
Uses cterm_build_rule to construct a properly-structured rule from
298+
init CTerm (at function call) → final CTerm (after return).
299+
The init CTerm's K_CELL is #execTerminatorCall(...) and the final
300+
CTerm's K_CELL is #setLocalValue(DEST, RET) ~> #continueAt(TARGET).
301+
"""
302+
from pyk.cterm.cterm import cterm_build_rule
303+
287304
if path.return_value is None:
288305
_LOGGER.warning(f'CSE: no return value for {callee_name} path {path_idx}, skipping')
289306
return None
290307

291-
# Variables for the rule LHS pattern
292-
func_var = KVariable('FUNC', sort=KSort('MonoItemKind'))
293-
args_var = KVariable('ARGS', sort=KSort('Operands'))
294-
dest_var = KVariable('DEST', sort=KSort('Place'))
295-
target_var = KVariable('TARGET', sort=KSort('MaybeBasicBlockIdx'))
296-
unwind_var = KVariable('_UNWIND', sort=KSort('UnwindAction'))
297-
span_var = KVariable('_SPAN', sort=KSort('Span'))
298-
ty_var = KVariable('_TY', sort=KSort('Ty'))
299-
300-
# LHS: #execTerminatorCall(_, FUNC, ARGS, DEST, TARGET, _UNWIND, _SPAN)
301-
lhs_k = KApply(
302-
'#execTerminatorCall',
303-
[ty_var, func_var, args_var, dest_var, target_var, unwind_var, span_var],
304-
)
305-
306-
# RHS: #setLocalValue(DEST, RET_VALUE) ~> #execBlockIdx(TARGET)
307-
rhs_k = KSequence(
308-
[
309-
KApply('#setLocalValue', [dest_var, path.return_value]),
310-
KApply('#execBlockIdx', [target_var]),
311-
]
312-
)
308+
# Variables for the rule
309+
func_var = KVariable('CSE_FUNC')
310+
dest_var = KVariable('CSE_DEST')
311+
target_var = KVariable('CSE_TARGET')
312+
cont_var = KVariable('CSE_CONT')
313+
314+
# Build init CTerm: same as init_cterm but with K_CELL = #execTerminatorCall ~> CONT
315+
lhs_k = KSequence([
316+
KApply(_EXEC_TERMINATOR_CALL, [
317+
KVariable('CSE_TY'), func_var, KVariable('CSE_ARGS'),
318+
dest_var, target_var, KVariable('CSE_UNWIND'), KVariable('CSE_SPAN'),
319+
]),
320+
cont_var,
321+
])
313322

314-
# Build the rule body as a <k> cell rewrite
315-
body = KApply('<generatedTop>', [
316-
KApply('<kmir>', [
317-
KRewrite(lhs_k, rhs_k), # <k> cell rewrite — simplified, will need full config
318-
])
323+
# Build final CTerm: same config but K_CELL = #setLocalValue(DEST, RET) ~> #continueAt(TARGET)
324+
rhs_k = KSequence([
325+
KApply(_SET_LOCAL_VALUE, [dest_var, path.return_value]),
326+
KApply(_CONTINUE_AT, [target_var]),
319327
])
320328

321-
# For now, build requires clause with function name match
322-
func_name_check = KApply(
323-
'_==String_',
324-
[
325-
KApply('getFunctionName', [func_var]),
326-
KToken(f'"{callee_name}"', KSort('String')),
327-
],
328-
)
329+
# Use the init_cterm config as the base, replace K_CELL
330+
from pyk.kast.manip import set_cell
331+
332+
init_config = set_cell(init_cterm.config, 'K_CELL', lhs_k)
333+
final_config = set_cell(init_cterm.config, 'K_CELL', rhs_k)
334+
335+
# Build requires: getFunctionName(FUNC) ==String "callee_name" andBool path constraints
336+
func_name_check = KApply(_EQ_STRING, [
337+
KApply(_GET_FUNCTION_NAME, [func_var]),
338+
KToken(f'"{callee_name}"', KSort('String')),
339+
])
329340

330-
# Combine function name check with path constraints
331-
requires_clauses = [func_name_check]
332-
requires_clauses.extend(path.constraints)
341+
constraints: list[KInner] = [mlEqualsTrue(func_name_check)]
342+
constraints.extend(path.constraints)
333343

334-
requires = mlEqualsTrue(mlAnd(requires_clauses)) if len(requires_clauses) > 1 else mlEqualsTrue(requires_clauses[0])
344+
init_cterm_rule = CTerm(init_config, tuple(constraints))
345+
final_cterm_rule = CTerm(final_config)
335346

336347
rule_label = f'cse-summary-{_sanitize_name(callee_name)}-path-{path_idx}'
337348

338-
return KRule(
339-
body=body,
340-
requires=requires,
341-
ensures=KToken('true', KSort('Bool')),
342-
att={'priority': '30', 'label': rule_label},
349+
rule, _subst = cterm_build_rule(
350+
rule_label,
351+
init_cterm_rule,
352+
final_cterm_rule,
353+
priority=30,
343354
)
355+
return rule
344356

345357

346358
def _sanitize_name(name: str) -> str:

kmir/src/tests/integration/test_integration.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -950,8 +950,40 @@ def test_cse_callee_proof(tmp_path: Path) -> None:
950950
# become summary rules. The caller's constraints will exclude error paths.
951951

952952
# Step 3: Extract cover paths and verify return value extraction
953-
from kmir._cse import extract_cover_paths
953+
from kmir._cse import build_summary_module, extract_cover_paths, generate_summary_rules
954954

955955
cover_paths = extract_cover_paths(callee_proof)
956956
assert len(cover_paths) > 0, 'Should extract at least one cover path'
957957
assert cover_paths[0].return_value is not None, 'Cover path should have a return value'
958+
959+
# Step 4: Generate summary rules
960+
init_cterm = callee_proof.kcfg.node(callee_proof.init).cterm
961+
rules = generate_summary_rules('double', cover_paths, init_cterm)
962+
assert len(rules) > 0, 'Should generate at least one summary rule'
963+
964+
# Step 5: Build summary module
965+
module = build_summary_module('double', rules)
966+
assert module is not None
967+
assert len(module.sentences) > 0
968+
969+
# Step 6: Prove caller with summary via add-module
970+
from pyk.cterm import cterm_symbolic
971+
from pyk.kcfg.explore import KCFGExplore
972+
from pyk.proof.reachability import APRProver
973+
974+
from kmir._prove import apr_proof_from_smir
975+
from kmir.kmir import KMIRSemantics
976+
977+
reuse_proof = apr_proof_from_smir(kmir_instance, 'cse-reuse', smir_info, start_symbol='main', proof_dir=tmp_path)
978+
with cterm_symbolic(
979+
kmir_instance.definition,
980+
kmir_instance.definition_dir,
981+
llvm_definition_dir=kmir_instance.llvm_library_dir,
982+
simplify_each=30,
983+
) as cts:
984+
module_name = cts.add_module(module, name_as_id=True)
985+
kcfg_explore = KCFGExplore(cts, kcfg_semantics=KMIRSemantics())
986+
prover = APRProver(kcfg_explore, execute_depth=10000)
987+
prover.advance_proof(reuse_proof, max_iterations=1000)
988+
989+
assert reuse_proof.passed, f'CSE reuse proof should pass, status={reuse_proof.status}'

0 commit comments

Comments
 (0)