xref: /wasmtime-44.0.1/tests/all/threads.rs (revision 0a55f804)
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