Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow including lemmas dynamically into APRProver #4681

Open
wants to merge 24 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a87d5de
pyk/ktool/{claim_loader,kprove}: rename KProve.{get_claim_modules => …
ehildenb Nov 9, 2024
fc07a70
pyk/konvert/_kast_to_kore: minor refactoring
ehildenb Nov 9, 2024
be60d21
pyk/tests/konvert/test_simple_proofs: add failing test of konverting …
ehildenb Nov 9, 2024
967b79a
pyk/kast/outer: add simple table of function labels to KDefinition
ehildenb Nov 9, 2024
dd4a8b3
pyk/konvert/_kast_to_kore: support simple translation of functional r…
ehildenb Nov 9, 2024
a1b6e4d
pyk/tests/integration/konvert/test_simple_proofs: sss
ehildenb Nov 9, 2024
2f7fe10
pyk/tests/integration/konvert/test_simple_proofs: add test of require…
ehildenb Nov 9, 2024
30c4274
pyk/tests/integration/konvert/test_simple_proofs: add test of simplif…
ehildenb Nov 9, 2024
8092e50
pyk/tests/integration/konvert/test_simple_proofs: priority attribute …
ehildenb Nov 9, 2024
d3ccdfe
pyk/kast/manip: allow bool_to_ml_pred to specify the target sort
ehildenb Nov 10, 2024
8dbaa74
pyk/konvert/_kast_to_kore: allow translating functional rules from Ka…
ehildenb Nov 10, 2024
7492dd1
pyk/tests/integration/k-files/simple-proofs.k: add more examples of f…
ehildenb Nov 10, 2024
1ba2eec
pyk/konvert/_kast_to_kore: factor out attribute konversion
ehildenb Nov 10, 2024
d33b9bc
pyk/konvert/_kast_to_kore: add support for converting SIMPLIFICATION …
ehildenb Nov 10, 2024
183044a
pyk/kast/att: add smt-lemma and symbolic attributes
ehildenb Nov 10, 2024
d091d9d
pyk/konvert/kast_to_kore: support more translation of kast attributes…
ehildenb Nov 10, 2024
1636750
pyk/kast/outer: typo in comment
ehildenb Nov 10, 2024
76d33e9
pyk/tests/integration/{simple-proofs.k,test_simple_proofs.py}: update…
ehildenb Nov 10, 2024
b90e65d
pyk/konvert/_kast_to_kore: correctly handle conversion of variables i…
ehildenb Nov 10, 2024
1af260b
pyk/tests/integration/konvert/test_simple_proofs: update tests
ehildenb Nov 12, 2024
ff1d21f
pyk/tests/integration/proof/test_imp: update function argument names
ehildenb Nov 12, 2024
f394c77
pyk/konvert/_kast_to_kore: working conversion of K functional rules t…
ehildenb Nov 12, 2024
610949c
pyk/reachability: support adding extra Kast modules to proof context
ehildenb Nov 12, 2024
cbdc23c
pyk/konvert/_kast_to_kore: make sure to sort attribute entries first
ehildenb Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pyk/src/pyk/kast/att.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,9 +339,11 @@ class Atts:
SEQSTRICT: Final = AttKey('seqstrict', type=_ANY)
SORT: Final = AttKey('org.kframework.kore.Sort', type=_ANY)
SOURCE: Final = AttKey('org.kframework.attributes.Source', type=_PATH)
SMTLEMMA: Final = AttKey('smt-lemma', type=_NONE)
STRICT: Final = AttKey('strict', type=_ANY)
SYMBOL: Final = AttKey('symbol', type=_STR)
SYNTAX_MODULE: Final = AttKey('syntaxModule', type=_STR)
SYMBOLIC: Final = AttKey('symbolic', type=OptionalType(_STR))
TERMINALS: Final = AttKey('terminals', type=_STR)
TOKEN: Final = AttKey('token', type=_NONE)
TOTAL: Final = AttKey('total', type=_NONE)
Expand Down
6 changes: 3 additions & 3 deletions pyk/src/pyk/kast/manip.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,15 @@ def fun(term: KInner) -> KInner:
return fun


def bool_to_ml_pred(kast: KInner) -> KInner:
def bool_to_ml_pred(kast: KInner, sort: str | KSort = GENERATED_TOP_CELL) -> KInner:
def _bool_constraint_to_ml(_kast: KInner) -> KInner:
if _kast == TRUE:
return mlTop()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return mlTop()
return mlTop(sort=sort)

if _kast == FALSE:
return mlBottom()
return mlEqualsTrue(_kast)
return mlEqualsTrue(_kast, sort=sort)

return mlAnd([_bool_constraint_to_ml(cond) for cond in flatten_label('_andBool_', kast)])
return mlAnd([_bool_constraint_to_ml(cond) for cond in flatten_label('_andBool_', kast)], sort=sort)


def ml_pred_to_bool(kast: KInner, unsafe: bool = False) -> KInner:
Expand Down
7 changes: 6 additions & 1 deletion pyk/src/pyk/kast/outer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,11 @@ def functions(self) -> tuple[KProduction, ...]:
"""Returns the `KProduction` which are function declarations transitively imported by the main module of this definition."""
return tuple(func for module in self.modules for func in module.functions)

@cached_property
def function_labels(self) -> tuple[str, ...]:
"""Returns the label names of all the `KProduction` which are function symbols for all modules in this definition."""
return tuple(func.klabel.name for func in self.functions if func.klabel is not None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's a function symbol without a klabel, consider asserting instead.


@cached_property
def constructors(self) -> tuple[KProduction, ...]:
"""Returns the `KProduction` which are constructor declarations transitively imported by the main module of this definition."""
Expand Down Expand Up @@ -1394,7 +1399,7 @@ def _add_ksequence_under_k_productions(_kast: KInner) -> KInner:
return top_down(_add_ksequence_under_k_productions, kast)

def sort_vars(self, kast: KInner, sort: KSort | None = None) -> KInner:
"""Return the original term with all the variables having there sorts added or specialized, failing if recieving conflicting sorts for a given variable."""
"""Return the original term with all the variables having the sorts added or specialized, failing if recieving conflicting sorts for a given variable."""
if type(kast) is KVariable and kast.sort is None and sort is not None:
return kast.let(sort=sort)

Expand Down
173 changes: 136 additions & 37 deletions pyk/src/pyk/konvert/_kast_to_kore.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,33 @@
from typing import TYPE_CHECKING

from ..kast import Atts
from ..kast.inner import KApply, KLabel, KSequence, KSort, KToken, KVariable, top_down
from ..kast.manip import bool_to_ml_pred, extract_lhs, extract_rhs, flatten_label
from ..kast.inner import KApply, KLabel, KRewrite, KSequence, KSort, KToken, KVariable, top_down
from ..kast.manip import bool_to_ml_pred, extract_lhs, extract_rhs, flatten_label, ml_pred_to_bool, var_occurrences
from ..kast.outer import KRule
from ..kore.prelude import BOOL as KORE_BOOL
from ..kore.prelude import SORT_K
from ..kore.syntax import DV, And, App, Axiom, EVar, Import, MLPattern, MLQuant, Module, Rewrites, SortApp, String, Top
from ..kore.prelude import TRUE as KORE_TRUE
from ..kore.syntax import (
DV,
And,
App,
Axiom,
Equals,
EVar,
Implies,
Import,
MLPattern,
MLQuant,
Module,
Rewrites,
SortApp,
SortVar,
String,
Top,
)
from ..prelude.bytes import BYTES, pretty_bytes_str
from ..prelude.k import K_ITEM, K, inj
from ..prelude.kbool import TRUE
from ..prelude.kbool import BOOL, TRUE, andBool
from ..prelude.ml import mlAnd
from ..prelude.string import STRING, pretty_string
from ._utils import munge
Expand All @@ -21,6 +40,7 @@
from typing import Final

from ..kast import KInner
from ..kast.att import AttEntry
from ..kast.outer import KDefinition, KFlatModule, KImport
from ..kore.syntax import Pattern, Sentence, Sort

Expand Down Expand Up @@ -198,45 +218,105 @@ def _inject(definition: KDefinition, term: KInner, sort: KSort) -> KInner:

# 'krule' should have sorts on variables
def krule_to_kore(definition: KDefinition, krule: KRule) -> Axiom:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

krule_body = krule.body
krule_lhs_config = extract_lhs(krule_body)
krule_rhs_config = extract_rhs(krule_body)
krule_lhs_constraints = [bool_to_ml_pred(c) for c in flatten_label('_andBool_', krule.requires) if not c == TRUE]
krule_rhs_constraints = [bool_to_ml_pred(c) for c in flatten_label('_andBool_', krule.ensures) if not c == TRUE]
krule_lhs = mlAnd([krule_lhs_config] + krule_lhs_constraints)
krule_rhs = mlAnd([krule_rhs_config] + krule_rhs_constraints)

top_level_kore_sort = SortApp('SortGeneratedTopCell')
top_level_k_sort = KSort('GeneratedTopCell')
# The backend does not like rewrite rules without a precondition
if len(krule_lhs_constraints) > 0:
kore_lhs: Pattern = kast_to_kore(definition, krule_lhs, sort=top_level_k_sort)

is_functional = isinstance(krule_lhs_config, KApply) and krule_lhs_config.label.name in definition.function_labels

top_level_k_sort = KSort('GeneratedTopCell') if not is_functional else definition.sort_strict(krule_lhs_config)
top_level_kore_sort = _ksort_to_kore(top_level_k_sort)

# Do sort inference on the entire rule at once
kast_lhs = mlAnd(
[krule_lhs_config]
+ [
bool_to_ml_pred(constraint, sort=top_level_k_sort)
for constraint in flatten_label('_andBool_', krule.requires)
if not constraint == TRUE
],
sort=top_level_k_sort,
)
kast_rhs = mlAnd(
[krule_rhs_config]
+ [
bool_to_ml_pred(constraint, sort=top_level_k_sort)
for constraint in flatten_label('_andBool_', krule.ensures)
if not constraint == TRUE
],
sort=top_level_k_sort,
)
kast_rule_sorted = definition.sort_vars(KRewrite(kast_lhs, kast_rhs))

kast_lhs_body, *kast_lhs_constraints = flatten_label('#And', extract_lhs(kast_rule_sorted))
kast_rhs_body, *kast_rhs_constraints = flatten_label('#And', extract_rhs(kast_rule_sorted))
kore_lhs_body = kast_to_kore(definition, kast_lhs_body, sort=top_level_k_sort)
kore_rhs_body = kast_to_kore(definition, kast_rhs_body, sort=top_level_k_sort)

axiom_vars: tuple[SortVar, ...] = ()
kore_axiom: Pattern
if not is_functional:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider extracting two helpers: one for the functional case, one for a normal rewrite.

kore_lhs_constraints = [
kast_to_kore(definition, kast_lhs_constraint, sort=top_level_k_sort)
for kast_lhs_constraint in kast_lhs_constraints
]
kore_rhs_constraints = [
kast_to_kore(definition, kast_rhs_constraint, sort=top_level_k_sort)
for kast_rhs_constraint in kast_rhs_constraints
]
kore_lhs_constraint: Pattern = Top(top_level_kore_sort)
if len(kore_lhs_constraints) == 1:
kore_lhs_constraint = kore_lhs_constraints[0]
elif len(kore_lhs_constraints) > 1:
kore_lhs_constraint = And(top_level_kore_sort, kore_lhs_constraints)
kore_lhs = And(top_level_kore_sort, [kore_lhs_body, kore_lhs_constraint])
kore_rhs = (
kore_rhs_body
if not kore_rhs_constraints
else And(top_level_kore_sort, [kore_rhs_body] + kore_rhs_constraints)
)
kore_axiom = Rewrites(sort=top_level_kore_sort, left=kore_lhs, right=kore_rhs)
else:
kore_lhs = And(
top_level_kore_sort,
(
kast_to_kore(definition, krule_lhs, sort=top_level_k_sort),
Top(top_level_kore_sort),
),
axiom_sort = SortVar('R')
axiom_vars = (axiom_sort,)
kast_lhs_constraints_bool = [
ml_pred_to_bool(kast_lhs_constraint) for kast_lhs_constraint in kast_lhs_constraints
]
kore_antecedent = Equals(
KORE_BOOL, axiom_sort, kast_to_kore(definition, andBool(kast_lhs_constraints_bool), sort=BOOL), KORE_TRUE
)
kore_ensures: Pattern = Top(top_level_kore_sort)
if kast_rhs_constraints:
kast_rhs_constraints_bool = [
ml_pred_to_bool(kast_rhs_constraint) for kast_rhs_constraint in kast_rhs_constraints
]
kore_ensures = Equals(
KORE_BOOL,
top_level_kore_sort,
kast_to_kore(definition, andBool(kast_rhs_constraints_bool), sort=BOOL),
KORE_TRUE,
)
kore_consequent = Equals(
top_level_kore_sort, axiom_sort, kore_lhs_body, And(top_level_kore_sort, [kore_rhs_body, kore_ensures])
)
kore_axiom = Implies(axiom_sort, kore_antecedent, kore_consequent)

kore_rhs: Pattern = kast_to_kore(definition, krule_rhs, sort=top_level_k_sort)

prio = krule.priority
attrs = [App(symbol='priority', sorts=(), args=(String(str(prio)),))]
if Atts.LABEL in krule.att:
label = krule.att[Atts.LABEL]
attrs.append(App(symbol='label', sorts=(), args=(String(label),)))
axiom = Axiom(
vars=(),
pattern=Rewrites(
sort=top_level_kore_sort,
left=kore_lhs,
right=kore_rhs,
),
attrs=attrs,
)
return axiom
# Make adjustments to Rule attributes
att = krule.att.discard([Atts.PRODUCTION, Atts.UNIQUE_ID, Atts.SOURCE, Atts.LOCATION])
if Atts.PRIORITY not in att:
if Atts.OWISE in att:
att = att.update([Atts.PRIORITY(200)])
att = att.discard([Atts.OWISE])
elif Atts.SIMPLIFICATION not in att:
att = att.update([Atts.PRIORITY(50)])

attrs = [
_krule_att_to_kore(att_entry, var_occurrences(kast_rule_sorted))
for att_entry in sorted(att.entries(), key=(lambda a: a.key.name))
]

return Axiom(vars=axiom_vars, pattern=kore_axiom, attrs=attrs)


def kflatmodule_to_kore(definition: KDefinition, kflatmodule: KFlatModule) -> Module:
Expand All @@ -249,6 +329,25 @@ def kflatmodule_to_kore(definition: KDefinition, kflatmodule: KFlatModule) -> Mo
return Module(name=kflatmodule.name, sentences=(imports + kore_axioms))


def _krule_att_to_kore(att_entry: AttEntry, kast_rule_vars: dict[str, list[KVariable]]) -> App:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's similar logic in _module_to_kore, maybe it's possible to extract that into it's own module and reuse it here.

match att_entry.key:
case Atts.LABEL | Atts.PRIORITY | Atts.SIMPLIFICATION:
return App(symbol=att_entry.key.name, sorts=(), args=(String(str(att_entry.value)),))
case Atts.SYMBOLIC | Atts.CONCRETE:
if not att_entry.value:
return App(symbol=att_entry.key.name, sorts=(), args=())
kore_vars = []
for var_name in att_entry.value.split(','):
if var_name not in kast_rule_vars:
raise ValueError(f'Variable in {att_entry.key} not present in rule: {var_name}')
kore_vars.append(_kvariable_to_kore(kast_rule_vars[var_name][0]))
return App(symbol=att_entry.key.name, sorts=(), args=tuple(kore_vars))
case Atts.SMTLEMMA:
return App(symbol=att_entry.key.name, sorts=(), args=())
case _:
raise ValueError(f'Do not know how to convert AttEntry to Kore: {att_entry}')


def _kimport_to_kore(kimport: KImport) -> Import:
return Import(module_name=kimport.name, attrs=())

Expand Down
6 changes: 3 additions & 3 deletions pyk/src/pyk/ktool/claim_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ def load_claims(

if not cache_hit:
_LOGGER.info('Generating claim modules')
module_list = self._kprove.get_claim_modules(
spec_file=spec_file,
spec_module_name=spec_module_name,
module_list = self._kprove.parse_modules(
file_path=spec_file,
module_name=spec_module_name,
include_dirs=include_dirs,
md_selector=md_selector,
type_inference_mode=type_inference_mode,
Expand Down
18 changes: 9 additions & 9 deletions pyk/src/pyk/ktool/kprove.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,19 +255,19 @@ def prove_claim(
depth=depth,
)

def get_claim_modules(
def parse_modules(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the module parser exposed by kprove general enough to parse an arbitrary module?

self,
spec_file: Path,
spec_module_name: str | None = None,
file_path: Path,
module_name: str | None = None,
include_dirs: Iterable[Path] = (),
md_selector: str | None = None,
type_inference_mode: TypeInferenceMode | None = None,
) -> KFlatModuleList:
with self._temp_file(prefix=f'{spec_file.name}.parsed.json.') as ntf:
with self._temp_file(prefix=f'{file_path.name}.parsed.json.') as ntf:
_kprove(
spec_file=spec_file,
spec_file=file_path,
kompiled_dir=self.definition_dir,
spec_module_name=spec_module_name,
spec_module_name=module_name,
include_dirs=include_dirs,
md_selector=md_selector,
output=KProveOutput.JSON,
Expand All @@ -288,9 +288,9 @@ def get_claim_index(
md_selector: str | None = None,
type_inference_mode: TypeInferenceMode | None = None,
) -> ClaimIndex:
module_list = self.get_claim_modules(
spec_file=spec_file,
spec_module_name=spec_module_name,
module_list = self.parse_modules(
file_path=spec_file,
module_name=spec_module_name,
include_dirs=include_dirs,
md_selector=md_selector,
type_inference_mode=type_inference_mode,
Expand Down
12 changes: 11 additions & 1 deletion pyk/src/pyk/proof/reachability.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,7 @@ class APRProver(Prover[APRProof, APRProofStep, APRProofResult]):
direct_subproof_rules: bool
assume_defined: bool
kcfg_explore: KCFGExplore
extra_module: KFlatModule | None

def __init__(
self,
Expand All @@ -725,6 +726,7 @@ def __init__(
fast_check_subsumption: bool = False,
direct_subproof_rules: bool = False,
assume_defined: bool = False,
extra_module: KFlatModule | None = None,
) -> None:

self.kcfg_explore = kcfg_explore
Expand All @@ -736,11 +738,19 @@ def __init__(
self.fast_check_subsumption = fast_check_subsumption
self.direct_subproof_rules = direct_subproof_rules
self.assume_defined = assume_defined
self.extra_module = extra_module

def close(self) -> None:
self.kcfg_explore.cterm_symbolic._kore_client.close()

def init_proof(self, proof: APRProof) -> None:
main_module_name = self.main_module_name
if self.extra_module:
_kore_module = kflatmodule_to_kore(self.kcfg_explore.cterm_symbolic._definition, self.extra_module)
_LOGGER.warning(f'_kore_module: {_kore_module.text}')
self.kcfg_explore.cterm_symbolic._kore_client.add_module(_kore_module, name_as_id=True)
main_module_name = self.extra_module.name

def _inject_module(module_name: str, import_name: str, sentences: list[KRule]) -> None:
_module = KFlatModule(module_name, sentences, [KImport(import_name)])
_kore_module = kflatmodule_to_kore(self.kcfg_explore.cterm_symbolic._definition, _module)
Expand All @@ -759,7 +769,7 @@ def _inject_module(module_name: str, import_name: str, sentences: list[KRule]) -
]
circularity_rule = proof.as_rule(priority=20)

_inject_module(proof.dependencies_module_name, self.main_module_name, dependencies_as_rules)
_inject_module(proof.dependencies_module_name, main_module_name, dependencies_as_rules)
_inject_module(proof.circularities_module_name, proof.dependencies_module_name, [circularity_rule])

for node_id in [proof.init, proof.target]:
Expand Down
Loading
Loading