Skip to content

Commit 0b8189a

Browse files
Stevengredkcumming
andauthored
fix(rt): align transparent pointer casts (#811)
Co-authored-by: Daniel Cumming <[email protected]>
1 parent 1b10abd commit 0b8189a

File tree

11 files changed

+211
-31
lines changed

11 files changed

+211
-31
lines changed

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

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1265,15 +1265,78 @@ the `Value` sort.
12651265
Conversion is especially possible for the case of _Slices_ (of dynamic length) and _Arrays_ (of static length),
12661266
which have the same representation `Value::Range`.
12671267

1268+
When the cast crosses transparent wrappers (newtypes that just forward field `0` e.g. `struct Wrapper<T>(T)`), the pointer's
1269+
`Place` must be realigned. `#alignTransparentPlace` rewrites the projection list until the source and target
1270+
expose the same inner value:
1271+
- if the source unwraps more than the target, append an explicit `field(0)` so the target still sees that field;
1272+
- if the target unwraps more, strip any redundant tail projections with `#popTransparentTailTo`, leaving the
1273+
canonical prefix shared by both sides.
1274+
12681275
```k
12691276
rule <k> #cast(PtrLocal(OFFSET, PLACE, MUT, META), castKindPtrToPtr, TY_SOURCE, TY_TARGET)
12701277
=>
1271-
PtrLocal(OFFSET, PLACE, MUT, #convertMetadata(META, lookupTy(TY_TARGET)))
1278+
PtrLocal(
1279+
OFFSET,
1280+
#alignTransparentPlace(
1281+
PLACE,
1282+
#lookupMaybeTy(pointeeTy(lookupTy(TY_SOURCE))),
1283+
#lookupMaybeTy(pointeeTy(lookupTy(TY_TARGET)))
1284+
),
1285+
MUT,
1286+
#convertMetadata(META, lookupTy(TY_TARGET))
1287+
)
12721288
...
12731289
</k>
12741290
requires #typesCompatible(lookupTy(TY_SOURCE), lookupTy(TY_TARGET))
12751291
[preserves-definedness] // valid map lookups checked
12761292
1293+
syntax Place ::= #alignTransparentPlace ( Place , TypeInfo , TypeInfo ) [function, total]
1294+
syntax ProjectionElems ::= #popTransparentTailTo ( ProjectionElems , TypeInfo ) [function, total]
1295+
1296+
rule #alignTransparentPlace(place(LOCAL, PROJS), typeInfoStructType(_, _, FIELD_TY .Tys, LAYOUT) #as SOURCE, TARGET)
1297+
=> #alignTransparentPlace(
1298+
place(
1299+
LOCAL,
1300+
appendP(PROJS, projectionElemField(fieldIdx(0), FIELD_TY) .ProjectionElems)
1301+
),
1302+
lookupTy(FIELD_TY),
1303+
TARGET
1304+
)
1305+
requires #transparentDepth(SOURCE) >Int #transparentDepth(TARGET)
1306+
andBool #zeroFieldOffset(LAYOUT)
1307+
1308+
rule #alignTransparentPlace(
1309+
place(LOCAL, PROJS),
1310+
SOURCE,
1311+
typeInfoStructType(_, _, FIELD_TY .Tys, LAYOUT) #as TARGET
1312+
)
1313+
=> #alignTransparentPlace(
1314+
place(LOCAL, #popTransparentTailTo(PROJS, lookupTy(FIELD_TY))),
1315+
SOURCE,
1316+
lookupTy(FIELD_TY)
1317+
)
1318+
requires #transparentDepth(SOURCE) <Int #transparentDepth(TARGET)
1319+
andBool #zeroFieldOffset(LAYOUT)
1320+
andBool PROJS =/=K #popTransparentTailTo(PROJS, lookupTy(FIELD_TY))
1321+
1322+
rule #alignTransparentPlace(PLACE, _, _) => PLACE [owise]
1323+
1324+
rule #popTransparentTailTo(
1325+
projectionElemField(fieldIdx(0), FIELD_TY) .ProjectionElems,
1326+
TARGET
1327+
)
1328+
=> .ProjectionElems
1329+
requires lookupTy(FIELD_TY) ==K TARGET
1330+
1331+
rule #popTransparentTailTo(
1332+
X:ProjectionElem REST:ProjectionElems,
1333+
TARGET
1334+
)
1335+
=> X #popTransparentTailTo(REST, TARGET)
1336+
requires REST =/=K .ProjectionElems
1337+
1338+
rule #popTransparentTailTo(PROJS, _) => PROJS [owise]
1339+
12771340
syntax Metadata ::= #convertMetadata ( Metadata , TypeInfo ) [function, total]
12781341
// -------------------------------------------------------------------------------------
12791342
```

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,6 @@ syntax Int ::= #msBytes ( MachineSize ) [function, total]
111111
rule #msBytes(machineSize(mirInt(NBITS))) => NBITS /Int 8 [preserves-definedness]
112112
rule #msBytes(machineSize(NBITS)) => NBITS /Int 8 [owise, preserves-definedness]
113113
114-
// Extract field offsets from the struct layout when available (Arbitrary only).
115-
syntax MachineSizes ::= #layoutOffsets ( MaybeLayoutShape ) [function, total]
116-
rule #layoutOffsets(someLayoutShape(layoutShape(fieldsShapeArbitrary(mk(OFFSETS)), _, _, _, _))) => OFFSETS
117-
rule #layoutOffsets(noLayoutShape) => .MachineSizes
118-
rule #layoutOffsets(_) => .MachineSizes [owise]
119-
120114
// Minimum number of input bytes required to decode all fields by the chosen offsets.
121115
// Uses builtin maxInt to compute max(offset + size). The lists of types and
122116
// offsets must have the same length; if not, this function returns -1 to signal

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,33 @@ Pointers to arrays/slices are compatible with pointers to the element type
6161
rule #isArrayOf( _ , _ ) => false [owise]
6262
```
6363

64+
Pointers to structs with a single zero-offset field are compatible with pointers to that field's type
65+
```k
66+
rule #typesCompatible(SRC, OTHER) => true
67+
requires #zeroSizedType(SRC) orBool #zeroSizedType(OTHER)
68+
69+
rule #typesCompatible(typeInfoStructType(_, _, FIELD .Tys, LAYOUT), OTHER)
70+
=> #typesCompatible(lookupTy(FIELD), OTHER)
71+
requires #zeroFieldOffset(LAYOUT)
72+
73+
rule #typesCompatible(OTHER, typeInfoStructType(_, _, FIELD .Tys, LAYOUT))
74+
=> #typesCompatible(OTHER, lookupTy(FIELD))
75+
requires #zeroFieldOffset(LAYOUT)
76+
77+
syntax Bool ::= #zeroFieldOffset ( MaybeLayoutShape ) [function, total]
78+
79+
rule #zeroFieldOffset(LAYOUT)
80+
=> #layoutOffsets(LAYOUT) ==K .MachineSizes
81+
orBool #layoutOffsets(LAYOUT) ==K machineSize(mirInt(0)) .MachineSizes
82+
orBool #layoutOffsets(LAYOUT) ==K machineSize(0) .MachineSizes
83+
84+
// Extract field offsets from the struct layout when available (Arbitrary only).
85+
syntax MachineSizes ::= #layoutOffsets ( MaybeLayoutShape ) [function, total]
86+
rule #layoutOffsets(someLayoutShape(layoutShape(fieldsShapeArbitrary(mk(OFFSETS)), _, _, _, _))) => OFFSETS
87+
rule #layoutOffsets(noLayoutShape) => .MachineSizes
88+
rule #layoutOffsets(_) => .MachineSizes [owise]
89+
```
90+
6491
## Determining types of places with projection
6592

6693
A helper function `getTyOf` traverses type metadata (using the type metadata map `Ty -> TypeInfo`) along the applied projections to determine the `Ty` of the projected place.
@@ -70,6 +97,24 @@ To make this function total, an optional `MaybeTy` is used.
7097
syntax MaybeTy ::= Ty
7198
| "TyUnknown"
7299
100+
syntax MaybeTy ::= #transparentFieldTy ( TypeInfo ) [function, total]
101+
102+
rule #transparentFieldTy(typeInfoStructType(_, _, FIELD .Tys, LAYOUT)) => FIELD
103+
requires #zeroFieldOffset(LAYOUT)
104+
rule #transparentFieldTy(_) => TyUnknown [owise]
105+
106+
syntax Int ::= #transparentDepth ( TypeInfo ) [function, total]
107+
108+
rule #transparentDepth(typeInfoStructType(_, _, FIELD .Tys, LAYOUT))
109+
=> 1 +Int #transparentDepth(lookupTy(FIELD))
110+
requires #zeroFieldOffset(LAYOUT)
111+
rule #transparentDepth(_) => 0 [owise]
112+
113+
syntax TypeInfo ::= #lookupMaybeTy ( MaybeTy ) [function, total]
114+
115+
rule #lookupMaybeTy(TY:Ty) => lookupTy(TY)
116+
rule #lookupMaybeTy(TyUnknown) => typeInfoVoidType
117+
73118
syntax MaybeTy ::= getTyOf( MaybeTy , ProjectionElems ) [function, total]
74119
// -----------------------------------------------------------
75120
rule getTyOf(TyUnknown, _ ) => TyUnknown
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
struct Wrapper(u32);
2+
3+
fn roundtrip(ptr: *const Wrapper) -> u32 {
4+
unsafe {
5+
let erased = ptr as *const ();
6+
let restored = erased as *const Wrapper;
7+
(*restored).0
8+
}
9+
}
10+
11+
fn main() {
12+
let value = Wrapper(0xCAFE_BABE);
13+
assert!(roundtrip(&value) == 0xCAFE_BABE);
14+
}

kmir/src/tests/integration/data/exec-smir/pointers/pointer-cast-zst.smir.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<kmir>
2+
<k>
3+
PtrLocal ( 1 , place (... local: local ( 1 ) , projection: projectionElemField ( fieldIdx ( 0 ) , ty ( 29 ) ) .ProjectionElems ) , mutabilityNot , metadata ( noMetadataSize , 0 , noMetadataSize ) ) ~> #freezer#setLocalValue(_,_)_RT-DATA_KItem_Place_Evaluation1_ ( place (... local: local ( 2 ) , projection: .ProjectionElems ) ~> .K ) ~> #execStmts ( statement (... kind: statementKindAssign (... place: place (... local: local ( 3 ) , projection: .ProjectionElems ) , rvalue: rvalueCast ( castKindPtrToPtr , operandCopy ( place (... local: local ( 2 ) , projection: .ProjectionElems ) ) , ty ( 26 ) ) ) , span: span ( 52 ) ) statement (... kind: statementKindAssign (... place: place (... local: local ( 4 ) , projection: .ProjectionElems ) , rvalue: rvalueCast ( castKindPtrToPtr , operandCopy ( place (... local: local ( 3 ) , projection: .ProjectionElems ) ) , ty ( 25 ) ) ) , span: span ( 50 ) ) statement (... kind: statementKindAssign (... place: place (... local: local ( 5 ) , projection: .ProjectionElems ) , rvalue: rvalueCast ( castKindTransmute , operandCopy ( place (... local: local ( 4 ) , projection: .ProjectionElems ) ) , ty ( 27 ) ) ) , span: span ( 50 ) ) statement (... kind: statementKindAssign (... place: place (... local: local ( 6 ) , projection: .ProjectionElems ) , rvalue: rvalueNullaryOp ( nullOpAlignOf , ty ( 28 ) ) ) , span: span ( 50 ) ) statement (... kind: statementKindAssign (... place: place (... local: local ( 7 ) , projection: .ProjectionElems ) , rvalue: rvalueBinaryOp ( binOpSub , operandCopy ( place (... local: local ( 6 ) , projection: .ProjectionElems ) ) , operandConstant ( constOperand (... span: span ( 50 ) , userTy: noUserTypeAnnotationIndex , const: mirConst (... kind: constantKindAllocated ( allocation (... bytes: b"\x01\x00\x00\x00\x00\x00\x00\x00" , provenance: provenanceMap (... ptrs: .ProvenanceMapEntries ) , align: align ( 8 ) , mutability: mutabilityMut ) ) , ty: ty ( 27 ) , id: mirConstId ( 9 ) ) ) ) ) ) , span: span ( 50 ) ) statement (... kind: statementKindAssign (... place: place (... local: local ( 8 ) , projection: .ProjectionElems ) , rvalue: rvalueBinaryOp ( binOpBitAnd , operandCopy ( place (... local: local ( 5 ) , projection: .ProjectionElems ) ) , operandCopy ( place (... local: local ( 7 ) , projection: .ProjectionElems ) ) ) ) , span: span ( 50 ) ) statement (... kind: statementKindAssign (... place: place (... local: local ( 9 ) , projection: .ProjectionElems ) , rvalue: rvalueBinaryOp ( binOpEq , operandCopy ( place (... local: local ( 8 ) , projection: .ProjectionElems ) ) , operandConstant ( constOperand (... span: span ( 50 ) , userTy: noUserTypeAnnotationIndex , const: mirConst (... kind: constantKindAllocated ( allocation (... bytes: b"\x00\x00\x00\x00\x00\x00\x00\x00" , provenance: provenanceMap (... ptrs: .ProvenanceMapEntries ) , align: align ( 8 ) , mutability: mutabilityMut ) ) , ty: ty ( 27 ) , id: mirConstId ( 10 ) ) ) ) ) ) , span: span ( 50 ) ) .Statements ) ~> #execTerminator ( terminator (... kind: assert (... cond: operandCopy ( place (... local: local ( 9 ) , projection: .ProjectionElems ) ) , expected: true , msg: assertMessageMisalignedPointerDereference (... required: operandCopy ( place (... local: local ( 6 ) , projection: .ProjectionElems ) ) , found: operandCopy ( place (... local: local ( 5 ) , projection: .ProjectionElems ) ) ) , target: basicBlockIdx ( 1 ) , unwind: unwindActionUnreachable ) , span: span ( 50 ) ) ) ~> .K
4+
</k>
5+
<retVal>
6+
noReturn
7+
</retVal>
8+
<currentFunc>
9+
ty ( 31 )
10+
</currentFunc>
11+
<currentFrame>
12+
<currentBody>
13+
ListItem ( basicBlock (... statements: statement (... kind: statementKindAssign (... place: place (... local: local ( 2 ) , projection: .ProjectionElems ) , rvalue: rvalueCast ( castKindPtrToPtr , operandCopy ( place (... local: local ( 1 ) , projection: .ProjectionElems ) ) , ty ( 25 ) ) ) , span: span ( 51 ) ) statement (... kind: statementKindAssign (... place: place (... local: local ( 3 ) , projection: .ProjectionElems ) , rvalue: rvalueCast ( castKindPtrToPtr , operandCopy ( place (... local: local ( 2 ) , projection: .ProjectionElems ) ) , ty ( 26 ) ) ) , span: span ( 52 ) ) statement (... kind: statementKindAssign (... place: place (... local: local ( 4 ) , projection: .ProjectionElems ) , rvalue: rvalueCast ( castKindPtrToPtr , operandCopy ( place (... local: local ( 3 ) , projection: .ProjectionElems ) ) , ty ( 25 ) ) ) , span: span ( 50 ) ) statement (... kind: statementKindAssign (... place: place (... local: local ( 5 ) , projection: .ProjectionElems ) , rvalue: rvalueCast ( castKindTransmute , operandCopy ( place (... local: local ( 4 ) , projection: .ProjectionElems ) ) , ty ( 27 ) ) ) , span: span ( 50 ) ) statement (... kind: statementKindAssign (... place: place (... local: local ( 6 ) , projection: .ProjectionElems ) , rvalue: rvalueNullaryOp ( nullOpAlignOf , ty ( 28 ) ) ) , span: span ( 50 ) ) statement (... kind: statementKindAssign (... place: place (... local: local ( 7 ) , projection: .ProjectionElems ) , rvalue: rvalueBinaryOp ( binOpSub , operandCopy ( place (... local: local ( 6 ) , projection: .ProjectionElems ) ) , operandConstant ( constOperand (... span: span ( 50 ) , userTy: noUserTypeAnnotationIndex , const: mirConst (... kind: constantKindAllocated ( allocation (... bytes: b"\x01\x00\x00\x00\x00\x00\x00\x00" , provenance: provenanceMap (... ptrs: .ProvenanceMapEntries ) , align: align ( 8 ) , mutability: mutabilityMut ) ) , ty: ty ( 27 ) , id: mirConstId ( 9 ) ) ) ) ) ) , span: span ( 50 ) ) statement (... kind: statementKindAssign (... place: place (... local: local ( 8 ) , projection: .ProjectionElems ) , rvalue: rvalueBinaryOp ( binOpBitAnd , operandCopy ( place (... local: local ( 5 ) , projection: .ProjectionElems ) ) , operandCopy ( place (... local: local ( 7 ) , projection: .ProjectionElems ) ) ) ) , span: span ( 50 ) ) statement (... kind: statementKindAssign (... place: place (... local: local ( 9 ) , projection: .ProjectionElems ) , rvalue: rvalueBinaryOp ( binOpEq , operandCopy ( place (... local: local ( 8 ) , projection: .ProjectionElems ) ) , operandConstant ( constOperand (... span: span ( 50 ) , userTy: noUserTypeAnnotationIndex , const: mirConst (... kind: constantKindAllocated ( allocation (... bytes: b"\x00\x00\x00\x00\x00\x00\x00\x00" , provenance: provenanceMap (... ptrs: .ProvenanceMapEntries ) , align: align ( 8 ) , mutability: mutabilityMut ) ) , ty: ty ( 27 ) , id: mirConstId ( 10 ) ) ) ) ) ) , span: span ( 50 ) ) .Statements , terminator: terminator (... kind: assert (... cond: operandCopy ( place (... local: local ( 9 ) , projection: .ProjectionElems ) ) , expected: true , msg: assertMessageMisalignedPointerDereference (... required: operandCopy ( place (... local: local ( 6 ) , projection: .ProjectionElems ) ) , found: operandCopy ( place (... local: local ( 5 ) , projection: .ProjectionElems ) ) ) , target: basicBlockIdx ( 1 ) , unwind: unwindActionUnreachable ) , span: span ( 50 ) ) ) )
14+
ListItem ( basicBlock (... statements: statement (... kind: statementKindAssign (... place: place (... local: local ( 0 ) , projection: .ProjectionElems ) , rvalue: rvalueUse ( operandCopy ( place (... local: local ( 3 ) , projection: projectionElemDeref projectionElemField ( fieldIdx ( 0 ) , ty ( 29 ) ) .ProjectionElems ) ) ) ) , span: span ( 50 ) ) .Statements , terminator: terminator (... kind: terminatorKindReturn , span: span ( 53 ) ) ) )
15+
</currentBody>
16+
<caller>
17+
ty ( -1 )
18+
</caller>
19+
<dest>
20+
place (... local: local ( 2 ) , projection: .ProjectionElems )
21+
</dest>
22+
<target>
23+
someBasicBlockIdx ( basicBlockIdx ( 1 ) )
24+
</target>
25+
<unwind>
26+
unwindActionContinue
27+
</unwind>
28+
<locals>
29+
ListItem ( newLocal ( ty ( 29 ) , mutabilityMut ) )
30+
ListItem ( typedValue ( PtrLocal ( 1 , place (... local: local ( 1 ) , projection: .ProjectionElems ) , mutabilityNot , metadata ( noMetadataSize , 0 , noMetadataSize ) ) , ty ( 26 ) , mutabilityNot ) )
31+
ListItem ( newLocal ( ty ( 25 ) , mutabilityNot ) )
32+
ListItem ( newLocal ( ty ( 26 ) , mutabilityNot ) )
33+
ListItem ( newLocal ( ty ( 25 ) , mutabilityMut ) )
34+
ListItem ( newLocal ( ty ( 27 ) , mutabilityMut ) )
35+
ListItem ( newLocal ( ty ( 27 ) , mutabilityMut ) )
36+
ListItem ( newLocal ( ty ( 27 ) , mutabilityMut ) )
37+
ListItem ( newLocal ( ty ( 27 ) , mutabilityMut ) )
38+
ListItem ( newLocal ( ty ( 30 ) , mutabilityMut ) )
39+
</locals>
40+
</currentFrame>
41+
<stack>
42+
ListItem ( StackFrame ( ty ( -1 ) , place (... local: local ( 0 ) , projection: .ProjectionElems ) , noBasicBlockIdx , unwindActionContinue , ListItem ( newLocal ( ty ( 1 ) , mutabilityMut ) )
43+
ListItem ( typedValue ( Aggregate ( variantIdx ( 0 ) , ListItem ( Integer ( 3405691582 , 32 , false ) ) ) , ty ( 28 ) , mutabilityNot ) )
44+
ListItem ( newLocal ( ty ( 29 ) , mutabilityMut ) )
45+
ListItem ( typedValue ( Moved , ty ( 26 ) , mutabilityMut ) )
46+
ListItem ( typedValue ( Reference ( 0 , place (... local: local ( 1 ) , projection: .ProjectionElems ) , mutabilityNot , metadata ( noMetadataSize , 0 , noMetadataSize ) ) , ty ( 34 ) , mutabilityNot ) )
47+
ListItem ( newLocal ( ty ( 35 ) , mutabilityMut ) ) ) )
48+
ListItem ( StackFrame ( ty ( -1 ) , place (... local: local ( -1 ) , projection: .ProjectionElems ) , noBasicBlockIdx , unwindActionUnreachable , .List ) )
49+
</stack>
50+
</kmir>

kmir/src/tests/integration/data/prove-rs/interior-mut2-fail.rs renamed to kmir/src/tests/integration/data/prove-rs/interior-mut2.rs

File renamed without changes.

kmir/src/tests/integration/data/prove-rs/show/interior-mut-fail.main.expected

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
│ #execTerminator ( terminator ( ... kind: terminatorKindCall ( ... func: operandC
44
│ span: 0
55
6-
│ (198 steps)
6+
│ (866 steps)
77
└─ 3 (stuck, leaf)
8-
#traverseProjection ( toLocal ( 11 ) , thunk ( #cast ( PtrLocal ( 3 , place ( ..
9-
span: 91
8+
#setUpCalleeData ( monoItemFn ( ... name: symbol ( "** UNKNOWN FUNCTION **" ) ,
9+
span: 32
1010

1111

1212
┌─ 2 (root, leaf, target, terminal)

kmir/src/tests/integration/data/prove-rs/show/interior-mut2-fail.main.expected

Lines changed: 0 additions & 15 deletions
This file was deleted.

kmir/src/tests/integration/data/prove-rs/show/interior-mut3-fail.main.expected

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,34 @@
2626
│ #execBlockIdx ( basicBlockIdx ( 8 ) ) ~> .K
2727
│ function: main
2828
29-
│ (8 steps)
30-
└─ 7 (stuck, leaf)
31-
#traverseProjection ( toLocal ( 2 ) , thunk ( #cast ( PtrLocal ( 1 , place ( ...
32-
function: main
33-
span: 80
29+
│ (111 steps)
30+
├─ 7
31+
│ #expect ( thunk ( #applyBinOp ( binOpEq , thunk ( #applyBinOp ( binOpBitAnd , th
32+
│ function: main
33+
34+
┃ (1 step)
35+
┣━━┓
36+
┃ │
37+
┃ ├─ 8
38+
┃ │ AssertError ( assertMessageMisalignedPointerDereference ( ... required: operandC
39+
┃ │ function: main
40+
┃ │
41+
┃ │ (1 step)
42+
┃ └─ 10 (stuck, leaf)
43+
┃ #ProgramError ( AssertError ( assertMessageMisalignedPointerDereference ( ... re
44+
┃ function: main
45+
46+
┗━━┓
47+
48+
├─ 9
49+
│ #execBlockIdx ( basicBlockIdx ( 7 ) ) ~> .K
50+
│ function: main
51+
52+
│ (17 steps)
53+
└─ 11 (stuck, leaf)
54+
#setLocalValue ( place ( ... local: local ( 1 ) , projection: .ProjectionElems )
55+
function: main
56+
span: 79
3457

3558

3659
┌─ 2 (root, leaf, target, terminal)

0 commit comments

Comments
 (0)