Skip to content

Commit ebf20d1

Browse files
Stevengrejbertholddkcumming
committed
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 0c187b7 commit ebf20d1

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
@@ -1433,6 +1433,94 @@ The first cast is reified as a `thunk`, the second one resolves it and eliminate
14331433
andBool lookupTy(TY_DEST_INNER) ==K lookupTy(TY_SRC_OUTER) // and is well-formed (invariant)
14341434
```
14351435

1436+
Casting a byte array/slice to an integer reinterprets the bytes in little-endian order.
1437+
1438+
```k
1439+
rule <k> #cast(
1440+
Range(ELEMS),
1441+
castKindTransmute,
1442+
_TY_SOURCE,
1443+
TY_TARGET
1444+
)
1445+
=>
1446+
#intAsType(
1447+
#littleEndianFromBytes(ELEMS),
1448+
size(ELEMS) *Int 8,
1449+
#numTypeOf(lookupTy(TY_TARGET))
1450+
)
1451+
...
1452+
</k>
1453+
requires #isIntType(lookupTy(TY_TARGET))
1454+
andBool size(ELEMS) *Int 8 ==Int #bitWidth(#numTypeOf(lookupTy(TY_TARGET)))
1455+
andBool #areLittleEndianBytes(ELEMS)
1456+
[preserves-definedness] // ensures #numTypeOf is defined
1457+
1458+
syntax Bool ::= #areLittleEndianBytes ( List ) [function, total]
1459+
// -------------------------------------------------------------
1460+
rule #areLittleEndianBytes(.List) => true
1461+
rule #areLittleEndianBytes(ListItem(Integer(_, 8, false)) REST)
1462+
=> #areLittleEndianBytes(REST)
1463+
rule #areLittleEndianBytes(ListItem(_OTHER) _) => false [owise]
1464+
1465+
syntax Int ::= #littleEndianFromBytes ( List ) [function]
1466+
// -----------------------------------------------------
1467+
rule #littleEndianFromBytes(.List) => 0
1468+
rule #littleEndianFromBytes(ListItem(Integer(BYTE, 8, false)) REST)
1469+
=> BYTE +Int 256 *Int #littleEndianFromBytes(REST)
1470+
```
1471+
1472+
Casting an integer to a `[u8; N]` array materialises its little-endian bytes.
1473+
1474+
```k
1475+
rule <k> #cast(
1476+
Integer(VAL, WIDTH, _SIGNEDNESS),
1477+
castKindTransmute,
1478+
_TY_SOURCE,
1479+
TY_TARGET
1480+
)
1481+
=>
1482+
Range(#littleEndianBytesFromInt(VAL, WIDTH))
1483+
...
1484+
</k>
1485+
requires #isStaticU8Array(lookupTy(TY_TARGET))
1486+
andBool WIDTH >=Int 0
1487+
andBool WIDTH %Int 8 ==Int 0
1488+
andBool WIDTH ==Int #staticArrayLenBits(lookupTy(TY_TARGET))
1489+
[preserves-definedness] // ensures element type/length are well-formed
1490+
1491+
syntax List ::= #littleEndianBytesFromInt ( Int, Int ) [function]
1492+
// -------------------------------------------------------------
1493+
rule #littleEndianBytesFromInt(VAL, WIDTH)
1494+
=> #littleEndianBytes(truncate(VAL, WIDTH, Unsigned), WIDTH /Int 8)
1495+
requires WIDTH %Int 8 ==Int 0
1496+
andBool WIDTH >=Int 0
1497+
[preserves-definedness]
1498+
1499+
syntax List ::= #littleEndianBytes ( Int, Int ) [function]
1500+
// -------------------------------------------------------------
1501+
rule #littleEndianBytes(_, COUNT)
1502+
=> .List
1503+
requires COUNT <=Int 0
1504+
1505+
rule #littleEndianBytes(VAL, COUNT)
1506+
=> ListItem(Integer(VAL %Int 256, 8, false)) #littleEndianBytes(VAL /Int 256, COUNT -Int 1)
1507+
requires COUNT >Int 0
1508+
[preserves-definedness]
1509+
1510+
syntax Bool ::= #isStaticU8Array ( TypeInfo ) [function, total]
1511+
// -------------------------------------------------------------
1512+
rule #isStaticU8Array(typeInfoArrayType(ELEM_TY, someTyConst(_)))
1513+
=> lookupTy(ELEM_TY) ==K typeInfoPrimitiveType(primTypeUint(uintTyU8))
1514+
rule #isStaticU8Array(_OTHER) => false [owise]
1515+
1516+
syntax Int ::= #staticArrayLenBits ( TypeInfo ) [function, total]
1517+
// -------------------------------------------------------------
1518+
rule #staticArrayLenBits(typeInfoArrayType(_, someTyConst(tyConst(KIND, _))))
1519+
=> readTyConstInt(KIND) *Int 8
1520+
[preserves-definedness]
1521+
rule #staticArrayLenBits(_OTHER) => 0 [owise]
1522+
```
1523+
14361524
Another specialisation is getting the discriminant of `enum`s without fields after converting some integer data to it
14371525
(see `#discriminant` and `rvalueDiscriminant`).
14381526
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)