wain_exec/
memory.rs

1use crate::globals::Globals;
2use crate::trap::{Result, Trap, TrapReason};
3use crate::value::LittleEndian;
4use std::any;
5use std::mem::size_of;
6use wain_ast as ast;
7
8const PAGE_SIZE: usize = 65536; // 64Ki
9const MAX_MEMORY_BYTES: usize = u32::MAX as usize; // Address space of Wasm is 32bits
10
11// Memory instance
12//
13// Note: It is more efficient to implement memory buffer by memory mapped buffer. However there is
14// no way to use mmap without unsafe.
15pub struct Memory {
16    max: Option<u32>,
17    data: Vec<u8>,
18}
19
20impl Memory {
21    // https://webassembly.github.io/spec/core/exec/modules.html#alloc-mem
22    pub fn allocate(memories: &[ast::Memory]) -> Result<Self> {
23        // Note: Only one memory exists thanks to validation
24        assert!(memories.len() <= 1);
25        if let Some(memory) = memories.get(0) {
26            if let Some(i) = &memory.import {
27                Err(Trap::unknown_import(i, "memory", memory.start))
28            } else {
29                let (min, max) = match &memory.ty.limit {
30                    ast::Limits::Range(min, max) => (*min, Some(*max)),
31                    ast::Limits::From(min) => (*min, None),
32                };
33                let data = if min == 0 {
34                    vec![]
35                } else {
36                    let len = (min as usize) * PAGE_SIZE;
37                    vec![0; len]
38                };
39                Ok(Self { max, data })
40            }
41        } else {
42            // When no table is set use dummy empty table
43            Ok(Self {
44                max: Some(0),
45                data: vec![],
46            })
47        }
48    }
49
50    // 10. and 14. https://webassembly.github.io/spec/core/exec/modules.html#instantiation
51    pub fn new_data(&mut self, segment: &ast::DataSegment, globals: &Globals) -> Result<()> {
52        // By validation of constant expression, at least one instruction in the sequence is guaranteed
53        // and type must be i32
54        let offset = match &segment.offset[segment.offset.len() - 1].kind {
55            ast::InsnKind::GlobalGet(idx) => globals.get(*idx),
56            ast::InsnKind::I32Const(i) => *i,
57            _ => unreachable!("unexpected instruction for element offset"),
58        };
59        let offset = offset as usize;
60        let data = &segment.data;
61        let end_addr = offset + data.len();
62
63        if let Some(max) = self.max {
64            let max = max as usize;
65            if end_addr > max * PAGE_SIZE {
66                return Err(Trap::new(
67                    TrapReason::OutOfLimit {
68                        max,
69                        idx: end_addr,
70                        kind: "data element",
71                    },
72                    segment.start,
73                ));
74            }
75        }
76
77        if self.data.len() < end_addr {
78            return Err(Trap::new(
79                TrapReason::DataSegmentOutOfBuffer {
80                    segment_end: end_addr,
81                    buffer_size: self.data.len(),
82                },
83                segment.start,
84            ));
85        }
86
87        // TODO: copy_from_slice is slower here. I need to investigate the reason
88        #[allow(clippy::manual_memcpy)]
89        {
90            for i in 0..data.len() {
91                self.data[offset + i] = data[i];
92            }
93        }
94
95        Ok(())
96    }
97
98    pub fn size(&self) -> u32 {
99        (self.data.len() / PAGE_SIZE) as u32
100    }
101
102    pub fn grow(&mut self, num_pages: u32) -> i32 {
103        // https://webassembly.github.io/spec/core/exec/instructions.html#exec-memory-grow
104        let prev = self.size();
105        let (next, overflow) = prev.overflowing_add(num_pages);
106        if overflow {
107            return -1;
108        }
109        if let Some(max) = self.max {
110            if next > max {
111                return -1;
112            }
113        }
114        let next_len = (next as usize) * PAGE_SIZE;
115        if next_len > MAX_MEMORY_BYTES {
116            // Note: WebAssembly spec does not limit max size of memory when no limit is specified
117            // to memory section. However, an address value is u32. When memory size is larger than
118            // UINT32_MAX, there is no way to refer it (except for using static offset value).
119            // And memory_grow.wast expects allocating more than 2^32 - 1 to fail.
120            return -1;
121        }
122        self.data.resize(next_len, 0);
123        prev as i32
124    }
125
126    fn check_addr<V: LittleEndian>(&self, addr: usize, at: usize, operation: &'static str) -> Result<()> {
127        if addr.saturating_add(size_of::<V>()) > self.data.len() {
128            Err(Trap::new(
129                TrapReason::LoadMemoryOutOfRange {
130                    max: self.data.len(),
131                    addr,
132                    operation,
133                    ty: any::type_name::<V>(),
134                },
135                at,
136            ))
137        } else {
138            Ok(())
139        }
140    }
141
142    // https://webassembly.github.io/spec/core/exec/instructions.html#and
143    pub fn load<V: LittleEndian>(&self, addr: usize, at: usize) -> Result<V> {
144        self.check_addr::<V>(addr, at, "load")?;
145        Ok(LittleEndian::read(&self.data, addr))
146    }
147
148    // https://webassembly.github.io/spec/core/exec/instructions.html#and
149    pub fn store<V: LittleEndian>(&mut self, addr: usize, v: V, at: usize) -> Result<()> {
150        self.check_addr::<V>(addr, at, "store")?;
151        LittleEndian::write(&mut self.data, addr, v);
152        Ok(())
153    }
154
155    pub fn data(&self) -> &'_ [u8] {
156        &self.data
157    }
158
159    pub fn data_mut(&mut self) -> &mut [u8] {
160        &mut self.data
161    }
162}