Skip to content

Commit a322781

Browse files
authored
Collapse prove-rs into prove (#986)
Collapse `prove-rs` into `prove`: - ProveRSOpts merged into ProveOpts - `kmir prove` is the primary command (`prove-rs` kept as alias for backwards compatibility) - adds `parsed_smir` option for programmatic use (useful for tests with many start symbols like #985)
1 parent 9cb1679 commit a322781

9 files changed

Lines changed: 101 additions & 157 deletions

File tree

CLAUDE.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ make test-unit
3232
make test-integration
3333

3434
# Run a single test
35-
uv --directory kmir run pytest kmir/src/tests/integration/test_prove.py::test_prove_rs -k "test_name"
35+
uv --directory kmir run pytest kmir/src/tests/integration/test_prove.py::test_prove -k "test_name"
3636

3737
# Generate and parse SMIR for test files
3838
make smir-parse-tests
@@ -61,7 +61,7 @@ source kmir/.venv/bin/activate
6161
uv --directory kmir run kmir <command>
6262

6363
# Prove Rust code directly (recommended)
64-
uv --directory kmir run kmir prove-rs path/to/file.rs --verbose
64+
uv --directory kmir run kmir prove path/to/file.rs --verbose
6565

6666
# Generate SMIR JSON from Rust
6767
./scripts/generate-smir-json.sh file.rs output_dir
@@ -79,7 +79,7 @@ uv --directory kmir run kmir show proof_id --proof-dir ./proof_dir
7979
- `smir.py` - SMIR JSON parsing and info extraction
8080
- `kdist/mir-semantics/` - K semantics definitions
8181
- `src/tests/` - Test suites
82-
- `integration/data/prove-rs/` - Rust test programs for prove-rs
82+
- `integration/data/prove-rs/` - Rust test programs for prove
8383
- `integration/data/exec-smir/` - Rust programs for execution tests
8484

8585
### Key K Semantics Files
@@ -105,17 +105,17 @@ Intrinsic functions (like `black_box`, `raw_eq`) don't have regular function bod
105105

106106
## Testing Patterns
107107

108-
### prove-rs Tests
108+
### prove Tests
109109
Tests in `kmir/src/tests/integration/data/prove-rs/` follow this pattern:
110110
- Simple Rust programs with assertions
111111
- File naming: `test-name.rs` (passes), `test-name-fail.rs` (expected to fail)
112-
- Tests run via `kmir prove-rs` command
112+
- Tests run via `kmir prove` command
113113
- Generate SMIR automatically during test execution
114114

115115
### Adding New Tests
116116
1. Add Rust file to `prove-rs/` directory
117117
2. Use assertions to verify behavior
118-
3. Run with: `uv --directory kmir run kmir prove-rs your-test.rs`
118+
3. Run with: `uv --directory kmir run kmir prove your-test.rs`
119119

120120
## Development Workflow
121121

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Every subcommand supports `--help` for the full option list.
7070
| Command | Purpose |
7171
| --- | --- |
7272
| `kmir run` | Execute a Rust program from SMIR JSON |
73-
| `kmir prove-rs` | Prove properties of a Rust source file (recommended entry point) |
73+
| `kmir prove` | Prove properties of a Rust source file (recommended entry point) |
7474
| `kmir show` | Inspect a proof graph — nodes, deltas, rules, statistics |
7575
| `kmir view` | Interactive proof viewer |
7676
| `kmir prune` | Remove a node (and its subtree) from a proof |
@@ -82,7 +82,7 @@ Every subcommand supports `--help` for the full option list.
8282

8383
```bash
8484
# 1. Run a proof
85-
uv --project kmir run kmir prove-rs program.rs --proof-dir ./proofs --verbose
85+
uv --project kmir run kmir prove program.rs --proof-dir ./proofs --verbose
8686

8787
# 2. Overview — see all leaves and statistics
8888
uv --project kmir run kmir show proof_id --proof-dir ./proofs --leaves --statistics
@@ -104,27 +104,27 @@ When a proof does not close, the typical cycle is **inspect → refine → re-pr
104104

105105
```bash
106106
# Narrow down where things go wrong — break on every function call
107-
uv --project kmir run kmir prove-rs program.rs --proof-dir ./proofs \
107+
uv --project kmir run kmir prove program.rs --proof-dir ./proofs \
108108
--break-on-calls --max-depth 200
109109

110110
# Or break only when a specific function is entered
111-
uv --project kmir run kmir prove-rs program.rs --proof-dir ./proofs \
111+
uv --project kmir run kmir prove program.rs --proof-dir ./proofs \
112112
--break-on-function "my_module::suspect_fn"
113113

114114
# Split a large edge to find the exact divergence point
115115
uv --project kmir run kmir section-edge proof_id "4,5" --proof-dir ./proofs --sections 4
116116

117117
# Prune a bad subtree and re-run
118118
uv --project kmir run kmir prune proof_id 5 --proof-dir ./proofs
119-
uv --project kmir run kmir prove-rs program.rs --proof-dir ./proofs
119+
uv --project kmir run kmir prove program.rs --proof-dir ./proofs
120120

121121
# Export a proof subgraph as a reusable K module
122122
uv --project kmir run kmir show proof_id --proof-dir ./proofs --to-module lemma.json --minimize-proof
123123
# then re-prove with the lemma
124-
uv --project kmir run kmir prove-rs program.rs --proof-dir ./proofs --add-module lemma.json
124+
uv --project kmir run kmir prove program.rs --proof-dir ./proofs --add-module lemma.json
125125
```
126126

127-
Other useful `prove-rs` break flags: `--break-every-step`, `--break-every-terminator`, `--break-on-thunk`, `--terminate-on-thunk`.
127+
Other useful `prove` break flags: `--break-every-step`, `--break-every-terminator`, `--break-on-thunk`, `--terminate-on-thunk`.
128128

129129
### Generate Stable MIR JSON manually
130130

kmir/src/kmir/__main__.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from .options import (
2222
InfoOpts,
2323
LinkOpts,
24-
ProveRSOpts,
24+
ProveOpts,
2525
PruneOpts,
2626
RunOpts,
2727
SectionEdgeOpts,
@@ -72,8 +72,8 @@ def run(target_dir: Path):
7272
run(target_dir=Path(target_dir))
7373

7474

75-
def _kmir_prove_rs(opts: ProveRSOpts) -> None:
76-
proof = KMIR.prove_rs(opts)
75+
def _kmir_prove(opts: ProveOpts) -> None:
76+
proof = KMIR.prove_program(opts)
7777
print(str(proof.summary))
7878
if not proof.passed:
7979
sys.exit(1)
@@ -268,8 +268,8 @@ def kmir(args: Sequence[str]) -> None:
268268
_kmir_prune(opts)
269269
case SectionEdgeOpts():
270270
_kmir_section_edge(opts)
271-
case ProveRSOpts():
272-
_kmir_prove_rs(opts)
271+
case ProveOpts():
272+
_kmir_prove(opts)
273273
case LinkOpts():
274274
_kmir_link(opts)
275275
case _:
@@ -542,26 +542,24 @@ def _arg_parser() -> ArgumentParser:
542542
section_edge_parser.add_argument('--haskell-target', metavar='TARGET', help='Haskell target to use')
543543
section_edge_parser.add_argument('--llvm-lib-target', metavar='TARGET', help='LLVM lib target to use')
544544

545-
prove_rs_parser = command_parser.add_parser(
546-
'prove-rs', help='Prove a rust program', parents=[kcli_args.logging_args, prove_args]
545+
prove_parser = command_parser.add_parser(
546+
'prove', help='Prove a Rust program', aliases=['prove-rs'], parents=[kcli_args.logging_args, prove_args]
547547
)
548-
prove_rs_parser.add_argument(
549-
'rs_file', type=Path, metavar='FILE', help='Rust file with the spec function (e.g. main)'
550-
)
551-
prove_rs_parser.add_argument(
548+
prove_parser.add_argument('rs_file', type=Path, metavar='FILE', help='Rust file with the spec function (e.g. main)')
549+
prove_parser.add_argument(
552550
'--save-smir', action='store_true', help='Do not delete the intermediate generated SMIR JSON file.'
553551
)
554-
prove_rs_parser.add_argument('--smir', action='store_true', help='Treat the input file as a smir json.')
555-
prove_rs_parser.add_argument(
552+
prove_parser.add_argument('--smir', action='store_true', help='Treat the input file as a smir json.')
553+
prove_parser.add_argument(
556554
'--start-symbol', type=str, metavar='SYMBOL', default='main', help='Symbol name to begin execution from'
557555
)
558-
prove_rs_parser.add_argument(
556+
prove_parser.add_argument(
559557
'--add-module',
560558
type=Path,
561559
metavar='FILE',
562560
help='K module file to include (.json format from --to-module)',
563561
)
564-
prove_rs_parser.add_argument(
562+
prove_parser.add_argument(
565563
'--max-workers', metavar='N', type=int, help='Maximum number of workers for parallel exploration'
566564
)
567565

@@ -640,8 +638,8 @@ def _parse_args(ns: Namespace) -> KMirOpts:
640638
haskell_target=ns.haskell_target,
641639
llvm_lib_target=ns.llvm_lib_target,
642640
)
643-
case 'prove-rs':
644-
return ProveRSOpts(
641+
case 'prove' | 'prove-rs':
642+
return ProveOpts(
645643
rs_file=Path(ns.rs_file),
646644
proof_dir=ns.proof_dir,
647645
haskell_target=ns.haskell_target,

kmir/src/kmir/_prove.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525

2626
from pyk.kast.inner import KInner
2727

28-
from .options import ProveRSOpts
28+
from .options import ProveOpts
2929

3030

3131
_LOGGER: Final = logging.getLogger(__name__)
3232

3333

34-
def prove_rs(opts: ProveRSOpts) -> APRProof:
34+
def prove(opts: ProveOpts) -> APRProof:
3535
if not opts.rs_file.is_file():
3636
raise ValueError(f'Input file does not exist: {opts.rs_file}')
3737

@@ -42,14 +42,14 @@ def prove_rs(opts: ProveRSOpts) -> APRProof:
4242

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

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

5151

52-
def _prove_rs(opts: ProveRSOpts, target_path: Path, label: str) -> APRProof:
52+
def _prove(opts: ProveOpts, target_path: Path, label: str) -> 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)
@@ -67,7 +67,9 @@ def _prove_rs(opts: ProveRSOpts, target_path: Path, label: str) -> APRProof:
6767
)
6868
else:
6969
_LOGGER.info(f'Constructing initial proof: {label}')
70-
if opts.smir:
70+
if opts.parsed_smir is not None:
71+
smir_info = SMIRInfo(opts.parsed_smir)
72+
elif opts.smir:
7173
smir_info = SMIRInfo.from_file(opts.rs_file)
7274
else:
7375
smir_info = SMIRInfo(cargo_get_smir_json(opts.rs_file, save_smir=opts.save_smir))
@@ -138,7 +140,7 @@ def _prove_parallel(
138140
kmir: KMIR,
139141
proof: APRProof,
140142
*,
141-
opts: ProveRSOpts,
143+
opts: ProveOpts,
142144
label: str,
143145
cut_point_rules: list[str],
144146
) -> None:
@@ -192,7 +194,7 @@ def _prove_sequential(
192194
kmir: KMIR,
193195
proof: APRProof,
194196
*,
195-
opts: ProveRSOpts,
197+
opts: ProveOpts,
196198
label: str,
197199
cut_point_rules: list[str],
198200
) -> None:

kmir/src/kmir/kmir.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from pyk.proof.reachability import APRProof
3434
from pyk.utils import BugReport
3535

36-
from .options import DisplayOpts, ProveRSOpts
36+
from .options import DisplayOpts, ProveOpts
3737

3838

3939
_LOGGER: Final = logging.getLogger(__name__)
@@ -121,10 +121,10 @@ def run_smir(
121121
return result
122122

123123
@staticmethod
124-
def prove_rs(opts: ProveRSOpts) -> APRProof:
125-
from ._prove import prove_rs
124+
def prove_program(opts: ProveOpts) -> APRProof:
125+
from ._prove import prove
126126

127-
return prove_rs(opts)
127+
return prove(opts)
128128

129129

130130
class KMIRSemantics(DefaultSemantics):

kmir/src/kmir/options.py

Lines changed: 9 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,22 @@ class ProofOpts(KMirOpts):
6161

6262
@dataclass
6363
class ProveOpts(KMirOpts):
64+
rs_file: Path
6465
proof_dir: Path | None
6566
haskell_target: str | None
6667
llvm_lib_target: str | None
6768
bug_report: Path | None
6869
max_depth: int | None
6970
max_iterations: int | None
71+
max_workers: int | None
7072
reload: bool
7173
fail_fast: bool
7274
maintenance_rate: int
75+
save_smir: bool
76+
smir: bool
77+
parsed_smir: dict | None
78+
start_symbol: str
79+
add_module: Path | None
7380
break_on_calls: bool
7481
break_on_function_calls: bool
7582
break_on_intrinsic_calls: bool
@@ -87,71 +94,6 @@ class ProveOpts(KMirOpts):
8794
terminate_on_thunk: bool
8895
break_on_function: list[str]
8996

90-
def __init__(
91-
self,
92-
*,
93-
proof_dir: Path | str | None = None,
94-
haskell_target: str | None = None,
95-
llvm_lib_target: str | None = None,
96-
bug_report: Path | None = None,
97-
max_depth: int | None = None,
98-
max_iterations: int | None = None,
99-
reload: bool = False,
100-
fail_fast: bool = False,
101-
maintenance_rate: int = 1,
102-
break_on_calls: bool = False,
103-
break_on_function_calls: bool = False,
104-
break_on_intrinsic_calls: bool = False,
105-
break_on_thunk: bool = False,
106-
break_every_statement: bool = False,
107-
break_on_terminator_goto: bool = False,
108-
break_on_terminator_switch_int: bool = False,
109-
break_on_terminator_return: bool = False,
110-
break_on_terminator_call: bool = False,
111-
break_on_terminator_assert: bool = False,
112-
break_on_terminator_drop: bool = False,
113-
break_on_terminator_unreachable: bool = False,
114-
break_every_terminator: bool = False,
115-
break_every_step: bool = False,
116-
terminate_on_thunk: bool = False,
117-
break_on_function: list[str] | None = None,
118-
) -> None:
119-
self.proof_dir = Path(proof_dir).resolve() if proof_dir is not None else None
120-
self.haskell_target = haskell_target
121-
self.llvm_lib_target = llvm_lib_target
122-
self.bug_report = bug_report
123-
self.max_depth = max_depth
124-
self.max_iterations = max_iterations
125-
self.reload = reload
126-
self.fail_fast = fail_fast
127-
self.maintenance_rate = maintenance_rate
128-
self.break_on_calls = break_on_calls
129-
self.break_on_function_calls = break_on_function_calls
130-
self.break_on_intrinsic_calls = break_on_intrinsic_calls
131-
self.break_on_thunk = break_on_thunk
132-
self.break_every_statement = break_every_statement
133-
self.break_on_terminator_goto = break_on_terminator_goto
134-
self.break_on_terminator_switch_int = break_on_terminator_switch_int
135-
self.break_on_terminator_return = break_on_terminator_return
136-
self.break_on_terminator_call = break_on_terminator_call
137-
self.break_on_terminator_assert = break_on_terminator_assert
138-
self.break_on_terminator_drop = break_on_terminator_drop
139-
self.break_on_terminator_unreachable = break_on_terminator_unreachable
140-
self.break_every_terminator = break_every_terminator
141-
self.break_every_step = break_every_step
142-
self.terminate_on_thunk = terminate_on_thunk
143-
self.break_on_function = break_on_function if break_on_function is not None else []
144-
145-
146-
@dataclass
147-
class ProveRSOpts(ProveOpts):
148-
rs_file: Path
149-
save_smir: bool
150-
smir: bool
151-
start_symbol: str
152-
add_module: Path | None
153-
max_workers: int | None
154-
15597
def __init__(
15698
self,
15799
rs_file: Path,
@@ -168,6 +110,7 @@ def __init__(
168110
maintenance_rate: int = 1,
169111
save_smir: bool = False,
170112
smir: bool = False,
113+
parsed_smir: dict | None = None,
171114
start_symbol: str = 'main',
172115
break_on_calls: bool = False,
173116
break_on_function_calls: bool = False,
@@ -200,6 +143,7 @@ def __init__(
200143
self.maintenance_rate = maintenance_rate
201144
self.save_smir = save_smir
202145
self.smir = smir
146+
self.parsed_smir = parsed_smir
203147
self.start_symbol = start_symbol
204148
self.break_on_calls = break_on_calls
205149
self.break_on_function_calls = break_on_function_calls

0 commit comments

Comments
 (0)