walrus/module/
elements.rs

1//! Table elements within a wasm module.
2
3use crate::emit::{Emit, EmitContext};
4use crate::parse::IndicesToIds;
5use crate::tombstone_arena::{Id, Tombstone, TombstoneArena};
6use crate::{ir::Value, ConstExpr, FunctionId, Module, RefType, Result, TableId, ValType};
7use anyhow::{bail, Context};
8
9/// A passive element segment identifier
10pub type ElementId = Id<Element>;
11
12/// A passive segment which contains a list of functions
13#[derive(Debug)]
14pub struct Element {
15    id: Id<Element>,
16    /// The kind of the element segment.
17    pub kind: ElementKind,
18    /// The initial elements of the element segment.
19    pub items: ElementItems,
20    /// The name of this element, used for debugging purposes in the `name`
21    /// custom section.
22    pub name: Option<String>,
23}
24
25/// The kind of element segment.
26#[derive(Debug, Clone)]
27pub enum ElementKind {
28    /// The element segment is passive.
29    Passive,
30    /// The element segment is declared.
31    Declared,
32    /// The element segment is active.
33    Active {
34        /// The ID of the table being initialized.
35        table: TableId,
36        /// A constant defining an offset into that table.
37        offset: ConstExpr,
38    },
39}
40
41/// Represents the items of an element segment.
42#[derive(Debug, Clone)]
43pub enum ElementItems {
44    /// This element contains function indices.
45    Functions(Vec<FunctionId>),
46    /// This element contains constant expressions used to initialize the table.
47    Expressions(RefType, Vec<ConstExpr>),
48}
49
50impl Element {
51    /// Get this segment's id
52    pub fn id(&self) -> Id<Element> {
53        self.id
54    }
55}
56
57impl Tombstone for Element {
58    fn on_delete(&mut self) {
59        // Nothing to do here
60    }
61}
62
63/// All element segments of a wasm module, used to initialize `anyfunc` tables,
64/// used as function pointers.
65#[derive(Debug, Default)]
66pub struct ModuleElements {
67    arena: TombstoneArena<Element>,
68}
69
70impl ModuleElements {
71    /// Get an element associated with an ID
72    pub fn get(&self, id: ElementId) -> &Element {
73        &self.arena[id]
74    }
75
76    /// Get an element associated with an ID
77    pub fn get_mut(&mut self, id: ElementId) -> &mut Element {
78        &mut self.arena[id]
79    }
80
81    /// Delete an elements entry from this module.
82    ///
83    /// It is up to you to ensure that all references to this deleted element
84    /// are removed.
85    pub fn delete(&mut self, id: ElementId) {
86        self.arena.delete(id);
87    }
88
89    /// Get a shared reference to this module's elements.
90    pub fn iter(&self) -> impl Iterator<Item = &Element> {
91        self.arena.iter().map(|(_, f)| f)
92    }
93
94    /// Get a mutable reference to this module's elements.
95    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Element> {
96        self.arena.iter_mut().map(|(_, f)| f)
97    }
98
99    /// Add an element segment
100    pub fn add(&mut self, kind: ElementKind, items: ElementItems) -> ElementId {
101        let id = self.arena.next_id();
102        let id2 = self.arena.alloc(Element {
103            id,
104            kind,
105            items,
106            name: None,
107        });
108        debug_assert_eq!(id, id2);
109        id
110    }
111}
112
113impl Module {
114    /// Parses a raw was section into a fully-formed `ModuleElements` instance.
115    pub(crate) fn parse_elements(
116        &mut self,
117        section: wasmparser::ElementSectionReader,
118        ids: &mut IndicesToIds,
119    ) -> Result<()> {
120        log::debug!("parse element section");
121        for (i, segment) in section.into_iter().enumerate() {
122            let element = segment?;
123
124            let items = match element.items {
125                wasmparser::ElementItems::Functions(funcs) => {
126                    let mut function_ids = Vec::with_capacity(funcs.count() as usize);
127                    for func in funcs {
128                        match ids.get_func(func?) {
129                            Ok(func) => function_ids.push(func),
130                            Err(_) => bail!("invalid function index in element segment {}", i),
131                        }
132                    }
133                    ElementItems::Functions(function_ids)
134                }
135                wasmparser::ElementItems::Expressions(ref_type, items) => {
136                    let ty = match ref_type {
137                        wasmparser::RefType::FUNCREF => RefType::Funcref,
138                        wasmparser::RefType::EXTERNREF => RefType::Externref,
139                        wasmparser::RefType::EXNREF => RefType::Exnref,
140                        _ => bail!("unsupported ref type in element segment {}", i),
141                    };
142                    let mut const_exprs = Vec::with_capacity(items.count() as usize);
143                    for item in items {
144                        let const_expr = item?;
145                        let expr = ConstExpr::eval(&const_expr, ids).with_context(|| {
146                            format!(
147                                "Failed to evaluate a const expr in element segment {}:\n{:?}",
148                                i, const_expr
149                            )
150                        })?;
151                        const_exprs.push(expr);
152                    }
153                    ElementItems::Expressions(ty, const_exprs)
154                }
155            };
156
157            let id = self.elements.arena.next_id();
158
159            let kind = match element.kind {
160                wasmparser::ElementKind::Passive => ElementKind::Passive,
161                wasmparser::ElementKind::Declared => ElementKind::Declared,
162                wasmparser::ElementKind::Active {
163                    table_index,
164                    offset_expr,
165                } => {
166                    // TODO: Why table_index is Option?
167                    let table_id = ids.get_table(table_index.unwrap_or_default())?;
168                    let table = self.tables.get_mut(table_id);
169                    table.elem_segments.insert(id);
170
171                    let offset = ConstExpr::eval(&offset_expr, ids).with_context(|| {
172                        format!("failed to evaluate the offset of element {}", i)
173                    })?;
174                    if table.table64 {
175                        match offset {
176                            ConstExpr::Value(Value::I64(_)) => {}
177                            ConstExpr::Global(global)
178                                if self.globals.get(global).ty == ValType::I64 => {}
179                            _ => bail!(
180                                "element {} is active for 64-bit table but has non-i64 offset",
181                                i
182                            ),
183                        }
184                    } else {
185                        match offset {
186                            ConstExpr::Value(Value::I32(_)) => {}
187                            ConstExpr::Global(global)
188                                if self.globals.get(global).ty == ValType::I32 => {}
189                            _ => bail!(
190                                "element {} is active for 32-bit table but has non-i32 offset",
191                                i
192                            ),
193                        }
194                    }
195
196                    ElementKind::Active {
197                        table: table_id,
198                        offset,
199                    }
200                }
201            };
202            self.elements.arena.alloc(Element {
203                id,
204                kind,
205                items,
206                name: None,
207            });
208            ids.push_element(id);
209        }
210        Ok(())
211    }
212}
213
214impl Emit for ModuleElements {
215    fn emit(&self, cx: &mut EmitContext) {
216        if self.arena.len() == 0 {
217            return;
218        }
219
220        let mut wasm_element_section = wasm_encoder::ElementSection::new();
221
222        for (id, element) in self.arena.iter() {
223            cx.indices.push_element(id);
224
225            match &element.items {
226                ElementItems::Functions(function_ids) => {
227                    let idx = function_ids
228                        .iter()
229                        .map(|&func| cx.indices.get_func_index(func))
230                        .collect::<Vec<_>>();
231                    let els = wasm_encoder::Elements::Functions(std::borrow::Cow::Borrowed(&idx));
232                    emit_elem(cx, &mut wasm_element_section, &element.kind, els);
233                }
234                ElementItems::Expressions(ty, const_exprs) => {
235                    let ref_type = match ty {
236                        RefType::Funcref => wasm_encoder::RefType::FUNCREF,
237                        RefType::Externref => wasm_encoder::RefType::EXTERNREF,
238                        RefType::Exnref => wasm_encoder::RefType::EXNREF,
239                    };
240                    let const_exprs = const_exprs
241                        .iter()
242                        .map(|expr| expr.to_wasmencoder_type(cx))
243                        .collect::<Vec<_>>();
244                    let els = wasm_encoder::Elements::Expressions(
245                        ref_type,
246                        std::borrow::Cow::Borrowed(&const_exprs),
247                    );
248                    emit_elem(cx, &mut wasm_element_section, &element.kind, els);
249                }
250            }
251
252            fn emit_elem(
253                cx: &mut EmitContext,
254                wasm_element_section: &mut wasm_encoder::ElementSection,
255                kind: &ElementKind,
256                els: wasm_encoder::Elements,
257            ) {
258                match kind {
259                    ElementKind::Active { table, offset } => {
260                        // When the table index is 0, set this to `None` to tell `wasm-encoder` to use
261                        // the backwards-compatible MVP encoding.
262                        let table_index =
263                            Some(cx.indices.get_table_index(*table)).filter(|&index| index != 0);
264                        wasm_element_section.active(
265                            table_index,
266                            &offset.to_wasmencoder_type(cx),
267                            els,
268                        );
269                    }
270                    ElementKind::Passive => {
271                        wasm_element_section.passive(els);
272                    }
273                    ElementKind::Declared => {
274                        wasm_element_section.declared(els);
275                    }
276                }
277            }
278        }
279
280        cx.wasm_module.section(&wasm_element_section);
281    }
282}