Skip to content

Commit ec95dd6

Browse files
Stevengrerv-auditortothtamas28
authored
Better loadBytes for symbolic execution (#91)
This PR is introduced to simplify the process of loading multiple bytes, simplifying the result of loadbytes for symbolic execution. ------ Co-authored-by: devops <devops@runtimeverification.com> Co-authored-by: Tamás Tóth <tothtamas28@users.noreply.github.com>
1 parent 3a2bbc0 commit ec95dd6

9 files changed

Lines changed: 43 additions & 60 deletions

File tree

package/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.69
1+
0.1.70

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.69"
7+
version = "0.1.70"
88
description = "K tooling for the RISC-V architecture"
99
authors = [
1010
"Runtime Verification, Inc. <contact@runtimeverification.com>",

src/kriscv/kdist/riscv-semantics/lemmas/sparse-bytes-simplifications.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,20 @@ module SPARSE-BYTES-SIMPLIFICATIONS
1010
imports SPARSE-BYTES
1111
```
1212

13-
## readByteBF
13+
## pickFront and dropFront
1414

15-
For symbolic execution, we need to tackle the patterns of `#bytes(B +Bytes _) _` and `#bytes(B +Bytes BS) EF` to obtain as exact as possible values for `readByte`.
15+
For symbolic execution, we need to tackle the patterns of `#bytes(B +Bytes _) _` and `#bytes(B +Bytes BS) EF` to obtain as exact as possible values for `pickFront`.
1616

1717
```k
18-
rule readByteBF(#bytes(B +Bytes _) _ , I) => B[ I ]
19-
requires I >=Int 0 andBool I <Int lengthBytes(B) [simplification(45)]
20-
rule readByteBF(#bytes(B +Bytes BS) EF , I) => readByteBF(#bytes(BS) EF , I -Int lengthBytes(B))
21-
requires I >=Int lengthBytes(B) [simplification(45)]
18+
rule pickFront(#bytes(B +Bytes _) _ , I) => substrBytes(B, 0, I)
19+
requires I >Int 0 andBool I <=Int lengthBytes(B) [simplification(45)]
20+
rule pickFront(#bytes(B +Bytes BS) EF, I) => B +Bytes pickFront(#bytes(BS) EF, I -Int lengthBytes(B))
21+
requires I >Int lengthBytes(B) [simplification(45)]
22+
23+
rule dropFront(#bytes(B +Bytes BS) EF , I) => dropFront(#bytes(substrBytes(B, I, lengthBytes(B)) +Bytes BS) EF, 0)
24+
requires I >Int 0 andBool I <Int lengthBytes(B) [simplification(45)]
25+
rule dropFront(#bytes(B +Bytes BS) EF, I) => dropFront(#bytes(BS) EF, I -Int lengthBytes(B))
26+
requires I >=Int lengthBytes(B) [simplification(45)]
2227
```
2328

2429

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

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -99,32 +99,22 @@ module RISCV-MEMORY
9999
100100
syntax Memory = SparseBytes
101101
```
102-
We abstract the particular memory representation behind `loadBytes` and `storeBytes` functions.
102+
We abstract the particular memory representation behind `loadBytes` and `storeBytes` functions. For multi-byte loads and stores, we presume a little-endian architecture.
103103
```k
104-
syntax Int ::= loadByte(memory: Memory, address: Word) [function, symbol(Memory:loadByte)]
105-
rule loadByte(MEM, W(ADDR)) => MaybeByte2Int(readByte(MEM, ADDR))
106-
```
107-
For multi-byte loads and stores, we presume a little-endian architecture.
108-
```k
109-
syntax Int ::= loadBytes(memory: Memory, address: Word, numBytes: Int) [function]
110-
rule loadBytes(MEM, ADDR, 1 ) => loadByte(MEM, ADDR)
111-
rule loadBytes(MEM, ADDR, NUM) => (loadBytes(MEM, ADDR +Word W(1), NUM -Int 1) <<Int 8) |Int loadByte(MEM, ADDR) requires NUM >Int 1
104+
syntax Int ::= loadBytes(memory: Memory, address: Word, numBytes: Int) [function, total, symbol(Memory:loadBytes)]
105+
rule loadBytes(MEM, W(ADDR), NUM) => readBytes(MEM, ADDR, NUM)
112106
113107
syntax Memory ::= storeBytes(memory: Memory, address: Word, bytes: Int, numBytes: Int) [function, total, symbol(Memory:storeBytes)]
114108
rule storeBytes(MEM, W(ADDR), BS, NUM) => writeBytes(MEM, ADDR, BS, NUM)
115109
```
116110
Instructions are always 32-bits, and are stored in little-endian format regardless of the endianness of the overall architecture.
117111
```k
118-
syntax Instruction ::= fetchInstr(memory: Memory, address: Word) [function]
119-
rule fetchInstr(MEM, ADDR) =>
120-
disassemble((loadByte(MEM, ADDR +Word W(3)) <<Int 24) |Int
121-
(loadByte(MEM, ADDR +Word W(2)) <<Int 16) |Int
122-
(loadByte(MEM, ADDR +Word W(1)) <<Int 8 ) |Int
123-
loadByte(MEM, ADDR ))
112+
syntax Instruction ::= fetchInstr(memory: Memory, address: Word) [function, total]
113+
rule fetchInstr(MEM, ADDR) => disassemble(loadBytes(MEM, ADDR, 4))
124114
```
125115
Registers should be manipulated with the `writeReg` and `readReg` functions, which account for `x0` always being hard-wired to contain all `0`s.
126116
```k
127-
syntax Map ::= writeReg(regs: Map, rd: Int, value: Word) [function]
117+
syntax Map ::= writeReg(regs: Map, rd: Int, value: Word) [function, total]
128118
rule writeReg(REGS, 0 , _ ) => REGS
129119
rule writeReg(REGS, RD, VAL) => REGS[RD <- VAL] [owise]
130120
@@ -323,7 +313,7 @@ The remaining branch instructions proceed analogously, but performing different
323313
`LB`, `LH`, and `LW` load `1`, `2`, and `4` bytes respectively from the memory address which is `OFFSET` greater than the value in register `RS1`, then sign extends the loaded bytes and places them in register `RD`.
324314
```k
325315
rule <instrs> LB RD , OFFSET ( RS1 ) => .K ...</instrs>
326-
<regs> REGS => writeReg(REGS, RD, signExtend(loadByte(MEM, readReg(REGS, RS1) +Word chop(OFFSET)), 8)) </regs>
316+
<regs> REGS => writeReg(REGS, RD, signExtend(loadBytes(MEM, readReg(REGS, RS1) +Word chop(OFFSET), 1), 8)) </regs>
327317
<mem> MEM </mem>
328318
329319
rule <instrs> LH RD , OFFSET ( RS1 ) => .K ...</instrs>
@@ -337,7 +327,7 @@ The remaining branch instructions proceed analogously, but performing different
337327
`LBU` and `LHU` are analogous to `LB` and `LH`, but zero-extending rather than sign-extending.
338328
```k
339329
rule <instrs> LBU RD , OFFSET ( RS1 ) => .K ...</instrs>
340-
<regs> REGS => writeReg(REGS, RD, W(loadByte(MEM, readReg(REGS, RS1) +Word chop(OFFSET)))) </regs>
330+
<regs> REGS => writeReg(REGS, RD, W(loadBytes(MEM, readReg(REGS, RS1) +Word chop(OFFSET), 1))) </regs>
341331
<mem> MEM </mem>
342332
343333
rule <instrs> LHU RD , OFFSET ( RS1 ) => .K ...</instrs>

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

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -47,45 +47,36 @@ We provide helpers to prepend either data or an empty region to an existing `Spa
4747
rule prependEmpty(I, #empty(N) BF ) => #empty(I +Int N) BF requires I >Int 0
4848
rule prependEmpty(I, BF:SparseBytesBF) => #empty(I) BF requires I >Int 0
4949
```
50+
`pickFront` is a helper function for picking the first `N` bytes from a `SparseBytes` value.
51+
```k
52+
syntax Bytes ::= pickFront(SparseBytes, Int) [function, total]
53+
rule pickFront(_, I) => .Bytes requires I <=Int 0
54+
rule pickFront(.SparseBytes, I) => .Bytes requires I >Int 0
55+
rule pickFront(#empty(N) _, I) => padRightBytes(.Bytes, I, 0)
56+
requires I >Int 0 andBool I <=Int N
57+
rule pickFront(#empty(N) BF, I) => padRightBytes(.Bytes, I, 0) +Bytes pickFront(BF, I -Int N)
58+
requires I >Int 0 andBool I >Int N
59+
rule pickFront(#bytes(B) _, I) => substrBytes(B, 0, I)
60+
requires I >Int 0 andBool I <=Int lengthBytes(B)
61+
rule pickFront(#bytes(B) EF, I) => B +Bytes pickFront(EF, I -Int lengthBytes(B))
62+
requires I >Int lengthBytes(B)
63+
```
5064
`dropFront` is a helper function for dropping the first `N` bytes from a `SparseBytes` value.
5165
```k
5266
syntax SparseBytes ::= dropFront(SparseBytes, Int) [function, total]
5367
rule dropFront(SBS, I) => SBS requires I <=Int 0
5468
rule dropFront(.SparseBytes, I) => .SparseBytes requires I >Int 0
5569
rule dropFront(#empty(N) BF, I) => #empty(N -Int I) BF requires I >Int 0 andBool I <Int N
56-
rule dropFront(#empty(N) BF, I) => dropFront(BF, I -Int N) requires I >Int 0 andBool I >=Int N
70+
rule dropFront(#empty(N) BF, I) => dropFront(BF, I -Int N) requires I >Int 0 andBool I >=Int N
5771
rule dropFront(#bytes(B) EF, I) => dropFront(#bytes(substrBytes(B, I, lengthBytes(B))) EF, 0)
5872
requires I >Int 0 andBool I <Int lengthBytes(B)
5973
rule dropFront(#bytes(B) EF, I) => dropFront(EF, I -Int lengthBytes(B)) requires I >=Int lengthBytes(B)
6074
```
61-
`readByte` reads a single byte from a given index in `O(E)` time, where `E` is the number of `#empty(_)` or `#bytes(_)` entries in the list up to the location of the index. The result is either
62-
- an `Int` in the range `[0, 255)` giving the byte value at the index, or
63-
- `.Byte` if the index does not point to initialized data
75+
`readBytes(SBS, I, NUM)` reads `NUM` bytes from a given index `I` in `O(E)` time, where `E` is the number of `#empty(_)` or `#bytes(_)` entries in the list up to the location of the index.
6476
```k
65-
syntax MaybeByte ::=
66-
Int
67-
| ".Byte"
68-
69-
syntax Int ::= MaybeByte2Int(MaybeByte) [function, total]
70-
rule MaybeByte2Int(I:Int) => I
71-
rule MaybeByte2Int(.Byte) => 0
72-
73-
syntax MaybeByte ::=
74-
readByte (SparseBytes , Int) [function, total]
75-
| readByteEF(SparseBytesEF, Int) [function, total]
76-
| readByteBF(SparseBytesBF, Int) [function, total]
77-
78-
rule readByte(EF:SparseBytesEF, I) => readByteEF(EF, I)
79-
rule readByte(BF:SparseBytesBF, I) => readByteBF(BF, I)
80-
81-
rule readByteEF(.SparseBytes, _) => .Byte
82-
rule readByteEF(#empty(N) _ , I) => .Byte requires I <Int N
83-
rule readByteEF(#empty(N) BF, I) => readByteBF(BF, I -Int N) requires I >=Int N
77+
syntax Int ::= readBytes(SparseBytes, Int, Int) [function, total]
8478
85-
rule readByteBF(#bytes(_) _ , I) => .Byte requires I <Int 0 // error case for totality
86-
rule readByteBF(#bytes(B) _ , I) => B[ I ] requires I >=Int 0 andBool I <Int lengthBytes(B)
87-
rule readByteBF(#bytes(B) EF, I) => readByteEF(EF, I -Int lengthBytes(B))
88-
requires I >=Int lengthBytes(B)
79+
rule readBytes(SBS, I, NUM) => Bytes2Int(pickFront(dropFront(SBS, I), NUM), LE, Unsigned)
8980
```
9081
`writeBytes(SBS, I, V, NUM)` writes value `V` with length `NUM` bytes to a given index `I`. With regards to time complexity,
9182
- If the index is in the middle of an existing `#empty(_)` or `#bytes(_)` region, time complexity is `O(E)` where `E` is the number of entries up to the index.

src/kriscv/term_builder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ def sort_memory() -> KSort:
314314
return KSort('SparseBytes')
315315

316316

317-
def load_byte(mem: KInner, addr: KInner) -> KInner:
318-
return KApply('Memory:loadByte', mem, addr)
317+
def load_bytes(mem: KInner, addr: KInner, num_bytes: KInner) -> KInner:
318+
return KApply('Memory:loadBytes', mem, addr, num_bytes)
319319

320320

321321
def store_bytes(mem: KInner, addr: KInner, value: KInner, num_bytes: KInner) -> KInner:

src/tests/integration/test-data/specs/lw-spec.k

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module LW-SPEC
77
<instrs> (.K => #HALT) ~> #EXECUTE ... </instrs>
88
<regs>
99
1 |-> (W(16) => W(1))
10-
2 |-> (W(24) => signExtend (Y[3] <<Int 8 |Int Y[2] <<Int 8 |Int Y[1] <<Int 8 |Int Y[0], 32 ))
10+
2 |-> (W(24) => signExtend ( ... bits: Bytes2Int ( substrBytes ( Y:Bytes , 0 , 4 ) , LE , Unsigned ) , signBitIdx: 32 ))
1111
3 |-> (W(28) => W(2))
1212
</regs>
1313
<pc> W ( 0 => 12 ) </pc>

src/tests/integration/test_prove.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,6 @@ def test_specs(
5353
temp_dir: Path,
5454
spec_file: Path,
5555
) -> None:
56-
if spec_file.name == 'lw-spec.k':
57-
pytest.skip('Skipping lw-spec.k due to the weird implies failed')
58-
5956
# Given
6057
spec_file = load_spec(spec_file.name)
6158

src/tests/unit/test_unit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ def test_memory(memory: dict[int, bytes], addr: int, byte: int) -> None:
312312

313313
# Also execute loadByte and check that we correctly read back the written value
314314
memory_actual_sb = tools.krun.kore_to_kast(memory_actual_sb_kore)
315-
load_call = term_builder.load_byte(memory_actual_sb, addr_word)
315+
load_call = term_builder.load_bytes(memory_actual_sb, addr_word, intToken(1))
316316
load_actual = kore_int(_eval_call_to_kore(tools, load_call, INT))
317317

318318
assert load_actual == byte

0 commit comments

Comments
 (0)