diff --git a/kmir/src/kmir/kdist/mir-semantics/lemmas/kmir-lemmas.md b/kmir/src/kmir/kdist/mir-semantics/lemmas/kmir-lemmas.md index e0f2c8d00..0c9ca8957 100644 --- a/kmir/src/kmir/kdist/mir-semantics/lemmas/kmir-lemmas.md +++ b/kmir/src/kmir/kdist/mir-semantics/lemmas/kmir-lemmas.md @@ -118,6 +118,20 @@ power of two but the semantics will always operate with these particular ones. rule VAL &Int bitmask128 => VAL requires 0 <=Int VAL andBool VAL <=Int bitmask128 [simplification, preserves-definedness, smt-lemma] ``` +```k + rule (VAL +Int 256 *Int REST) %Int 256 => VAL + requires 0 <=Int VAL + andBool VAL <=Int 255 + andBool 0 <=Int REST + [simplification, preserves-definedness] + + rule (VAL +Int 256 *Int REST) /Int 256 => REST + requires 0 <=Int VAL + andBool VAL <=Int 255 + andBool 0 <=Int REST + [simplification, preserves-definedness] +``` + ```k endmodule diff --git a/kmir/src/kmir/kdist/mir-semantics/rt/data.md b/kmir/src/kmir/kdist/mir-semantics/rt/data.md index b6170942f..071cbd665 100644 --- a/kmir/src/kmir/kdist/mir-semantics/rt/data.md +++ b/kmir/src/kmir/kdist/mir-semantics/rt/data.md @@ -1414,6 +1414,94 @@ The first cast is reified as a `thunk`, the second one resolves it and eliminate andBool lookupTy(TY_DEST_INNER) ==K lookupTy(TY_SRC_OUTER) // and is well-formed (invariant) ``` +Casting a byte array/slice to an integer reinterprets the bytes in little-endian order. + +```k + rule #cast( + Range(ELEMS), + castKindTransmute, + _TY_SOURCE, + TY_TARGET + ) + => + #intAsType( + #littleEndianFromBytes(ELEMS), + size(ELEMS) *Int 8, + #numTypeOf(lookupTy(TY_TARGET)) + ) + ... + + requires #isIntType(lookupTy(TY_TARGET)) + andBool size(ELEMS) *Int 8 ==Int #bitWidth(#numTypeOf(lookupTy(TY_TARGET))) + andBool #areLittleEndianBytes(ELEMS) + [preserves-definedness] // ensures #numTypeOf is defined + + syntax Bool ::= #areLittleEndianBytes ( List ) [function, total] + // ------------------------------------------------------------- + rule #areLittleEndianBytes(.List) => true + rule #areLittleEndianBytes(ListItem(Integer(_, 8, false)) REST) + => #areLittleEndianBytes(REST) + rule #areLittleEndianBytes(ListItem(_OTHER) _) => false [owise] + + syntax Int ::= #littleEndianFromBytes ( List ) [function] + // ----------------------------------------------------- + rule #littleEndianFromBytes(.List) => 0 + rule #littleEndianFromBytes(ListItem(Integer(BYTE, 8, false)) REST) + => BYTE +Int 256 *Int #littleEndianFromBytes(REST) +``` + +Casting an integer to a `[u8; N]` array materialises its little-endian bytes. + +```k + rule #cast( + Integer(VAL, WIDTH, _SIGNEDNESS), + castKindTransmute, + _TY_SOURCE, + TY_TARGET + ) + => + Range(#littleEndianBytesFromInt(VAL, WIDTH)) + ... + + requires #isStaticU8Array(lookupTy(TY_TARGET)) + andBool WIDTH >=Int 0 + andBool WIDTH %Int 8 ==Int 0 + andBool WIDTH ==Int #staticArrayLenBits(lookupTy(TY_TARGET)) + [preserves-definedness] // ensures element type/length are well-formed + + syntax List ::= #littleEndianBytesFromInt ( Int, Int ) [function] + // ------------------------------------------------------------- + rule #littleEndianBytesFromInt(VAL, WIDTH) + => #littleEndianBytes(truncate(VAL, WIDTH, Unsigned), WIDTH /Int 8) + requires WIDTH %Int 8 ==Int 0 + andBool WIDTH >=Int 0 + [preserves-definedness] + + syntax List ::= #littleEndianBytes ( Int, Int ) [function] + // ------------------------------------------------------------- + rule #littleEndianBytes(_, COUNT) + => .List + requires COUNT <=Int 0 + + rule #littleEndianBytes(VAL, COUNT) + => ListItem(Integer(VAL %Int 256, 8, false)) #littleEndianBytes(VAL /Int 256, COUNT -Int 1) + requires COUNT >Int 0 + [preserves-definedness] + + syntax Bool ::= #isStaticU8Array ( TypeInfo ) [function, total] + // ------------------------------------------------------------- + rule #isStaticU8Array(typeInfoArrayType(ELEM_TY, someTyConst(_))) + => lookupTy(ELEM_TY) ==K typeInfoPrimitiveType(primTypeUint(uintTyU8)) + rule #isStaticU8Array(_OTHER) => false [owise] + + syntax Int ::= #staticArrayLenBits ( TypeInfo ) [function, total] + // ------------------------------------------------------------- + rule #staticArrayLenBits(typeInfoArrayType(_, someTyConst(tyConst(KIND, _)))) + => readTyConstInt(KIND) *Int 8 + [preserves-definedness] + rule #staticArrayLenBits(_OTHER) => 0 [owise] +``` + Another specialisation is getting the discriminant of `enum`s without fields after converting some integer data to it (see `#discriminant` and `rvalueDiscriminant`). 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: diff --git a/kmir/src/tests/integration/data/prove-rs/transmute-bytes.rs b/kmir/src/tests/integration/data/prove-rs/transmute-bytes.rs new file mode 100644 index 000000000..654e6ca1e --- /dev/null +++ b/kmir/src/tests/integration/data/prove-rs/transmute-bytes.rs @@ -0,0 +1,31 @@ +#![allow(clippy::transmute_bytes_to_bytes)] +#![allow(clippy::unnecessary_transmute)] + +use std::mem::transmute; + +fn bytes_to_u64(bytes: [u8; 8]) -> u64 { + let value = unsafe { transmute::<[u8; 8], u64>(bytes) }; + let roundtrip = unsafe { transmute::(value) }; + assert_eq!(roundtrip, bytes); + value +} + +fn u64_to_bytes(value: u64) -> [u8; 8] { + let bytes = unsafe { transmute::(value) }; + let roundtrip = unsafe { transmute::<[u8; 8], u64>(bytes) }; + assert_eq!(roundtrip, value); + bytes +} + +fn main() { + let bytes = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; + let int = bytes_to_u64(bytes); + assert_eq!(int, 0xEFCD_AB89_6745_2301); + + let roundtrip = u64_to_bytes(int); + assert_eq!(roundtrip, bytes); + + let value = 0x1020_3040_5060_7080_u64; + let value_roundtrip = bytes_to_u64(u64_to_bytes(value)); + assert_eq!(value_roundtrip, value); +} diff --git a/kmir/src/tests/integration/test_integration.py b/kmir/src/tests/integration/test_integration.py index a42265cc9..387dc40d6 100644 --- a/kmir/src/tests/integration/test_integration.py +++ b/kmir/src/tests/integration/test_integration.py @@ -36,6 +36,7 @@ 'pointer-cast-length-test-fail': ['array_cast_test'], 'assume-cheatcode': ['check_assume'], 'assume-cheatcode-conflict-fail': ['check_assume_conflict'], + 'transmute-bytes': ['bytes_to_u64', 'u64_to_bytes'], } PROVE_RS_SHOW_SPECS = [ 'local-raw-fail',