1 use std::{
2 collections::{HashSet, hash_map::RandomState},
3 sync::{
4 Arc, RwLock,
5 atomic::{AtomicBool, Ordering},
6 },
7 };
8 use wasmtime::*;
9
engine() -> Option<Engine>10 pub fn engine() -> Option<Engine> {
11 let mut config = Config::new();
12 config.wasm_threads(true);
13 config.shared_memory(true);
14 match Engine::new(&config) {
15 Ok(engine) => {
16 assert!(cfg!(target_pointer_width = "64"));
17 Some(engine)
18 }
19 Err(e) => {
20 assert!(cfg!(target_pointer_width = "32"), "unexpected error {e:?}");
21 None
22 }
23 }
24 }
25
26 #[test]
27 #[cfg_attr(miri, ignore)]
shared_memory_failed_creation() -> Result<()>28 fn shared_memory_failed_creation() -> Result<()> {
29 let mut config = Config::new();
30 config.wasm_threads(true);
31 config.shared_memory(false);
32 let Ok(engine) = Engine::new(&config) else {
33 return Ok(());
34 };
35 assert!(SharedMemory::new(&engine, MemoryType::shared(1, 1)).is_err());
36 let wat = r#"(module (memory 1 1 shared))"#;
37 let module = Module::new(&engine, wat)?;
38 let mut store = Store::new(&engine, ());
39 assert!(Instance::new(&mut store, &module, &[]).is_err());
40 Ok(())
41 }
42
43 #[test]
44 #[cfg_attr(miri, ignore)]
test_instantiate_shared_memory() -> Result<()>45 fn test_instantiate_shared_memory() -> Result<()> {
46 let wat = r#"(module (memory 1 1 shared))"#;
47 let Some(engine) = engine() else {
48 return Ok(());
49 };
50 let module = Module::new(&engine, wat)?;
51 let mut store = Store::new(&engine, ());
52 let _instance = Instance::new(&mut store, &module, &[])?;
53 Ok(())
54 }
55
56 #[test]
57 #[cfg_attr(miri, ignore)]
test_import_shared_memory() -> Result<()>58 fn test_import_shared_memory() -> Result<()> {
59 let wat = r#"(module (import "env" "memory" (memory 1 5 shared)))"#;
60 let Some(engine) = engine() else {
61 return Ok(());
62 };
63 let module = Module::new(&engine, wat)?;
64 let mut store = Store::new(&engine, ());
65 let shared_memory = SharedMemory::new(&engine, MemoryType::shared(1, 5))?;
66 let _instance = Instance::new(&mut store, &module, &[shared_memory.into()])?;
67 Ok(())
68 }
69
70 #[test]
71 #[cfg_attr(miri, ignore)]
test_export_shared_memory() -> Result<()>72 fn test_export_shared_memory() -> Result<()> {
73 let wat = r#"(module (memory (export "memory") 1 5 shared))"#;
74 let Some(engine) = engine() else {
75 return Ok(());
76 };
77 let module = Module::new(&engine, wat)?;
78 let mut store = Store::new(&engine, ());
79 let instance = Instance::new(&mut store, &module, &[])?;
80 let shared_memory = instance.get_shared_memory(&mut store, "memory").unwrap();
81
82 assert_eq!(shared_memory.size(), 1);
83 assert!(shared_memory.ty().is_shared());
84 assert_eq!(shared_memory.ty().maximum(), Some(5));
85
86 Ok(())
87 }
88
89 #[test]
90 #[cfg_attr(miri, ignore)]
test_sharing_of_shared_memory() -> Result<()>91 fn test_sharing_of_shared_memory() -> Result<()> {
92 let wat = r#"(module
93 (import "env" "memory" (memory 1 5 shared))
94 (func (export "first_word") (result i32) (i32.load (i32.const 0)))
95 )"#;
96 let Some(engine) = engine() else {
97 return Ok(());
98 };
99 let module = Module::new(&engine, wat)?;
100 let mut store = Store::new(&engine, ());
101 let shared_memory = SharedMemory::new(&engine, MemoryType::shared(1, 5))?;
102 let instance1 = Instance::new(&mut store, &module, &[shared_memory.clone().into()])?;
103 let instance2 = Instance::new(&mut store, &module, &[shared_memory.clone().into()])?;
104 let data = shared_memory.data();
105
106 // Modify the memory in one place.
107 unsafe {
108 *data[0].get() = 42;
109 }
110
111 // Verify that the memory is the same in all shared locations.
112 let shared_memory_first_word = i32::from_le_bytes(unsafe {
113 [
114 *data[0].get(),
115 *data[1].get(),
116 *data[2].get(),
117 *data[3].get(),
118 ]
119 });
120 let instance1_first_word = instance1
121 .get_typed_func::<(), i32>(&mut store, "first_word")?
122 .call(&mut store, ())?;
123 let instance2_first_word = instance2
124 .get_typed_func::<(), i32>(&mut store, "first_word")?
125 .call(&mut store, ())?;
126 assert_eq!(shared_memory_first_word, 42);
127 assert_eq!(instance1_first_word, 42);
128 assert_eq!(instance2_first_word, 42);
129
130 Ok(())
131 }
132
133 #[test]
134 #[cfg_attr(miri, ignore)]
test_probe_shared_memory_size() -> Result<()>135 fn test_probe_shared_memory_size() -> Result<()> {
136 let wat = r#"(module
137 (memory (export "memory") 1 2 shared)
138 (func (export "size") (result i32) (memory.size))
139 )"#;
140 let Some(engine) = engine() else {
141 return Ok(());
142 };
143 let module = Module::new(&engine, wat)?;
144 let mut store = Store::new(&engine, ());
145 let instance = Instance::new(&mut store, &module, &[])?;
146 let size_fn = instance.get_typed_func::<(), i32>(&mut store, "size")?;
147 let shared_memory = instance.get_shared_memory(&mut store, "memory").unwrap();
148
149 assert_eq!(size_fn.call(&mut store, ())?, 1);
150 assert_eq!(shared_memory.size(), 1);
151
152 shared_memory.grow(1)?;
153
154 assert_eq!(shared_memory.size(), 2);
155 assert_eq!(size_fn.call(&mut store, ())?, 2);
156
157 Ok(())
158 }
159
160 #[test]
161 #[cfg_attr(miri, ignore)]
test_multi_memory() -> Result<()>162 fn test_multi_memory() -> Result<()> {
163 let wat = r#"(module
164 (import "env" "imported" (memory $imported 5 10 shared))
165 (memory (export "owned") 10 20)
166 (memory (export "shared") 1 2 shared)
167 (export "imported" (memory $imported))
168 )"#;
169 let Some(engine) = engine() else {
170 return Ok(());
171 };
172 let module = Module::new(&engine, wat)?;
173 let mut store = Store::new(&engine, ());
174 let incoming_shared_memory = SharedMemory::new(&engine, MemoryType::shared(5, 10))?;
175 let instance = Instance::new(&mut store, &module, &[incoming_shared_memory.into()])?;
176 let owned_memory = instance.get_memory(&mut store, "owned").unwrap();
177 let shared_memory = instance.get_shared_memory(&mut store, "shared").unwrap();
178 let imported_memory = instance.get_shared_memory(&mut store, "imported").unwrap();
179
180 assert_eq!(owned_memory.size(&store), 10);
181 assert_eq!(owned_memory.ty(&store).minimum(), 10);
182 assert_eq!(owned_memory.ty(&store).maximum(), Some(20));
183 assert_eq!(owned_memory.ty(&store).is_shared(), false);
184 assert_eq!(shared_memory.size(), 1);
185 assert_eq!(shared_memory.ty().minimum(), 1);
186 assert_eq!(shared_memory.ty().maximum(), Some(2));
187 assert_eq!(shared_memory.ty().is_shared(), true);
188 assert_eq!(imported_memory.size(), 5);
189 assert_eq!(imported_memory.ty().minimum(), 5);
190 assert_eq!(imported_memory.ty().maximum(), Some(10));
191 assert_eq!(imported_memory.ty().is_shared(), true);
192
193 Ok(())
194 }
195
196 #[test]
197 #[cfg_attr(miri, ignore)]
test_grow_memory_in_multiple_threads() -> Result<()>198 fn test_grow_memory_in_multiple_threads() -> Result<()> {
199 const NUM_THREADS: usize = 4;
200 const NUM_GROW_OPS: usize = 1000;
201
202 let wat = r#"(module
203 (import "env" "memory" (memory 1 4000 shared))
204 (func (export "grow") (param $delta i32) (result i32) (memory.grow (local.get $delta)))
205 )"#;
206
207 let Some(engine) = engine() else {
208 return Ok(());
209 };
210 let module = Module::new(&engine, wat)?;
211 let shared_memory = SharedMemory::new(&engine, MemoryType::shared(1, NUM_GROW_OPS as u32))?;
212 let mut threads = vec![];
213 let observed_sizes = Arc::new(RwLock::new(vec![]));
214
215 // Spawn several threads using a single shared memory and grow the memory
216 // concurrently on all threads.
217 for _ in 0..NUM_THREADS {
218 let engine = engine.clone();
219 let module = module.clone();
220 let observed_sizes = observed_sizes.clone();
221 let shared_memory = shared_memory.clone();
222 let thread = std::thread::spawn(move || {
223 let mut store = Store::new(&engine, ());
224 let instance = Instance::new(&mut store, &module, &[shared_memory.into()]).unwrap();
225 let grow_fn = instance
226 .get_typed_func::<i32, i32>(&mut store, "grow")
227 .unwrap();
228 let mut thread_local_observed_sizes: Vec<_> = (0..NUM_GROW_OPS / NUM_THREADS)
229 .map(|_| grow_fn.call(&mut store, 1).unwrap() as u32)
230 .collect();
231 println!(
232 "Returned memory sizes for {:?}: {:?}",
233 std::thread::current().id(),
234 thread_local_observed_sizes
235 );
236 assert!(is_sorted(thread_local_observed_sizes.as_slice()));
237 observed_sizes
238 .write()
239 .unwrap()
240 .append(&mut thread_local_observed_sizes);
241 });
242 threads.push(thread);
243 }
244
245 // Wait for all threads to finish.
246 for t in threads {
247 t.join().unwrap()
248 }
249
250 // Ensure the returned "old memory sizes" are all unique--i.e., we have not
251 // observed the same growth twice.
252 let unique_observed_sizes: HashSet<u32, RandomState> =
253 HashSet::from_iter(observed_sizes.read().unwrap().iter().cloned());
254 assert_eq!(
255 observed_sizes.read().unwrap().len(),
256 unique_observed_sizes.len()
257 );
258
259 Ok(())
260 }
261
is_sorted(data: &[u32]) -> bool262 fn is_sorted(data: &[u32]) -> bool {
263 data.windows(2).all(|d| d[0] <= d[1])
264 }
265
266 #[test]
267 #[cfg_attr(miri, ignore)]
test_memory_size_accessibility() -> Result<()>268 fn test_memory_size_accessibility() -> Result<()> {
269 const NUM_GROW_OPS: usize = 1000;
270 let wat = r#"(module
271 (import "env" "memory" (memory $memory 1 1000 shared))
272 (func (export "probe_last_available") (result i32)
273 (local $last_address i32)
274 (local.set $last_address (i32.sub (i32.mul (memory.size) (i32.const 0x10000)) (i32.const 4)))
275 (i32.load $memory (local.get $last_address))
276 )
277 )"#;
278
279 let Some(engine) = engine() else {
280 return Ok(());
281 };
282 let module = Module::new(&engine, wat)?;
283 let shared_memory = SharedMemory::new(&engine, MemoryType::shared(1, NUM_GROW_OPS as u32))?;
284 let done = Arc::new(AtomicBool::new(false));
285
286 let grow_memory = shared_memory.clone();
287 let grow_thread = std::thread::spawn(move || {
288 for i in 0..NUM_GROW_OPS {
289 if grow_memory.grow(1).is_err() {
290 println!("stopping at grow operation #{i}");
291 break;
292 }
293 }
294 });
295
296 let probe_memory = shared_memory.clone();
297 let probe_done = done.clone();
298 let probe_thread = std::thread::spawn(move || {
299 let mut store = Store::new(&engine, ());
300 let instance = Instance::new(&mut store, &module, &[probe_memory.into()]).unwrap();
301 let probe_fn = instance
302 .get_typed_func::<(), i32>(&mut store, "probe_last_available")
303 .unwrap();
304 while !probe_done.load(Ordering::SeqCst) {
305 let value = probe_fn.call(&mut store, ()).unwrap() as u32;
306 assert_eq!(value, 0);
307 }
308 });
309
310 grow_thread.join().unwrap();
311 done.store(true, Ordering::SeqCst);
312 probe_thread.join().unwrap();
313
314 Ok(())
315 }
316
317 #[test]
create_shared_memory_through_memory() -> Result<()>318 fn create_shared_memory_through_memory() -> Result<()> {
319 let engine = Engine::default();
320 let mut store = Store::new(&engine, ());
321 assert!(Memory::new(&mut store, MemoryType::shared(1, 1)).is_err());
322 Ok(())
323 }
324