Skip to content

Commit bc9101e

Browse files
authored
CastKind::Transmute for T -> std::mem::MaybeUninit<T> (#863)
#### Context Motivated by [iterator-simple-fail.rs](https://github.com/runtimeverification/mir-semantics/blob/3d763c00389dcdb45a68523d6515c6d6fc8b2928/kmir/src/tests/integration/data/prove-rs/iterator-simple-fail.rs#L1-L7) (which should pass). The type [std::mem::MaybeUninit](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html) is a union that represents a potentially uninitialised location in memory. This union has two fields, first `()`, and second [std::mem::ManuallyDrop<T>](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html) which represents the initialised data. When [converting an array to an iterator](https://github.com/rust-lang/rust/blob/a2545fd6fc66b4323f555223a860c451885d1d2b/library/core/src/array/iter.rs#L57-L70) a `Transmute` cast is invoked for the array element type (`T` from `[T; N]`) into `std::mem::MaybeUninit<T>`. #### This PR This PR implements the cast `CastKind::Transmute` of `T` into `std::mem:MaybeUninit<T>`. The logic of the semantics and saftey of this cast for us is: - that there is a `Value` to be cast as we cannot construct a `Value` that is not initialised; - that the type being cast from `T` is the same as the type of the unions second field `std::mem::ManuallyDrop<T>`; - we can then then create a union that is constructed with the second field, instantiating the `Value` in a struct; - otherwise we error so that the cast does not `thunk`. Tests are added to show the passing cases for `transmute` and `transmute_unchecked`, and the failing case where the types are not valid for the transmute as explained above. A follow up PR will handle converting the elements of the array when an array, this PR is just for a single element.
1 parent b4182cd commit bc9101e

File tree

7 files changed

+113
-6
lines changed

7 files changed

+113
-6
lines changed

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

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,12 @@ that data as a different type.
10171017
...
10181018
</k>
10191019
1020+
rule <k> #evalUnion(rvalueCast(_, _, _) #as CAST)
1021+
=>
1022+
CAST
1023+
...
1024+
</k>
1025+
10201026
rule <k> ListItem(ARG) .List ~> #mkUnion(FIELD)
10211027
=>
10221028
Union(FIELD, ARG)
@@ -1628,8 +1634,7 @@ index; if not, return `#UBErrorInvalidDiscriminantsInEnumCast`.
16281634
rule #isEnumWithoutFields(typeInfoEnumType(_, _, _, FIELDSS, _)) => #noFields(FIELDSS)
16291635
rule #isEnumWithoutFields(_OTHER) => false [owise]
16301636
1631-
// TODO: Connect this with MirError
1632-
syntax Evaulation ::= "#UBErrorInvalidDiscriminantsInEnumCast"
1637+
syntax MIRError ::= "#UBErrorInvalidDiscriminantsInEnumCast"
16331638
rule <k>
16341639
#cast( Integer ( VAL , WIDTH , _SIGNED ) , castKindTransmute , _TY_FROM , TY_TO ) ~> _REST
16351640
=>
@@ -1664,6 +1669,39 @@ index; if not, return `#UBErrorInvalidDiscriminantsInEnumCast`.
16641669
rule #validDiscriminantAux( _VAL, .Discriminants ) => false
16651670
```
16661671

1672+
The type `std::mem::MaybeUninit` is a union that represents a potentially uninitialised location in memory.
1673+
This union has two fields, first `()`, and second `std::mem::ManuallyDrop<T>` which represents the initialised data.
1674+
When [converting an array to an iterator](https://github.com/rust-lang/rust/blob/a2545fd6fc66b4323f555223a860c451885d1d2b/library/core/src/array/iter.rs#L57-L70)
1675+
a `Transmute` cast is invoked for the array element type (`T` from `[T; N]`) into `std::mem::MaybeUninit<T>`. Therefore,
1676+
it is required for iterator use that we handle this transmute. There are details in the safety comment linked above for
1677+
the safety of this cast. The logic of the semantics and saftey of this cast for us is:
1678+
- that there is a `Value` to be cast as we cannot construct a `Value` that is not initialised;
1679+
- that the type being cast from `T` is the same as the type of the unions second field `std::mem::ManuallyDrop<T>`;
1680+
- we can then then create a union that is constructed with the second field, instantiating the `Value` in a struct;
1681+
- otherwise we error so that the cast does not `thunk`.
1682+
```k
1683+
syntax MIRError ::= "#UBInvalidTransmuteMaybeUninit"
1684+
rule <k>
1685+
#cast( _VAL:Value , castKindTransmute , TY_FROM , TY_TO )
1686+
=>
1687+
#UBInvalidTransmuteMaybeUninit
1688+
...
1689+
</k>
1690+
requires #isUnionType(lookupTy(TY_TO))
1691+
andBool #typeNameIs(lookupTy(TY_TO), "std::mem::MaybeUninit<")
1692+
andBool TY_FROM =/=K getFieldTy(#lookupMaybeTy(getFieldTy(lookupTy(TY_TO), 1)), 0)
1693+
1694+
rule <k>
1695+
#cast( VAL:Value , castKindTransmute , TY_FROM , TY_TO )
1696+
=>
1697+
Union( fieldIdx ( 1 ) , Aggregate ( variantIdx ( 0 ) , ListItem(VAL) .List ))
1698+
...
1699+
</k>
1700+
requires #isUnionType(lookupTy(TY_TO))
1701+
andBool #typeNameIs(lookupTy(TY_TO), "std::mem::MaybeUninit<")
1702+
andBool TY_FROM ==K getFieldTy(#lookupMaybeTy(getFieldTy(lookupTy(TY_TO), 1)), 0)
1703+
```
1704+
16671705

16681706
### Other casts involving pointers
16691707

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

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,36 @@ To make this function total, an optional `MaybeTy` is used.
119119
requires #zeroFieldOffset(LAYOUT)
120120
rule #transparentDepth(_) => 0 [owise]
121121
122-
syntax TypeInfo ::= #lookupMaybeTy ( MaybeTy ) [function, total]
122+
syntax String ::= #typeName ( TypeInfo ) [function, total]
123+
// -------------------------------------------------------
124+
rule #typeName(typeInfoUnionType(NAME, _, _, _)) => NAME
125+
rule #typeName(typeInfoStructType(NAME, _, _, _)) => NAME
126+
rule #typeName(typeInfoEnumType(NAME, _, _, _, _)) => NAME
127+
rule #typeName(_) => "" [owise]
128+
129+
syntax Bool ::= #typeNameIs( TypeInfo, String ) [function, total]
130+
// --------------------------------------------------------------
131+
rule #typeNameIs( TY_TO, STRING) => findString(#typeName(TY_TO), STRING, 0) ==Int 0
132+
133+
syntax MaybeTy ::= getFieldTy ( TypeInfo , Int ) [function, total]
134+
// ---------------------------------------------------------------
135+
rule getFieldTy(typeInfoStructType(_, _, FIELDS, _) , IDX) => getFieldTyFromList(FIELDS, IDX)
136+
rule getFieldTy(typeInfoUnionType(_, _, FIELDS, _) , IDX) => getFieldTyFromList(FIELDS, IDX)
137+
rule getFieldTy(_, _) => TyUnknown [owise]
138+
139+
syntax MaybeTy ::= getFieldTyFromList ( Tys , Int ) [function, total]
140+
// ------------------------------------------------------------------
141+
rule getFieldTyFromList(FIELD _REST, 0) => FIELD
142+
rule getFieldTyFromList(_ REST, IDX) => getFieldTyFromList(REST, IDX -Int 1) requires IDX >Int 0
143+
rule getFieldTyFromList(_, _) => TyUnknown [owise]
123144
145+
syntax TypeInfo ::= #lookupMaybeTy ( MaybeTy ) [function, total]
146+
// -------------------------------------------------------------
124147
rule #lookupMaybeTy(TY:Ty) => lookupTy(TY)
125148
rule #lookupMaybeTy(TyUnknown) => typeInfoVoidType
126149
127150
syntax MaybeTy ::= getTyOf( MaybeTy , ProjectionElems ) [function, total]
128-
// -----------------------------------------------------------
151+
// ----------------------------------------------------------------------
129152
rule getTyOf(TyUnknown, _ ) => TyUnknown
130153
rule getTyOf(TY, .ProjectionElems ) => TY
131154
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
┌─ 1 (root, init)
3+
│ #execTerminator ( terminator ( ... kind: terminatorKindCall ( ... func: operandC
4+
│ span: 0
5+
6+
│ (15 steps)
7+
└─ 3 (stuck, leaf)
8+
#ProgramError ( #UBInvalidTransmuteMaybeUninit )
9+
~> #freezer#setLocalValue(_,_)_
10+
function: main
11+
span: 60
12+
13+
14+
┌─ 2 (root, leaf, target, terminal)
15+
│ #EndProgram ~> .K
16+
17+

kmir/src/tests/integration/data/prove-rs/show/transmute-u8-to-enum-fail.main.expected

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
│ #execTerminator ( terminator ( ... kind: terminatorKindCall ( ... func: operandC
44
│ span: 0
55
6-
│ (13 steps)
6+
│ (14 steps)
77
└─ 3 (stuck, leaf)
8-
#UBErrorInvalidDiscriminantsInEnumCast ~> .K
8+
#ProgramError ( #UBErrorInvalidDiscriminantsInEnumCast ) ~> .K
99
function: main
1010

1111

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(core_intrinsics)]
2+
use std::mem::MaybeUninit;
3+
4+
fn main() {
5+
unsafe {
6+
let maybe_unsigned = std::intrinsics::transmute::<i128, MaybeUninit::<u128>>(9999);
7+
// Note different types ^^^^ ^^^^
8+
assert!(maybe_unsigned.assume_init() == 9999);
9+
}
10+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#![feature(core_intrinsics)]
2+
use std::mem::MaybeUninit;
3+
4+
struct Something([u8; 3]);
5+
6+
fn main() {
7+
unsafe {
8+
let maybe_signed = std::intrinsics::transmute_unchecked::<i32, MaybeUninit::<i32>>(42);
9+
assert!(maybe_signed.assume_init() == 42);
10+
11+
let maybe_unsigned = std::intrinsics::transmute::<u128, MaybeUninit::<u128>>(9999);
12+
assert!(maybe_unsigned.assume_init() == 9999);
13+
14+
let something = Something([1, 2, 3]);
15+
let maybe_something = std::intrinsics::transmute::<Something, MaybeUninit::<Something>>(something);
16+
assert!(maybe_something.assume_init().0 == [1, 2, 3]);
17+
}
18+
}

kmir/src/tests/integration/test_integration.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
'assert-inhabited-fail',
5858
'iterator-simple-fail',
5959
'unions-fail',
60+
'transmute-maybe-uninit-fail',
6061
]
6162

6263

0 commit comments

Comments
 (0)