Skip to content

Commit 71bc6f9

Browse files
authored
Implement decoding for references with a single provenance entry (#728)
1 parent 729a96a commit 71bc6f9

4 files changed

Lines changed: 111 additions & 28 deletions

File tree

kmir/src/kmir/alloc.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,21 +71,21 @@ def from_dict(dct: dict[str, Any]) -> Allocation:
7171

7272
@dataclass
7373
class ProvenanceMap:
74-
ptrs: list[ProvenanceItem]
74+
ptrs: list[ProvenanceEntry]
7575

7676
@staticmethod
7777
def from_dict(dct: dict[str, Any]) -> ProvenanceMap:
7878
return ProvenanceMap(
7979
ptrs=[
80-
ProvenanceItem(
81-
size=int(size),
80+
ProvenanceEntry(
81+
offset=int(size),
8282
prov=AllocId(prov),
8383
)
8484
for size, prov in dct['ptrs']
8585
],
8686
)
8787

8888

89-
class ProvenanceItem(NamedTuple):
90-
size: int
89+
class ProvenanceEntry(NamedTuple):
90+
offset: int
9191
prov: AllocId

kmir/src/kmir/decoding.py

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from typing import TYPE_CHECKING, NamedTuple
4+
from typing import TYPE_CHECKING
55

66
from pyk.kast.inner import KApply
77
from pyk.kast.prelude.bytes import bytesToken
88
from pyk.kast.prelude.kint import intToken
99

10-
from .alloc import Allocation, AllocInfo, Memory, ProvenanceMap
11-
from .ty import ArrayT, Bool, EnumT, Int, IntTy, Uint
12-
from .value import AggregateValue, BoolValue, IntValue, RangeValue, Value
10+
from .alloc import Allocation, AllocInfo, Memory, ProvenanceEntry, ProvenanceMap
11+
from .ty import ArrayT, Bool, EnumT, Int, IntTy, PtrT, RefT, Uint
12+
from .value import (
13+
NO_METADATA,
14+
AggregateValue,
15+
AllocRefValue,
16+
BoolValue,
17+
DynamicSize,
18+
IntValue,
19+
RangeValue,
20+
StaticSize,
21+
Value,
22+
)
1323

1424
if TYPE_CHECKING:
1525
from collections.abc import Mapping
1626

1727
from pyk.kast import KInner
1828

19-
from .alloc import AllocId
2029
from .ty import Ty, TypeMetadata, UintTy
30+
from .value import Metadata
2131

2232

2333
@dataclass
@@ -47,11 +57,6 @@ def to_kast(self) -> KInner:
4757
)
4858

4959

50-
class ProvenanceMapEntry(NamedTuple):
51-
offset: int
52-
alloc_id: AllocId
53-
54-
5560
def decode_alloc_or_unable(alloc_info: AllocInfo, types: Mapping[Ty, TypeMetadata]) -> Value:
5661
match alloc_info:
5762
case AllocInfo(
@@ -66,16 +71,52 @@ def decode_alloc_or_unable(alloc_info: AllocInfo, types: Mapping[Ty, TypeMetadat
6671
),
6772
):
6873
data = bytes(n or 0 for n in bytez)
74+
type_info = types[ty]
75+
76+
match ptrs:
77+
case []:
78+
return decode_value_or_unable(data=data, type_info=type_info, types=types)
79+
80+
case [ProvenanceEntry(0, alloc_id)]:
81+
if (pointee_ty := _pointee_ty(type_info)) is not None: # ensures this is a reference type
82+
pointee_type_info = types[pointee_ty]
83+
metadata = _metadata(pointee_type_info)
6984

70-
if not ptrs: # TODO generalize to lists with at most one entry
71-
type_info = types[ty]
72-
return decode_value_or_unable(data=data, type_info=type_info, types=types)
85+
if len(data) == 8:
86+
# single slim pointer (assumes usize == u64)
87+
return AllocRefValue(alloc_id=alloc_id, metadata=metadata)
88+
89+
if len(data) == 16 and metadata == DynamicSize(1):
90+
# sufficient data to decode dynamic size (assumes usize == u64)
91+
# expect fat pointer
92+
return AllocRefValue(
93+
alloc_id=alloc_id,
94+
metadata=DynamicSize(int.from_bytes(data[8:16], byteorder='little', signed=False)),
95+
)
7396

7497
return UnableToDecodeAlloc(data=data, ty=ty)
7598
case _:
7699
raise AssertionError('Unhandled case')
77100

78101

102+
def _pointee_ty(type_info: TypeMetadata) -> Ty | None:
103+
match type_info:
104+
case PtrT(ty) | RefT(ty):
105+
return ty
106+
case _:
107+
return None
108+
109+
110+
def _metadata(type_info: TypeMetadata) -> Metadata:
111+
match type_info:
112+
case ArrayT(length=None):
113+
return DynamicSize(1) # 1 is a placeholder, the actual size is inferred from the slice data
114+
case ArrayT(length=int() as length):
115+
return StaticSize(length)
116+
case _:
117+
return NO_METADATA
118+
119+
79120
def decode_value_or_unable(data: bytes, type_info: TypeMetadata, types: Mapping[Ty, TypeMetadata]) -> Value:
80121
try:
81122
return decode_value(data=data, type_info=type_info, types=types)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ The special `Moved` value represents values that have been used and should not b
4343
// pointer to a local TypedValue (on the stack)
4444
// first 3 fields are the same as in Reference, plus pointee metadata
4545
| AllocRef ( AllocId , ProjectionElems , Metadata )
46+
[symbol(Value::AllocRef)]
4647
// reference to static allocation, by AllocId, possibly projected, carrying metadata if applicable
4748
| "Moved"
4849
// The value has been used and is gone now

kmir/src/kmir/value.py

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@
55
from typing import TYPE_CHECKING
66

77
from pyk.kast.inner import KApply
8+
from pyk.kast.prelude.collections import list_of
9+
from pyk.kast.prelude.kbool import boolToken
10+
from pyk.kast.prelude.kint import intToken
811

912
if TYPE_CHECKING:
1013
from collections.abc import Iterable
14+
from typing import Final
1115

1216
from pyk.kast import KInner
1317

18+
from .alloc import AllocId
19+
1420

1521
class Value(ABC):
1622
@abstractmethod
@@ -22,8 +28,6 @@ class BoolValue(Value):
2228
value: bool
2329

2430
def to_kast(self) -> KInner:
25-
from pyk.kast.prelude.kbool import boolToken
26-
2731
return KApply('Value::BoolVal', boolToken(self.value))
2832

2933

@@ -34,9 +38,6 @@ class IntValue(Value):
3438
signed: bool
3539

3640
def to_kast(self) -> KInner:
37-
from pyk.kast.prelude.kbool import boolToken
38-
from pyk.kast.prelude.kint import intToken
39-
4041
return KApply(
4142
'Value::Integer',
4243
intToken(self.value),
@@ -53,8 +54,6 @@ def __init__(self, elems: Iterable[Value]):
5354
self.elems = tuple(elems)
5455

5556
def to_kast(self) -> KInner:
56-
from pyk.kast.prelude.collections import list_of
57-
5857
return KApply('Value::Range', list_of(elem.to_kast() for elem in self.elems))
5958

6059

@@ -68,11 +67,53 @@ def __init__(self, variant_idx: int, fields: Iterable[Value]):
6867
self.fields = tuple(fields)
6968

7069
def to_kast(self) -> KInner:
71-
from pyk.kast.prelude.collections import list_of
72-
from pyk.kast.prelude.kint import intToken
73-
7470
return KApply(
7571
'Value::Aggregate',
7672
KApply('variantIdx', intToken(self.variant_idx)),
7773
list_of(field.to_kast() for field in self.fields),
7874
)
75+
76+
77+
@dataclass
78+
class AllocRefValue(Value):
79+
alloc_id: AllocId
80+
# projection_elems: tuple[ProjectionElem, ...]
81+
metadata: Metadata
82+
83+
def to_kast(self) -> KInner:
84+
return KApply(
85+
'Value::AllocRef',
86+
KApply('allocId', intToken(self.alloc_id)),
87+
KApply('ProjectionElems::empty'), # TODO
88+
self.metadata.to_kast(),
89+
)
90+
91+
92+
class Metadata(ABC):
93+
@abstractmethod
94+
def to_kast(self) -> KInner: ...
95+
96+
97+
@dataclass
98+
class NoMetadata(Metadata):
99+
def to_kast(self) -> KInner:
100+
return KApply('noMetadata')
101+
102+
103+
NO_METADATA: Final = NoMetadata()
104+
105+
106+
@dataclass
107+
class StaticSize(Metadata):
108+
size: int
109+
110+
def to_kast(self) -> KInner:
111+
return KApply('staticSize', intToken(self.size))
112+
113+
114+
@dataclass
115+
class DynamicSize(Metadata):
116+
size: int
117+
118+
def to_kast(self) -> KInner:
119+
return KApply('dynamicSize', intToken(self.size))

0 commit comments

Comments
 (0)