Skip to content

Commit 7530e1e

Browse files
dkcummingjberthold
andauthored
Cut point rules and section edge command in KMIR (#780)
This PR adds: - The option `--break-on-calls` to `kmir prove` which will break the proof at the rule `#setUpCalleeData` for easy linking of proof to source code - The command `kmir section-edge` which can be provided an existing proof and an edge (two linked nodes in the kcfg) as a tuple (e.g. `4,5`) and will proof an amount of nodes breaking the edge up. In particular I find this may be useful if we can identify when a thunk is written to `<locals>` and we can section out the edge between the node it appears and the call above it. If the distance between the nodes is `n` steps, then the max sections is `n - 1` which will take 1 step. ### Current Problems - `--break-on-calls` is outputing the node at `#setUpCalleeData` but is also outputting the node afterwards `#setArgsFromStack` which is doubling the amount of output - I did look but can't understand why this is happening currently, however it is also not a deal breaker for me right now - I am still able to see the nodes I want and just some extra. UPDATE: This behaviour is also the same with Kontrol so I think this is a broader thing with K pr pyk --------- Co-authored-by: Jost Berthold <jost.berthold@gmail.com>
1 parent 21261b0 commit 7530e1e

File tree

4 files changed

+96
-2
lines changed

4 files changed

+96
-2
lines changed

kmir/src/kmir/__main__.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
ProveRSOpts,
2525
PruneOpts,
2626
RunOpts,
27+
SectionEdgeOpts,
2728
ShowOpts,
2829
ViewOpts,
2930
)
@@ -141,6 +142,38 @@ def _kmir_prune(opts: PruneOpts) -> None:
141142
proof.write_proof_data()
142143

143144

145+
def _kmir_section_edge(opts: SectionEdgeOpts) -> None:
146+
# Proof dir is checked to be some in arg parsing for instructive errors
147+
assert opts.proof_dir is not None
148+
149+
if not APRProof.proof_data_exists(opts.id, opts.proof_dir):
150+
raise ValueError(f'Proof id {opts.id} not found in proof dir {opts.proof_dir}')
151+
152+
target_path = opts.proof_dir / opts.id
153+
154+
_LOGGER.info(f'Reading proof from disc: {opts.proof_dir}, {opts.id}')
155+
apr_proof = APRProof.read_proof_data(opts.proof_dir, opts.id)
156+
157+
smir_info = SMIRInfo.from_file(target_path / 'smir.json')
158+
159+
kmir = KMIR.from_kompiled_kore(smir_info, symbolic=True, bug_report=opts.bug_report, target_dir=target_path)
160+
161+
source_id, target_id = opts.edge
162+
_LOGGER.info(f'Attempting to add {opts.sections} sections from node {source_id} to node {target_id}')
163+
164+
with kmir.kcfg_explore(apr_proof.id) as kcfg_explore:
165+
node_ids = kcfg_explore.section_edge(
166+
apr_proof.kcfg,
167+
source_id=int(source_id),
168+
target_id=int(target_id),
169+
logs=apr_proof.logs,
170+
sections=opts.sections,
171+
)
172+
_LOGGER.info(f'Added nodes on edge {(source_id, target_id)}: {node_ids}')
173+
174+
apr_proof.write_proof_data()
175+
176+
144177
def _kmir_info(opts: InfoOpts) -> None:
145178
smir_info = SMIRInfo.from_file(opts.smir_file)
146179

@@ -171,6 +204,8 @@ def kmir(args: Sequence[str]) -> None:
171204
_kmir_show(opts)
172205
case PruneOpts():
173206
_kmir_prune(opts)
207+
case SectionEdgeOpts():
208+
_kmir_section_edge(opts)
174209
case ProveRSOpts():
175210
_kmir_prove_rs(opts)
176211
case LinkOpts():
@@ -210,6 +245,9 @@ def _arg_parser() -> ArgumentParser:
210245
'--max-iterations', metavar='ITERATIONS', type=int, help='max number of proof iterations to take'
211246
)
212247
prove_args.add_argument('--reload', action='store_true', help='Force restarting proof')
248+
prove_args.add_argument(
249+
'--break-on-calls', dest='break_on_calls', action='store_true', help='Break on function calls'
250+
)
213251

214252
proof_args = ArgumentParser(add_help=False)
215253
proof_args.add_argument('id', metavar='PROOF_ID', help='The id of the proof to view')
@@ -287,6 +325,16 @@ def _arg_parser() -> ArgumentParser:
287325
)
288326
prune_parser.add_argument('node_id', metavar='NODE', type=int, help='The node to prune')
289327

