Skip to content

Commit 7b09892

Browse files
Stevengrejbertholddkcumming
authored
feat(rt): support round-trip transmute bytes <> u64 casts (#804)
- close #697 --------- Co-authored-by: Jost Berthold <jost.berthold@gmail.com> Co-authored-by: Daniel Cumming <124537596+dkcumming@users.noreply.github.com>
1 parent 1f5836c commit 7b09892

File tree

4 files changed

+134
-0
lines changed

4 files changed

+134
-0
lines changed

kmir/src/kmir/kdist/mir-semantics/lemmas/kmir-lemmas.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,20 @@ power of two but the semantics will always operate with these particular ones.
118118
rule VAL &Int bitmask128 => VAL requires 0 <=Int VAL andBool VAL <=Int bitmask128 [simplification, preserves-definedness, smt-lemma]
119119
```
120120

121+
```k
122+
rule (VAL +Int 256 *Int REST) %Int 256 => VAL
123+
requires 0 <=Int VAL
124+
andBool VAL <=Int 255
125+
andBool 0 <=Int REST
126+
[simplification, preserves-definedness]
127+
128+
rule (VAL +Int 256 *Int REST) /Int 256 => REST
129+
requires 0 <=Int VAL
130+
andBool VAL <=Int 255
131+
andBool 0 <=Int REST
132+
[simplification, preserves-definedness]
133+
```
134+
121135

122136
```k
123137
endmodule

kmir/src/kmir/kdist/mir-semantics/rt/data.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,6 +1414,94 @@ The first cast is reified as a `thunk`, the second one resolves it and eliminate
14141414
andBool lookupTy(TY_DEST_INNER) ==K lookupTy(TY_SRC_OUTER) // and is well-formed (invariant)
14151415
```
14161416

1417+
Casting a byte array/slice to an integer reinterprets the bytes in little-endian order.
1418+
1419+
```k
1420+
rule <k> #cast(
1421+
Range(ELEMS),
1422+
castKindTransmute,
1423+
_TY_SOURCE,
1424+
TY_TARGET
1425+
)
1426+
=>
1427+
#intAsType(
1428+
#littleEndianFromBytes(ELEMS),
1429+
size(ELEMS) *Int 8,
1430+
#numTypeOf(lookupTy(TY_TARGET))
1431+
)
1432+
...
1433+
</k>
1434+
requires #isIntType(lookupTy(TY_TARGET))
1435+
andBool size(ELEMS) *Int 8 ==Int #bitWidth(#numTypeOf(lookupTy(TY_TARGET)))
1436+
andBool #areLittleEndianBytes(ELEMS)
1437+
[preserves-definedness] // ensures #numTypeOf is defined
1438+
1439+
syntax Bool ::= #areLittleEndianBytes ( List ) [function, total]
1440+
// -------------------------------------------------------------
1441+
rule #areLittleEndianBytes(.List) => true
1442+
rule #areLittleEndianBytes(ListItem(Integer(_, 8, false)) REST)
1443+
=> #areLittleEndianBytes(REST)
1444+
rule #areLittleEndianBytes(ListItem(_OTHER) _) => false [owise]
1445+
1446+
syntax Int ::= #littleEndianFromBytes ( List ) [function]
1447+
// -----------------------------------------------------
1448+
rule #littleEndianFromBytes(.List) => 0
1449+
rule #littleEndianFromBytes(ListItem(Integer(BYTE, 8, false)) REST)
1450+
=> BYTE +Int 256 *Int #littleEndianFromBytes(REST)
1451+
```
1452+
1453+
Casting an integer to a `[u8; N]` array materialises its little-endian bytes.
1454+
1455+
```k
1456+
rule <k> #cast(
1457+
Integer(VAL, WIDTH, _SIGNEDNESS),
1458+
castKindTransmute,
1459+
_TY_SOURCE,
1460+
TY_TARGET
1461+
)
1462+
=>
1463+
Range(#littleEndianBytesFromInt(VAL, WIDTH))
1464+
...
1465+
</k>
1466+
requires #isStaticU8Array(lookupTy(TY_TARGET))
1467+
andBool WIDTH >=Int 0
1468+
andBool WIDTH %Int 8 ==Int 0
1469+
andBool WIDTH ==Int #staticArrayLenBits(lookupTy(TY_TARGET))
1470+
[preserves-definedness] // ensures element type/length are well-formed
1471+
1472+
syntax List ::= #littleEndianBytesFromInt ( Int, Int ) [function]
1473+
// -------------------------------------------------------------
1474+
rule #littleEndianBytesFromInt(VAL, WIDTH)
1475+
=> #littleEndianBytes(truncate(VAL, WIDTH, Unsigned), WIDTH /Int 8)
1476+
requires WIDTH %Int 8 ==Int 0
1477+
andBool WIDTH >=Int 0
1478+
[preserves-definedness]
1479+
1480+
syntax List ::= #littleEndianBytes ( Int, Int ) [function]
1481+
// -------------------------------------------------------------
1482+
rule #littleEndianBytes(_, COUNT)
1483+
=> .List
1484+
requires COUNT <=Int 0
1485+
1486+
rule #littleEndianBytes(VAL, COUNT)
1487+
=> ListItem(Integer(VAL %Int 256, 8, false)) #littleEndianBytes(VAL /Int 256, COUNT -Int 1)
1488+
requires COUNT >Int 0
1489+
[preserves-definedness]
1490+
1491+
syntax Bool ::= #isStaticU8Array ( TypeInfo ) [function, total]
1492+
// -------------------------------------------------------------
1493+
rule #isStaticU8Array(typeInfoArrayType(ELEM_TY, someTyConst(_)))
1494+
=> lookupTy(ELEM_TY) ==K typeInfoPrimitiveType(primTypeUint(uintTyU8))
1495+
rule #isStaticU8Array(_OTHER) => false [owise]
1496+
1497+
syntax Int ::= #staticArrayLenBits ( TypeInfo ) [function, total]
1498+
// -------------------------------------------------------------
1499+
rule #staticArrayLenBits(typeInfoArrayType(_, someTyConst(tyConst(KIND, _))))
1500+
=> readTyConstInt(KIND) *Int 8
1501+
[preserves-definedness]
1502+
rule #staticArrayLenBits(_OTHER) => 0 [owise]
1503+
```
1504+
14171505
Another specialisation is getting the discriminant of `enum`s without fields after converting some integer data to it
14181506
(see `#discriminant` and `rvalueDiscriminant`).
14191507
If none of the `enum` variants has any fields, the `Transmute` of a number to the `enum` data is necessarily just the discriminant itself., and can be returned as the integer value afgter adjusting to the byte length of the discriminant:
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#![allow(clippy::transmute_bytes_to_bytes)]
2+
#![allow(clippy::unnecessary_transmute)]
3+
4+
use std::mem::transmute;
5+
6+
fn bytes_to_u64(bytes: [u8; 8]) -> u64 {
7+
let value = unsafe { transmute::<[u8; 8], u64>(bytes) };
8+
let roundtrip = unsafe { transmute::<u64, [u8; 8]>(value) };
9+
assert_eq!(roundtrip, bytes);
10+
value
11+
}
12+
13+
fn u64_to_bytes(value: u64) -> [u8; 8] {
14+
let bytes = unsafe { transmute::<u64, [u8; 8]>(value) };
15+
let roundtrip = unsafe { transmute::<[u8; 8], u64>(bytes) };
16+
assert_eq!(roundtrip, value);
17+
bytes
18+
}
19+
20+
fn main() {
21+
let bytes = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF];
22+
let int = bytes_to_u64(bytes);
23+
assert_eq!(int, 0xEFCD_AB89_6745_2301);
24+
25+
let roundtrip = u64_to_bytes(int);
26+
assert_eq!(roundtrip, bytes);
27+
28+
let value = 0x1020_3040_5060_7080_u64;
29+
let value_roundtrip = bytes_to_u64(u64_to_bytes(value));
30+
assert_eq!(value_roundtrip, value);
31+
}

kmir/src/tests/integration/test_integration.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
'pointer-cast-length-test-fail': ['array_cast_test'],
3737
'assume-cheatcode': ['check_assume'],
3838
'assume-cheatcode-conflict-fail': ['check_assume_conflict'],
39+
'transmute-bytes': ['bytes_to_u64', 'u64_to_bytes'],
3940
}
4041
PROVE_RS_SHOW_SPECS = [
4142
'local-raw-fail',

0 commit comments

Comments
 (0)