Skip to content

Commit 51a716a

Browse files
authored
Extract SMIR kompilation into module kmir.kompile (#751)
1 parent f741dfd commit 51a716a

File tree

3 files changed

+308
-233
lines changed

3 files changed

+308
-233
lines changed

kmir/src/kmir/kmir.py

Lines changed: 10 additions & 232 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
import logging
4-
import shutil
54
import tempfile
65
from contextlib import contextmanager
76
from functools import cached_property
@@ -13,8 +12,6 @@
1312
from pyk.kast.inner import KApply, KSequence, KSort, KToken, KVariable, Subst
1413
from pyk.kast.manip import abstract_term_safely, free_vars, split_config_from
1514
from pyk.kast.prelude.collections import list_empty, list_of
16-
from pyk.kast.prelude.kint import intToken
17-
from pyk.kast.prelude.utils import token
1815
from pyk.kcfg import KCFG
1916
from pyk.kcfg.explore import KCFGExplore
2017
from pyk.kcfg.semantics import DefaultSemantics
@@ -24,7 +21,6 @@
2421
from pyk.proof.reachability import APRProof, APRProver
2522
from pyk.proof.show import APRProofNodePrinter
2623

27-
from .build import HASKELL_DEF_DIR, LLVM_DEF_DIR, LLVM_LIB_DIR
2824
from .cargo import cargo_get_smir_json
2925
from .kast import mk_call_terminator, symbolic_locals
3026
from .kparse import KParse
@@ -33,7 +29,7 @@
3329

3430
if TYPE_CHECKING:
3531
from collections.abc import Iterator
36-
from typing import Any, Final
32+
from typing import Final
3733

3834
from pyk.cterm.show import CTermShow
3935
from pyk.kast.inner import KInner
@@ -62,127 +58,15 @@ def __init__(
6258
def from_kompiled_kore(
6359
smir_info: SMIRInfo, target_dir: str, bug_report: Path | None = None, symbolic: bool = True
6460
) -> KMIR:
65-
kmir = KMIR(HASKELL_DEF_DIR)
66-
67-
def _insert_rules_and_write(input_file: Path, rules: list[str], output_file: Path) -> None:
68-
with open(input_file, 'r') as f:
69-
lines = f.readlines()
70-
71-
# last line must start with 'endmodule'
72-
last_line = lines[-1]
73-
assert last_line.startswith('endmodule')
74-
new_lines = lines[:-1]
75-
76-
# Insert rules before the endmodule line
77-
new_lines.append(f'\n// Generated from file {input_file}\n\n')
78-
new_lines.extend(rules)
79-
new_lines.append('\n' + last_line)
80-
81-
# Write to output file
82-
with open(output_file, 'w') as f:
83-
f.writelines(new_lines)
84-
85-
target_path = Path(target_dir)
86-
# TODO if target dir exists and contains files, check file dates (definition files and interpreter)
87-
# to decide whether or not to recompile. For now we always recompile.
88-
target_path.mkdir(parents=True, exist_ok=True)
89-
90-
rules = kmir.make_kore_rules(smir_info)
91-
_LOGGER.info(f'Generated {len(rules)} function equations to add to `definition.kore')
92-
93-
if symbolic:
94-
# Create output directories
95-
target_llvm_path = target_path / 'llvm-library'
96-
target_llvmdt_path = target_llvm_path / 'dt'
97-
target_hs_path = target_path / 'haskell'
98-
99-
_LOGGER.info(f'Creating directories {target_llvmdt_path} and {target_hs_path}')
100-
target_llvmdt_path.mkdir(parents=True, exist_ok=True)
101-
target_hs_path.mkdir(parents=True, exist_ok=True)
102-
103-
# Process LLVM definition
104-
_LOGGER.info('Writing LLVM definition file')
105-
llvm_def_file = LLVM_LIB_DIR / 'definition.kore'
106-
llvm_def_output = target_llvm_path / 'definition.kore'
107-
_insert_rules_and_write(llvm_def_file, rules, llvm_def_output)
108-
109-
# Run llvm-kompile-matching and llvm-kompile for LLVM
110-
# TODO use pyk to do this if possible (subprocess wrapper, maybe llvm-kompile itself?)
111-
# TODO align compilation options to what we use in plugin.py
112-
import subprocess
113-
114-
_LOGGER.info('Running llvm-kompile-matching')
115-
subprocess.run(
116-
['llvm-kompile-matching', str(llvm_def_output), 'qbaL', str(target_llvmdt_path), '1/2'], check=True
117-
)
118-
_LOGGER.info('Running llvm-kompile')
119-
subprocess.run(
120-
[
121-
'llvm-kompile',
122-
str(llvm_def_output),
123-
str(target_llvmdt_path),
124-
'c',
125-
'-O2',
126-
'--',
127-
'-o',
128-
target_llvm_path / 'interpreter',
129-
],
130-
check=True,
131-
)
132-
133-
# Process Haskell definition
134-
_LOGGER.info('Writing Haskell definition file')
135-
hs_def_file = HASKELL_DEF_DIR / 'definition.kore'
136-
_insert_rules_and_write(hs_def_file, rules, target_hs_path / 'definition.kore')
137-
138-
# Copy all files except definition.kore and binary from HASKELL_DEF_DIR to out/hs
139-
_LOGGER.info('Copying other artefacts into HS output directory')
140-
for file_path in HASKELL_DEF_DIR.iterdir():
141-
if file_path.name != 'definition.kore' and file_path.name != 'haskellDefinition.bin':
142-
if file_path.is_file():
143-
shutil.copy2(file_path, target_hs_path / file_path.name)
144-
elif file_path.is_dir():
145-
shutil.copytree(file_path, target_hs_path / file_path.name, dirs_exist_ok=True)
146-
return KMIR(target_hs_path, target_llvm_path, bug_report=bug_report)
147-
else:
148-
149-
target_llvm_path = target_path / 'llvm'
150-
target_llvmdt_path = target_llvm_path / 'dt'
151-
_LOGGER.info(f'Creating directory {target_llvmdt_path}')
152-
target_llvmdt_path.mkdir(parents=True, exist_ok=True)
153-
154-
# Process LLVM definition
155-
_LOGGER.info('Writing LLVM definition file')
156-
llvm_def_file = LLVM_LIB_DIR / 'definition.kore'
157-
llvm_def_output = target_llvm_path / 'definition.kore'
158-
_insert_rules_and_write(llvm_def_file, rules, llvm_def_output)
159-
160-
import subprocess
161-
162-
_LOGGER.info('Running llvm-kompile-matching')
163-
subprocess.run(
164-
['llvm-kompile-matching', str(llvm_def_output), 'qbaL', str(target_llvmdt_path), '1/2'], check=True
165-
)
166-
_LOGGER.info('Running llvm-kompile')
167-
subprocess.run(
168-
[
169-
'llvm-kompile',
170-
str(llvm_def_output),
171-
str(target_llvmdt_path),
172-
'main',
173-
'-O2',
174-
'--',
175-
'-o',
176-
target_llvm_path / 'interpreter',
177-
],
178-
check=True,
179-
)
180-
blacklist = ['definition.kore', 'interpreter', 'dt']
181-
to_copy = [file.name for file in LLVM_DEF_DIR.iterdir() if file.name not in blacklist]
182-
for file in to_copy:
183-
_LOGGER.info(f'Copying file {file}')
184-
shutil.copy2(LLVM_DEF_DIR / file, target_llvm_path / file)
185-
return KMIR(target_llvm_path, None, bug_report=bug_report)
61+
from .kompile import kompile_smir
62+
63+
kompiled_smir = kompile_smir(
64+
smir_info=smir_info,
65+
target_dir=target_dir,
66+
bug_report=bug_report,
67+
symbolic=symbolic,
68+
)
69+
return kompiled_smir.create_kmir(bug_report_file=bug_report)
18670

18771
class Symbols:
18872
END_PROGRAM: Final = KApply('#EndProgram_KMIR-CONTROL-FLOW_KItem')
@@ -203,96 +87,6 @@ def kcfg_explore(self, label: str | None = None) -> Iterator[KCFGExplore]:
20387
) as cts:
20488
yield KCFGExplore(cts, kcfg_semantics=KMIRSemantics())
20589

206-
def functions(self, smir_info: SMIRInfo) -> dict[int, KInner]:
207-
functions: dict[int, KInner] = {}
208-
209-
# Parse regular functions
210-
for item_name, item in smir_info.items.items():
211-
if not item_name in smir_info.function_symbols_reverse:
212-
_LOGGER.warning(f'Item not found in SMIR: {item_name}')
213-
continue
214-
parsed_item = self.parser.parse_mir_json(item, 'MonoItem')
215-
if not parsed_item:
216-
raise ValueError(f'Could not parse MonoItemKind: {parsed_item}')
217-
parsed_item_kinner, _ = parsed_item
218-
assert isinstance(parsed_item_kinner, KApply) and parsed_item_kinner.label.name == 'monoItemWrapper'
219-
# each item can have several entries in the function table for linked SMIR JSON
220-
for ty in smir_info.function_symbols_reverse[item_name]:
221-
functions[ty] = parsed_item_kinner.args[1]
222-
223-
# Add intrinsic functions
224-
for ty, sym in smir_info.function_symbols.items():
225-
if 'IntrinsicSym' in sym and ty not in functions:
226-
functions[ty] = KApply(
227-
'IntrinsicFunction',
228-
[KApply('symbol(_)_LIB_Symbol_String', [token(sym['IntrinsicSym'])])],
229-
)
230-
231-
return functions
232-
233-
def make_kore_rules(self, smir_info: SMIRInfo) -> list[str]: # generates kore syntax directly as string
234-
equations = []
235-
236-
# kprint tool is too chatty
237-
kprint_logger = logging.getLogger('pyk.ktool.kprint')
238-
kprint_logger.setLevel(logging.WARNING)
239-
240-
for fty, kind in self.functions(smir_info).items():
241-
equations.append(
242-
self._mk_equation('lookupFunction', KApply('ty', (token(fty),)), 'Ty', kind, 'MonoItemKind')
243-
)
244-
245-
types: set[KInner] = set()
246-
for type in smir_info._smir['types']:
247-
parse_result = self.parser.parse_mir_json(type, 'TypeMapping')
248-
assert parse_result is not None
249-
type_mapping, _ = parse_result
250-
assert isinstance(type_mapping, KApply) and len(type_mapping.args) == 2
251-
ty, tyinfo = type_mapping.args
252-
if ty in types:
253-
raise ValueError(f'Key collision in type map: {ty}')
254-
types.add(ty)
255-
equations.append(self._mk_equation('lookupTy', ty, 'Ty', tyinfo, 'TypeInfo'))
256-
257-
for alloc in smir_info._smir['allocs']:
258-
alloc_id, value = self._decode_alloc(smir_info=smir_info, raw_alloc=alloc)
259-
equations.append(self._mk_equation('lookupAlloc', alloc_id, 'AllocId', value, 'Evaluation'))
260-
261-
return equations
262-
263-
def _mk_equation(self, fun: str, arg: KInner, arg_sort: str, result: KInner, result_sort: str) -> str:
264-
from pyk.kore.rule import FunctionRule
265-
from pyk.kore.syntax import App, SortApp
266-
267-
arg_kore = self.kast_to_kore(arg, KSort(arg_sort))
268-
fun_app = App('Lbl' + fun, (), (arg_kore,))
269-
result_kore = self.kast_to_kore(result, KSort(result_sort))
270-
271-
assert isinstance(fun_app, App)
272-
rule = FunctionRule(
273-
lhs=fun_app,
274-
rhs=result_kore,
275-
req=None,
276-
ens=None,
277-
sort=SortApp('Sort' + result_sort),
278-
arg_sorts=(SortApp('Sort' + arg_sort),),
279-
anti_left=None,
280-
priority=50,
281-
uid='fubar',
282-
label='fubaz',
283-
)
284-
return '\n'.join(
285-
[
286-
'',
287-
'',
288-
(
289-
f'// {fun}({self.pretty_print(arg)})'
290-
+ (f' => {self.pretty_print(result)}' if len(self.pretty_print(result)) < 1000 else '')
291-
),
292-
rule.to_axiom().text,
293-
]
294-
)
295-
29690
def make_call_config(
29791
self,
29892
smir_info: SMIRInfo,
@@ -327,22 +121,6 @@ def make_call_config(
327121
config = self.definition.empty_config(KSort(sort))
328122
return (subst.apply(config), constraints)
329123

330-
def _decode_alloc(self, smir_info: SMIRInfo, raw_alloc: Any) -> tuple[KInner, KInner]:
331-
from .decoding import UnableToDecodeValue, decode_alloc_or_unable
332-
333-
alloc_id = raw_alloc['alloc_id']
334-
alloc_info = smir_info.allocs[alloc_id]
335-
value = decode_alloc_or_unable(alloc_info=alloc_info, types=smir_info.types)
336-
337-
match value:
338-
case UnableToDecodeValue(msg):
339-
_LOGGER.debug(f'Decoding failed: {msg}')
340-
case _:
341-
pass
342-
343-
alloc_id_term = KApply('allocId', intToken(alloc_id))
344-
return alloc_id_term, value.to_kast()
345-
346124
def run_smir(self, smir_info: SMIRInfo, start_symbol: str = 'main', depth: int | None = None) -> Pattern:
347125
smir_info = smir_info.reduce_to(start_symbol)
348126
init_config, init_constraints = self.make_call_config(smir_info, start_symbol=start_symbol, init=True)

0 commit comments

Comments
 (0)