Skip to content

Commit 8d29907

Browse files
ehildenbrv-auditor
authored andcommitted
Split out CTerm manipulation functionalities of KCFGExplore (runtimeverification/pyk#915)
`KCFGExplore` class has grown quite a bit, and broadly contains two types of things: (i) wrappers around the `KoreClient` for the `CTerm` class, and (ii) functionalities for manipulating KCFGs using said wrappers. This PR pulls the first category out into a new submodule of the `cterm` submodule. - Module `cterm/symbolic.py` is added, with class `CTermSymbolic`, which wraps a `KoreClient` and lifts its functionalities to from Kore to `CTerm` level. - The class `CTermSymbolic` doesn't need access to a `KPrint`, instead just a `KDefinition` and a `KompiledKore`. - The class `CTermSymbolic` implements two new methods `CTermSymbolic.kast_to_kore(KInner) -> Pattern` and `CTermSymbolic.kore_to_kast(Pattern) -> KInner` as wrappers around the methods in `konvert` which have the definitions applied, and uses them in the various methods instead. In particular, that means we do _not_ have fallback to `kast` binary for these methods (instead crashes), but these codepaths are very well tested now. - The methods from `KCFGExplore.cterm_*` are moved to the `CTermSymbolic` class, with the prefix `cterm_` dropped. - The `KCFGExplore` is updated to use this class instead of a `KoreClient` directly, and various `*Prover` classes are updated as well. - Updates to the testing harness are made: - A new fixture `CTermSymbolic` is added, for when only that is needed. - The directory `src/tests/integration/kcfg` is moved to `src/tests/integration/cterm`, and lowered to `CTermSymbolic` instead of `KCFGExplore`. --------- Co-authored-by: devops <[email protected]>
1 parent c59d395 commit 8d29907

File tree

16 files changed

+280
-205
lines changed

16 files changed

+280
-205
lines changed

pyk/docs/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
project = 'pyk'
1010
author = 'Runtime Verification, Inc'
1111
copyright = '2024, Runtime Verification, Inc'
12-
version = '0.1.652'
13-
release = '0.1.652'
12+
version = '0.1.653'
13+
release = '0.1.653'
1414

1515
# -- General configuration ---------------------------------------------------
1616
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

pyk/package/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.652
1+
0.1.653

pyk/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "pyk"
7-
version = "0.1.652"
7+
version = "0.1.653"
88
description = ""
99
authors = [
1010
"Runtime Verification, Inc. <[email protected]>",

pyk/src/pyk/cterm/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from .cterm import CSubst, CTerm, build_claim, build_rule
2+
from .symbolic import CTermSymbolic

pyk/src/pyk/cterm/symbolic.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
from typing import TYPE_CHECKING, NamedTuple
5+
6+
from ..cterm import CSubst, CTerm
7+
from ..kast.inner import KApply, KLabel, KVariable, Subst
8+
from ..kast.manip import flatten_label, free_vars
9+
from ..konvert import kast_to_kore, kore_to_kast
10+
from ..kore.rpc import AbortedResult, SatResult, StopReason, UnknownResult, UnsatResult
11+
from ..prelude.k import GENERATED_TOP_CELL
12+
from ..prelude.ml import is_top, mlEquals, mlTop
13+
14+
if TYPE_CHECKING:
15+
from collections.abc import Iterable
16+
from typing import Final
17+
18+
from ..kast import KInner
19+
from ..kast.outer import KDefinition
20+
from ..kore.kompiled import KompiledKore
21+
from ..kore.rpc import KoreClient, LogEntry
22+
from ..kore.syntax import Pattern
23+
24+
25+
_LOGGER: Final = logging.getLogger(__name__)
26+
27+
28+
class CTermExecute(NamedTuple):
29+
state: CTerm
30+
next_states: tuple[CTerm, ...]
31+
depth: int
32+
vacuous: bool
33+
logs: tuple[LogEntry, ...]
34+
35+
36+
class CTermSymbolic:
37+
_kore_client: KoreClient
38+
_definition: KDefinition
39+
_kompiled_kore: KompiledKore
40+
_trace_rewrites: bool
41+
42+
def __init__(
43+
self,
44+
kore_client: KoreClient,
45+
definition: KDefinition,
46+
kompiled_kore: KompiledKore,
47+
*,
48+
trace_rewrites: bool = False,
49+
):
50+
self._kore_client = kore_client
51+
self._definition = definition
52+
self._kompiled_kore = kompiled_kore
53+
self._trace_rewrites = trace_rewrites
54+
55+
def kast_to_kore(self, kinner: KInner) -> Pattern:
56+
return kast_to_kore(self._definition, self._kompiled_kore, kinner, sort=GENERATED_TOP_CELL)
57+
58+
def kore_to_kast(self, pattern: Pattern) -> KInner:
59+
return kore_to_kast(self._definition, pattern)
60+
61+
def execute(
62+
self,
63+
cterm: CTerm,
64+
depth: int | None = None,
65+
cut_point_rules: Iterable[str] | None = None,
66+
terminal_rules: Iterable[str] | None = None,
67+
module_name: str | None = None,
68+
) -> CTermExecute:
69+
_LOGGER.debug(f'Executing: {cterm}')
70+
kore = self.kast_to_kore(cterm.kast)
71+
response = self._kore_client.execute(
72+
kore,
73+
max_depth=depth,
74+
cut_point_rules=cut_point_rules,
75+
terminal_rules=terminal_rules,
76+
module_name=module_name,
77+
log_successful_rewrites=True,
78+
log_failed_rewrites=self._trace_rewrites,
79+
log_successful_simplifications=self._trace_rewrites,
80+
log_failed_simplifications=self._trace_rewrites,
81+
)
82+
83+
if isinstance(response, AbortedResult):
84+
unknown_predicate = response.unknown_predicate.text if response.unknown_predicate else None
85+
raise ValueError(f'Backend responded with aborted state. Unknown predicate: {unknown_predicate}')
86+
87+
state = CTerm.from_kast(self.kore_to_kast(response.state.kore))
88+
resp_next_states = response.next_states or ()
89+
next_states = tuple(CTerm.from_kast(self.kore_to_kast(ns.kore)) for ns in resp_next_states)
90+
91+
assert all(not cterm.is_bottom for cterm in next_states)
92+
assert len(next_states) != 1 or response.reason is StopReason.CUT_POINT_RULE
93+
94+
return CTermExecute(
95+
state=state,
96+
next_states=next_states,
97+
depth=response.depth,
98+
vacuous=response.reason is StopReason.VACUOUS,
99+
logs=response.logs,
100+
)
101+
102+
def simplify(self, cterm: CTerm) -> tuple[CTerm, tuple[LogEntry, ...]]:
103+
_LOGGER.debug(f'Simplifying: {cterm}')
104+
kore = self.kast_to_kore(cterm.kast)
105+
kore_simplified, logs = self._kore_client.simplify(kore)
106+
kast_simplified = self.kore_to_kast(kore_simplified)
107+
return CTerm.from_kast(kast_simplified), logs
108+
109+
def kast_simplify(self, kast: KInner) -> tuple[KInner, tuple[LogEntry, ...]]:
110+
_LOGGER.debug(f'Simplifying: {kast}')
111+
kore = self.kast_to_kore(kast)
112+
kore_simplified, logs = self._kore_client.simplify(kore)
113+
kast_simplified = self.kore_to_kast(kore_simplified)
114+
return kast_simplified, logs
115+
116+
def get_model(self, cterm: CTerm, module_name: str | None = None) -> Subst | None:
117+
_LOGGER.info(f'Getting model: {cterm}')
118+
kore = self.kast_to_kore(cterm.kast)
119+
result = self._kore_client.get_model(kore, module_name=module_name)
120+
if type(result) is UnknownResult:
121+
_LOGGER.debug('Result is Unknown')
122+
return None
123+
elif type(result) is UnsatResult:
124+
_LOGGER.debug('Result is UNSAT')
125+
return None
126+
elif type(result) is SatResult:
127+
_LOGGER.debug('Result is SAT')
128+
if not result.model:
129+
return Subst({})
130+
model_subst = self.kore_to_kast(result.model)
131+
try:
132+
return Subst.from_pred(model_subst)
133+
except ValueError as err:
134+
raise AssertionError(f'Received a non-substitution from get-model endpoint: {model_subst}') from err
135+
136+
else:
137+
raise AssertionError('Received an invalid response from get-model endpoint')
138+
139+
def implies(
140+
self,
141+
antecedent: CTerm,
142+
consequent: CTerm,
143+
bind_universally: bool = False,
144+
) -> CSubst | None:
145+
_LOGGER.debug(f'Checking implication: {antecedent} #Implies {consequent}')
146+
_consequent = consequent.kast
147+
fv_antecedent = free_vars(antecedent.kast)
148+
unbound_consequent = [v for v in free_vars(_consequent) if v not in fv_antecedent]
149+
if len(unbound_consequent) > 0:
150+
bind_text, bind_label = ('existentially', '#Exists')
151+
if bind_universally:
152+
bind_text, bind_label = ('universally', '#Forall')
153+
_LOGGER.debug(f'Binding variables in consequent {bind_text}: {unbound_consequent}')
154+
for uc in unbound_consequent:
155+
_consequent = KApply(KLabel(bind_label, [GENERATED_TOP_CELL]), [KVariable(uc), _consequent])
156+
antecedent_kore = self.kast_to_kore(antecedent.kast)
157+
consequent_kore = self.kast_to_kore(_consequent)
158+
result = self._kore_client.implies(antecedent_kore, consequent_kore)
159+
if not result.satisfiable:
160+
if result.substitution is not None:
161+
_LOGGER.debug(f'Received a non-empty substitution for unsatisfiable implication: {result.substitution}')
162+
if result.predicate is not None:
163+
_LOGGER.debug(f'Received a non-empty predicate for unsatisfiable implication: {result.predicate}')
164+
return None
165+
if result.substitution is None:
166+
raise ValueError('Received empty substutition for satisfiable implication.')
167+
if result.predicate is None:
168+
raise ValueError('Received empty predicate for satisfiable implication.')
169+
ml_subst = self.kore_to_kast(result.substitution)
170+
ml_pred = self.kore_to_kast(result.predicate) if result.predicate is not None else mlTop()
171+
ml_preds = flatten_label('#And', ml_pred)
172+
if is_top(ml_subst):
173+
return CSubst(subst=Subst({}), constraints=ml_preds)
174+
subst_pattern = mlEquals(KVariable('###VAR'), KVariable('###TERM'))
175+
_subst: dict[str, KInner] = {}
176+
for subst_pred in flatten_label('#And', ml_subst):
177+
m = subst_pattern.match(subst_pred)
178+
if m is not None and type(m['###VAR']) is KVariable:
179+
_subst[m['###VAR'].name] = m['###TERM']
180+
else:
181+
raise AssertionError(f'Received a non-substitution from implies endpoint: {subst_pred}')
182+
return CSubst(subst=Subst(_subst), constraints=ml_preds)
183+
184+
def assume_defined(self, cterm: CTerm) -> CTerm:
185+
_LOGGER.debug(f'Computing definedness condition for: {cterm}')
186+
kast = KApply(KLabel('#Ceil', [GENERATED_TOP_CELL, GENERATED_TOP_CELL]), [cterm.config])
187+
kore = self.kast_to_kore(kast)
188+
kore_simplified, _logs = self._kore_client.simplify(kore)
189+
kast_simplified = self.kore_to_kast(kore_simplified)
190+
_LOGGER.debug(f'Definedness condition computed: {kast_simplified}')
191+
return cterm.add_constraint(kast_simplified)

0 commit comments

Comments
 (0)