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}