1 use anyhow::Context; 2 use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; 3 use std::sync::Arc; 4 use wasmtime::*; 5 use wasmtime_test_macros::wasmtime_test; 6 7 #[test] 8 fn checks_incompatible_target() -> Result<()> { 9 // For platforms that Cranelift supports make sure a mismatch generates an 10 // error 11 if cfg!(target_arch = "x86_64") 12 || cfg!(target_arch = "aarch64") 13 || cfg!(target_arch = "s390x") 14 || cfg!(target_arch = "riscv64") 15 { 16 let mut target = target_lexicon::Triple::host(); 17 target.operating_system = target_lexicon::OperatingSystem::Unknown; 18 assert_invalid_target(&target.to_string())?; 19 } 20 21 // Otherwise make sure that the wrong pulley target is rejected on all 22 // platforms. 23 let wrong_pulley = if cfg!(target_pointer_width = "32") { 24 "pulley64" 25 } else { 26 "pulley32" 27 }; 28 assert_invalid_target(wrong_pulley)?; 29 30 return Ok(()); 31 32 fn assert_invalid_target(target: &str) -> Result<()> { 33 match Module::new(&Engine::new(Config::new().target(target)?)?, "(module)") { 34 Ok(_) => unreachable!(), 35 Err(e) => assert!( 36 format!("{e:?}").contains("configuration does not match the host"), 37 "bad error: {e:?}" 38 ), 39 } 40 41 Ok(()) 42 } 43 } 44 45 #[test] 46 fn caches_across_engines() { 47 let c = Config::new(); 48 49 let bytes = Module::new(&Engine::new(&c).unwrap(), "(module)") 50 .unwrap() 51 .serialize() 52 .unwrap(); 53 54 unsafe { 55 let res = Module::deserialize(&Engine::default(), &bytes); 56 assert!(res.is_ok()); 57 58 // differ in runtime settings 59 let res = Module::deserialize( 60 &Engine::new(Config::new().memory_reservation(0)).unwrap(), 61 &bytes, 62 ); 63 assert!(res.is_err()); 64 65 // differ in wasm features enabled (which can affect 66 // runtime/compilation settings) 67 let res = Module::deserialize( 68 &Engine::new(Config::new().wasm_relaxed_simd(false)).unwrap(), 69 &bytes, 70 ); 71 assert!(res.is_err()); 72 } 73 } 74 75 #[test] 76 #[cfg_attr(miri, ignore)] 77 fn aot_compiles() -> Result<()> { 78 let engine = Engine::default(); 79 let bytes = engine.precompile_module( 80 "(module (func (export \"f\") (param i32) (result i32) local.get 0))".as_bytes(), 81 )?; 82 83 let module = unsafe { Module::deserialize(&engine, &bytes)? }; 84 85 let mut store = Store::new(&engine, ()); 86 let instance = Instance::new(&mut store, &module, &[])?; 87 88 let f = instance.get_typed_func::<i32, i32>(&mut store, "f")?; 89 assert_eq!(f.call(&mut store, 101)?, 101); 90 91 Ok(()) 92 } 93 94 #[test] 95 #[cfg_attr(miri, ignore)] 96 fn serialize_deterministic() { 97 let engine = Engine::default(); 98 99 let assert_deterministic = |wasm: &str| { 100 let p1 = engine.precompile_module(wasm.as_bytes()).unwrap(); 101 let p2 = engine.precompile_module(wasm.as_bytes()).unwrap(); 102 if p1 != p2 { 103 panic!("precompile_module not deterministic for:\n{wasm}"); 104 } 105 106 let module1 = Module::new(&engine, wasm).unwrap(); 107 let a1 = module1.serialize().unwrap(); 108 let a2 = module1.serialize().unwrap(); 109 if a1 != a2 { 110 panic!("Module::serialize not deterministic for:\n{wasm}"); 111 } 112 113 let module2 = Module::new(&engine, wasm).unwrap(); 114 let b1 = module2.serialize().unwrap(); 115 let b2 = module2.serialize().unwrap(); 116 if b1 != b2 { 117 panic!("Module::serialize not deterministic for:\n{wasm}"); 118 } 119 120 if a1 != b2 { 121 panic!("not matching across modules:\n{wasm}"); 122 } 123 if b1 != p2 { 124 panic!("not matching across engine/module:\n{wasm}"); 125 } 126 }; 127 128 assert_deterministic("(module)"); 129 assert_deterministic("(module (func))"); 130 assert_deterministic("(module (func nop))"); 131 assert_deterministic("(module (func) (func (param i32)))"); 132 assert_deterministic("(module (func (export \"f\")) (func (export \"y\")))"); 133 assert_deterministic("(module (func $f) (func $g))"); 134 assert_deterministic("(module (data \"\") (data \"\"))"); 135 assert_deterministic("(module (elem func) (elem func))"); 136 } 137 138 // This test asserts that the optimization to transform separate data segments 139 // into an initialization image doesn't unnecessarily create a massive module by 140 // accident with a very large initialization image in it. 141 #[test] 142 fn serialize_not_overly_massive() -> Result<()> { 143 let mut config = Config::new(); 144 config.memory_guaranteed_dense_image_size(1 << 20); 145 let engine = Engine::new(&config)?; 146 147 let assert_smaller_than_1mb = |module: &str| -> Result<()> { 148 println!("{module}"); 149 let bytes = Module::new(&engine, module)?.serialize()?; 150 assert!(bytes.len() < (1 << 20)); 151 Ok(()) 152 }; 153 154 // Tons of space between data segments should use sparse initialization, 155 // along with various permutations of empty and nonempty segments. 156 assert_smaller_than_1mb( 157 r#"(module 158 (memory 20000) 159 (data (i32.const 0) "a") 160 (data (i32.const 0x200000) "b") 161 )"#, 162 )?; 163 assert_smaller_than_1mb( 164 r#"(module 165 (memory 20000) 166 (data (i32.const 0) "a") 167 (data (i32.const 0x200000) "") 168 )"#, 169 )?; 170 assert_smaller_than_1mb( 171 r#"(module 172 (memory 20000) 173 (data (i32.const 0) "") 174 (data (i32.const 0x200000) "b") 175 )"#, 176 )?; 177 assert_smaller_than_1mb( 178 r#"(module 179 (memory 20000) 180 (data (i32.const 0) "") 181 (data (i32.const 0x200000) "") 182 )"#, 183 )?; 184 185 // lone data segment 186 assert_smaller_than_1mb( 187 r#"(module 188 (memory 20000) 189 (data (i32.const 0x200000) "b") 190 )"#, 191 )?; 192 193 Ok(()) 194 } 195 196 // This test specifically disables SSE4.1 in Cranelift which force wasm 197 // instructions like `f32.ceil` to go through libcalls instead of using native 198 // instructions. Note that SIMD is also disabled here because SIMD otherwise 199 // requires SSE4.1 to be enabled. 200 // 201 // This test then also tests that loading modules through various means, e.g. 202 // through precompiled artifacts, all works. 203 #[test] 204 #[cfg_attr(any(not(target_arch = "x86_64"), miri), ignore)] 205 fn missing_sse_and_floats_still_works() -> Result<()> { 206 let mut config = Config::new(); 207 config.wasm_simd(false).wasm_relaxed_simd(false); 208 unsafe { 209 config.cranelift_flag_set("has_sse41", "false"); 210 } 211 let engine = Engine::new(&config)?; 212 let module = Module::new( 213 &engine, 214 r#" 215 (module 216 (func (export "f32.ceil") (param f32) (result f32) 217 local.get 0 218 f32.ceil) 219 ) 220 "#, 221 )?; 222 let bytes = module.serialize()?; 223 let module2 = unsafe { Module::deserialize(&engine, &bytes)? }; 224 let tmpdir = tempfile::TempDir::new()?; 225 let path = tmpdir.path().join("module.cwasm"); 226 std::fs::write(&path, &bytes)?; 227 let module3 = unsafe { Module::deserialize_file(&engine, &path)? }; 228 229 for module in [module, module2, module3] { 230 let mut store = Store::new(&engine, ()); 231 let instance = Instance::new(&mut store, &module, &[])?; 232 let ceil = instance.get_typed_func::<f32, f32>(&mut store, "f32.ceil")?; 233 234 for f in [1.0, 2.3, -1.3] { 235 assert_eq!(ceil.call(&mut store, f)?, f.ceil()); 236 } 237 } 238 239 Ok(()) 240 } 241 242 #[test] 243 #[cfg_attr(miri, ignore)] 244 fn large_add_chain_no_stack_overflow() -> Result<()> { 245 let mut config = Config::new(); 246 config.cranelift_opt_level(OptLevel::None); 247 let engine = Engine::new(&config)?; 248 let mut wat = String::from( 249 " 250 (module 251 (func (result i64) 252 (i64.const 1) 253 ", 254 ); 255 for _ in 0..20_000 { 256 wat.push_str("(i64.add (i64.const 1))\n"); 257 } 258 259 wat.push_str(")\n)"); 260 Module::new(&engine, &wat)?; 261 262 Ok(()) 263 } 264 265 #[test] 266 fn compile_a_component() -> Result<()> { 267 let engine = Engine::default(); 268 let err = Module::new(&engine, "(component)").unwrap_err(); 269 let err = format!("{err:?}"); 270 assert!( 271 err.contains("expected a WebAssembly module but was given a WebAssembly component"), 272 "bad error: {err}" 273 ); 274 Ok(()) 275 } 276 277 #[test] 278 #[cfg_attr(miri, ignore)] 279 fn tail_call_defaults() -> Result<()> { 280 let wasm_with_tail_calls = "(module (func $a return_call $a))"; 281 282 // on by default 283 Module::new(&Engine::default(), wasm_with_tail_calls)?; 284 285 // on by default for cranelift 286 Module::new( 287 &Engine::new(Config::new().strategy(Strategy::Cranelift))?, 288 wasm_with_tail_calls, 289 )?; 290 291 if cfg!(target_arch = "x86_64") { 292 // off by default for winch 293 let err = Module::new( 294 &Engine::new(Config::new().strategy(Strategy::Winch))?, 295 wasm_with_tail_calls, 296 ); 297 assert!(err.is_err()); 298 } 299 Ok(()) 300 } 301 302 #[test] 303 #[cfg_attr(miri, ignore)] 304 fn cross_engine_module_exports() -> Result<()> { 305 let a_engine = Engine::default(); 306 let b_engine = Engine::default(); 307 308 let a_module = Module::new(&a_engine, "(module)")?; 309 let b_module = Module::new( 310 &b_engine, 311 r#" 312 (module 313 (func (export "x")) 314 ) 315 "#, 316 )?; 317 318 let export = b_module.get_export_index("x").unwrap(); 319 320 let mut store = Store::new(&a_engine, ()); 321 let instance = Instance::new(&mut store, &a_module, &[])?; 322 assert!(instance.get_module_export(&mut store, &export).is_none()); 323 Ok(()) 324 } 325 326 /// Smoke test for registering and unregistering modules (and their rec group 327 /// entries) concurrently. 328 #[wasmtime_test(wasm_features(gc, function_references))] 329 #[cfg_attr(miri, ignore)] 330 fn concurrent_type_registry_modifications(config: &mut Config) -> Result<()> { 331 let _ = env_logger::try_init(); 332 333 // The number of seconds to run the smoke test. 334 const TEST_DURATION_SECONDS: u64 = 5; 335 336 // The number of worker threads to spawn for this smoke test. 337 const NUM_WORKER_THREADS: usize = 32; 338 339 let engine = Engine::new(config)?; 340 341 /// Tests of various kinds of modifications to the type registry. 342 enum Test { 343 /// Creating a module (from its text format) should register new entries 344 /// in the type registry. 345 Module(&'static str), 346 /// Creating an individual func type registers a singleton entry in the 347 /// registry which is managed slightly differently from modules. 348 Func(fn(&Engine) -> FuncType), 349 /// Create a single struct type like a single function type. 350 Struct(fn(&Engine) -> StructType), 351 /// Create a single array type like a single function type. 352 Array(fn(&Engine) -> ArrayType), 353 } 354 const TESTS: &'static [Test] = &[ 355 Test::Func(|engine| FuncType::new(engine, [], [])), 356 Test::Func(|engine| FuncType::new(engine, [], [ValType::I32])), 357 Test::Func(|engine| FuncType::new(engine, [ValType::I32], [])), 358 Test::Struct(|engine| StructType::new(engine, []).unwrap()), 359 Test::Array(|engine| { 360 ArrayType::new(engine, FieldType::new(Mutability::Const, StorageType::I8)) 361 }), 362 Test::Array(|engine| { 363 ArrayType::new(engine, FieldType::new(Mutability::Var, StorageType::I8)) 364 }), 365 Test::Module( 366 r#" 367 (module 368 ;; A handful of function types. 369 (type (func)) 370 (type (func (param i32))) 371 (type (func (result i32))) 372 (type (func (param i32) (result i32))) 373 374 ;; A handful of recursive types. 375 (rec) 376 (rec (type $s (struct (field (ref null $s))))) 377 (rec (type $a (struct (field (ref null $b)))) 378 (type $b (struct (field (ref null $a))))) 379 (rec (type $c (struct (field (ref null $b)) 380 (field (ref null $d)))) 381 (type $d (struct (field (ref null $a)) 382 (field (ref null $c))))) 383 384 ;; Some GC types 385 (type (struct)) 386 (type (array i8)) 387 (type (array (mut i8))) 388 ) 389 "#, 390 ), 391 Test::Module( 392 r#" 393 (module 394 ;; Just the function types. 395 (type (func)) 396 (type (func (param i32))) 397 (type (func (result i32))) 398 (type (func (param i32) (result i32))) 399 ) 400 "#, 401 ), 402 Test::Module( 403 r#" 404 (module 405 ;; Just the recursive types. 406 (rec) 407 (rec (type $s (struct (field (ref null $s))))) 408 (rec (type $a (struct (field (ref null $b)))) 409 (type $b (struct (field (ref null $a))))) 410 (rec (type $c (struct (field (ref null $b)) 411 (field (ref null $d)))) 412 (type $d (struct (field (ref null $a)) 413 (field (ref null $c))))) 414 ) 415 "#, 416 ), 417 Test::Module( 418 r#" 419 (module 420 ;; One of each kind of type. 421 (type (func (param i32) (result i32))) 422 (rec (type $a (struct (field (ref null $b)))) 423 (type $b (struct (field (ref null $a))))) 424 ) 425 "#, 426 ), 427 ]; 428 429 // Spawn the worker threads, each of them just registering and unregistering 430 // modules (and their types) constantly for the duration of the smoke test. 431 let handles = (0..NUM_WORKER_THREADS) 432 .map(|_| { 433 let engine = engine.clone(); 434 std::thread::spawn(move || -> Result<()> { 435 let mut tests = TESTS.iter().cycle(); 436 let start = std::time::Instant::now(); 437 while start.elapsed().as_secs() < TEST_DURATION_SECONDS { 438 match tests.next() { 439 Some(Test::Module(wat)) => { 440 let _ = Module::new(&engine, wat)?; 441 } 442 Some(Test::Func(ctor)) => { 443 let _ = ctor(&engine); 444 } 445 Some(Test::Struct(ctor)) => { 446 let _ = ctor(&engine); 447 } 448 Some(Test::Array(ctor)) => { 449 let _ = ctor(&engine); 450 } 451 None => unreachable!(), 452 } 453 } 454 455 Ok(()) 456 }) 457 }) 458 .collect::<Vec<_>>(); 459 460 // Join all of the thread handles. 461 for handle in handles { 462 handle 463 .join() 464 .expect("should join thread handle") 465 .context("error during thread execution")?; 466 } 467 468 Ok(()) 469 } 470 471 #[wasmtime_test(wasm_features(function_references))] 472 #[cfg_attr(miri, ignore)] 473 fn concurrent_type_modifications_and_checks(config: &mut Config) -> Result<()> { 474 const THREADS_CHECKING: usize = 4; 475 476 let _ = env_logger::try_init(); 477 478 let engine = Engine::new(&config)?; 479 480 let mut threads = Vec::new(); 481 let keep_going = Arc::new(AtomicBool::new(true)); 482 483 // Spawn a number of threads that are all working with a module and testing 484 // various properties about type-checks in the module. 485 for _ in 0..THREADS_CHECKING { 486 threads.push(std::thread::spawn({ 487 let engine = engine.clone(); 488 let keep_going = keep_going.clone(); 489 move || -> Result<()> { 490 while keep_going.load(Relaxed) { 491 let module = Module::new( 492 &engine, 493 r#" 494 (module 495 (func (export "f") (param funcref) 496 i32.const 0 497 local.get 0 498 table.set 499 i32.const 0 500 call_indirect (result f64) 501 drop 502 ) 503 504 (table 1 funcref) 505 ) 506 "#, 507 )?; 508 let ty = FuncType::new(&engine, [], [ValType::I32]); 509 let mut store = Store::new(&engine, ()); 510 let func = Func::new(&mut store, ty, |_, _, results| { 511 results[0] = Val::I32(0); 512 Ok(()) 513 }); 514 515 let instance = Instance::new(&mut store, &module, &[])?; 516 assert!(instance.get_typed_func::<(), i32>(&mut store, "f").is_err()); 517 assert!(instance.get_typed_func::<(), f64>(&mut store, "f").is_err()); 518 let f = instance.get_typed_func::<Func, ()>(&mut store, "f")?; 519 let err = f.call(&mut store, func).unwrap_err(); 520 assert_eq!(err.downcast::<Trap>()?, Trap::BadSignature); 521 } 522 Ok(()) 523 } 524 })); 525 } 526 527 // Spawn threads in the background creating/destroying `FuncType`s related 528 // to the module above. 529 threads.push(std::thread::spawn({ 530 let engine = engine.clone(); 531 let keep_going = keep_going.clone(); 532 move || -> Result<()> { 533 while keep_going.load(Relaxed) { 534 FuncType::new(&engine, [], [ValType::F64]); 535 } 536 Ok(()) 537 } 538 })); 539 threads.push(std::thread::spawn({ 540 let engine = engine.clone(); 541 let keep_going = keep_going.clone(); 542 move || -> Result<()> { 543 while keep_going.load(Relaxed) { 544 FuncType::new(&engine, [], [ValType::I32]); 545 } 546 Ok(()) 547 } 548 })); 549 550 std::thread::sleep(std::time::Duration::new(2, 0)); 551 keep_going.store(false, Relaxed); 552 553 for thread in threads { 554 thread.join().unwrap()?; 555 } 556 557 Ok(()) 558 } 559 560 #[test] 561 #[cfg_attr(miri, ignore)] 562 fn validate_deterministic() { 563 let mut faulty_wat = "(module ".to_string(); 564 for i in 0..100 { 565 faulty_wat.push_str(&format!( 566 "(func (export \"foo_{i}\") (result i64) (i64.add (i32.const 0) (i64.const 1)))" 567 )); 568 } 569 faulty_wat.push_str(")"); 570 let binary = wat::parse_str(faulty_wat).unwrap(); 571 572 let engine_parallel = Engine::new(&Config::new().parallel_compilation(true)).unwrap(); 573 let result_parallel = Module::validate(&engine_parallel, &binary) 574 .unwrap_err() 575 .to_string(); 576 577 let engine_sequential = Engine::new(&Config::new().parallel_compilation(false)).unwrap(); 578 let result_sequential = Module::validate(&engine_sequential, &binary) 579 .unwrap_err() 580 .to_string(); 581 assert_eq!(result_parallel, result_sequential); 582 } 583