1 use std::{ 2 collections::{HashSet, hash_map::RandomState}, 3 sync::{ 4 Arc, RwLock, 5 atomic::{AtomicBool, Ordering}, 6 }, 7 }; 8 use wasmtime::*; 9 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)] 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)] 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)] 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)] 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)] 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)] 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)] 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)] 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 262 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)] 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] 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