wain_syntax_text/
compose.rs

1// https://webassembly.github.io/spec/core/text/modules.html#text-module
2//
3// The following restrictions are imposed on the composition of modules: `m1 ⊕ m2` is
4// defined if and only if
5//
6// - m1.start = ϵ ∨ m2.start = ϵ
7// - m1.funcs = m1.tables = m1.mems = m1.globals = ϵ ∨ m2.imports = ϵ
8//
9use crate::source::describe_position;
10use std::fmt;
11use wain_ast::*;
12
13pub struct ComposeError<'source> {
14    dest_mod_id: Option<&'source str>,
15    dest_mod_offset: usize,
16    src_mod_id: Option<&'source str>,
17    src_mod_offset: usize,
18    offset: usize,
19    source: &'source str,
20    msg: String,
21}
22
23impl<'s> ComposeError<'s> {
24    pub fn source(&self) -> &'s str {
25        self.source
26    }
27    pub fn offset(&self) -> usize {
28        self.offset
29    }
30}
31
32impl<'s> fmt::Display for ComposeError<'s> {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        f.write_str("module ")?;
35        if let Some(id) = self.src_mod_id {
36            write!(f, "'{}' ", id)?;
37        }
38        write!(
39            f,
40            "at offset {} cannot be merged into existing module ",
41            self.src_mod_offset
42        )?;
43
44        if let Some(id) = self.dest_mod_id {
45            write!(f, "'{}' ", id)?;
46        }
47        write!(f, "at offset {}: {}", self.dest_mod_offset, self.msg)?;
48
49        describe_position(f, self.source, self.offset)
50    }
51}
52
53type Result<'s, T> = ::std::result::Result<T, Box<ComposeError<'s>>>;
54
55pub(crate) struct Composer<'source> {
56    source: &'source str,
57    target: Module<'source>,
58    composed_mod_id: Option<&'source str>,
59    composed_mod_offset: usize,
60    composing_imports: bool,
61}
62
63impl<'s> Composer<'s> {
64    pub(crate) fn new(target: Module<'s>, source: &'s str) -> Self {
65        Composer {
66            source,
67            target,
68            composed_mod_id: None,
69            composed_mod_offset: Default::default(),
70            composing_imports: false,
71        }
72    }
73
74    pub(crate) fn error<T>(&self, msg: String, offset: usize) -> Result<'s, T> {
75        Err(Box::new(ComposeError {
76            dest_mod_id: self.target.id,
77            dest_mod_offset: self.target.start,
78            src_mod_id: self.composed_mod_id,
79            src_mod_offset: self.composed_mod_offset,
80            offset,
81            source: self.source,
82            msg,
83        }))
84    }
85
86    pub(crate) fn compose(mut self, mut composed: Module<'s>) -> Result<Module<'s>> {
87        self.composed_mod_id = composed.id;
88        self.composed_mod_offset = composed.start;
89
90        // Check m1.start = ϵ ∨ m2.start = ϵ
91        if let (Some(s1), Some(s2)) = (&self.target.entrypoint, &composed.entrypoint) {
92            let msg = format!(
93                "start function can appear only once across modules: previous start function was defined at offset {}",
94                s1.start
95            );
96            return self.error(msg, s2.start);
97        }
98
99        // Adjust indices and check import exists
100        composed.adjust(&mut self)?;
101
102        // Check m1.funcs = m1.tables = m1.mems = m1.globals = ϵ ∨ m2.imports = ϵ
103        if !(self.target.funcs.is_empty()
104            && self.target.tables.is_empty()
105            && self.target.memories.is_empty()
106            && self.target.globals.is_empty()
107            || !self.composing_imports)
108        {
109            let msg = "when module M1 is merged into module M2, one of (1) or (2) must be met. \
110                        (1) M1 has no 'import' section. \
111                        (2) M2 has no 'func', 'table', 'memory' sections"
112                .to_string();
113            return self.error(msg, self.target.start);
114        }
115
116        // Compose module fields
117        self.target.types.append(&mut composed.types);
118        self.target.exports.append(&mut composed.exports);
119        self.target.funcs.append(&mut composed.funcs);
120        self.target.elems.append(&mut composed.elems);
121        self.target.tables.append(&mut composed.tables);
122        self.target.data.append(&mut composed.data);
123        self.target.memories.append(&mut composed.memories);
124        self.target.globals.append(&mut composed.globals);
125        if let Some(start) = composed.entrypoint {
126            self.target.entrypoint = Some(start);
127        }
128
129        Ok(self.target)
130    }
131
132    fn saw_import(&mut self, has_import: bool) {
133        self.composing_imports = self.composing_imports || has_import;
134    }
135
136    fn adjust_func_idx(&self, idx: &mut u32) {
137        *idx += self.target.funcs.len() as u32;
138    }
139
140    fn adjust_type_idx(&self, idx: &mut u32) {
141        *idx += self.target.types.len() as u32;
142    }
143
144    fn adjust_global_idx(&self, idx: &mut u32) {
145        *idx += self.target.globals.len() as u32;
146    }
147
148    fn adjust_mem_idx(&self, idx: &mut u32) {
149        *idx += self.target.memories.len() as u32;
150    }
151
152    fn adjust_table_idx(&self, idx: &mut u32) {
153        *idx += self.target.tables.len() as u32;
154    }
155}
156
157// Adjust fields of AST nodes for composing one module into another.
158// Indices of functions, tables, memories, globals and types are module local things. When two modules
159// are composed, these indices in the merged module need to be updated.
160// This visitor also checks the condition of composing two modules.
161trait Adjust<'s>: Sized {
162    fn adjust(&mut self, composer: &mut Composer) -> Result<'s, ()>;
163}
164
165impl<'s, A: Adjust<'s>> Adjust<'s> for Vec<A> {
166    fn adjust(&mut self, composer: &mut Composer) -> Result<'s, ()> {
167        self.iter_mut().try_for_each(|a| a.adjust(composer))
168    }
169}
170
171impl<'s, A: Adjust<'s>> Adjust<'s> for Option<A> {
172    fn adjust(&mut self, composer: &mut Composer) -> Result<'s, ()> {
173        match self {
174            Some(node) => node.adjust(composer),
175            None => Ok(()),
176        }
177    }
178}
179
180impl<'s> Adjust<'s> for Module<'s> {
181    fn adjust(&mut self, composer: &mut Composer) -> Result<'s, ()> {
182        self.entrypoint.adjust(composer)?;
183        self.exports.adjust(composer)?;
184        self.funcs.adjust(composer)?;
185        self.elems.adjust(composer)?;
186        for table in self.tables.iter() {
187            composer.saw_import(table.import.is_some());
188        }
189        self.data.adjust(composer)?;
190        self.memories.adjust(composer)?;
191        self.globals.adjust(composer)?;
192        Ok(())
193    }
194}
195
196impl<'s> Adjust<'s> for StartFunction {
197    fn adjust(&mut self, composer: &mut Composer) -> Result<'s, ()> {
198        composer.adjust_func_idx(&mut self.idx);
199        Ok(())
200    }
201}
202
203impl<'s> Adjust<'s> for Func<'s> {
204    fn adjust(&mut self, composer: &mut Composer) -> Result<'s, ()> {
205        composer.adjust_type_idx(&mut self.idx);
206        match &mut self.kind {
207            FuncKind::Import(_) => composer.saw_import(true),
208            FuncKind::Body { expr, .. } => expr.adjust(composer)?,
209        }
210        Ok(())
211    }
212}
213
214impl<'s> Adjust<'s> for Instruction {
215    fn adjust(&mut self, composer: &mut Composer) -> Result<'s, ()> {
216        use InsnKind::*;
217        match &mut self.kind {
218            Block { body, .. } => body.adjust(composer)?,
219            Loop { body, .. } => body.adjust(composer)?,
220            If {
221                then_body, else_body, ..
222            } => {
223                then_body.adjust(composer)?;
224                else_body.adjust(composer)?;
225            }
226            Call(idx) => composer.adjust_func_idx(idx),
227            CallIndirect(idx) => composer.adjust_type_idx(idx),
228            GlobalGet(idx) => composer.adjust_global_idx(idx),
229            GlobalSet(idx) => composer.adjust_global_idx(idx),
230            _ => {}
231        }
232        Ok(())
233    }
234}
235
236impl<'s> Adjust<'s> for Export<'s> {
237    fn adjust(&mut self, composer: &mut Composer) -> Result<'s, ()> {
238        match &mut self.kind {
239            ExportKind::Func(idx) => composer.adjust_func_idx(idx),
240            ExportKind::Table(idx) => composer.adjust_table_idx(idx),
241            ExportKind::Memory(idx) => composer.adjust_mem_idx(idx),
242            ExportKind::Global(idx) => composer.adjust_global_idx(idx),
243        }
244        Ok(())
245    }
246}
247
248impl<'s> Adjust<'s> for ElemSegment {
249    fn adjust(&mut self, composer: &mut Composer) -> Result<'s, ()> {
250        composer.adjust_table_idx(&mut self.idx);
251        for idx in self.init.iter_mut() {
252            composer.adjust_func_idx(idx);
253        }
254        self.offset.adjust(composer)
255    }
256}
257
258impl<'s> Adjust<'s> for DataSegment<'s> {
259    fn adjust(&mut self, composer: &mut Composer) -> Result<'s, ()> {
260        composer.adjust_mem_idx(&mut self.idx);
261        self.offset.adjust(composer)
262    }
263}
264
265impl<'s> Adjust<'s> for Memory<'s> {
266    fn adjust(&mut self, composer: &mut Composer) -> Result<'s, ()> {
267        composer.saw_import(self.import.is_some());
268        Ok(())
269    }
270}
271
272impl<'s> Adjust<'s> for Global<'s> {
273    fn adjust(&mut self, composer: &mut Composer) -> Result<'s, ()> {
274        match &mut self.kind {
275            GlobalKind::Import(_) => composer.saw_import(true),
276            GlobalKind::Init(init) => init.adjust(composer)?,
277        }
278        Ok(())
279    }
280}