Skip to content

Commit ae15f5d

Browse files
tothtamas28Copilotrv-auditor
authored
Add symbolic execution support (#67)
Co-authored-by: Copilot <[email protected]> Co-authored-by: devops <[email protected]>
1 parent 344cc7e commit ae15f5d

File tree

8 files changed

+214
-6
lines changed

8 files changed

+214
-6
lines changed

package/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.53
1+
0.1.54

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 = "kriscv"
7-
version = "0.1.53"
7+
version = "0.1.54"
88
description = "K tooling for the RISC-V architecture"
99
authors = [
1010
"Runtime Verification, Inc. <[email protected]>",

src/kriscv/kdist/plugin.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from pyk.kbuild.utils import k_version
99
from pyk.kdist.api import Target
1010
from pyk.kllvm.compiler import compile_kllvm, compile_runtime
11-
from pyk.ktool.kompile import kompile
11+
from pyk.ktool.kompile import LLVMKompileType, PykBackend, kompile
1212

1313
if TYPE_CHECKING:
1414
from collections.abc import Callable, Mapping
@@ -84,6 +84,26 @@ def context(self) -> dict[str, str]:
8484
'warnings_to_errors': True,
8585
},
8686
),
87+
'llvm-lib': KompileTarget(
88+
lambda src_dir: {
89+
'main_file': src_dir / 'riscv-semantics/riscv.md',
90+
'include_dirs': [src_dir],
91+
'syntax_module': 'RISCV',
92+
'llvm_kompile_type': LLVMKompileType.C,
93+
'md_selector': 'k',
94+
'warnings_to_errors': True,
95+
},
96+
),
97+
'haskell': KompileTarget(
98+
lambda src_dir: {
99+
'main_file': src_dir / 'riscv-semantics/riscv.md',
100+
'backend': PykBackend.HASKELL,
101+
'include_dirs': [src_dir],
102+
'syntax_module': 'RISCV',
103+
'md_selector': 'k',
104+
'warnings_to_errors': True,
105+
},
106+
),
87107
'func-test': KompileTarget(
88108
lambda src_dir: {
89109
'main_file': src_dir / 'riscv-semantics/func-test.md',

src/kriscv/kdist/riscv-semantics/riscv.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ As we do not model the surrounding environment, for testing purposes we add our
5050
This is done with three components:
5151
- A `HaltCondition` value stored in the configuation indicating under which conditions we should halt.
5252
- A `#CHECK_HALT` operation indicating that the halt condition should be checked.
53-
- A `#HALT` operation which terminates the simulation by consuming all following operations in the pipeline without executing them.
53+
- A `#HALT` operation which terminates the simulation by not matching any semantic rule.
5454
```k
5555
module RISCV-TERMINATION
5656
imports RISCV-CONFIGURATION
@@ -61,8 +61,6 @@ module RISCV-TERMINATION
6161
"#HALT"
6262
| "#CHECK_HALT"
6363
64-
rule <instrs> #HALT ~> (_ => .K) ...</instrs>
65-
6664
syntax HaltCondition ::=
6765
"NEVER" [symbol(HaltNever)]
6866
| "ADDRESS" "(" Word ")" [symbol(HaltAtAddress)]

src/kriscv/symtools.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
from __future__ import annotations
2+
3+
from contextlib import contextmanager
4+
from dataclasses import dataclass
5+
from functools import cached_property
6+
from pathlib import Path
7+
from typing import TYPE_CHECKING
8+
9+
from pyk.cterm.symbolic import CTermSymbolic
10+
from pyk.kcfg.explore import KCFGExplore
11+
from pyk.ktool.kprove import KProve
12+
from pyk.proof.reachability import APRProof
13+
14+
if TYPE_CHECKING:
15+
from collections.abc import Iterable, Iterator
16+
17+
18+
@dataclass
19+
class SymTools:
20+
haskell_dir: Path
21+
llvm_lib_dir: Path
22+
proof_dir: Path
23+
source_dirs: tuple[Path, ...]
24+
25+
@staticmethod
26+
def default(*, proof_dir: Path) -> SymTools:
27+
from pyk.kdist import kdist
28+
29+
return SymTools(
30+
haskell_dir=kdist.get('riscv-semantics.haskell'),
31+
llvm_lib_dir=kdist.get('riscv-semantics.llvm-lib'),
32+
source_dirs=(kdist.get('riscv-semantics.source'),),
33+
proof_dir=proof_dir,
34+
)
35+
36+
@cached_property
37+
def kprove(self) -> KProve:
38+
return KProve(definition_dir=self.haskell_dir, use_directory=self.proof_dir)
39+
40+
@contextmanager
41+
def explore(self, *, id: str) -> Iterator[KCFGExplore]:
42+
from pyk.kore.rpc import BoosterServer, KoreClient
43+
44+
with BoosterServer(
45+
{
46+
'kompiled_dir': self.haskell_dir,
47+
'llvm_kompiled_dir': self.llvm_lib_dir,
48+
'module_name': self.kprove.main_module,
49+
}
50+
) as server:
51+
with KoreClient('localhost', server.port) as client:
52+
cterm_symbolic = CTermSymbolic(
53+
kore_client=client,
54+
definition=self.kprove.definition,
55+
)
56+
yield KCFGExplore(
57+
id=id,
58+
cterm_symbolic=cterm_symbolic,
59+
)
60+
61+
def prove(
62+
self,
63+
*,
64+
spec_file: str | Path,
65+
spec_module: str,
66+
claim_id: str,
67+
reinit: bool | None = None,
68+
max_depth: int | None = None,
69+
max_iterations: int | None = None,
70+
includes: Iterable[str | Path] | None = None,
71+
) -> APRProof:
72+
from pyk.ktool.claim_loader import ClaimLoader
73+
from pyk.proof.reachability import APRProver
74+
75+
spec_file = Path(spec_file)
76+
include_dirs = self.source_dirs + (tuple(Path(include) for include in includes) if includes is not None else ())
77+
78+
claims = ClaimLoader(self.kprove).load_claims(
79+
spec_file=spec_file,
80+
spec_module_name=spec_module,
81+
claim_labels=[claim_id],
82+
include_dirs=include_dirs,
83+
)
84+
(claim,) = claims
85+
spec_label = f'{spec_module}.{claim_id}'
86+
87+
if not reinit and APRProof.proof_data_exists(spec_label, self.proof_dir):
88+
# load an existing proof (to continue work on it)
89+
proof = APRProof.read_proof_data(proof_dir=self.proof_dir, id=f'{spec_module}.{claim_id}')
90+
else:
91+
# ignore existing proof data and reinitialize it from a claim
92+
proof = APRProof.from_claim(self.kprove.definition, claim=claim, logs={}, proof_dir=self.proof_dir)
93+
94+
with self.explore(id=spec_label) as kcfg_explore:
95+
prover = APRProver(
96+
kcfg_explore=kcfg_explore,
97+
execute_depth=max_depth,
98+
)
99+
prover.advance_proof(proof, max_iterations=max_iterations)
100+
101+
return proof
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
requires "riscv-semantics/riscv.md"
2+
3+
module ADD-SPEC
4+
imports RISCV
5+
6+
claim [add]:
7+
<instrs> (.K => #HALT) ~> #EXECUTE ... </instrs>
8+
<regs>
9+
1 |-> X
10+
2 |-> Y
11+
3 |-> (_ => Y +Word X)
12+
</regs>
13+
<pc> W ( 0 => 4 ) </pc>
14+
<mem>
15+
#bytes ( b"\xb3\x01\x11\x00" ) // add x3, x2, x1
16+
.SparseBytes
17+
</mem>
18+
<test>
19+
<haltCond> ADDRESS ( W ( 4 ) ) </haltCond>
20+
</test>
21+
endmodule

src/tests/integration/test_prove.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
from typing import TYPE_CHECKING
5+
6+
import pytest
7+
from pyk.proof import ProofStatus
8+
9+
from kriscv.symtools import SymTools
10+
11+
from .utils import TEST_DATA_DIR
12+
13+
if TYPE_CHECKING:
14+
from pathlib import Path
15+
from typing import Final
16+
17+
18+
SPEC_DIR: Final = TEST_DATA_DIR / 'specs'
19+
20+
21+
@dataclass
22+
class SpecLoader:
23+
temp_dir: Path
24+
25+
def __call__(self, file_name: str) -> Path:
26+
import shutil
27+
28+
res = self.temp_dir / file_name
29+
shutil.copy(SPEC_DIR / file_name, res)
30+
return res
31+
32+
33+
@pytest.fixture
34+
def load_spec(tmp_path: Path) -> SpecLoader:
35+
return SpecLoader(temp_dir=tmp_path)
36+
37+
38+
@pytest.fixture
39+
def symtools(tmp_path: Path) -> SymTools:
40+
return SymTools.default(proof_dir=tmp_path)
41+
42+
43+
def test_add(
44+
load_spec: SpecLoader,
45+
symtools: SymTools,
46+
) -> None:
47+
# Given
48+
spec_file = load_spec('add-spec.k')
49+
50+
# When
51+
proof = symtools.prove(
52+
spec_file=spec_file,
53+
spec_module='ADD-SPEC',
54+
claim_id='ADD-SPEC.add',
55+
)
56+
57+
# Then
58+
assert proof.status == ProofStatus.PASSED

src/tests/integration/utils.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
from typing import TYPE_CHECKING
5+
6+
if TYPE_CHECKING:
7+
from typing import Final
8+
9+
10+
TEST_DATA_DIR: Final = Path(__file__).parent / 'test-data'

0 commit comments

Comments
 (0)