cranelift_codegen/ir/
memtype.rs

1//! Definitions for "memory types" in CLIF.
2//!
3//! A memory type is a struct-like definition -- fields with offsets,
4//! each field having a type and possibly an attached fact -- that we
5//! can use in proof-carrying code to validate accesses to structs and
6//! propagate facts onto the loaded values as well.
7//!
8//! Memory types are meant to be rich enough to describe the *layout*
9//! of values in memory, but do not necessarily need to embody
10//! higher-level features such as subtyping directly. Rather, they
11//! should encode an implementation of a type or object system.
12//!
13//! Note also that it is a non-goal for now for this type system to be
14//! "complete" or fully orthogonal: we have some restrictions now
15//! (e.g., struct fields are only primitives) because this is all we
16//! need for existing PCC applications, and it keeps the
17//! implementation simpler.
18//!
19//! There are a few basic kinds of types:
20//!
21//! - A struct is an aggregate of fields and an overall size. Each
22//!   field has a *primitive Cranelift type*. This is for simplicity's
23//!   sake: we do not allow nested memory types because to do so
24//!   invites cycles, requires recursive computation of sizes, creates
25//!   complicated questions when field types are dynamically-sized,
26//!   and in general is more complexity than we need.
27//!
28//!   The expectation (validated by PCC) is that when a checked load
29//!   or store accesses memory typed by a memory type, accesses will
30//!   only be to fields at offsets named in the type, and will be via
31//!   the given Cranelift type -- i.e., no type-punning occurs in
32//!   memory.
33//!
34//!   The overall size of the struct may be larger than that implied
35//!   by the fields because (i) we may not want or need to name all
36//!   the actually-existing fields in the memory type, and (ii) there
37//!   may be alignment padding that we also don't want or need to
38//!   represent explicitly.
39//!
40//! - A static memory is an untyped blob of storage with a static
41//!   size. This is memory that can be accessed with any type of load
42//!   or store at any valid offset.
43//!
44//!   Note that this is *distinct* from an "array of u8" kind of
45//!   representation of memory, if/when we can represent such a thing,
46//!   because the expectation with memory types' fields (including
47//!   array elements) is that they are strongly typed, only accessed
48//!   via that type, and not type-punned. We don't want to imply any
49//!   restriction on load/store size, or any actual structure, with
50//!   untyped memory; it's just a blob.
51//!
52//! Eventually we plan to also have:
53//!
54//! - A dynamic array is a sequence of struct memory types, with a
55//!   length given by a global value (GV). This is useful to model,
56//!   e.g., tables.
57//!
58//! - A discriminated union is a union of several memory types
59//!   together with a tag field. This will be useful to model and
60//!   verify subtyping/downcasting for Wasm GC, among other uses.
61//!
62//! - Nullability on pointer fields: the fact will hold only if the
63//!   field is not null (all zero bits).
64
65use crate::ir::pcc::Fact;
66use crate::ir::{GlobalValue, Type};
67use alloc::vec::Vec;
68
69#[cfg(feature = "enable-serde")]
70use serde_derive::{Deserialize, Serialize};
71
72/// Data defining a memory type.
73///
74/// A memory type corresponds to a layout of data in memory. It may
75/// have a statically-known or dynamically-known size.
76#[derive(Clone, PartialEq, Hash)]
77#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
78pub enum MemoryTypeData {
79    /// An aggregate consisting of certain fields at certain offsets.
80    ///
81    /// Fields must be sorted by offset, must be within the struct's
82    /// overall size, and must not overlap. These conditions are
83    /// checked by the CLIF verifier.
84    Struct {
85        /// Size of this type.
86        size: u64,
87
88        /// Fields in this type. Sorted by offset.
89        fields: Vec<MemoryTypeField>,
90    },
91
92    /// A statically-sized untyped blob of memory.
93    Memory {
94        /// Accessible size.
95        size: u64,
96    },
97
98    /// A dynamically-sized untyped blob of memory, with bound given
99    /// by a global value plus some static amount.
100    DynamicMemory {
101        /// Static part of size.
102        size: u64,
103        /// Dynamic part of size.
104        gv: GlobalValue,
105    },
106
107    /// A type with no size.
108    Empty,
109}
110
111impl std::default::Default for MemoryTypeData {
112    fn default() -> Self {
113        Self::Empty
114    }
115}
116
117impl std::fmt::Display for MemoryTypeData {
118    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
119        match self {
120            Self::Struct { size, fields } => {
121                write!(f, "struct {size} {{")?;
122                let mut first = true;
123                for field in fields {
124                    if first {
125                        first = false;
126                    } else {
127                        write!(f, ",")?;
128                    }
129                    write!(f, " {}: {}", field.offset, field.ty)?;
130                    if field.readonly {
131                        write!(f, " readonly")?;
132                    }
133                    if let Some(fact) = &field.fact {
134                        write!(f, " ! {fact}")?;
135                    }
136                }
137                write!(f, " }}")?;
138                Ok(())
139            }
140            Self::Memory { size } => {
141                write!(f, "memory {size:#x}")
142            }
143            Self::DynamicMemory { size, gv } => {
144                write!(f, "dynamic_memory {gv}+{size:#x}")
145            }
146            Self::Empty => {
147                write!(f, "empty")
148            }
149        }
150    }
151}
152
153/// One field in a memory type.
154#[derive(Clone, PartialEq, Hash)]
155#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
156pub struct MemoryTypeField {
157    /// The offset of this field in the memory type.
158    pub offset: u64,
159    /// The primitive type of the value in this field. Accesses to the
160    /// field must use this type (i.e., cannot bitcast/type-pun in
161    /// memory).
162    pub ty: Type,
163    /// A proof-carrying-code fact about this value, if any.
164    pub fact: Option<Fact>,
165    /// Whether this field is read-only, i.e., stores should be
166    /// disallowed.
167    pub readonly: bool,
168}
169
170impl MemoryTypeField {
171    /// Get the fact, if any, on a field.
172    pub fn fact(&self) -> Option<&Fact> {
173        self.fact.as_ref()
174    }
175}
176
177impl MemoryTypeData {
178    /// Provide the static size of this type, if known.
179    ///
180    /// (The size may not be known for dynamically-sized arrays or
181    /// memories, when those memtype kinds are added.)
182    pub fn static_size(&self) -> Option<u64> {
183        match self {
184            Self::Struct { size, .. } => Some(*size),
185            Self::Memory { size } => Some(*size),
186            Self::DynamicMemory { .. } => None,
187            Self::Empty => Some(0),
188        }
189    }
190}