3131_LOGGER : Final = logging .getLogger (__name__ )
3232
3333
34- def prove (opts : ProveOpts ) -> APRProof :
35- """Run a proof creating a new KMIR instance ."""
34+ def prove (opts : ProveOpts ) -> list [ APRProof ] :
35+ """Prove one or more start symbols from the same file, kompiling only once ."""
3636 if not opts .rs_file .is_file ():
3737 raise ValueError (f'Input file does not exist: { opts .rs_file } ' )
3838
3939 if opts .max_workers is not None and opts .max_workers < 1 :
4040 raise ValueError (f'Expected positive integer for `max_workers, got: { opts .max_workers } ' )
4141
42- label = f'{ opts .rs_file .stem } .{ opts .start_symbol } '
43-
4442 if opts .proof_dir is not None :
45- target_path = opts .proof_dir / label
46- return _prove (opts , target_path , label )
43+ if len (opts .start_symbols ) == 1 :
44+ label = f'{ opts .rs_file .stem } .{ opts .start_symbols [0 ]} '
45+ target_path = opts .proof_dir / label
46+ else :
47+ # Multiple start symbols share a separate target dir
48+ target_path = opts .proof_dir / f'{ opts .rs_file .stem } .kompiled'
49+ return _prove_multi (opts , target_path )
4750
4851 with tempfile .TemporaryDirectory () as tmp_dir :
4952 target_path = Path (tmp_dir )
50- return _prove (opts , target_path , label )
53+ return _prove_multi (opts , target_path )
5154
5255
5356def prove_with_kmir (
5457 kmir : KMIR ,
5558 smir_info : SMIRInfo ,
5659 opts : ProveOpts ,
5760) -> APRProof :
58- """Run a proof using a pre-built KMIR instance, avoiding redundant kompilation."""
61+ """Prove a single symbol using a pre-built KMIR instance.
62+
63+ The intended use case for this function is internal with the test runner.
64+ Use this instead of `prove()` when the caller manages kompilation externally
65+ (e.g. the test harness kompiles once and loops over symbols). Only the first
66+ entry in `opts.start_symbols` is used; for multi-symbol proving use `prove()`
67+ which kompiles once internally but is for external use via `kmir prove`.
68+ """
69+ assert len (opts .start_symbols ) == 1 , f'prove_with_kmir handles a single symbol; got { len (opts .start_symbols )} .'
5970
6071 if opts .max_workers is not None and opts .max_workers < 1 :
6172 raise ValueError (f'Expected positive integer for `max_workers, got: { opts .max_workers } ' )
6273
6374 # No check for rs_file as smir_info already exists
64- label = f'{ opts .rs_file .stem } .{ opts .start_symbol } '
75+ start_symbol = opts .start_symbols [0 ]
76+ label = f'{ opts .rs_file .stem } .{ start_symbol } '
6577
6678 _LOGGER .info (f'Using pre-built KMIR for proof: { label } ' )
6779 proof = apr_proof_from_smir (
6880 kmir ,
6981 label ,
7082 smir_info ,
71- start_symbol = opts . start_symbol ,
83+ start_symbol = start_symbol ,
7284 proof_dir = opts .proof_dir ,
7385 )
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' )
86+ if opts .proof_dir is not None :
87+ kompiled_smir_path = opts .proof_dir / f'{ opts .rs_file .stem } .kompiled' / 'smir.json'
88+ kompiled_smir_path .parent .mkdir (parents = True , exist_ok = True )
89+ smir_info .dump (kompiled_smir_path )
7690
7791 return _advance_proof (kmir , proof , opts , label )
7892
7993
80- def _prove (opts : ProveOpts , target_path : Path , label : str ) -> APRProof :
81- if not opts .reload and opts .proof_dir is not None and APRProof .proof_data_exists (label , opts .proof_dir ):
82- _LOGGER .info (f'Reading proof from disc: { opts .proof_dir } , { label } ' )
83- proof = APRProof .read_proof_data (opts .proof_dir , label )
84-
85- smir_info = SMIRInfo .from_file (target_path / 'smir.json' )
86- kmir = KMIR .from_kompiled_kore (
87- smir_info ,
88- target_dir = target_path ,
89- extra_module = opts .add_module ,
90- bug_report = opts .bug_report ,
91- symbolic = True ,
92- haskell_target = opts .haskell_target ,
93- llvm_lib_target = opts .llvm_lib_target ,
94- break_on_function = opts .break_on_function or None ,
95- )
94+ def _prove_multi (opts : ProveOpts , target_path : Path ) -> list [APRProof ]:
95+ """Prove single or multiple symbols with a single kompilation."""
96+ labels = [f'{ opts .rs_file .stem } .{ sym } ' for sym in opts .start_symbols ]
97+
98+ if not labels :
99+ raise ValueError ('No label to prove' )
100+
101+ # Check which proofs can be resumed
102+ resumable : dict [str , APRProof ] = {}
103+ if not opts .reload and opts .proof_dir is not None :
104+ for label in labels :
105+ if APRProof .proof_data_exists (label , opts .proof_dir ):
106+ _LOGGER .info (f'Reading proof from disc: { opts .proof_dir } , { label } ' )
107+ resumable [label ] = APRProof .read_proof_data (opts .proof_dir , label )
108+
109+ # Load SMIR info (once)
110+ kompiled_smir_path = target_path / 'smir.json'
111+ if len (resumable ) == len (labels ):
112+ # All proofs are resumed, load SMIR from saved data
113+ smir_info = SMIRInfo .from_file (kompiled_smir_path )
96114 else :
97- _LOGGER . info ( f'Constructing initial proof: { label } ' )
115+ # Need fresh SMIR for at least one proof
98116 if opts .parsed_smir is not None :
99117 smir_info = SMIRInfo (opts .parsed_smir )
100118 elif opts .smir :
101119 smir_info = SMIRInfo .from_file (opts .rs_file )
102120 else :
103121 smir_info = SMIRInfo (cargo_get_smir_json (opts .rs_file , save_smir = opts .save_smir ))
104122
105- smir_info = smir_info .reduce_to (opts .start_symbol )
123+ smir_info = smir_info .reduce_to (opts .start_symbols )
106124 # Report whether the reduced call graph includes any functions without MIR bodies
107125 missing_body_syms = [
108126 sym
@@ -114,28 +132,38 @@ def _prove(opts: ProveOpts, target_path: Path, label: str) -> APRProof:
114132 _LOGGER .info (f'missing-bodies-present={ has_missing } count={ len (missing_body_syms )} ' )
115133 _LOGGER .debug (f'Missing-body function symbols (first 5): { missing_body_syms [:5 ]} ' )
116134
117- kmir = KMIR .from_kompiled_kore (
118- smir_info ,
119- target_dir = target_path ,
120- extra_module = opts .add_module ,
121- bug_report = opts .bug_report ,
122- symbolic = True ,
123- haskell_target = opts .haskell_target ,
124- llvm_lib_target = opts .llvm_lib_target ,
125- break_on_function = opts .break_on_function or None ,
126- )
135+ kmir = KMIR .from_kompiled_kore (
136+ smir_info ,
137+ target_dir = target_path ,
138+ extra_module = opts .add_module ,
139+ bug_report = opts .bug_report ,
140+ symbolic = True ,
141+ haskell_target = opts .haskell_target ,
142+ llvm_lib_target = opts .llvm_lib_target ,
143+ break_on_function = opts .break_on_function or None ,
144+ )
127145
128- proof = apr_proof_from_smir (
129- kmir ,
130- label ,
131- smir_info ,
132- start_symbol = opts .start_symbol ,
133- proof_dir = opts .proof_dir ,
134- )
135- if proof .proof_dir is not None and (proof .proof_dir / label ).is_dir ():
136- smir_info .dump (proof .proof_dir / proof .id / 'smir.json' )
146+ smir_info .dump (kompiled_smir_path )
137147
138- return _advance_proof (kmir , proof , opts , label )
148+ # Prove each symbol sequentially using shared definition
149+ results : list [APRProof ] = []
150+ for label , start_symbol in zip (labels , opts .start_symbols , strict = True ):
151+ if label in resumable :
152+ proof = resumable [label ]
153+ else :
154+ _LOGGER .info (f'Constructing initial proof: { label } ' )
155+ proof = apr_proof_from_smir (
156+ kmir ,
157+ label ,
158+ smir_info ,
159+ start_symbol = start_symbol ,
160+ proof_dir = opts .proof_dir ,
161+ )
162+
163+ proof = _advance_proof (kmir , proof , opts , label )
164+ results .append (proof )
165+
166+ return results
139167
140168
141169def _advance_proof (kmir : KMIR , proof : APRProof , opts : ProveOpts , label : str ) -> APRProof :
0 commit comments