Skip to content

Commit 2d70882

Browse files
authored
Reuse prebuilt KMIR definition to avoid redundant llvm-kompile processes (#1069)
If a Rust file contains multiple targets to prove (e.g. [verify-rust-std challenge 11](https://github.com/runtimeverification/mir-semantics/tree/master/kmir/src/tests/integration/data/verify-rust-std/0011-floats-ints)) then multiple `llvm-kompile` processes will be invoked by [_prove](https://github.com/runtimeverification/mir-semantics/blob/c92c8ce0b4b0d762d679a15351b562fa243fd8f0/kmir/src/kmir/_prove.py#L70-L77). This PR pushes this process earlier to create a KMIR definition with all the required start symbols so that `llvm-kompile` is called once per file. In particular: - `test_verify_rust_std` now compiles a prebuilt KMIR definition with reduced start symbols. This definition is reused for all proof harnesses in a file; - `prove_with_kmir` is added as separate control-flow to `prove`. This may not be _elegant_, but right now I think it is the clearest separation, and I was hesitant to add flags or refactor too aggressively; Results from verify-std-rust challenge 11: - Prior to this refactoring: 1 `llvm-kompile` / start-symbol = 138 `llvm-kompile` invocations - After this refactoring: 1 `llvm-kompile` / file = 16 `llvm-kompile` invocations - Approximately 3x increase of performance <details> PRIOR: real 31m16.872s user 170m18.245s sys 5m51.240s real 31m36.755s user 175m15.830s sys 6m32.859s AFTER: real 10m20.730s user 44m10.271s sys 2m15.364s real 10m58s user 47m0s sys 2m24s real 10m40.395s user 45m54.822s sys 2m20.399s real 10m38.370s user 45m32.886s sys 2m19.782s </details>
1 parent 47e9021 commit 2d70882

3 files changed

Lines changed: 74 additions & 21 deletions

File tree

kmir/src/kmir/_prove.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333

3434
def prove(opts: ProveOpts) -> APRProof:
35+
"""Run a proof creating a new KMIR instance."""
3536
if not opts.rs_file.is_file():
3637
raise ValueError(f'Input file does not exist: {opts.rs_file}')
3738

@@ -49,6 +50,33 @@ def prove(opts: ProveOpts) -> APRProof:
4950
return _prove(opts, target_path, label)
5051

5152

53+
def prove_with_kmir(
54+
kmir: KMIR,
55+
smir_info: SMIRInfo,
56+
opts: ProveOpts,
57+
) -> APRProof:
58+
"""Run a proof using a pre-built KMIR instance, avoiding redundant kompilation."""
59+
60+
if opts.max_workers is not None and opts.max_workers < 1:
61+
raise ValueError(f'Expected positive integer for `max_workers, got: {opts.max_workers}')
62+
63+
# No check for rs_file as smir_info already exists
64+
label = f'{opts.rs_file.stem}.{opts.start_symbol}'
65+
66+
_LOGGER.info(f'Using pre-built KMIR for proof: {label}')
67+
proof = apr_proof_from_smir(
68+
kmir,
69+
label,
70+
smir_info,
71+
start_symbol=opts.start_symbol,
72+
proof_dir=opts.proof_dir,
73+
)
74+
if proof.proof_dir is not None and (proof.proof_dir / label).is_dir():
75+
smir_info.dump(proof.proof_dir / proof.id / 'smir.json')
76+
77+
return _advance_proof(kmir, proof, opts, label)
78+
79+
5280
def _prove(opts: ProveOpts, target_path: Path, label: str) -> APRProof:
5381
if not opts.reload and opts.proof_dir is not None and APRProof.proof_data_exists(label, opts.proof_dir):
5482
_LOGGER.info(f'Reading proof from disc: {opts.proof_dir}, {label}')
@@ -107,6 +135,10 @@ def _prove(opts: ProveOpts, target_path: Path, label: str) -> APRProof:
107135
if proof.proof_dir is not None and (proof.proof_dir / label).is_dir():
108136
smir_info.dump(proof.proof_dir / proof.id / 'smir.json')
109137

138+
return _advance_proof(kmir, proof, opts, label)
139+
140+
141+
def _advance_proof(kmir: KMIR, proof: APRProof, opts: ProveOpts, label: str) -> APRProof:
110142
if proof.passed:
111143
return proof
112144

kmir/src/kmir/kmir.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ def prove_program(opts: ProveOpts) -> APRProof:
126126

127127
return prove(opts)
128128

129+
@staticmethod
130+
def prove_program_with_kmir(kmir: KMIR, smir_info: SMIRInfo, opts: ProveOpts) -> APRProof:
131+
from ._prove import prove_with_kmir
132+
133+
return prove_with_kmir(kmir, smir_info, opts)
134+
129135

130136
class KMIRSemantics(DefaultSemantics):
131137
terminate_on_thunk: bool

kmir/src/tests/integration/test_integration.py

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,10 @@ def test_prove(rs_file: Path, kmir: KMIR, update_expected_output: bool) -> None:
305305
ids=[f'{spec.parent.name}/{spec.stem}' for spec in VERIFY_RUST_STD_FILES],
306306
)
307307
def test_verify_rust_std(rs_file: Path, kmir: KMIR, update_expected_output: bool) -> None:
308+
from kmir.kompile import kompile_smir
309+
310+
# kmir fixture is unused here; kept so a kompile failure fails fast before per-file work begins
311+
308312
should_fail = rs_file.stem.endswith('fail')
309313
should_show = rs_file.stem in VERIFY_RUST_STD_SHOW_SPECS
310314

@@ -313,33 +317,44 @@ def test_verify_rust_std(rs_file: Path, kmir: KMIR, update_expected_output: bool
313317

314318
parsed_smir = cargo_get_smir_json(rs_file)
315319
prove_opts = ProveOpts(rs_file, terminate_on_thunk=True, parsed_smir=parsed_smir)
316-
printer = PrettyPrinter(kmir.definition)
317-
cterm_show = CTermShow(printer.print)
318320

319321
start_symbols = ['main']
320322
if rs_file.stem in VERIFY_RUST_STD_START_SYMBOLS:
321323
start_symbols = VERIFY_RUST_STD_START_SYMBOLS[rs_file.stem]
322324

323-
for start_symbol in start_symbols:
324-
prove_opts.start_symbol = start_symbol
325-
apr_proof = kmir.prove_program(prove_opts)
326-
327-
if should_show:
328-
display_opts = ShowOpts(
329-
rs_file.parent, apr_proof.id, full_printer=False, smir_info=None, omit_current_body=False
330-
)
331-
shower = APRProofShow(kmir.definition, node_printer=KMIRAPRNodePrinter(cterm_show, apr_proof, display_opts))
332-
show_res = '\n'.join(shower.show(apr_proof))
333-
show_dir = rs_file.parent / 'show'
334-
show_dir.mkdir(exist_ok=True)
335-
assert_or_update_show_output(
336-
show_res, show_dir / f'{rs_file.stem}.{start_symbol}.expected', update=update_expected_output
337-
)
325+
# Kompile once with full SMIR (reduced to all start symbols), reuse for all proofs
326+
smir_info = SMIRInfo(parsed_smir)
327+
smir_info = smir_info.reduce_to(start_symbols)
328+
with tempfile.TemporaryDirectory() as tmp_dir:
329+
target_path = Path(tmp_dir) # required for kompilation
330+
kompiled = kompile_smir(smir_info=smir_info, target_dir=target_path, symbolic=True)
331+
prebuilt_kmir = kompiled.create_kmir() # this KMIR has the kompiled function defs
332+
printer = PrettyPrinter(prebuilt_kmir.definition)
333+
cterm_show = CTermShow(printer.print)
338334

339-
if not should_fail:
340-
assert apr_proof.passed
341-
else:
342-
assert apr_proof.failed
335+
for start_symbol in start_symbols:
336+
prove_opts.start_symbol = start_symbol
337+
apr_proof = KMIR.prove_program_with_kmir(prebuilt_kmir, smir_info, prove_opts)
338+
339+
if should_show:
340+
display_opts = ShowOpts(
341+
rs_file.parent, apr_proof.id, full_printer=False, smir_info=None, omit_current_body=False
342+
)
343+
shower = APRProofShow(
344+
prebuilt_kmir.definition,
345+
node_printer=KMIRAPRNodePrinter(cterm_show, apr_proof, display_opts),
346+
)
347+
show_res = '\n'.join(shower.show(apr_proof))
348+
show_dir = rs_file.parent / 'show'
349+
show_dir.mkdir(exist_ok=True)
350+
assert_or_update_show_output(
351+
show_res, show_dir / f'{rs_file.stem}.{start_symbol}.expected', update=update_expected_output
352+
)
353+
354+
if not should_fail:
355+
assert apr_proof.passed
356+
else:
357+
assert apr_proof.failed
343358

344359

345360
MULTI_CRATE_DIR = (Path(__file__).parent / 'data' / 'crate-tests').resolve(strict=True)

0 commit comments

Comments
 (0)