328+
section_edge_parser = command_parser.add_parser(
329+
'section-edge', help='Break an edge into sections', parents=[kcli_args.logging_args, proof_args]
330+
)
331+
section_edge_parser.add_argument(
332+
'edge', type=lambda s: tuple(s.split(',')), help='Edge to section in CFG (format: `source,target`)'
333+
)
334+
section_edge_parser.add_argument(
335+
'--sections', type=int, default=2, help='Number of sections to make from edge (>= 2, default: 2)'
336+
)
337+
290338
prove_rs_parser = command_parser.add_parser(
291339
'prove-rs', help='Prove a rust program', parents=[kcli_args.logging_args, prove_args]
292340
)
@@ -354,6 +402,11 @@ def _parse_args(ns: Namespace) -> KMirOpts:
354402
case 'prune':
355403
proof_dir = Path(ns.proof_dir)
356404
return PruneOpts(proof_dir, ns.id, ns.node_id)
405+
case 'section-edge':
406+
if ns.proof_dir is None:
407+
raise ValueError('Must pass --proof-dir to section-edge command')
408+
proof_dir = Path(ns.proof_dir)
409+
return SectionEdgeOpts(proof_dir, ns.id, ns.edge, ns.sections)
357410
case 'prove-rs':
358411
return ProveRSOpts(
359412
rs_file=Path(ns.rs_file),
@@ -365,6 +418,7 @@ def _parse_args(ns: Namespace) -> KMirOpts:
365418
save_smir=ns.save_smir,
366419
smir=ns.smir,
367420
start_symbol=ns.start_symbol,
421+
break_on_calls=ns.break_on_calls,
368422
)
369423
case 'link':
370424
return LinkOpts(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ An operand may be a `Reference` (the only way a function could access another fu
324324
syntax KItem ::= #setUpCalleeData(MonoItemKind, Operands)
325325
326326
// reserve space for local variables and copy/move arguments from old locals into their place
327-
rule <k> #setUpCalleeData(
327+
rule [call]: <k> #setUpCalleeData(
328328
monoItemFn(_, _, someBody(body((FIRST:BasicBlock _) #as BLOCKS, NEWLOCALS, _, _, _, _))),
329329
ARGS
330330
)

kmir/src/kmir/kmir.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ def __init__(
5353
KParse.__init__(self, definition_dir)
5454
self.llvm_library_dir = llvm_library_dir
5555

56+
@staticmethod
57+
def cut_point_rules(
58+
break_on_calls: bool,
59+
) -> list[str]:
60+
cut_point_rules = []
61+
if break_on_calls:
62+
cut_point_rules.extend(
63+
[
64+
'KMIR-CONTROL-FLOW.call',
65+
]
66+
)
67+
return cut_point_rules
68+
5669
@staticmethod
5770
def from_kompiled_kore(
5871
smir_info: SMIRInfo, target_dir: Path, bug_report: Path | None = None, symbolic: bool = True
@@ -184,8 +197,10 @@ def prove_rs(opts: ProveRSOpts) -> APRProof:
184197
if apr_proof.passed:
185198
return apr_proof
186199

200+
cut_point_rules = KMIR.cut_point_rules(break_on_calls=opts.break_on_calls)
201+
187202
with kmir.kcfg_explore(label) as kcfg_explore:
188-
prover = APRProver(kcfg_explore, execute_depth=opts.max_depth)
203+
prover = APRProver(kcfg_explore, execute_depth=opts.max_depth, cut_point_rules=cut_point_rules)
189204
prover.advance_proof(apr_proof, max_iterations=opts.max_iterations)
190205
return apr_proof
191206

kmir/src/kmir/options.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class ProveOpts(KMirOpts):
4141
max_depth: int | None
4242
max_iterations: int | None
4343
reload: bool
44+
break_on_calls: bool
4445

4546
def __init__(
4647
self,
@@ -49,12 +50,14 @@ def __init__(
4950
max_depth: int | None = None,
5051
max_iterations: int | None = None,
5152
reload: bool = False,
53+
break_on_calls: bool = False,
5254
) -> None:
5355
self.proof_dir = Path(proof_dir).resolve() if proof_dir is not None else None
5456
self.bug_report = bug_report
5557
self.max_depth = max_depth
5658
self.max_iterations = max_iterations
5759
self.reload = reload
60+
self.break_on_calls = break_on_calls
5861

5962

6063
@dataclass
@@ -75,6 +78,7 @@ def __init__(
7578
save_smir: bool = False,
7679
smir: bool = False,
7780
start_symbol: str = 'main',
81+
break_on_calls: bool = False,
7882
) -> None:
7983
self.rs_file = rs_file
8084
self.proof_dir = Path(proof_dir).resolve() if proof_dir is not None else None
@@ -85,6 +89,7 @@ def __init__(
8589
self.save_smir = save_smir
8690
self.smir = smir
8791
self.start_symbol = start_symbol
92+
self.break_on_calls = break_on_calls
8893

8994

9095
@dataclass
@@ -187,6 +192,26 @@ def __init__(self, smir_file: Path, types: str | None = None) -> None:
187192
self.types = tuple(int(t.strip()) for t in types.split(',')) if types is not None else None
188193

189194

195+
@dataclass
196+
class SectionEdgeOpts(ProofOpts):
197+
edge: tuple[str, str]
198+
sections: int
199+
200+
def __init__(
201+
self,
202+
proof_dir: Path | str,
203+
id: str,
204+
edge: tuple[str, str],
205+
sections: int = 2,
206+
bug_report: Path | None = None,
207+
) -> None:
208+
self.proof_dir = Path(proof_dir).resolve() if proof_dir is not None else None
209+
self.id = id
210+
self.edge = edge
211+
self.sections = sections
212+
self.bug_report = bug_report
213+
214+
190215
@dataclass
191216
class LinkOpts(KMirOpts):
192217
smir_files: list[Path]

0 commit comments

Comments
 (0)