Skip to content

Commit 206b1c1

Browse files
jbertholddkcumming
andauthored
527 basic support for allocated data (#680)
* Allocated constants are stored in the `<memory>` cell as a map `AllocId -> Value` (by python code inserting a `#decodeAllocs` function). * The allocation decoder (currently) only handles allocations without a provenance or with a single provenance entry at offset 0 when the allocation type is a reference. Otherwise, undecoded data is stored in raw form, in a supersort of `Value`. * A new value constructor `AllocRef` is added, containing an `AllocId` and reference metadata. - This value can be `Deref`erenced like a `PtrLocal` or `Reference` if the `<memory>` map has a `Value` entry for the given `AllocId` (will get stuck on undecoded data). * `AllocRef`s are created by `#decodeConstant` from `operandConstant` operands with a single provenance map entry at offset 0 when a reference type is expected (same restrictions as the allocation decoder above). Otherwise `#decodeConstant` becomes a thunk. * `AllocRef`s can be `Unsize`d, i.e., dynamic metadata added to replace known static metadata. * `AllocRef`s support trivial `Transmute` calls where source and target type are the same --------- Co-authored-by: Daniel Cumming <124537596+dkcumming@users.noreply.github.com>
1 parent 6b2fd3c commit 206b1c1

74 files changed

Lines changed: 35084 additions & 94030 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ syntax GlobalAllocKind ::= globalAllocFunction(Instance)
3030
| Memory(Allocation)
3131
[group(mir-enum), symbol(GlobalAllocKind::Memory)]
3232
33-
syntax GlobalAlloc ::= globalAllocEntry(MIRInt, Ty, GlobalAllocKind)
33+
syntax GlobalAlloc ::= globalAllocEntry(AllocId, Ty, GlobalAllocKind)
3434
[symbol(globalAllocEntry), group(mir---alloc-id--ty--global-alloc)]
3535
3636
syntax GlobalAllocs ::= List {GlobalAlloc, ""}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ module KMIR-CONFIGURATION
4646
</currentFrame>
4747
// remaining call stack (without top frame)
4848
<stack> .List </stack>
49+
// static and dynamic allocations: AllocId -> Value
4950
<memory> .Map </memory>
5051
// ============ static information ============
5152
// function store, Ty -> MonoItemFn
5253
<functions> .Map </functions>
53-
// heap
5454
<start-symbol> symbol($STARTSYM:String) </start-symbol>
5555
// static information about the base type interning in the MIR
5656
<types> .Map </types>

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

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,14 @@ A `Deref` projection in the projections list changes the target of the write ope
292292
requires 0 <Int FRAME andBool FRAME <=Int size(STACK)
293293
andBool isStackFrame(STACK[FRAME -Int 1])
294294
[preserves-definedness] // valid context ensured upon context construction
295+
296+
rule <k> #traverseProjection(toAlloc(ALLOC_ID), _ORIGINAL, .ProjectionElems, CONTEXTS)
297+
~> #writeMoved
298+
=> .K
299+
...
300+
</k>
301+
<memory> MEMORY => MEMORY[ALLOC_ID <- #buildUpdate(Moved, CONTEXTS)] </memory>
302+
[preserves-definedness] // valid ALLOC_ID ensured when destination set
295303
```
296304

297305
These helpers mark down, as we traverse the projection, what `Place` we are currently looking up in the traversal.
@@ -301,6 +309,7 @@ These helpers mark down, as we traverse the projection, what `Place` we are curr
301309
// stores the target of the write operation, which may change when references are dereferenced.
302310
syntax WriteTo ::= toLocal ( Int )
303311
| toStack ( Int , Local )
312+
| toAlloc ( AllocId )
304313
305314
// retains information about the value that was deconstructed by a projection
306315
syntax Context ::= CtxField( VariantIdx, List, Int , Ty )
@@ -681,6 +690,32 @@ An attempt to read more elements than the length of the accessed array is undefi
681690
[preserves-definedness]
682691
```
683692

693+
References also exist into the `<memory>` heap which is populated with allocated constants.
694+
A reference to a statically allocated constant is typically never written to,
695+
even though this could be supported.
696+
697+
```k
698+
rule <k> #traverseProjection(
699+
_DEST,
700+
AllocRef(ALLOC_ID, META),
701+
projectionElemDeref PROJS,
702+
_CTXTS
703+
)
704+
=> #traverseProjection(
705+
toAlloc(ALLOC_ID),
706+
{MEMORYMAP[ALLOC_ID]}:>Value,
707+
.ProjectionElems, // no place projections
708+
.Contexts // previous contexts obsolete
709+
)
710+
~> #derefTruncate(META, PROJS) // then truncate, then continue with remaining projections
711+
...
712+
</k>
713+
<memory> MEMORYMAP </memory>
714+
requires ALLOC_ID in_keys(MEMORYMAP)
715+
andBool isValue(MEMORYMAP[ALLOC_ID])
716+
[preserves-definedness]
717+
```
718+
684719
## Evaluation of R-Values (`Rvalue` sort)
685720

686721
The `Rvalue` sort in MIR represents various operations that produce a value which can be assigned to a `Place`.
@@ -1206,6 +1241,12 @@ The original metadata is therefore already stored as `staticSize` to avoid havin
12061241
// andBool isTypeInfo(TYPEMAP[TY_SOURCE])
12071242
// andBool notBool hasMetadata(_TY_TARGET, TYPEMAP)
12081243
// [preserves-definedness] // valid type map indexing and sort coercion
1244+
1245+
rule <k> #cast(AllocRef(ID, staticSize(SIZE)), castKindPointerCoercion(pointerCoercionUnsize), _TY_SOURCE, _TY_TARGET)
1246+
=>
1247+
AllocRef(ID, dynamicSize(SIZE))
1248+
...
1249+
</k>
12091250
```
12101251

12111252
### `Transmute` casts
@@ -1224,6 +1265,13 @@ What can be supported without additional layout consideration is trivial casts b
12241265
andBool TYPEMAP[TY_SOURCE] ==K TYPEMAP[TY_TARGET]
12251266
[preserves-definedness] // valid map lookups checked
12261267
1268+
rule <k> #cast(AllocRef(_, _) #as REF, castKindTransmute, TY_SOURCE, TY_TARGET) => REF ... </k>
1269+
<types> TYPEMAP </types>
1270+
requires TY_SOURCE in_keys(TYPEMAP)
1271+
andBool TY_TARGET in_keys(TYPEMAP)
1272+
andBool TYPEMAP[TY_SOURCE] ==K TYPEMAP[TY_TARGET]
1273+
[preserves-definedness] // valid map lookups checked
1274+
12271275
rule <k> #cast(PtrLocal(_, _, _, _) #as PTR, castKindTransmute, TY_SOURCE, TY_TARGET) => PTR ... </k>
12281276
<types> TYPEMAP </types>
12291277
requires TY_SOURCE in_keys(TYPEMAP)
@@ -1265,8 +1313,6 @@ For allocated constants without provenance, the decoder works directly with the
12651313

12661314
Zero-sized types can be decoded trivially into their respective representation.
12671315

1268-
**FIXME test the new cases for tuple and array/slice**
1269-
12701316
```k
12711317
// zero-sized struct
12721318
rule <k> #decodeConstant(constantKindZeroSized, _TY, typeInfoStructType(_, _, _))
@@ -1279,6 +1325,49 @@ Zero-sized types can be decoded trivially into their respective representation.
12791325
=> Range(.List) ... </k>
12801326
```
12811327

1328+
Allocated constants of reference type with a single provenance map entry are decoded as references
1329+
into the `<memory>` heap where all allocated constants have been decoded at program start.
1330+
1331+
```k
1332+
rule <k> #decodeConstant(
1333+
constantKindAllocated(
1334+
allocation(
1335+
BYTES,
1336+
provenanceMap(provenanceMapEntry(0, ALLOC_ID) .ProvenanceMapEntries),
1337+
_,
1338+
_)),
1339+
_TY,
1340+
typeInfoRefType(POINTEE_TY)
1341+
)
1342+
=> AllocRef(ALLOC_ID, #metadata(POINTEE_TY, TYPEMAP))
1343+
...
1344+
</k>
1345+
<memory> ALLOCMAP </memory>
1346+
<types> TYPEMAP </types>
1347+
requires ALLOC_ID in_keys(ALLOCMAP)
1348+
andBool lengthBytes(BYTES) ==Int 8 // no dynamic metadata
1349+
1350+
rule <k> #decodeConstant(
1351+
constantKindAllocated(
1352+
allocation(
1353+
BYTES,
1354+
provenanceMap(provenanceMapEntry(0, ALLOC_ID) .ProvenanceMapEntries),
1355+
_,
1356+
_)),
1357+
_TY,
1358+
typeInfoRefType(_)
1359+
)
1360+
=> AllocRef(ALLOC_ID, dynamicSize(Bytes2Int(substrBytes(BYTES, 8, 16), LE, Unsigned)) )
1361+
// assumes usize == u64
1362+
...
1363+
</k>
1364+
<memory> ALLOCMAP </memory>
1365+
requires ALLOC_ID in_keys(ALLOCMAP)
1366+
andBool lengthBytes(BYTES) ==Int 16 // fat pointer (assumes usize == u64)
1367+
[preserves-definedness] // Byte length checked to be sufficient
1368+
```
1369+
1370+
12821371
## Primitive operations on numeric data
12831372

12841373
The `RValue:BinaryOp` performs built-in binary operations on two operands.

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

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ from raw bytes to typed `Value` objects according to Rust's memory layout rules.
1414
requires "../ty.md"
1515
requires "value.md"
1616
requires "numbers.md"
17+
requires "../alloc.md"
1718
1819
module RT-DECODING
1920
imports BOOL
2021
imports MAP
2122
2223
imports TYPES
24+
imports ALLOC
2325
imports RT-VALUE-SYNTAX
2426
imports RT-NUMBERS
2527
imports RT-TYPES
@@ -37,6 +39,7 @@ and arrays (where layout is trivial).
3739
```k
3840
syntax Evaluation ::= #decodeValue ( Bytes , TypeInfo , Map ) [function, total]
3941
| UnableToDecode( Bytes , TypeInfo )
42+
| UnableToDecode( Bytes , Ty , ProvenanceMapEntries )
4043
4144
// Boolean: should be one byte with value one or zero
4245
rule #decodeValue(BYTES, typeInfoPrimitiveType(primTypeBool), _TYPEMAP) => BoolVal(false)
@@ -168,6 +171,100 @@ by the element size, then uses the same element-by-element decoding approach as
168171
[preserves-definedness]
169172
```
170173

174+
# Decoding the allocated constants into a memory map
175+
176+
When the program starts, all allocated constants are stored in the `<memory>` cell in the configuration,
177+
as a mapping of `AllocId -> Value`, decoding the allocations to values using the above functions.
178+
179+
Besides actual byte allocations, we often find allocations with provenance,
180+
as slim or fat pointers pointing to other static allocations.
181+
One example of this is allocated error strings, whose length is stored in a fat pointer that is itself an allocated constant.
182+
183+
184+
The basic decoding function is very similar to `#decodeConstant` but returns its result as a singleton `Map` instead of a mere value.
185+
186+
```k
187+
syntax Map ::= #decodeAlloc ( AllocId , Ty , Allocation , Map ) [function] // returns AllocId |-> Value
188+
// ----------------------------------------------------------
189+
rule #decodeAlloc(ID, TY, allocation(BYTES, provenanceMap(.ProvenanceMapEntries), _ALIGN, _MUT), TYPEMAP)
190+
=> ID |-> #decodeValue(BYTES, {TYPEMAP[TY]}:>TypeInfo, TYPEMAP)
191+
requires TY in_keys(TYPEMAP)
192+
andBool isTypeInfo(TYPEMAP[TY])
193+
```
194+
195+
If there are entries in the provenance map, then the decoder must create references to other allocations.
196+
Each entry replaces a pointer starting at a certain offset in the bytes.
197+
The pointee of the respective pointer is determined by type _layout_ of the type to decode.
198+
199+
Simple cases of offset 0 and reference types can be implemented here without consideration of layout.
200+
The reference metadata is either determined statically by type, or filled in from the bytes for fat pointers.
201+
202+
```k
203+
// if length(BYTES) ==Int 16 decode BYTES[9..16] as dynamic size.
204+
rule #decodeAlloc(
205+
ID,
206+
TY,
207+
allocation(BYTES, provenanceMap(provenanceMapEntry(0, REF_ID) ), _ALIGN, _MUT),
208+
TYPEMAP
209+
)
210+
=> ID |-> AllocRef(REF_ID, dynamicSize(Bytes2Int(substrBytes(BYTES, 8, 16), LE, Unsigned)))
211+
requires TY in_keys(TYPEMAP)
212+
andBool isTypeInfo(TYPEMAP[TY])
213+
andBool isTy(pointeeTy({TYPEMAP[TY]}:>TypeInfo)) // ensures this is a reference type
214+
andBool lengthBytes(BYTES) ==Int 16 // sufficient data to decode dynamic size (assumes usize == u64)
215+
andBool dynamicSize(1) ==K #metadata(pointeeTy({TYPEMAP[TY]}:>TypeInfo), TYPEMAP) // expect fat pointer
216+
[preserves-definedness] // valid type lookup ensured
217+
218+
// Otherwise query type information for static size and insert it.
219+
rule #decodeAlloc(
220+
ID,
221+
TY,
222+
allocation(BYTES, provenanceMap(provenanceMapEntry(0, REF_ID) ), _ALIGN, _MUT),
223+
TYPEMAP
224+
)
225+
=> ID |-> AllocRef(REF_ID, #metadata(pointeeTy({TYPEMAP[TY]}:>TypeInfo), TYPEMAP))
226+
requires TY in_keys(TYPEMAP)
227+
andBool isTypeInfo(TYPEMAP[TY])
228+
andBool isTy(pointeeTy({TYPEMAP[TY]}:>TypeInfo)) // ensures this is a reference type
229+
andBool lengthBytes(BYTES) ==Int 8 // single slim pointer (assumes usize == u64)
230+
[owise, preserves-definedness] // valid type lookup ensured
231+
```
232+
233+
Allocations with more than one provenance map entry or witn non-reference types remain undecoded.
234+
235+
```k
236+
rule #decodeAlloc(ID, TY, allocation(BYTES, provenanceMap(ENTRIES), _ALIGN, _MUT), _TYPEMAP)
237+
=> ID |-> UnableToDecode(BYTES, TY, ENTRIES)
238+
[owise]
239+
```
240+
241+
The entire set of `GlobalAllocs` is decoded by iterating over the list.
242+
It is assumed that the given `Ty -> TypeInfo` map contains all types required.
243+
244+
```k
245+
syntax Map ::= #decodeAllocs ( GlobalAllocs , Map ) [function, total, symbol("decodeAllocs")] // AllocId |-> Thing
246+
| #decodeAllocs ( Map , GlobalAllocs , Map ) [function, total] // accumulating version
247+
// -----------------------------------------------------------------------------------------------
248+
rule #decodeAllocs(ALLOCS, TYPEMAP) => #decodeAllocs(.Map, ALLOCS, TYPEMAP)
249+
250+
rule #decodeAllocs(ACCUM, .GlobalAllocs, _TYPEMAP) => ACCUM
251+
rule #decodeAllocs(ACCUM, globalAllocEntry(ID, TY, Memory(ALLOC)) ALLOCS, TYPEMAP)
252+
=> #decodeAllocs(ACCUM #decodeAlloc(ID, TY, ALLOC, TYPEMAP), ALLOCS, TYPEMAP)
253+
requires TY in_keys(TYPEMAP)
254+
[preserves-definedness]
255+
```
256+
257+
If the type of an alloc cannot be found, or for non-`Memory` allocs, the raw data is stored in a super-sort of `Value`.
258+
This ensures that the function is total (anyway lookups require constraining the sort).
259+
260+
```k
261+
syntax AllocData ::= Value | AllocData ( Ty , GlobalAllocKind )
262+
263+
rule #decodeAllocs(ACCUM, globalAllocEntry(ID, TY, OTHER) ALLOCS, TYPEMAP)
264+
=> #decodeAllocs(ACCUM ID |-> AllocData(TY, OTHER), ALLOCS, TYPEMAP)
265+
[owise]
266+
```
267+
171268
```k
172269
endmodule
173-
```
270+
```

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ The special `Moved` value represents values that have been used and should not b
4242
[symbol(Value::PtrLocal)]
4343
// pointer to a local TypedValue (on the stack)
4444
// first 3 fields are the same as in Reference, plus pointee metadata
45+
| AllocRef ( AllocId , Metadata )
46+
// reference to static allocation, by AllocId, carrying metadata if applicable
4547
| "Moved"
4648
// The value has been used and is gone now
4749
```

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ syntax MaybePromoted ::= promoted(Int) [group(mir-option-int)]
289289
290290
syntax Align ::= align(Int) [group(mir-int), symbol(align)]
291291
292-
syntax ProvenanceMapEntry ::= provenanceMapEntry(provSize: MIRInt, allocId: AllocId)
292+
syntax ProvenanceMapEntry ::= provenanceMapEntry(offset: MIRInt, allocId: AllocId)
293293
[group(mir), symbol(provenanceMapEntry)]
294294
295295
syntax ProvenanceMapEntries ::= List {ProvenanceMapEntry, ""}

kmir/src/kmir/kmir.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,23 @@ def make_call_config(
129129
locals, constraints = symbolic_locals(smir_info, args_info)
130130
types, adts = self._make_type_and_adt_maps(smir_info)
131131

132+
parser = Parser(self.definition)
133+
allocs: KInner = KApply('GlobalAllocs::empty')
134+
allocs_json = smir_info._smir['allocs']
135+
assert isinstance(allocs_json, list)
136+
allocs_json.reverse()
137+
for alloc in allocs_json:
138+
parse_result = parser.parse_mir_json(alloc, 'GlobalAlloc')
139+
assert parse_result is not None
140+
a, _ = parse_result
141+
allocs = KApply('GlobalAllocs::append', (a, allocs))
142+
132143
_subst = {
133144
'K_CELL': mk_call_terminator(smir_info.function_tys[start_symbol], len(args_info)),
134145
'STARTSYMBOL_CELL': KApply('symbol(_)_LIB_Symbol_String', (token(start_symbol),)),
135146
'STACK_CELL': list_empty(), # FIXME see #560, problems matching a symbolic stack
136147
'LOCALS_CELL': list_of(locals),
148+
'MEMORY_CELL': KApply('decodeAllocs', (allocs, types)),
137149
'FUNCTIONS_CELL': self._make_function_map(smir_info),
138150
'TYPES_CELL': types,
139151
'ADTTOTY_CELL': adts,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const I8_ARRAY: [i8; 3] = [1, -2, 3];
2+
const U16_ARRAY: [u16; 3] = [100, 200, 300];
3+
4+
fn main() {
5+
// the array will be allocated twice, even if the same constant is used
6+
assert_eq!(I8_ARRAY, I8_ARRAY);
7+
// this uses an inlined array constant instead of the global one
8+
assert!(compare_to_stored(U16_ARRAY));
9+
// println!("All assertions passed!");
10+
}
11+
12+
fn compare_to_stored(array: [u16;3]) -> bool {
13+
U16_ARRAY == array
14+
}

0 commit comments

Comments
 (0)