Path: blob/main/tests/all/pooling_allocator.rs
1690 views
use super::{ErrorExt, skip_pooling_allocator_tests};1use wasmtime::*;23#[test]4fn successful_instantiation() -> Result<()> {5let pool = crate::small_pool_config();6let mut config = Config::new();7config.allocation_strategy(pool);8config.memory_guard_size(0);9config.memory_reservation(1 << 16);1011let engine = Engine::new(&config)?;12let module = Module::new(&engine, r#"(module (memory 1) (table 10 funcref))"#)?;1314// Module should instantiate15let mut store = Store::new(&engine, ());16Instance::new(&mut store, &module, &[])?;1718Ok(())19}2021#[test]22#[cfg_attr(miri, ignore)]23fn memory_limit() -> Result<()> {24let mut pool = crate::small_pool_config();25pool.max_memory_size(3 << 16);26let mut config = Config::new();27config.allocation_strategy(pool);28config.memory_guard_size(1 << 16);29config.memory_reservation(3 << 16);30config.wasm_multi_memory(true);3132let engine = Engine::new(&config)?;3334// Module should fail to instantiate because it has too many memories35match Module::new(&engine, r#"(module (memory 1) (memory 1))"#) {36Ok(_) => panic!("module instantiation should fail"),37Err(e) => {38e.assert_contains("defined memories count of 2 exceeds the per-instance limit of 1")39}40}4142// Module should fail to instantiate because the minimum is greater than43// the configured limit44match Module::new(&engine, r#"(module (memory 4))"#) {45Ok(_) => panic!("module instantiation should fail"),46Err(e) => {47e.assert_contains(48"memory index 0 is unsupported in this pooling allocator \49configuration",50);51e.assert_contains(52"memory has a minimum byte size of 262144 which exceeds \53the limit of 0x30000 bytes",54);55}56}5758let module = Module::new(59&engine,60r#"(module (memory (export "m") 0) (func (export "f") (result i32) (memory.grow (i32.const 1))))"#,61)?;6263// Instantiate the module and grow the memory via the `f` function64{65let mut store = Store::new(&engine, ());66let instance = Instance::new(&mut store, &module, &[])?;67let f = instance.get_typed_func::<(), i32>(&mut store, "f")?;6869assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 0);70assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 1);71assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 2);72assert_eq!(73f.call(&mut store, ()).expect("function should not trap"),74-175);76assert_eq!(77f.call(&mut store, ()).expect("function should not trap"),78-179);80}8182// Instantiate the module and grow the memory via the Wasmtime API83let mut store = Store::new(&engine, ());84let instance = Instance::new(&mut store, &module, &[])?;8586let memory = instance.get_memory(&mut store, "m").unwrap();87assert_eq!(memory.size(&store), 0);88assert_eq!(memory.grow(&mut store, 1).expect("memory should grow"), 0);89assert_eq!(memory.size(&store), 1);90assert_eq!(memory.grow(&mut store, 1).expect("memory should grow"), 1);91assert_eq!(memory.size(&store), 2);92assert_eq!(memory.grow(&mut store, 1).expect("memory should grow"), 2);93assert_eq!(memory.size(&store), 3);94assert!(memory.grow(&mut store, 1).is_err());9596Ok(())97}9899#[test]100fn memory_init() -> Result<()> {101let mut pool = crate::small_pool_config();102pool.max_memory_size(2 << 16).table_elements(0);103let mut config = Config::new();104config.allocation_strategy(pool);105106let engine = Engine::new(&config)?;107108let module = Module::new(109&engine,110r#"111(module112(memory (export "m") 2)113(data (i32.const 65530) "this data spans multiple pages")114(data (i32.const 10) "hello world")115)116"#,117)?;118119let mut store = Store::new(&engine, ());120let instance = Instance::new(&mut store, &module, &[])?;121let memory = instance.get_memory(&mut store, "m").unwrap();122123assert_eq!(124&memory.data(&store)[65530..65560],125b"this data spans multiple pages"126);127assert_eq!(&memory.data(&store)[10..21], b"hello world");128129Ok(())130}131132#[test]133#[cfg_attr(miri, ignore)]134fn memory_guard_page_trap() -> Result<()> {135let mut pool = crate::small_pool_config();136pool.max_memory_size(2 << 16).table_elements(0);137let mut config = Config::new();138config.allocation_strategy(pool);139140let engine = Engine::new(&config)?;141142let module = Module::new(143&engine,144r#"145(module146(memory (export "m") 0)147(func (export "f") (param i32) local.get 0 i32.load drop)148)149"#,150)?;151152// Instantiate the module and check for out of bounds trap153for _ in 0..10 {154let mut store = Store::new(&engine, ());155let instance = Instance::new(&mut store, &module, &[])?;156let m = instance.get_memory(&mut store, "m").unwrap();157let f = instance.get_typed_func::<i32, ()>(&mut store, "f")?;158159let trap = f160.call(&mut store, 0)161.expect_err("function should trap")162.downcast::<Trap>()?;163assert_eq!(trap, Trap::MemoryOutOfBounds);164165let trap = f166.call(&mut store, 1)167.expect_err("function should trap")168.downcast::<Trap>()?;169assert_eq!(trap, Trap::MemoryOutOfBounds);170171m.grow(&mut store, 1).expect("memory should grow");172f.call(&mut store, 0).expect("function should not trap");173174let trap = f175.call(&mut store, 65536)176.expect_err("function should trap")177.downcast::<Trap>()?;178assert_eq!(trap, Trap::MemoryOutOfBounds);179180let trap = f181.call(&mut store, 65537)182.expect_err("function should trap")183.downcast::<Trap>()?;184assert_eq!(trap, Trap::MemoryOutOfBounds);185186m.grow(&mut store, 1).expect("memory should grow");187f.call(&mut store, 65536).expect("function should not trap");188189m.grow(&mut store, 1)190.expect_err("memory should be at the limit");191}192193Ok(())194}195196#[test]197fn memory_zeroed() -> Result<()> {198if skip_pooling_allocator_tests() {199return Ok(());200}201202let mut pool = crate::small_pool_config();203pool.max_memory_size(1 << 16).table_elements(0);204let mut config = Config::new();205config.allocation_strategy(pool);206config.memory_guard_size(0);207config.memory_reservation(1 << 16);208209let engine = Engine::new(&config)?;210211let module = Module::new(&engine, r#"(module (memory (export "m") 1))"#)?;212213// Instantiate the module repeatedly after writing data to the entire memory214for _ in 0..10 {215let mut store = Store::new(&engine, ());216let instance = Instance::new(&mut store, &module, &[])?;217let memory = instance.get_memory(&mut store, "m").unwrap();218219assert_eq!(memory.size(&store,), 1);220assert_eq!(memory.data_size(&store), 65536);221222let ptr = memory.data_mut(&mut store).as_mut_ptr();223224unsafe {225for i in 0..8192 {226assert_eq!(*ptr.cast::<u64>().offset(i), 0);227}228std::ptr::write_bytes(ptr, 0xFE, memory.data_size(&store));229}230}231232Ok(())233}234235#[test]236#[cfg_attr(miri, ignore)]237fn table_limit() -> Result<()> {238const TABLE_ELEMENTS: usize = 10;239let mut pool = crate::small_pool_config();240pool.table_elements(TABLE_ELEMENTS);241let mut config = Config::new();242config.allocation_strategy(pool);243config.memory_guard_size(0);244config.memory_reservation(1 << 16);245246let engine = Engine::new(&config)?;247248// Module should fail to instantiate because it has too many tables249match Module::new(&engine, r#"(module (table 1 funcref) (table 1 funcref))"#) {250Ok(_) => panic!("module compilation should fail"),251Err(e) => {252e.assert_contains("defined tables count of 2 exceeds the per-instance limit of 1")253}254}255256// Module should fail to instantiate because the minimum is greater than257// the configured limit258match Module::new(&engine, r#"(module (table 31 funcref))"#) {259Ok(_) => panic!("module compilation should fail"),260Err(e) => e.assert_contains(261"table index 0 has a minimum element size of 31 which exceeds the limit of 10",262),263}264265let module = Module::new(266&engine,267r#"(module (table (export "t") 0 funcref) (func (export "f") (result i32) (table.grow (ref.null func) (i32.const 1))))"#,268)?;269270// Instantiate the module and grow the table via the `f` function271{272let mut store = Store::new(&engine, ());273let instance = Instance::new(&mut store, &module, &[])?;274let f = instance.get_typed_func::<(), i32>(&mut store, "f")?;275276for i in 0..TABLE_ELEMENTS {277assert_eq!(278f.call(&mut store, ()).expect("function should not trap"),279i as i32280);281}282283assert_eq!(284f.call(&mut store, ()).expect("function should not trap"),285-1286);287assert_eq!(288f.call(&mut store, ()).expect("function should not trap"),289-1290);291}292293// Instantiate the module and grow the table via the Wasmtime API294let mut store = Store::new(&engine, ());295let instance = Instance::new(&mut store, &module, &[])?;296297let table = instance.get_table(&mut store, "t").unwrap();298299for i in 0..TABLE_ELEMENTS {300assert_eq!(table.size(&store), i as u64);301assert_eq!(302table303.grow(&mut store, 1, Ref::Func(None))304.expect("table should grow"),305i as u64306);307}308309assert_eq!(table.size(&store), TABLE_ELEMENTS as u64);310assert!(table.grow(&mut store, 1, Ref::Func(None)).is_err());311312Ok(())313}314315#[test]316#[cfg_attr(miri, ignore)]317fn table_init() -> Result<()> {318let mut pool = crate::small_pool_config();319pool.max_memory_size(0).table_elements(6);320let mut config = Config::new();321config.allocation_strategy(pool);322323let engine = Engine::new(&config)?;324325let module = Module::new(326&engine,327r#"328(module329(table (export "t") 6 funcref)330(elem (i32.const 1) 1 2 3 4)331(elem (i32.const 0) 0)332(func)333(func (param i32))334(func (param i32 i32))335(func (param i32 i32 i32))336(func (param i32 i32 i32 i32))337)338"#,339)?;340341let mut store = Store::new(&engine, ());342let instance = Instance::new(&mut store, &module, &[])?;343let table = instance.get_table(&mut store, "t").unwrap();344345for i in 0..5 {346let v = table.get(&mut store, i).expect("table should have entry");347let f = v348.as_func()349.expect("expected funcref")350.expect("expected non-null value");351assert_eq!(f.ty(&store).params().len(), i as usize);352}353354assert!(355table356.get(&mut store, 5)357.expect("table should have entry")358.as_func()359.expect("expected funcref")360.is_none(),361"funcref should be null"362);363364Ok(())365}366367#[test]368fn table_zeroed() -> Result<()> {369if skip_pooling_allocator_tests() {370return Ok(());371}372373let pool = crate::small_pool_config();374let mut config = Config::new();375config.allocation_strategy(pool);376config.memory_guard_size(0);377config.memory_reservation(1 << 16);378379let engine = Engine::new(&config)?;380381let module = Module::new(&engine, r#"(module (table (export "t") 10 funcref))"#)?;382383// Instantiate the module repeatedly after filling table elements384for _ in 0..10 {385let mut store = Store::new(&engine, ());386let instance = Instance::new(&mut store, &module, &[])?;387let table = instance.get_table(&mut store, "t").unwrap();388let f = Func::wrap(&mut store, || {});389390assert_eq!(table.size(&store), 10);391392for i in 0..10 {393match table.get(&mut store, i).unwrap() {394Ref::Func(r) => assert!(r.is_none()),395_ => panic!("expected a funcref"),396}397table.set(&mut store, i, Ref::Func(Some(f))).unwrap();398}399}400401Ok(())402}403404#[test]405fn total_core_instances_limit() -> Result<()> {406const INSTANCE_LIMIT: u32 = 10;407let mut pool = crate::small_pool_config();408pool.total_core_instances(INSTANCE_LIMIT);409let mut config = Config::new();410config.allocation_strategy(pool);411config.memory_guard_size(0);412config.memory_reservation(1 << 16);413414let engine = Engine::new(&config)?;415let module = Module::new(&engine, r#"(module)"#)?;416417// Instantiate to the limit418{419let mut store = Store::new(&engine, ());420421for _ in 0..INSTANCE_LIMIT {422Instance::new(&mut store, &module, &[])?;423}424425match Instance::new(&mut store, &module, &[]) {426Ok(_) => panic!("instantiation should fail"),427Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),428}429}430431// With the above store dropped, ensure instantiations can be made432433let mut store = Store::new(&engine, ());434435for _ in 0..INSTANCE_LIMIT {436Instance::new(&mut store, &module, &[])?;437}438439Ok(())440}441442#[test]443fn preserve_data_segments() -> Result<()> {444let mut pool = crate::small_pool_config();445pool.total_memories(2);446let mut config = Config::new();447config.allocation_strategy(pool);448let engine = Engine::new(&config)?;449let m = Module::new(450&engine,451r#"452(module453(memory (export "mem") 1 1)454(data (i32.const 0) "foo"))455"#,456)?;457let mut store = Store::new(&engine, ());458let i = Instance::new(&mut store, &m, &[])?;459460// Drop the module. This should *not* drop the actual data referenced by the461// module.462drop(m);463464// Spray some stuff on the heap. If wasm data lived on the heap this should465// paper over things and help us catch use-after-free here if it would466// otherwise happen.467if !cfg!(miri) {468let mut strings = Vec::new();469for _ in 0..1000 {470let mut string = String::new();471for _ in 0..1000 {472string.push('g');473}474strings.push(string);475}476drop(strings);477}478479let mem = i.get_memory(&mut store, "mem").unwrap();480481// Hopefully it's still `foo`!482assert!(mem.data(&store).starts_with(b"foo"));483484Ok(())485}486487#[test]488fn multi_memory_with_imported_memories() -> Result<()> {489// This test checks that the base address for the defined memory is correct for the instance490// despite the presence of an imported memory.491492let mut pool = crate::small_pool_config();493pool.total_memories(2).max_memories_per_module(2);494let mut config = Config::new();495config.allocation_strategy(pool);496config.wasm_multi_memory(true);497498let engine = Engine::new(&config)?;499let module = Module::new(500&engine,501r#"(module (import "" "m1" (memory 0)) (memory (export "m2") 1))"#,502)?;503504let mut store = Store::new(&engine, ());505506let m1 = Memory::new(&mut store, MemoryType::new(0, None))?;507let instance = Instance::new(&mut store, &module, &[m1.into()])?;508509let m2 = instance.get_memory(&mut store, "m2").unwrap();510511m2.data_mut(&mut store)[0] = 0x42;512assert_eq!(m2.data(&store)[0], 0x42);513514Ok(())515}516517#[test]518fn drop_externref_global_during_module_init() -> Result<()> {519struct Limiter;520521impl ResourceLimiter for Limiter {522fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {523Ok(false)524}525526fn table_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {527Ok(false)528}529}530531let pool = crate::small_pool_config();532let mut config = Config::new();533config.wasm_reference_types(true);534config.allocation_strategy(pool);535536let engine = Engine::new(&config)?;537538let module = Module::new(539&engine,540r#"541(module542(global i32 (i32.const 1))543(global i32 (i32.const 2))544(global i32 (i32.const 3))545(global i32 (i32.const 4))546(global i32 (i32.const 5))547)548"#,549)?;550551let mut store = Store::new(&engine, Limiter);552Instance::new(&mut store, &module, &[])?;553drop(store);554555let module = Module::new(556&engine,557r#"558(module559(memory 1)560(global (mut externref) (ref.null extern))561)562"#,563)?;564565let mut store = Store::new(&engine, Limiter);566store.limiter(|s| s);567assert!(Instance::new(&mut store, &module, &[]).is_err());568569Ok(())570}571572#[test]573#[cfg_attr(miri, ignore)]574fn switch_image_and_non_image() -> Result<()> {575let pool = crate::small_pool_config();576let mut c = Config::new();577c.allocation_strategy(pool);578let engine = Engine::new(&c)?;579let module1 = Module::new(580&engine,581r#"582(module583(memory 1)584(func (export "load") (param i32) (result i32)585local.get 0586i32.load587)588)589"#,590)?;591let module2 = Module::new(592&engine,593r#"594(module595(memory (export "memory") 1)596(data (i32.const 0) "1234")597)598"#,599)?;600601let assert_zero = || -> Result<()> {602let mut store = Store::new(&engine, ());603let instance = Instance::new(&mut store, &module1, &[])?;604let func = instance.get_typed_func::<i32, i32>(&mut store, "load")?;605assert_eq!(func.call(&mut store, 0)?, 0);606Ok(())607};608609// Initialize with a heap image and make sure the next instance, without an610// image, is zeroed611Instance::new(&mut Store::new(&engine, ()), &module2, &[])?;612assert_zero()?;613614// ... transition back to heap image and do this again615Instance::new(&mut Store::new(&engine, ()), &module2, &[])?;616assert_zero()?;617618// And go back to an image and make sure it's read/write on the host.619let mut store = Store::new(&engine, ());620let instance = Instance::new(&mut store, &module2, &[])?;621let memory = instance.get_memory(&mut store, "memory").unwrap();622let mem = memory.data_mut(&mut store);623assert!(mem.starts_with(b"1234"));624mem[..6].copy_from_slice(b"567890");625626Ok(())627}628629#[test]630#[cfg(target_pointer_width = "64")]631#[cfg_attr(miri, ignore)]632fn instance_too_large() -> Result<()> {633let mut pool = crate::small_pool_config();634pool.max_core_instance_size(16);635let mut config = Config::new();636config.allocation_strategy(pool);637638let engine = Engine::new(&config)?;639match Module::new(&engine, "(module)") {640Ok(_) => panic!("should have failed to compile"),641Err(e) => {642e.assert_contains("exceeds the configured maximum of 16 bytes");643e.assert_contains("breakdown of allocation requirement");644e.assert_contains("instance state management");645e.assert_contains("static vmctx data");646}647}648649let mut lots_of_globals = format!("(module");650for _ in 0..100 {651lots_of_globals.push_str("(global i32 i32.const 0)\n");652}653lots_of_globals.push_str(")");654655match Module::new(&engine, &lots_of_globals) {656Ok(_) => panic!("should have failed to compile"),657Err(e) => {658e.assert_contains("exceeds the configured maximum of 16 bytes");659e.assert_contains("breakdown of allocation requirement");660e.assert_contains("defined globals");661e.assert_contains("instance state management");662}663}664665Ok(())666}667668#[test]669#[cfg_attr(miri, ignore)]670fn dynamic_memory_pooling_allocator() -> Result<()> {671for guard_size in [0, 1 << 16] {672let max_size = 128 << 20;673let mut pool = crate::small_pool_config();674pool.max_memory_size(max_size as usize);675let mut config = Config::new();676config.memory_reservation(max_size);677config.memory_guard_size(guard_size);678config.allocation_strategy(pool);679680let engine = Engine::new(&config)?;681682let module = Module::new(683&engine,684r#"685(module686(memory (export "memory") 1)687688(func (export "grow") (param i32) (result i32)689local.get 0690memory.grow)691692(func (export "size") (result i32)693memory.size)694695(func (export "i32.load") (param i32) (result i32)696local.get 0697i32.load)698699(func (export "i32.store") (param i32 i32)700local.get 0701local.get 1702i32.store)703704(data (i32.const 100) "x")705)706"#,707)?;708709let mut store = Store::new(&engine, ());710let instance = Instance::new(&mut store, &module, &[])?;711712let grow = instance.get_typed_func::<u32, i32>(&mut store, "grow")?;713let size = instance.get_typed_func::<(), u32>(&mut store, "size")?;714let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;715let i32_store = instance.get_typed_func::<(u32, i32), ()>(&mut store, "i32.store")?;716let memory = instance.get_memory(&mut store, "memory").unwrap();717718// basic length 1 tests719// assert_eq!(memory.grow(&mut store, 1)?, 0);720assert_eq!(memory.size(&store), 1);721assert_eq!(size.call(&mut store, ())?, 1);722assert_eq!(i32_load.call(&mut store, 0)?, 0);723assert_eq!(i32_load.call(&mut store, 100)?, i32::from(b'x'));724i32_store.call(&mut store, (0, 0))?;725i32_store.call(&mut store, (100, i32::from(b'y')))?;726assert_eq!(i32_load.call(&mut store, 100)?, i32::from(b'y'));727728// basic length 2 tests729let page = 64 * 1024;730assert_eq!(grow.call(&mut store, 1)?, 1);731assert_eq!(memory.size(&store), 2);732assert_eq!(size.call(&mut store, ())?, 2);733i32_store.call(&mut store, (page, 200))?;734assert_eq!(i32_load.call(&mut store, page)?, 200);735736// test writes are visible737i32_store.call(&mut store, (2, 100))?;738assert_eq!(i32_load.call(&mut store, 2)?, 100);739740// test growth can't exceed maximum741let too_many = max_size / (64 * 1024);742assert_eq!(grow.call(&mut store, too_many as u32)?, -1);743assert!(memory.grow(&mut store, too_many).is_err());744745assert_eq!(memory.data(&store)[page as usize], 200);746747// Re-instantiate in another store.748store = Store::new(&engine, ());749let instance = Instance::new(&mut store, &module, &[])?;750let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;751let memory = instance.get_memory(&mut store, "memory").unwrap();752753// This is out of bounds...754assert!(i32_load.call(&mut store, page).is_err());755assert_eq!(memory.data_size(&store), page as usize);756757// ... but implementation-wise it should still be mapped memory from758// before if we don't have any guard pages.759//760// Note though that prior writes should all appear as zeros and we can't see761// data from the prior instance.762//763// Note that this part is only implemented on Linux which has764// `MADV_DONTNEED`.765if cfg!(target_os = "linux") && guard_size == 0 {766unsafe {767let ptr = memory.data_ptr(&store);768assert_eq!(*ptr.offset(page as isize), 0);769}770}771}772773Ok(())774}775776#[test]777#[cfg_attr(miri, ignore)]778fn zero_memory_pages_disallows_oob() -> Result<()> {779let mut pool = crate::small_pool_config();780pool.max_memory_size(0);781let mut config = Config::new();782config.allocation_strategy(pool);783784let engine = Engine::new(&config)?;785let module = Module::new(786&engine,787r#"788(module789(memory 0)790791(func (export "load") (param i32) (result i32)792local.get 0793i32.load)794795(func (export "store") (param i32 )796local.get 0797local.get 0798i32.store)799)800"#,801)?;802let mut store = Store::new(&engine, ());803let instance = Instance::new(&mut store, &module, &[])?;804let load32 = instance.get_typed_func::<i32, i32>(&mut store, "load")?;805let store32 = instance.get_typed_func::<i32, ()>(&mut store, "store")?;806for i in 0..31 {807assert!(load32.call(&mut store, 1 << i).is_err());808assert!(store32.call(&mut store, 1 << i).is_err());809}810Ok(())811}812813#[test]814#[cfg(feature = "component-model")]815fn total_component_instances_limit() -> Result<()> {816const TOTAL_COMPONENT_INSTANCES: u32 = 5;817818let mut pool = crate::small_pool_config();819pool.total_component_instances(TOTAL_COMPONENT_INSTANCES);820let mut config = Config::new();821config.wasm_component_model(true);822config.allocation_strategy(pool);823824let engine = Engine::new(&config)?;825let linker = wasmtime::component::Linker::new(&engine);826let component = wasmtime::component::Component::new(&engine, "(component)")?;827828let mut store = Store::new(&engine, ());829for _ in 0..TOTAL_COMPONENT_INSTANCES {830linker.instantiate(&mut store, &component)?;831}832833match linker.instantiate(&mut store, &component) {834Ok(_) => panic!("should have hit component instance limit"),835Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),836}837838drop(store);839let mut store = Store::new(&engine, ());840for _ in 0..TOTAL_COMPONENT_INSTANCES {841linker.instantiate(&mut store, &component)?;842}843844Ok(())845}846847#[test]848#[cfg(feature = "component-model")]849#[cfg(target_pointer_width = "64")] // error message tailored for 64-bit850fn component_instance_size_limit() -> Result<()> {851let mut pool = crate::small_pool_config();852pool.max_component_instance_size(1);853let mut config = Config::new();854config.wasm_component_model(true);855config.allocation_strategy(pool);856let engine = Engine::new(&config)?;857858match wasmtime::component::Component::new(&engine, "(component)") {859Ok(_) => panic!("should have hit limit"),860Err(e) => e.assert_contains(861"instance allocation for this component requires 48 bytes of \862`VMComponentContext` space which exceeds the configured maximum of 1 bytes",863),864}865866Ok(())867}868869#[test]870#[cfg_attr(miri, ignore)]871fn total_tables_limit() -> Result<()> {872const TOTAL_TABLES: u32 = 5;873874let mut pool = crate::small_pool_config();875pool.total_tables(TOTAL_TABLES)876.total_core_instances(TOTAL_TABLES + 1);877let mut config = Config::new();878config.allocation_strategy(pool);879880let engine = Engine::new(&config)?;881let linker = Linker::new(&engine);882let module = Module::new(&engine, "(module (table 0 1 funcref))")?;883884let mut store = Store::new(&engine, ());885for _ in 0..TOTAL_TABLES {886linker.instantiate(&mut store, &module)?;887}888889match linker.instantiate(&mut store, &module) {890Ok(_) => panic!("should have hit table limit"),891Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),892}893894drop(store);895let mut store = Store::new(&engine, ());896for _ in 0..TOTAL_TABLES {897linker.instantiate(&mut store, &module)?;898}899900Ok(())901}902903#[tokio::test]904#[cfg(not(miri))]905async fn total_stacks_limit() -> Result<()> {906use super::async_functions::PollOnce;907908const TOTAL_STACKS: u32 = 2;909910let mut pool = crate::small_pool_config();911pool.total_stacks(TOTAL_STACKS)912.total_core_instances(TOTAL_STACKS + 1);913let mut config = Config::new();914config.async_support(true);915config.allocation_strategy(pool);916917let engine = Engine::new(&config)?;918919let mut linker = Linker::new(&engine);920linker.func_new_async(921"async",922"yield",923FuncType::new(&engine, [], []),924|_caller, _params, _results| {925Box::new(async {926tokio::task::yield_now().await;927Ok(())928})929},930)?;931932let module = Module::new(933&engine,934r#"935(module936(import "async" "yield" (func $yield))937(func (export "run")938call $yield939)940941(func $empty)942(start $empty)943)944"#,945)?;946947// Allocate stacks up to the limit. (Poll the futures once to make sure we948// actually enter Wasm and force a stack allocation.)949950let mut store1 = Store::new(&engine, ());951let instance1 = linker.instantiate_async(&mut store1, &module).await?;952let run1 = instance1.get_func(&mut store1, "run").unwrap();953let future1 = PollOnce::new(Box::pin(run1.call_async(store1, &[], &mut [])))954.await955.unwrap_err();956957let mut store2 = Store::new(&engine, ());958let instance2 = linker.instantiate_async(&mut store2, &module).await?;959let run2 = instance2.get_func(&mut store2, "run").unwrap();960let future2 = PollOnce::new(Box::pin(run2.call_async(store2, &[], &mut [])))961.await962.unwrap_err();963964// Allocating more should fail.965let mut store3 = Store::new(&engine, ());966match linker.instantiate_async(&mut store3, &module).await {967Ok(_) => panic!("should have hit stack limit"),968Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),969}970971// Finish the futures and return their Wasm stacks to the pool.972future1.await?;973future2.await?;974975// Should be able to allocate new stacks again.976let mut store1 = Store::new(&engine, ());977let instance1 = linker.instantiate_async(&mut store1, &module).await?;978let run1 = instance1.get_func(&mut store1, "run").unwrap();979let future1 = run1.call_async(&mut store1, &[], &mut []);980981let mut store2 = Store::new(&engine, ());982let instance2 = linker.instantiate_async(&mut store2, &module).await?;983let run2 = instance2.get_func(&mut store2, "run").unwrap();984let future2 = run2.call_async(&mut store2, &[], &mut []);985986future1.await?;987future2.await?;988989// Dispose one store via `Drop`, the other via `into_data`, and ensure that990// any lingering stacks make their way back to the pool.991drop(store1);992store2.into_data();993994Ok(())995}996997#[test]998#[cfg(feature = "component-model")]999fn component_core_instances_limit() -> Result<()> {1000let mut pool = crate::small_pool_config();1001pool.max_core_instances_per_component(1);1002let mut config = Config::new();1003config.wasm_component_model(true);1004config.allocation_strategy(pool);1005let engine = Engine::new(&config)?;10061007// One core instance works.1008wasmtime::component::Component::new(1009&engine,1010r#"1011(component1012(core module $m)1013(core instance $a (instantiate $m))1014)1015"#,1016)?;10171018// Two core instances doesn't.1019match wasmtime::component::Component::new(1020&engine,1021r#"1022(component1023(core module $m)1024(core instance $a (instantiate $m))1025(core instance $b (instantiate $m))1026)1027"#,1028) {1029Ok(_) => panic!("should have hit limit"),1030Err(e) => e.assert_contains(1031"The component transitively contains 2 core module instances, which exceeds the \1032configured maximum of 1",1033),1034}10351036Ok(())1037}10381039#[test]1040#[cfg(feature = "component-model")]1041fn component_memories_limit() -> Result<()> {1042let mut pool = crate::small_pool_config();1043pool.max_memories_per_component(1).total_memories(2);1044let mut config = Config::new();1045config.wasm_component_model(true);1046config.allocation_strategy(pool);1047let engine = Engine::new(&config)?;10481049// One memory works.1050wasmtime::component::Component::new(1051&engine,1052r#"1053(component1054(core module $m (memory 1 1))1055(core instance $a (instantiate $m))1056)1057"#,1058)?;10591060// Two memories doesn't.1061match wasmtime::component::Component::new(1062&engine,1063r#"1064(component1065(core module $m (memory 1 1))1066(core instance $a (instantiate $m))1067(core instance $b (instantiate $m))1068)1069"#,1070) {1071Ok(_) => panic!("should have hit limit"),1072Err(e) => e.assert_contains(1073"The component transitively contains 2 Wasm linear memories, which exceeds the \1074configured maximum of 1",1075),1076}10771078Ok(())1079}10801081#[test]1082#[cfg(feature = "component-model")]1083fn component_tables_limit() -> Result<()> {1084let mut pool = crate::small_pool_config();1085pool.max_tables_per_component(1).total_tables(2);1086let mut config = Config::new();1087config.wasm_component_model(true);1088config.allocation_strategy(pool);1089let engine = Engine::new(&config)?;10901091// One table works.1092wasmtime::component::Component::new(1093&engine,1094r#"1095(component1096(core module $m (table 1 1 funcref))1097(core instance $a (instantiate $m))1098)1099"#,1100)?;11011102// Two tables doesn't.1103match wasmtime::component::Component::new(1104&engine,1105r#"1106(component1107(core module $m (table 1 1 funcref))1108(core instance $a (instantiate $m))1109(core instance $b (instantiate $m))1110)1111"#,1112) {1113Ok(_) => panic!("should have hit limit"),1114Err(e) => e.assert_contains(1115"The component transitively contains 2 tables, which exceeds the \1116configured maximum of 1",1117),1118}11191120Ok(())1121}11221123#[test]1124#[cfg_attr(miri, ignore)]1125fn total_memories_limit() -> Result<()> {1126const TOTAL_MEMORIES: u32 = 5;11271128let mut pool = crate::small_pool_config();1129pool.total_memories(TOTAL_MEMORIES)1130.total_core_instances(TOTAL_MEMORIES + 1)1131.memory_protection_keys(Enabled::No);1132let mut config = Config::new();1133config.allocation_strategy(pool);11341135let engine = Engine::new(&config)?;1136let linker = Linker::new(&engine);1137let module = Module::new(&engine, "(module (memory 1 1))")?;11381139let mut store = Store::new(&engine, ());1140for _ in 0..TOTAL_MEMORIES {1141linker.instantiate(&mut store, &module)?;1142}11431144match linker.instantiate(&mut store, &module) {1145Ok(_) => panic!("should have hit memory limit"),1146Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),1147}11481149drop(store);1150let mut store = Store::new(&engine, ());1151for _ in 0..TOTAL_MEMORIES {1152linker.instantiate(&mut store, &module)?;1153}11541155Ok(())1156}11571158#[test]1159#[cfg_attr(miri, ignore)]1160fn decommit_batching() -> Result<()> {1161for (capacity, batch_size) in [1162// A reasonable batch size.1163(10, 5),1164// Batch sizes of zero and one should effectively disable batching.1165(10, 1),1166(10, 0),1167// A bigger batch size than capacity, which forces the allocation path1168// to flush the decommit queue.1169(10, 99),1170] {1171let mut pool = crate::small_pool_config();1172pool.total_memories(capacity)1173.total_core_instances(capacity)1174.decommit_batch_size(batch_size)1175.memory_protection_keys(Enabled::No);1176let mut config = Config::new();1177config.allocation_strategy(pool);11781179let engine = Engine::new(&config)?;1180let linker = Linker::new(&engine);1181let module = Module::new(&engine, "(module (memory 1 1))")?;11821183// Just make sure that we can instantiate all slots a few times and the1184// pooling allocator must be flushing the decommit queue as necessary.1185for _ in 0..3 {1186let mut store = Store::new(&engine, ());1187for _ in 0..capacity {1188linker.instantiate(&mut store, &module)?;1189}1190}1191}11921193Ok(())1194}11951196#[test]1197fn tricky_empty_table_with_empty_virtual_memory_alloc() -> Result<()> {1198// Configure the pooling allocator to have no access to virtual memory, e.g.1199// no table elements but a single table. This should technically support a1200// single empty table being allocated into it but virtual memory isn't1201// actually allocated here.1202let mut cfg = PoolingAllocationConfig::default();1203cfg.table_elements(0);1204cfg.total_memories(0);1205cfg.total_tables(1);1206cfg.total_stacks(0);1207cfg.total_core_instances(1);1208cfg.max_memory_size(0);12091210let mut c = Config::new();1211c.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));12121213// Disable lazy init to actually try to get this to do something interesting1214// at runtime.1215c.table_lazy_init(false);12161217let engine = Engine::new(&c)?;12181219// This module has a single empty table, with a single empty element1220// segment. Nothing actually goes wrong here, it should instantiate1221// successfully. Along the way though the empty mmap above will get viewed1222// as an array-of-pointers, so everything internally should all line up to1223// work ok.1224let module = Module::new(1225&engine,1226r#"1227(module1228(table 0 funcref)1229(elem (i32.const 0) func)1230)1231"#,1232)?;1233let mut store = Store::new(&engine, ());1234Instance::new(&mut store, &module, &[])?;1235Ok(())1236}12371238#[test]1239#[cfg_attr(miri, ignore)]1240fn shared_memory_unsupported() -> Result<()> {1241// Skip this test on platforms that don't support threads.1242if crate::threads::engine().is_none() {1243return Ok(());1244}1245let mut config = Config::new();1246let mut cfg = PoolingAllocationConfig::default();1247// shrink the size of this allocator1248cfg.total_memories(1);1249config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));1250let engine = Engine::new(&config)?;12511252let err = Module::new(1253&engine,1254r#"1255(module1256(memory 5 5 shared)1257)1258"#,1259)1260.unwrap_err();1261err.assert_contains(1262"memory is shared which is not supported \1263in the pooling allocator",1264);1265err.assert_contains("memory index 0");1266Ok(())1267}12681269#[test]1270#[cfg_attr(miri, ignore)]1271fn custom_page_sizes_reusing_same_slot() -> Result<()> {1272let mut config = Config::new();1273config.wasm_custom_page_sizes(true);1274let mut cfg = crate::small_pool_config();1275// force the memories below to collide in the same memory slot1276cfg.total_memories(1);1277config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));1278let engine = Engine::new(&config)?;12791280// Instantiate one module, leaving the slot 5 bytes big (but one page1281// accessible)1282{1283let m1 = Module::new(1284&engine,1285r#"1286(module1287(memory 5 (pagesize 1))12881289(data (i32.const 0) "a")1290)1291"#,1292)?;1293let mut store = Store::new(&engine, ());1294Instance::new(&mut store, &m1, &[])?;1295}12961297// Instantiate a second module, which should work1298{1299let m2 = Module::new(1300&engine,1301r#"1302(module1303(memory 6 (pagesize 1))13041305(data (i32.const 0) "a")1306)1307"#,1308)?;1309let mut store = Store::new(&engine, ());1310Instance::new(&mut store, &m2, &[])?;1311}1312Ok(())1313}13141315#[test]1316#[cfg_attr(miri, ignore)]1317fn instantiate_non_page_aligned_sizes() -> Result<()> {1318let mut config = Config::new();1319config.wasm_custom_page_sizes(true);1320let mut cfg = crate::small_pool_config();1321cfg.total_memories(1);1322cfg.max_memory_size(761927);1323config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));1324let engine = Engine::new(&config)?;13251326let module = Module::new(1327&engine,1328r#"1329(module1330(memory 761927 761927 (pagesize 0x1))1331)1332"#,1333)?;1334let mut store = Store::new(&engine, ());1335Instance::new(&mut store, &module, &[])?;1336Ok(())1337}13381339#[test]1340#[cfg_attr(miri, ignore)]1341fn pagemap_scan_enabled_or_disabled() -> Result<()> {1342let mut config = Config::new();1343let mut cfg = crate::small_pool_config();1344cfg.total_memories(1);1345cfg.pagemap_scan(Enabled::Yes);1346config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));1347let result = Engine::new(&config);13481349if PoolingAllocationConfig::is_pagemap_scan_available() {1350assert!(result.is_ok());1351} else {1352assert!(result.is_err());1353}1354Ok(())1355}13561357// This test instantiates a memory with an image into a slot in the pooling1358// allocator in a way that maps the image into the allocator but fails1359// instantiation. Failure here is injected with `ResourceLimiter`. Afterwards1360// instantiation is allowed to succeed with a memory that has no image, and this1361// asserts that the previous image is indeed not available any more as that1362// would otherwise mean data was leaked between modules.1363#[test]1364fn memory_reset_if_instantiation_fails() -> Result<()> {1365struct Limiter;13661367impl ResourceLimiter for Limiter {1368fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {1369Ok(false)1370}13711372fn table_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {1373Ok(false)1374}1375}13761377let pool = crate::small_pool_config();1378let mut config = Config::new();1379config.allocation_strategy(pool);1380let engine = Engine::new(&config)?;13811382let module_with_image = Module::new(1383&engine,1384r#"1385(module1386(memory 1)1387(data (i32.const 0) "\aa")1388)1389"#,1390)?;1391let module_without_image = Module::new(1392&engine,1393r#"1394(module1395(memory (export "m") 1)1396)1397"#,1398)?;13991400let mut store = Store::new(&engine, Limiter);1401store.limiter(|s| s);1402assert!(Instance::new(&mut store, &module_with_image, &[]).is_err());1403drop(store);14041405let mut store = Store::new(&engine, Limiter);1406let instance = Instance::new(&mut store, &module_without_image, &[])?;1407let mem = instance.get_memory(&mut store, "m").unwrap();1408let data = mem.data(&store);1409assert_eq!(data[0], 0);14101411Ok(())1412}141314141415