|
| 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