Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/tests/all/pooling_allocator.rs
1690 views
1
use super::{ErrorExt, skip_pooling_allocator_tests};
2
use wasmtime::*;
3
4
#[test]
5
fn successful_instantiation() -> Result<()> {
6
let pool = crate::small_pool_config();
7
let mut config = Config::new();
8
config.allocation_strategy(pool);
9
config.memory_guard_size(0);
10
config.memory_reservation(1 << 16);
11
12
let engine = Engine::new(&config)?;
13
let module = Module::new(&engine, r#"(module (memory 1) (table 10 funcref))"#)?;
14
15
// Module should instantiate
16
let mut store = Store::new(&engine, ());
17
Instance::new(&mut store, &module, &[])?;
18
19
Ok(())
20
}
21
22
#[test]
23
#[cfg_attr(miri, ignore)]
24
fn memory_limit() -> Result<()> {
25
let mut pool = crate::small_pool_config();
26
pool.max_memory_size(3 << 16);
27
let mut config = Config::new();
28
config.allocation_strategy(pool);
29
config.memory_guard_size(1 << 16);
30
config.memory_reservation(3 << 16);
31
config.wasm_multi_memory(true);
32
33
let engine = Engine::new(&config)?;
34
35
// Module should fail to instantiate because it has too many memories
36
match Module::new(&engine, r#"(module (memory 1) (memory 1))"#) {
37
Ok(_) => panic!("module instantiation should fail"),
38
Err(e) => {
39
e.assert_contains("defined memories count of 2 exceeds the per-instance limit of 1")
40
}
41
}
42
43
// Module should fail to instantiate because the minimum is greater than
44
// the configured limit
45
match Module::new(&engine, r#"(module (memory 4))"#) {
46
Ok(_) => panic!("module instantiation should fail"),
47
Err(e) => {
48
e.assert_contains(
49
"memory index 0 is unsupported in this pooling allocator \
50
configuration",
51
);
52
e.assert_contains(
53
"memory has a minimum byte size of 262144 which exceeds \
54
the limit of 0x30000 bytes",
55
);
56
}
57
}
58
59
let module = Module::new(
60
&engine,
61
r#"(module (memory (export "m") 0) (func (export "f") (result i32) (memory.grow (i32.const 1))))"#,
62
)?;
63
64
// Instantiate the module and grow the memory via the `f` function
65
{
66
let mut store = Store::new(&engine, ());
67
let instance = Instance::new(&mut store, &module, &[])?;
68
let f = instance.get_typed_func::<(), i32>(&mut store, "f")?;
69
70
assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 0);
71
assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 1);
72
assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 2);
73
assert_eq!(
74
f.call(&mut store, ()).expect("function should not trap"),
75
-1
76
);
77
assert_eq!(
78
f.call(&mut store, ()).expect("function should not trap"),
79
-1
80
);
81
}
82
83
// Instantiate the module and grow the memory via the Wasmtime API
84
let mut store = Store::new(&engine, ());
85
let instance = Instance::new(&mut store, &module, &[])?;
86
87
let memory = instance.get_memory(&mut store, "m").unwrap();
88
assert_eq!(memory.size(&store), 0);
89
assert_eq!(memory.grow(&mut store, 1).expect("memory should grow"), 0);
90
assert_eq!(memory.size(&store), 1);
91
assert_eq!(memory.grow(&mut store, 1).expect("memory should grow"), 1);
92
assert_eq!(memory.size(&store), 2);
93
assert_eq!(memory.grow(&mut store, 1).expect("memory should grow"), 2);
94
assert_eq!(memory.size(&store), 3);
95
assert!(memory.grow(&mut store, 1).is_err());
96
97
Ok(())
98
}
99
100
#[test]
101
fn memory_init() -> Result<()> {
102
let mut pool = crate::small_pool_config();
103
pool.max_memory_size(2 << 16).table_elements(0);
104
let mut config = Config::new();
105
config.allocation_strategy(pool);
106
107
let engine = Engine::new(&config)?;
108
109
let module = Module::new(
110
&engine,
111
r#"
112
(module
113
(memory (export "m") 2)
114
(data (i32.const 65530) "this data spans multiple pages")
115
(data (i32.const 10) "hello world")
116
)
117
"#,
118
)?;
119
120
let mut store = Store::new(&engine, ());
121
let instance = Instance::new(&mut store, &module, &[])?;
122
let memory = instance.get_memory(&mut store, "m").unwrap();
123
124
assert_eq!(
125
&memory.data(&store)[65530..65560],
126
b"this data spans multiple pages"
127
);
128
assert_eq!(&memory.data(&store)[10..21], b"hello world");
129
130
Ok(())
131
}
132
133
#[test]
134
#[cfg_attr(miri, ignore)]
135
fn memory_guard_page_trap() -> Result<()> {
136
let mut pool = crate::small_pool_config();
137
pool.max_memory_size(2 << 16).table_elements(0);
138
let mut config = Config::new();
139
config.allocation_strategy(pool);
140
141
let engine = Engine::new(&config)?;
142
143
let module = Module::new(
144
&engine,
145
r#"
146
(module
147
(memory (export "m") 0)
148
(func (export "f") (param i32) local.get 0 i32.load drop)
149
)
150
"#,
151
)?;
152
153
// Instantiate the module and check for out of bounds trap
154
for _ in 0..10 {
155
let mut store = Store::new(&engine, ());
156
let instance = Instance::new(&mut store, &module, &[])?;
157
let m = instance.get_memory(&mut store, "m").unwrap();
158
let f = instance.get_typed_func::<i32, ()>(&mut store, "f")?;
159
160
let trap = f
161
.call(&mut store, 0)
162
.expect_err("function should trap")
163
.downcast::<Trap>()?;
164
assert_eq!(trap, Trap::MemoryOutOfBounds);
165
166
let trap = f
167
.call(&mut store, 1)
168
.expect_err("function should trap")
169
.downcast::<Trap>()?;
170
assert_eq!(trap, Trap::MemoryOutOfBounds);
171
172
m.grow(&mut store, 1).expect("memory should grow");
173
f.call(&mut store, 0).expect("function should not trap");
174
175
let trap = f
176
.call(&mut store, 65536)
177
.expect_err("function should trap")
178
.downcast::<Trap>()?;
179
assert_eq!(trap, Trap::MemoryOutOfBounds);
180
181
let trap = f
182
.call(&mut store, 65537)
183
.expect_err("function should trap")
184
.downcast::<Trap>()?;
185
assert_eq!(trap, Trap::MemoryOutOfBounds);
186
187
m.grow(&mut store, 1).expect("memory should grow");
188
f.call(&mut store, 65536).expect("function should not trap");
189
190
m.grow(&mut store, 1)
191
.expect_err("memory should be at the limit");
192
}
193
194
Ok(())
195
}
196
197
#[test]
198
fn memory_zeroed() -> Result<()> {
199
if skip_pooling_allocator_tests() {
200
return Ok(());
201
}
202
203
let mut pool = crate::small_pool_config();
204
pool.max_memory_size(1 << 16).table_elements(0);
205
let mut config = Config::new();
206
config.allocation_strategy(pool);
207
config.memory_guard_size(0);
208
config.memory_reservation(1 << 16);
209
210
let engine = Engine::new(&config)?;
211
212
let module = Module::new(&engine, r#"(module (memory (export "m") 1))"#)?;
213
214
// Instantiate the module repeatedly after writing data to the entire memory
215
for _ in 0..10 {
216
let mut store = Store::new(&engine, ());
217
let instance = Instance::new(&mut store, &module, &[])?;
218
let memory = instance.get_memory(&mut store, "m").unwrap();
219
220
assert_eq!(memory.size(&store,), 1);
221
assert_eq!(memory.data_size(&store), 65536);
222
223
let ptr = memory.data_mut(&mut store).as_mut_ptr();
224
225
unsafe {
226
for i in 0..8192 {
227
assert_eq!(*ptr.cast::<u64>().offset(i), 0);
228
}
229
std::ptr::write_bytes(ptr, 0xFE, memory.data_size(&store));
230
}
231
}
232
233
Ok(())
234
}
235
236
#[test]
237
#[cfg_attr(miri, ignore)]
238
fn table_limit() -> Result<()> {
239
const TABLE_ELEMENTS: usize = 10;
240
let mut pool = crate::small_pool_config();
241
pool.table_elements(TABLE_ELEMENTS);
242
let mut config = Config::new();
243
config.allocation_strategy(pool);
244
config.memory_guard_size(0);
245
config.memory_reservation(1 << 16);
246
247
let engine = Engine::new(&config)?;
248
249
// Module should fail to instantiate because it has too many tables
250
match Module::new(&engine, r#"(module (table 1 funcref) (table 1 funcref))"#) {
251
Ok(_) => panic!("module compilation should fail"),
252
Err(e) => {
253
e.assert_contains("defined tables count of 2 exceeds the per-instance limit of 1")
254
}
255
}
256
257
// Module should fail to instantiate because the minimum is greater than
258
// the configured limit
259
match Module::new(&engine, r#"(module (table 31 funcref))"#) {
260
Ok(_) => panic!("module compilation should fail"),
261
Err(e) => e.assert_contains(
262
"table index 0 has a minimum element size of 31 which exceeds the limit of 10",
263
),
264
}
265
266
let module = Module::new(
267
&engine,
268
r#"(module (table (export "t") 0 funcref) (func (export "f") (result i32) (table.grow (ref.null func) (i32.const 1))))"#,
269
)?;
270
271
// Instantiate the module and grow the table via the `f` function
272
{
273
let mut store = Store::new(&engine, ());
274
let instance = Instance::new(&mut store, &module, &[])?;
275
let f = instance.get_typed_func::<(), i32>(&mut store, "f")?;
276
277
for i in 0..TABLE_ELEMENTS {
278
assert_eq!(
279
f.call(&mut store, ()).expect("function should not trap"),
280
i as i32
281
);
282
}
283
284
assert_eq!(
285
f.call(&mut store, ()).expect("function should not trap"),
286
-1
287
);
288
assert_eq!(
289
f.call(&mut store, ()).expect("function should not trap"),
290
-1
291
);
292
}
293
294
// Instantiate the module and grow the table via the Wasmtime API
295
let mut store = Store::new(&engine, ());
296
let instance = Instance::new(&mut store, &module, &[])?;
297
298
let table = instance.get_table(&mut store, "t").unwrap();
299
300
for i in 0..TABLE_ELEMENTS {
301
assert_eq!(table.size(&store), i as u64);
302
assert_eq!(
303
table
304
.grow(&mut store, 1, Ref::Func(None))
305
.expect("table should grow"),
306
i as u64
307
);
308
}
309
310
assert_eq!(table.size(&store), TABLE_ELEMENTS as u64);
311
assert!(table.grow(&mut store, 1, Ref::Func(None)).is_err());
312
313
Ok(())
314
}
315
316
#[test]
317
#[cfg_attr(miri, ignore)]
318
fn table_init() -> Result<()> {
319
let mut pool = crate::small_pool_config();
320
pool.max_memory_size(0).table_elements(6);
321
let mut config = Config::new();
322
config.allocation_strategy(pool);
323
324
let engine = Engine::new(&config)?;
325
326
let module = Module::new(
327
&engine,
328
r#"
329
(module
330
(table (export "t") 6 funcref)
331
(elem (i32.const 1) 1 2 3 4)
332
(elem (i32.const 0) 0)
333
(func)
334
(func (param i32))
335
(func (param i32 i32))
336
(func (param i32 i32 i32))
337
(func (param i32 i32 i32 i32))
338
)
339
"#,
340
)?;
341
342
let mut store = Store::new(&engine, ());
343
let instance = Instance::new(&mut store, &module, &[])?;
344
let table = instance.get_table(&mut store, "t").unwrap();
345
346
for i in 0..5 {
347
let v = table.get(&mut store, i).expect("table should have entry");
348
let f = v
349
.as_func()
350
.expect("expected funcref")
351
.expect("expected non-null value");
352
assert_eq!(f.ty(&store).params().len(), i as usize);
353
}
354
355
assert!(
356
table
357
.get(&mut store, 5)
358
.expect("table should have entry")
359
.as_func()
360
.expect("expected funcref")
361
.is_none(),
362
"funcref should be null"
363
);
364
365
Ok(())
366
}
367
368
#[test]
369
fn table_zeroed() -> Result<()> {
370
if skip_pooling_allocator_tests() {
371
return Ok(());
372
}
373
374
let pool = crate::small_pool_config();
375
let mut config = Config::new();
376
config.allocation_strategy(pool);
377
config.memory_guard_size(0);
378
config.memory_reservation(1 << 16);
379
380
let engine = Engine::new(&config)?;
381
382
let module = Module::new(&engine, r#"(module (table (export "t") 10 funcref))"#)?;
383
384
// Instantiate the module repeatedly after filling table elements
385
for _ in 0..10 {
386
let mut store = Store::new(&engine, ());
387
let instance = Instance::new(&mut store, &module, &[])?;
388
let table = instance.get_table(&mut store, "t").unwrap();
389
let f = Func::wrap(&mut store, || {});
390
391
assert_eq!(table.size(&store), 10);
392
393
for i in 0..10 {
394
match table.get(&mut store, i).unwrap() {
395
Ref::Func(r) => assert!(r.is_none()),
396
_ => panic!("expected a funcref"),
397
}
398
table.set(&mut store, i, Ref::Func(Some(f))).unwrap();
399
}
400
}
401
402
Ok(())
403
}
404
405
#[test]
406
fn total_core_instances_limit() -> Result<()> {
407
const INSTANCE_LIMIT: u32 = 10;
408
let mut pool = crate::small_pool_config();
409
pool.total_core_instances(INSTANCE_LIMIT);
410
let mut config = Config::new();
411
config.allocation_strategy(pool);
412
config.memory_guard_size(0);
413
config.memory_reservation(1 << 16);
414
415
let engine = Engine::new(&config)?;
416
let module = Module::new(&engine, r#"(module)"#)?;
417
418
// Instantiate to the limit
419
{
420
let mut store = Store::new(&engine, ());
421
422
for _ in 0..INSTANCE_LIMIT {
423
Instance::new(&mut store, &module, &[])?;
424
}
425
426
match Instance::new(&mut store, &module, &[]) {
427
Ok(_) => panic!("instantiation should fail"),
428
Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),
429
}
430
}
431
432
// With the above store dropped, ensure instantiations can be made
433
434
let mut store = Store::new(&engine, ());
435
436
for _ in 0..INSTANCE_LIMIT {
437
Instance::new(&mut store, &module, &[])?;
438
}
439
440
Ok(())
441
}
442
443
#[test]
444
fn preserve_data_segments() -> Result<()> {
445
let mut pool = crate::small_pool_config();
446
pool.total_memories(2);
447
let mut config = Config::new();
448
config.allocation_strategy(pool);
449
let engine = Engine::new(&config)?;
450
let m = Module::new(
451
&engine,
452
r#"
453
(module
454
(memory (export "mem") 1 1)
455
(data (i32.const 0) "foo"))
456
"#,
457
)?;
458
let mut store = Store::new(&engine, ());
459
let i = Instance::new(&mut store, &m, &[])?;
460
461
// Drop the module. This should *not* drop the actual data referenced by the
462
// module.
463
drop(m);
464
465
// Spray some stuff on the heap. If wasm data lived on the heap this should
466
// paper over things and help us catch use-after-free here if it would
467
// otherwise happen.
468
if !cfg!(miri) {
469
let mut strings = Vec::new();
470
for _ in 0..1000 {
471
let mut string = String::new();
472
for _ in 0..1000 {
473
string.push('g');
474
}
475
strings.push(string);
476
}
477
drop(strings);
478
}
479
480
let mem = i.get_memory(&mut store, "mem").unwrap();
481
482
// Hopefully it's still `foo`!
483
assert!(mem.data(&store).starts_with(b"foo"));
484
485
Ok(())
486
}
487
488
#[test]
489
fn multi_memory_with_imported_memories() -> Result<()> {
490
// This test checks that the base address for the defined memory is correct for the instance
491
// despite the presence of an imported memory.
492
493
let mut pool = crate::small_pool_config();
494
pool.total_memories(2).max_memories_per_module(2);
495
let mut config = Config::new();
496
config.allocation_strategy(pool);
497
config.wasm_multi_memory(true);
498
499
let engine = Engine::new(&config)?;
500
let module = Module::new(
501
&engine,
502
r#"(module (import "" "m1" (memory 0)) (memory (export "m2") 1))"#,
503
)?;
504
505
let mut store = Store::new(&engine, ());
506
507
let m1 = Memory::new(&mut store, MemoryType::new(0, None))?;
508
let instance = Instance::new(&mut store, &module, &[m1.into()])?;
509
510
let m2 = instance.get_memory(&mut store, "m2").unwrap();
511
512
m2.data_mut(&mut store)[0] = 0x42;
513
assert_eq!(m2.data(&store)[0], 0x42);
514
515
Ok(())
516
}
517
518
#[test]
519
fn drop_externref_global_during_module_init() -> Result<()> {
520
struct Limiter;
521
522
impl ResourceLimiter for Limiter {
523
fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {
524
Ok(false)
525
}
526
527
fn table_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {
528
Ok(false)
529
}
530
}
531
532
let pool = crate::small_pool_config();
533
let mut config = Config::new();
534
config.wasm_reference_types(true);
535
config.allocation_strategy(pool);
536
537
let engine = Engine::new(&config)?;
538
539
let module = Module::new(
540
&engine,
541
r#"
542
(module
543
(global i32 (i32.const 1))
544
(global i32 (i32.const 2))
545
(global i32 (i32.const 3))
546
(global i32 (i32.const 4))
547
(global i32 (i32.const 5))
548
)
549
"#,
550
)?;
551
552
let mut store = Store::new(&engine, Limiter);
553
Instance::new(&mut store, &module, &[])?;
554
drop(store);
555
556
let module = Module::new(
557
&engine,
558
r#"
559
(module
560
(memory 1)
561
(global (mut externref) (ref.null extern))
562
)
563
"#,
564
)?;
565
566
let mut store = Store::new(&engine, Limiter);
567
store.limiter(|s| s);
568
assert!(Instance::new(&mut store, &module, &[]).is_err());
569
570
Ok(())
571
}
572
573
#[test]
574
#[cfg_attr(miri, ignore)]
575
fn switch_image_and_non_image() -> Result<()> {
576
let pool = crate::small_pool_config();
577
let mut c = Config::new();
578
c.allocation_strategy(pool);
579
let engine = Engine::new(&c)?;
580
let module1 = Module::new(
581
&engine,
582
r#"
583
(module
584
(memory 1)
585
(func (export "load") (param i32) (result i32)
586
local.get 0
587
i32.load
588
)
589
)
590
"#,
591
)?;
592
let module2 = Module::new(
593
&engine,
594
r#"
595
(module
596
(memory (export "memory") 1)
597
(data (i32.const 0) "1234")
598
)
599
"#,
600
)?;
601
602
let assert_zero = || -> Result<()> {
603
let mut store = Store::new(&engine, ());
604
let instance = Instance::new(&mut store, &module1, &[])?;
605
let func = instance.get_typed_func::<i32, i32>(&mut store, "load")?;
606
assert_eq!(func.call(&mut store, 0)?, 0);
607
Ok(())
608
};
609
610
// Initialize with a heap image and make sure the next instance, without an
611
// image, is zeroed
612
Instance::new(&mut Store::new(&engine, ()), &module2, &[])?;
613
assert_zero()?;
614
615
// ... transition back to heap image and do this again
616
Instance::new(&mut Store::new(&engine, ()), &module2, &[])?;
617
assert_zero()?;
618
619
// And go back to an image and make sure it's read/write on the host.
620
let mut store = Store::new(&engine, ());
621
let instance = Instance::new(&mut store, &module2, &[])?;
622
let memory = instance.get_memory(&mut store, "memory").unwrap();
623
let mem = memory.data_mut(&mut store);
624
assert!(mem.starts_with(b"1234"));
625
mem[..6].copy_from_slice(b"567890");
626
627
Ok(())
628
}
629
630
#[test]
631
#[cfg(target_pointer_width = "64")]
632
#[cfg_attr(miri, ignore)]
633
fn instance_too_large() -> Result<()> {
634
let mut pool = crate::small_pool_config();
635
pool.max_core_instance_size(16);
636
let mut config = Config::new();
637
config.allocation_strategy(pool);
638
639
let engine = Engine::new(&config)?;
640
match Module::new(&engine, "(module)") {
641
Ok(_) => panic!("should have failed to compile"),
642
Err(e) => {
643
e.assert_contains("exceeds the configured maximum of 16 bytes");
644
e.assert_contains("breakdown of allocation requirement");
645
e.assert_contains("instance state management");
646
e.assert_contains("static vmctx data");
647
}
648
}
649
650
let mut lots_of_globals = format!("(module");
651
for _ in 0..100 {
652
lots_of_globals.push_str("(global i32 i32.const 0)\n");
653
}
654
lots_of_globals.push_str(")");
655
656
match Module::new(&engine, &lots_of_globals) {
657
Ok(_) => panic!("should have failed to compile"),
658
Err(e) => {
659
e.assert_contains("exceeds the configured maximum of 16 bytes");
660
e.assert_contains("breakdown of allocation requirement");
661
e.assert_contains("defined globals");
662
e.assert_contains("instance state management");
663
}
664
}
665
666
Ok(())
667
}
668
669
#[test]
670
#[cfg_attr(miri, ignore)]
671
fn dynamic_memory_pooling_allocator() -> Result<()> {
672
for guard_size in [0, 1 << 16] {
673
let max_size = 128 << 20;
674
let mut pool = crate::small_pool_config();
675
pool.max_memory_size(max_size as usize);
676
let mut config = Config::new();
677
config.memory_reservation(max_size);
678
config.memory_guard_size(guard_size);
679
config.allocation_strategy(pool);
680
681
let engine = Engine::new(&config)?;
682
683
let module = Module::new(
684
&engine,
685
r#"
686
(module
687
(memory (export "memory") 1)
688
689
(func (export "grow") (param i32) (result i32)
690
local.get 0
691
memory.grow)
692
693
(func (export "size") (result i32)
694
memory.size)
695
696
(func (export "i32.load") (param i32) (result i32)
697
local.get 0
698
i32.load)
699
700
(func (export "i32.store") (param i32 i32)
701
local.get 0
702
local.get 1
703
i32.store)
704
705
(data (i32.const 100) "x")
706
)
707
"#,
708
)?;
709
710
let mut store = Store::new(&engine, ());
711
let instance = Instance::new(&mut store, &module, &[])?;
712
713
let grow = instance.get_typed_func::<u32, i32>(&mut store, "grow")?;
714
let size = instance.get_typed_func::<(), u32>(&mut store, "size")?;
715
let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;
716
let i32_store = instance.get_typed_func::<(u32, i32), ()>(&mut store, "i32.store")?;
717
let memory = instance.get_memory(&mut store, "memory").unwrap();
718
719
// basic length 1 tests
720
// assert_eq!(memory.grow(&mut store, 1)?, 0);
721
assert_eq!(memory.size(&store), 1);
722
assert_eq!(size.call(&mut store, ())?, 1);
723
assert_eq!(i32_load.call(&mut store, 0)?, 0);
724
assert_eq!(i32_load.call(&mut store, 100)?, i32::from(b'x'));
725
i32_store.call(&mut store, (0, 0))?;
726
i32_store.call(&mut store, (100, i32::from(b'y')))?;
727
assert_eq!(i32_load.call(&mut store, 100)?, i32::from(b'y'));
728
729
// basic length 2 tests
730
let page = 64 * 1024;
731
assert_eq!(grow.call(&mut store, 1)?, 1);
732
assert_eq!(memory.size(&store), 2);
733
assert_eq!(size.call(&mut store, ())?, 2);
734
i32_store.call(&mut store, (page, 200))?;
735
assert_eq!(i32_load.call(&mut store, page)?, 200);
736
737
// test writes are visible
738
i32_store.call(&mut store, (2, 100))?;
739
assert_eq!(i32_load.call(&mut store, 2)?, 100);
740
741
// test growth can't exceed maximum
742
let too_many = max_size / (64 * 1024);
743
assert_eq!(grow.call(&mut store, too_many as u32)?, -1);
744
assert!(memory.grow(&mut store, too_many).is_err());
745
746
assert_eq!(memory.data(&store)[page as usize], 200);
747
748
// Re-instantiate in another store.
749
store = Store::new(&engine, ());
750
let instance = Instance::new(&mut store, &module, &[])?;
751
let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;
752
let memory = instance.get_memory(&mut store, "memory").unwrap();
753
754
// This is out of bounds...
755
assert!(i32_load.call(&mut store, page).is_err());
756
assert_eq!(memory.data_size(&store), page as usize);
757
758
// ... but implementation-wise it should still be mapped memory from
759
// before if we don't have any guard pages.
760
//
761
// Note though that prior writes should all appear as zeros and we can't see
762
// data from the prior instance.
763
//
764
// Note that this part is only implemented on Linux which has
765
// `MADV_DONTNEED`.
766
if cfg!(target_os = "linux") && guard_size == 0 {
767
unsafe {
768
let ptr = memory.data_ptr(&store);
769
assert_eq!(*ptr.offset(page as isize), 0);
770
}
771
}
772
}
773
774
Ok(())
775
}
776
777
#[test]
778
#[cfg_attr(miri, ignore)]
779
fn zero_memory_pages_disallows_oob() -> Result<()> {
780
let mut pool = crate::small_pool_config();
781
pool.max_memory_size(0);
782
let mut config = Config::new();
783
config.allocation_strategy(pool);
784
785
let engine = Engine::new(&config)?;
786
let module = Module::new(
787
&engine,
788
r#"
789
(module
790
(memory 0)
791
792
(func (export "load") (param i32) (result i32)
793
local.get 0
794
i32.load)
795
796
(func (export "store") (param i32 )
797
local.get 0
798
local.get 0
799
i32.store)
800
)
801
"#,
802
)?;
803
let mut store = Store::new(&engine, ());
804
let instance = Instance::new(&mut store, &module, &[])?;
805
let load32 = instance.get_typed_func::<i32, i32>(&mut store, "load")?;
806
let store32 = instance.get_typed_func::<i32, ()>(&mut store, "store")?;
807
for i in 0..31 {
808
assert!(load32.call(&mut store, 1 << i).is_err());
809
assert!(store32.call(&mut store, 1 << i).is_err());
810
}
811
Ok(())
812
}
813
814
#[test]
815
#[cfg(feature = "component-model")]
816
fn total_component_instances_limit() -> Result<()> {
817
const TOTAL_COMPONENT_INSTANCES: u32 = 5;
818
819
let mut pool = crate::small_pool_config();
820
pool.total_component_instances(TOTAL_COMPONENT_INSTANCES);
821
let mut config = Config::new();
822
config.wasm_component_model(true);
823
config.allocation_strategy(pool);
824
825
let engine = Engine::new(&config)?;
826
let linker = wasmtime::component::Linker::new(&engine);
827
let component = wasmtime::component::Component::new(&engine, "(component)")?;
828
829
let mut store = Store::new(&engine, ());
830
for _ in 0..TOTAL_COMPONENT_INSTANCES {
831
linker.instantiate(&mut store, &component)?;
832
}
833
834
match linker.instantiate(&mut store, &component) {
835
Ok(_) => panic!("should have hit component instance limit"),
836
Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),
837
}
838
839
drop(store);
840
let mut store = Store::new(&engine, ());
841
for _ in 0..TOTAL_COMPONENT_INSTANCES {
842
linker.instantiate(&mut store, &component)?;
843
}
844
845
Ok(())
846
}
847
848
#[test]
849
#[cfg(feature = "component-model")]
850
#[cfg(target_pointer_width = "64")] // error message tailored for 64-bit
851
fn component_instance_size_limit() -> Result<()> {
852
let mut pool = crate::small_pool_config();
853
pool.max_component_instance_size(1);
854
let mut config = Config::new();
855
config.wasm_component_model(true);
856
config.allocation_strategy(pool);
857
let engine = Engine::new(&config)?;
858
859
match wasmtime::component::Component::new(&engine, "(component)") {
860
Ok(_) => panic!("should have hit limit"),
861
Err(e) => e.assert_contains(
862
"instance allocation for this component requires 48 bytes of \
863
`VMComponentContext` space which exceeds the configured maximum of 1 bytes",
864
),
865
}
866
867
Ok(())
868
}
869
870
#[test]
871
#[cfg_attr(miri, ignore)]
872
fn total_tables_limit() -> Result<()> {
873
const TOTAL_TABLES: u32 = 5;
874
875
let mut pool = crate::small_pool_config();
876
pool.total_tables(TOTAL_TABLES)
877
.total_core_instances(TOTAL_TABLES + 1);
878
let mut config = Config::new();
879
config.allocation_strategy(pool);
880
881
let engine = Engine::new(&config)?;
882
let linker = Linker::new(&engine);
883
let module = Module::new(&engine, "(module (table 0 1 funcref))")?;
884
885
let mut store = Store::new(&engine, ());
886
for _ in 0..TOTAL_TABLES {
887
linker.instantiate(&mut store, &module)?;
888
}
889
890
match linker.instantiate(&mut store, &module) {
891
Ok(_) => panic!("should have hit table limit"),
892
Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),
893
}
894
895
drop(store);
896
let mut store = Store::new(&engine, ());
897
for _ in 0..TOTAL_TABLES {
898
linker.instantiate(&mut store, &module)?;
899
}
900
901
Ok(())
902
}
903
904
#[tokio::test]
905
#[cfg(not(miri))]
906
async fn total_stacks_limit() -> Result<()> {
907
use super::async_functions::PollOnce;
908
909
const TOTAL_STACKS: u32 = 2;
910
911
let mut pool = crate::small_pool_config();
912
pool.total_stacks(TOTAL_STACKS)
913
.total_core_instances(TOTAL_STACKS + 1);
914
let mut config = Config::new();
915
config.async_support(true);
916
config.allocation_strategy(pool);
917
918
let engine = Engine::new(&config)?;
919
920
let mut linker = Linker::new(&engine);
921
linker.func_new_async(
922
"async",
923
"yield",
924
FuncType::new(&engine, [], []),
925
|_caller, _params, _results| {
926
Box::new(async {
927
tokio::task::yield_now().await;
928
Ok(())
929
})
930
},
931
)?;
932
933
let module = Module::new(
934
&engine,
935
r#"
936
(module
937
(import "async" "yield" (func $yield))
938
(func (export "run")
939
call $yield
940
)
941
942
(func $empty)
943
(start $empty)
944
)
945
"#,
946
)?;
947
948
// Allocate stacks up to the limit. (Poll the futures once to make sure we
949
// actually enter Wasm and force a stack allocation.)
950
951
let mut store1 = Store::new(&engine, ());
952
let instance1 = linker.instantiate_async(&mut store1, &module).await?;
953
let run1 = instance1.get_func(&mut store1, "run").unwrap();
954
let future1 = PollOnce::new(Box::pin(run1.call_async(store1, &[], &mut [])))
955
.await
956
.unwrap_err();
957
958
let mut store2 = Store::new(&engine, ());
959
let instance2 = linker.instantiate_async(&mut store2, &module).await?;
960
let run2 = instance2.get_func(&mut store2, "run").unwrap();
961
let future2 = PollOnce::new(Box::pin(run2.call_async(store2, &[], &mut [])))
962
.await
963
.unwrap_err();
964
965
// Allocating more should fail.
966
let mut store3 = Store::new(&engine, ());
967
match linker.instantiate_async(&mut store3, &module).await {
968
Ok(_) => panic!("should have hit stack limit"),
969
Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),
970
}
971
972
// Finish the futures and return their Wasm stacks to the pool.
973
future1.await?;
974
future2.await?;
975
976
// Should be able to allocate new stacks again.
977
let mut store1 = Store::new(&engine, ());
978
let instance1 = linker.instantiate_async(&mut store1, &module).await?;
979
let run1 = instance1.get_func(&mut store1, "run").unwrap();
980
let future1 = run1.call_async(&mut store1, &[], &mut []);
981
982
let mut store2 = Store::new(&engine, ());
983
let instance2 = linker.instantiate_async(&mut store2, &module).await?;
984
let run2 = instance2.get_func(&mut store2, "run").unwrap();
985
let future2 = run2.call_async(&mut store2, &[], &mut []);
986
987
future1.await?;
988
future2.await?;
989
990
// Dispose one store via `Drop`, the other via `into_data`, and ensure that
991
// any lingering stacks make their way back to the pool.
992
drop(store1);
993
store2.into_data();
994
995
Ok(())
996
}
997
998
#[test]
999
#[cfg(feature = "component-model")]
1000
fn component_core_instances_limit() -> Result<()> {
1001
let mut pool = crate::small_pool_config();
1002
pool.max_core_instances_per_component(1);
1003
let mut config = Config::new();
1004
config.wasm_component_model(true);
1005
config.allocation_strategy(pool);
1006
let engine = Engine::new(&config)?;
1007
1008
// One core instance works.
1009
wasmtime::component::Component::new(
1010
&engine,
1011
r#"
1012
(component
1013
(core module $m)
1014
(core instance $a (instantiate $m))
1015
)
1016
"#,
1017
)?;
1018
1019
// Two core instances doesn't.
1020
match wasmtime::component::Component::new(
1021
&engine,
1022
r#"
1023
(component
1024
(core module $m)
1025
(core instance $a (instantiate $m))
1026
(core instance $b (instantiate $m))
1027
)
1028
"#,
1029
) {
1030
Ok(_) => panic!("should have hit limit"),
1031
Err(e) => e.assert_contains(
1032
"The component transitively contains 2 core module instances, which exceeds the \
1033
configured maximum of 1",
1034
),
1035
}
1036
1037
Ok(())
1038
}
1039
1040
#[test]
1041
#[cfg(feature = "component-model")]
1042
fn component_memories_limit() -> Result<()> {
1043
let mut pool = crate::small_pool_config();
1044
pool.max_memories_per_component(1).total_memories(2);
1045
let mut config = Config::new();
1046
config.wasm_component_model(true);
1047
config.allocation_strategy(pool);
1048
let engine = Engine::new(&config)?;
1049
1050
// One memory works.
1051
wasmtime::component::Component::new(
1052
&engine,
1053
r#"
1054
(component
1055
(core module $m (memory 1 1))
1056
(core instance $a (instantiate $m))
1057
)
1058
"#,
1059
)?;
1060
1061
// Two memories doesn't.
1062
match wasmtime::component::Component::new(
1063
&engine,
1064
r#"
1065
(component
1066
(core module $m (memory 1 1))
1067
(core instance $a (instantiate $m))
1068
(core instance $b (instantiate $m))
1069
)
1070
"#,
1071
) {
1072
Ok(_) => panic!("should have hit limit"),
1073
Err(e) => e.assert_contains(
1074
"The component transitively contains 2 Wasm linear memories, which exceeds the \
1075
configured maximum of 1",
1076
),
1077
}
1078
1079
Ok(())
1080
}
1081
1082
#[test]
1083
#[cfg(feature = "component-model")]
1084
fn component_tables_limit() -> Result<()> {
1085
let mut pool = crate::small_pool_config();
1086
pool.max_tables_per_component(1).total_tables(2);
1087
let mut config = Config::new();
1088
config.wasm_component_model(true);
1089
config.allocation_strategy(pool);
1090
let engine = Engine::new(&config)?;
1091
1092
// One table works.
1093
wasmtime::component::Component::new(
1094
&engine,
1095
r#"
1096
(component
1097
(core module $m (table 1 1 funcref))
1098
(core instance $a (instantiate $m))
1099
)
1100
"#,
1101
)?;
1102
1103
// Two tables doesn't.
1104
match wasmtime::component::Component::new(
1105
&engine,
1106
r#"
1107
(component
1108
(core module $m (table 1 1 funcref))
1109
(core instance $a (instantiate $m))
1110
(core instance $b (instantiate $m))
1111
)
1112
"#,
1113
) {
1114
Ok(_) => panic!("should have hit limit"),
1115
Err(e) => e.assert_contains(
1116
"The component transitively contains 2 tables, which exceeds the \
1117
configured maximum of 1",
1118
),
1119
}
1120
1121
Ok(())
1122
}
1123
1124
#[test]
1125
#[cfg_attr(miri, ignore)]
1126
fn total_memories_limit() -> Result<()> {
1127
const TOTAL_MEMORIES: u32 = 5;
1128
1129
let mut pool = crate::small_pool_config();
1130
pool.total_memories(TOTAL_MEMORIES)
1131
.total_core_instances(TOTAL_MEMORIES + 1)
1132
.memory_protection_keys(Enabled::No);
1133
let mut config = Config::new();
1134
config.allocation_strategy(pool);
1135
1136
let engine = Engine::new(&config)?;
1137
let linker = Linker::new(&engine);
1138
let module = Module::new(&engine, "(module (memory 1 1))")?;
1139
1140
let mut store = Store::new(&engine, ());
1141
for _ in 0..TOTAL_MEMORIES {
1142
linker.instantiate(&mut store, &module)?;
1143
}
1144
1145
match linker.instantiate(&mut store, &module) {
1146
Ok(_) => panic!("should have hit memory limit"),
1147
Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),
1148
}
1149
1150
drop(store);
1151
let mut store = Store::new(&engine, ());
1152
for _ in 0..TOTAL_MEMORIES {
1153
linker.instantiate(&mut store, &module)?;
1154
}
1155
1156
Ok(())
1157
}
1158
1159
#[test]
1160
#[cfg_attr(miri, ignore)]
1161
fn decommit_batching() -> Result<()> {
1162
for (capacity, batch_size) in [
1163
// A reasonable batch size.
1164
(10, 5),
1165
// Batch sizes of zero and one should effectively disable batching.
1166
(10, 1),
1167
(10, 0),
1168
// A bigger batch size than capacity, which forces the allocation path
1169
// to flush the decommit queue.
1170
(10, 99),
1171
] {
1172
let mut pool = crate::small_pool_config();
1173
pool.total_memories(capacity)
1174
.total_core_instances(capacity)
1175
.decommit_batch_size(batch_size)
1176
.memory_protection_keys(Enabled::No);
1177
let mut config = Config::new();
1178
config.allocation_strategy(pool);
1179
1180
let engine = Engine::new(&config)?;
1181
let linker = Linker::new(&engine);
1182
let module = Module::new(&engine, "(module (memory 1 1))")?;
1183
1184
// Just make sure that we can instantiate all slots a few times and the
1185
// pooling allocator must be flushing the decommit queue as necessary.
1186
for _ in 0..3 {
1187
let mut store = Store::new(&engine, ());
1188
for _ in 0..capacity {
1189
linker.instantiate(&mut store, &module)?;
1190
}
1191
}
1192
}
1193
1194
Ok(())
1195
}
1196
1197
#[test]
1198
fn tricky_empty_table_with_empty_virtual_memory_alloc() -> Result<()> {
1199
// Configure the pooling allocator to have no access to virtual memory, e.g.
1200
// no table elements but a single table. This should technically support a
1201
// single empty table being allocated into it but virtual memory isn't
1202
// actually allocated here.
1203
let mut cfg = PoolingAllocationConfig::default();
1204
cfg.table_elements(0);
1205
cfg.total_memories(0);
1206
cfg.total_tables(1);
1207
cfg.total_stacks(0);
1208
cfg.total_core_instances(1);
1209
cfg.max_memory_size(0);
1210
1211
let mut c = Config::new();
1212
c.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));
1213
1214
// Disable lazy init to actually try to get this to do something interesting
1215
// at runtime.
1216
c.table_lazy_init(false);
1217
1218
let engine = Engine::new(&c)?;
1219
1220
// This module has a single empty table, with a single empty element
1221
// segment. Nothing actually goes wrong here, it should instantiate
1222
// successfully. Along the way though the empty mmap above will get viewed
1223
// as an array-of-pointers, so everything internally should all line up to
1224
// work ok.
1225
let module = Module::new(
1226
&engine,
1227
r#"
1228
(module
1229
(table 0 funcref)
1230
(elem (i32.const 0) func)
1231
)
1232
"#,
1233
)?;
1234
let mut store = Store::new(&engine, ());
1235
Instance::new(&mut store, &module, &[])?;
1236
Ok(())
1237
}
1238
1239
#[test]
1240
#[cfg_attr(miri, ignore)]
1241
fn shared_memory_unsupported() -> Result<()> {
1242
// Skip this test on platforms that don't support threads.
1243
if crate::threads::engine().is_none() {
1244
return Ok(());
1245
}
1246
let mut config = Config::new();
1247
let mut cfg = PoolingAllocationConfig::default();
1248
// shrink the size of this allocator
1249
cfg.total_memories(1);
1250
config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));
1251
let engine = Engine::new(&config)?;
1252
1253
let err = Module::new(
1254
&engine,
1255
r#"
1256
(module
1257
(memory 5 5 shared)
1258
)
1259
"#,
1260
)
1261
.unwrap_err();
1262
err.assert_contains(
1263
"memory is shared which is not supported \
1264
in the pooling allocator",
1265
);
1266
err.assert_contains("memory index 0");
1267
Ok(())
1268
}
1269
1270
#[test]
1271
#[cfg_attr(miri, ignore)]
1272
fn custom_page_sizes_reusing_same_slot() -> Result<()> {
1273
let mut config = Config::new();
1274
config.wasm_custom_page_sizes(true);
1275
let mut cfg = crate::small_pool_config();
1276
// force the memories below to collide in the same memory slot
1277
cfg.total_memories(1);
1278
config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));
1279
let engine = Engine::new(&config)?;
1280
1281
// Instantiate one module, leaving the slot 5 bytes big (but one page
1282
// accessible)
1283
{
1284
let m1 = Module::new(
1285
&engine,
1286
r#"
1287
(module
1288
(memory 5 (pagesize 1))
1289
1290
(data (i32.const 0) "a")
1291
)
1292
"#,
1293
)?;
1294
let mut store = Store::new(&engine, ());
1295
Instance::new(&mut store, &m1, &[])?;
1296
}
1297
1298
// Instantiate a second module, which should work
1299
{
1300
let m2 = Module::new(
1301
&engine,
1302
r#"
1303
(module
1304
(memory 6 (pagesize 1))
1305
1306
(data (i32.const 0) "a")
1307
)
1308
"#,
1309
)?;
1310
let mut store = Store::new(&engine, ());
1311
Instance::new(&mut store, &m2, &[])?;
1312
}
1313
Ok(())
1314
}
1315
1316
#[test]
1317
#[cfg_attr(miri, ignore)]
1318
fn instantiate_non_page_aligned_sizes() -> Result<()> {
1319
let mut config = Config::new();
1320
config.wasm_custom_page_sizes(true);
1321
let mut cfg = crate::small_pool_config();
1322
cfg.total_memories(1);
1323
cfg.max_memory_size(761927);
1324
config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));
1325
let engine = Engine::new(&config)?;
1326
1327
let module = Module::new(
1328
&engine,
1329
r#"
1330
(module
1331
(memory 761927 761927 (pagesize 0x1))
1332
)
1333
"#,
1334
)?;
1335
let mut store = Store::new(&engine, ());
1336
Instance::new(&mut store, &module, &[])?;
1337
Ok(())
1338
}
1339
1340
#[test]
1341
#[cfg_attr(miri, ignore)]
1342
fn pagemap_scan_enabled_or_disabled() -> Result<()> {
1343
let mut config = Config::new();
1344
let mut cfg = crate::small_pool_config();
1345
cfg.total_memories(1);
1346
cfg.pagemap_scan(Enabled::Yes);
1347
config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));
1348
let result = Engine::new(&config);
1349
1350
if PoolingAllocationConfig::is_pagemap_scan_available() {
1351
assert!(result.is_ok());
1352
} else {
1353
assert!(result.is_err());
1354
}
1355
Ok(())
1356
}
1357
1358
// This test instantiates a memory with an image into a slot in the pooling
1359
// allocator in a way that maps the image into the allocator but fails
1360
// instantiation. Failure here is injected with `ResourceLimiter`. Afterwards
1361
// instantiation is allowed to succeed with a memory that has no image, and this
1362
// asserts that the previous image is indeed not available any more as that
1363
// would otherwise mean data was leaked between modules.
1364
#[test]
1365
fn memory_reset_if_instantiation_fails() -> Result<()> {
1366
struct Limiter;
1367
1368
impl ResourceLimiter for Limiter {
1369
fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {
1370
Ok(false)
1371
}
1372
1373
fn table_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {
1374
Ok(false)
1375
}
1376
}
1377
1378
let pool = crate::small_pool_config();
1379
let mut config = Config::new();
1380
config.allocation_strategy(pool);
1381
let engine = Engine::new(&config)?;
1382
1383
let module_with_image = Module::new(
1384
&engine,
1385
r#"
1386
(module
1387
(memory 1)
1388
(data (i32.const 0) "\aa")
1389
)
1390
"#,
1391
)?;
1392
let module_without_image = Module::new(
1393
&engine,
1394
r#"
1395
(module
1396
(memory (export "m") 1)
1397
)
1398
"#,
1399
)?;
1400
1401
let mut store = Store::new(&engine, Limiter);
1402
store.limiter(|s| s);
1403
assert!(Instance::new(&mut store, &module_with_image, &[]).is_err());
1404
drop(store);
1405
1406
let mut store = Store::new(&engine, Limiter);
1407
let instance = Instance::new(&mut store, &module_without_image, &[])?;
1408
let mem = instance.get_memory(&mut store, "m").unwrap();
1409
let data = mem.data(&store);
1410
assert_eq!(data[0], 0);
1411
1412
Ok(())
1413
}
1414
1415