Skip to content

Commit

Permalink
initial enum decoding and encoding, including well-formedness
Browse files Browse the repository at this point in the history
  • Loading branch information
essickmango committed Nov 11, 2023
1 parent 443c8ea commit 7be7a78
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 9 deletions.
52 changes: 47 additions & 5 deletions spec/lang/representation.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,16 +229,58 @@ impl Type {

### Enums

TODO: implement Enum decoding & encoding.
Enum encoding and decoding.
Should we let the type decoding throw UB?

```rust
fn compute_discriminant<M: Memory>(bytes: List<AbstractByte<M::Provenance>>, discriminator: Discriminator) -> Option<Int> {
let mut disc = discriminator;
loop {
match disc {
Discriminator::Known(val) => break Some(val),
Discriminator::Unknown { offset, children } => {
if offset < 0 || offset >= bytes.len() { throw!(); }
let AbstractByte::Init(val, _) = bytes.get(offset).unwrap()
else { break None };
// else { throw_ub!("Encountered uninitialized byte while computing enum discriminant.") };
let Some(new_disc) = children.get(val)
else { break None };
// else { throw_ub!("Encountered invalid discriminant value.") };
disc = new_disc;
}
}
}
}

impl Type {
fn decode<M: Memory>(Type::Enum { .. }: Self, bytes: List<AbstractByte<M::Provenance>>) -> Option<Value<M>> {
todo!()
fn decode<M: Memory>(Type::Enum { variants, tag_encoding, size, .. }: Self, bytes: List<AbstractByte<M::Provenance>>) -> Option<Value<M>> {
if bytes.len() != size.bytes() { throw!(); }
let disc = compute_discriminant::<M>(bytes, tag_encoding.discriminator)?;

// The discriminator does the discriminant check and as such should not be able to return an invalid value.
if disc < Int::ZERO || disc >= variants.len() { throw!(); }
variants.get(disc).unwrap().decode(bytes)
}

fn encode<M: Memory>(Type::Enum { .. }: Self, val: Value<M>) -> List<AbstractByte<M::Provenance>> {
todo!()
fn encode<M: Memory>(Type::Enum { variants, tag_encoding, size, .. }: Self, val: Value<M>) -> List<AbstractByte<M::Provenance>> {
let Value::Variant { idx, data } = val else { panic!() };
assert_eq!(variants.len(), tag_encoding.tagger.len());

let mut bytes = list![AbstractByte::Uninit; size.bytes()];

// idx is to be guaranteed in bounds by the well-formed check in the typed store.
let variant = variants.get(idx).unwrap();
let encoded_data = variant.encode(data.extract());
assert!(encoded_data.len() <= size.bytes());
bytes.write_subslice_at_index(Int::ZERO, encoded_data);

let tagger = tag_encoding.tagger.get(idx).unwrap();

// TODO: is there a nicer way instead of all these temporary lists?
for (offset, value) in tagger.iter() {
bytes.set(offset, value);
}
bytes
}
}
```
Expand Down
13 changes: 12 additions & 1 deletion spec/lang/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,18 @@ pub type Fields = List<(Offset, Type)>;
/// We leave the details of enum tags to the future.
/// (We might want to extend the "variants" field of `Enum` to also have a
/// discriminant for each variant. We will see.)
pub enum TagEncoding { /* ... */ }
pub struct TagEncoding {
discriminator: Discriminator,
tagger: List<Map<Int, u8>>, // TODO: move to Variant struct
}

pub enum Discriminator {
Known(Int),
Unknown { // Branch
offset: Int,
children: Map<u8, Discriminator>,
},
}
```

Note that references have no lifetime, since the lifetime is irrelevant for their representation in memory!
Expand Down
12 changes: 9 additions & 3 deletions spec/lang/well-formed.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,16 @@ impl Type {
// And they must all fit into the size.
ensure(size >= last_end)?;
}
Enum { variants, size, tag_encoding: _, align: _ } => {
Enum { variants, size, tag_encoding, align: _ } => {
// All the variants need to be well-formed and fit in the enum.
for variant in variants {
variant.check_wf::<T>()?;
ensure(size >= variant.size::<T>())?;
}
// And we need a tagger for each variant (even if they are empty).
ensure(variants.len() == tag_encoding.tagger.len())?;
// TODO: should we ensure that the discriminator can reach a) all idx and b) only valid idx?
// not all idx need to be reachable, but tagger & discriminator need to be in bounds
}
}

Expand Down Expand Up @@ -564,8 +569,9 @@ impl<M: Memory> Value<M> {
}
}
(Value::Variant { idx, data }, Type::Enum { variants, .. }) => {
ensure(idx < variants.len())?;
data.check_wf(variants[idx])?;
// TODO: check if enum is uninhabited
let variant = variants.get(idx)?;
data.check_wf(variant)?;
}
_ => throw!()
}
Expand Down

0 comments on commit 7be7a78

Please sign in to comment.