From ecb7ede2597c9c5eee2e33e9a278e485c098307b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 3 Nov 2024 08:55:22 -0800 Subject: [PATCH] Don't use `MemoryStyle` for heap base pointer relocations Instead add a helper method to `Memory` which indicates whether the base pointer of memory can move. Use this and plumb the result around to the various locations that require it. This improves the `readonly` application of the base pointer in Cranelift by having the optimization kick in in more scenarios. It additionally fixes combining shared linear memories with 64-bit addressing or a page size of 1 by adding a runtime check before relocation of a linear memory that it's allowed to do so. A few codegen tests are added to ensure that `readonly` is applied where it wasn't previously and additionally a new `*.wast` test was added with the cross product of various features of linear memories and some basic tests to ensure that the memories all work as expected. This refactoring fixes two preexisting issues about `shared` memories requiring a "static" memory style. The restriction is now based on whether the pointer can relocate or not and that's upheld via the new trait method added here. To account for these bug fixes the fuzzers have been updated to allow mixing the `threads` proposal with `memory64` or `custom-page-sizes`. Closes #4267 Closes #9523 --- crates/cranelift/src/func_environ.rs | 8 +- crates/environ/src/types.rs | 28 +- crates/fuzzing/src/generators/module.rs | 11 +- .../wasmtime/src/runtime/trampoline/memory.rs | 6 + crates/wasmtime/src/runtime/vm/memory.rs | 45 ++- .../src/runtime/vm/threads/shared_memory.rs | 15 +- .../vm/threads/shared_memory_disabled.rs | 5 +- tests/disas/readonly-heap-base-pointer1.wat | 32 ++ tests/disas/readonly-heap-base-pointer2.wat | 34 +++ tests/disas/readonly-heap-base-pointer3.wat | 31 ++ tests/misc_testsuite/memory-combos.wast | 288 ++++++++++++++++++ tests/misc_testsuite/missing-atomics.wast | 1 - 12 files changed, 470 insertions(+), 34 deletions(-) create mode 100644 tests/disas/readonly-heap-base-pointer1.wat create mode 100644 tests/disas/readonly-heap-base-pointer2.wat create mode 100644 tests/disas/readonly-heap-base-pointer3.wat create mode 100644 tests/misc_testsuite/memory-combos.wast delete mode 100644 tests/misc_testsuite/missing-atomics.wast diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 7757d3ad456a..f37cbe830485 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -2371,7 +2371,7 @@ impl<'module_environment> crate::translate::FuncEnvironment // If we have a declared maximum, we can make this a "static" heap, which is // allocated up front and never moved. let style = MemoryStyle::for_memory(memory, self.tunables); - let (readonly_base, base_fact, memory_type) = match style { + let (base_fact, memory_type) = match style { MemoryStyle::Dynamic { .. } => { let (base_fact, data_mt) = if let Some(ptr_memtype) = ptr_memtype { // Create a memtype representing the untyped memory region. @@ -2428,7 +2428,7 @@ impl<'module_environment> crate::translate::FuncEnvironment (None, None) }; - (false, base_fact, data_mt) + (base_fact, data_mt) } MemoryStyle::Static { byte_reservation: bound_bytes, @@ -2476,12 +2476,12 @@ impl<'module_environment> crate::translate::FuncEnvironment } else { (None, None) }; - (true, base_fact, data_mt) + (base_fact, data_mt) } }; let mut flags = MemFlags::trusted().with_checked(); - if readonly_base { + if !memory.memory_may_move(self.tunables) { flags.set_readonly(); } let heap_base = func.create_global_value(ir::GlobalValueData::Load { diff --git a/crates/environ/src/types.rs b/crates/environ/src/types.rs index c3ee1f56ab26..d7ca52971185 100644 --- a/crates/environ/src/types.rs +++ b/crates/environ/src/types.rs @@ -1,4 +1,4 @@ -use crate::{wasm_unsupported, WasmResult}; +use crate::{wasm_unsupported, Tunables, WasmResult}; use alloc::borrow::Cow; use alloc::boxed::Box; use core::{fmt, ops::Range}; @@ -1810,6 +1810,32 @@ impl Memory { None } } + + /// Returs whether or not the base pointer of this memory is allowed to be + /// relocated at runtime. + /// + /// When this function returns `false` then it means that after the initial + /// allocation the base pointer is constant for the entire lifetime of a + /// memory. This can enable compiler optimizations, for example. + pub fn memory_may_move(&self, tunables: &Tunables) -> bool { + // Shared memories cannot ever relocate their base pointer so the + // settings configured in the engine must be appropriate for them ahead + // of time. + if self.shared { + return false; + } + + // If movement is disallowed in engine configuration, then the answer is + // "no". + if !tunables.memory_may_move { + return false; + } + + // If the maximum size of this memory is above the threshold of the + // initial memory reservation then the memory may move. + let max = self.maximum_byte_size().unwrap_or(u64::MAX); + max > tunables.memory_reservation + } } #[derive(Copy, Clone, Debug)] diff --git a/crates/fuzzing/src/generators/module.rs b/crates/fuzzing/src/generators/module.rs index 98f05b2c2476..f3fcd4a51636 100644 --- a/crates/fuzzing/src/generators/module.rs +++ b/crates/fuzzing/src/generators/module.rs @@ -39,10 +39,7 @@ impl<'a> Arbitrary<'a> for ModuleConfig { config.custom_page_sizes_enabled = u.arbitrary()?; config.wide_arithmetic_enabled = u.arbitrary()?; config.memory64_enabled = u.ratio(1, 20)?; - // Allow the threads proposal if memory64 is not already enabled. FIXME: - // to allow threads and memory64 to coexist, see - // https://github.com/bytecodealliance/wasmtime/issues/4267. - config.threads_enabled = !config.memory64_enabled && u.ratio(1, 20)?; + config.threads_enabled = u.ratio(1, 20)?; // Allow multi-memory but make it unlikely if u.ratio(1, 20)? { config.max_memories = config.max_memories.max(2); @@ -56,12 +53,6 @@ impl<'a> Arbitrary<'a> for ModuleConfig { // do that most of the time. config.disallow_traps = u.ratio(9, 10)?; - // FIXME(#9523) this `if` should ideally be deleted, requires some - // refactoring internally. - if config.custom_page_sizes_enabled && config.threads_enabled { - config.custom_page_sizes_enabled = false; - } - Ok(ModuleConfig { config }) } } diff --git a/crates/wasmtime/src/runtime/trampoline/memory.rs b/crates/wasmtime/src/runtime/trampoline/memory.rs index ef111b6555a4..2134bf7326e2 100644 --- a/crates/wasmtime/src/runtime/trampoline/memory.rs +++ b/crates/wasmtime/src/runtime/trampoline/memory.rs @@ -114,6 +114,12 @@ impl RuntimeLinearMemory for LinearMemoryProxy { fn wasm_accessible(&self) -> Range { self.mem.wasm_accessible() } + + fn memory_may_move(&self) -> bool { + // FIXME: should propagate this up to the `LinearMemory` trait, but for + // now pessimistically assume that consumers might move linear memory. + true + } } #[derive(Clone)] diff --git a/crates/wasmtime/src/runtime/vm/memory.rs b/crates/wasmtime/src/runtime/vm/memory.rs index 8ff2895c05d3..bfb2e43088ea 100644 --- a/crates/wasmtime/src/runtime/vm/memory.rs +++ b/crates/wasmtime/src/runtime/vm/memory.rs @@ -71,6 +71,10 @@ pub trait RuntimeLinearMemory: Send + Sync { /// Returns `None` if the memory is unbounded. fn maximum_byte_size(&self) -> Option; + /// Returns whether the base pointer of this memory may be relocated over + /// time to accommodate requests for growth. + fn memory_may_move(&self) -> bool; + /// Grows a memory by `delta_pages`. /// /// This performs the necessary checks on the growth before delegating to @@ -203,6 +207,9 @@ pub struct MmapMemory { // specified so that the cost of repeated growth is amortized. extra_to_reserve_on_growth: usize, + // Whether or not this memory is allowed to relocate the base pointer. + memory_may_move: bool, + // Size in bytes of extra guard pages before the start and after the end to // optimize loads and stores with constant offsets. pre_guard_size: usize, @@ -305,6 +312,7 @@ impl MmapMemory { offset_guard_size: offset_guard_bytes, extra_to_reserve_on_growth, memory_image, + memory_may_move: ty.memory_may_move(tunables), }) } @@ -332,6 +340,10 @@ impl RuntimeLinearMemory for MmapMemory { self.maximum } + fn memory_may_move(&self) -> bool { + self.memory_may_move + } + fn grow_to(&mut self, new_size: usize) -> Result<()> { assert!(usize_is_multiple_of_host_page_size(self.offset_guard_size)); assert!(usize_is_multiple_of_host_page_size(self.pre_guard_size)); @@ -339,6 +351,10 @@ impl RuntimeLinearMemory for MmapMemory { let new_accessible = round_usize_up_to_host_pages(new_size)?; if new_accessible > self.mmap.len() - self.offset_guard_size - self.pre_guard_size { + if !self.memory_may_move { + bail!("disallowing growth as base pointer of memory cannot move"); + } + // If the new size of this heap exceeds the current size of the // allocation we have, then this must be a dynamic heap. Use // `new_size` to calculate a new size of an allocation, allocate it, @@ -507,6 +523,10 @@ impl RuntimeLinearMemory for StaticMemory { Some(self.capacity) } + fn memory_may_move(&self) -> bool { + false + } + fn grow_to(&mut self, new_byte_size: usize) -> Result<()> { // Never exceed the static memory size; this check should have been made // prior to arriving here. @@ -556,10 +576,18 @@ impl Memory { let (minimum, maximum) = Self::limit_new(ty, Some(store))?; let allocation = creator.new_memory(ty, tunables, minimum, maximum, memory_image)?; let allocation = if ty.shared { - Box::new(SharedMemory::wrap(ty, tunables, allocation)?) + Box::new(SharedMemory::wrap(ty, allocation)?) } else { allocation }; + + // Double-check that the created memory respects the safety invariant of + // whether the memory may move or not at runtime. + assert!( + ty.memory_may_move(tunables) || !allocation.memory_may_move(), + "this linear memory should not be able to move its base pointer", + ); + Ok(Memory(allocation)) } @@ -652,16 +680,11 @@ impl Memory { // informing the limiter is lossy and may not be 100% accurate, but for // now the expected uses of limiter means that's ok. if let Some(store) = store { - // We ignore the store limits for shared memories since they are - // technically not created within a store (though, trickily, they - // may be associated with one in order to get a `vmctx`). - if !ty.shared { - if !store.memory_growing(0, minimum.unwrap_or(absolute_max), maximum)? { - bail!( - "memory minimum size of {} pages exceeds memory limits", - ty.limits.min - ); - } + if !store.memory_growing(0, minimum.unwrap_or(absolute_max), maximum)? { + bail!( + "memory minimum size of {} pages exceeds memory limits", + ty.limits.min + ); } } diff --git a/crates/wasmtime/src/runtime/vm/threads/shared_memory.rs b/crates/wasmtime/src/runtime/vm/threads/shared_memory.rs index 7794b3a099af..7afe5f1e4a87 100644 --- a/crates/wasmtime/src/runtime/vm/threads/shared_memory.rs +++ b/crates/wasmtime/src/runtime/vm/threads/shared_memory.rs @@ -8,7 +8,7 @@ use std::ops::Range; use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; use std::sync::{Arc, RwLock}; use std::time::{Duration, Instant}; -use wasmtime_environ::{MemoryStyle, Trap, Tunables}; +use wasmtime_environ::{Trap, Tunables}; /// For shared memory (and only for shared memory), this lock-version restricts /// access when growing the memory or checking its size. This is to conform with @@ -33,21 +33,19 @@ impl SharedMemory { pub fn new(ty: &wasmtime_environ::Memory, tunables: &Tunables) -> Result { let (minimum_bytes, maximum_bytes) = Memory::limit_new(ty, None)?; let mmap_memory = MmapMemory::new(ty, tunables, minimum_bytes, maximum_bytes, None)?; - Self::wrap(ty, tunables, Box::new(mmap_memory)) + Self::wrap(ty, Box::new(mmap_memory)) } /// Wrap an existing [Memory] with the locking provided by a [SharedMemory]. pub fn wrap( ty: &wasmtime_environ::Memory, - tunables: &Tunables, mut memory: Box, ) -> Result { if !ty.shared { bail!("shared memory must have a `shared` memory type"); } - let style = MemoryStyle::for_memory(*ty, tunables); - if !matches!(style, MemoryStyle::Static { .. }) { - bail!("shared memory can only be built from a static memory allocation") + if memory.memory_may_move() { + bail!("shared memory cannot use a memory which may relocate the base pointer") } assert!( memory.as_any_mut().type_id() != std::any::TypeId::of::(), @@ -233,4 +231,9 @@ impl RuntimeLinearMemory for SharedMemory { fn wasm_accessible(&self) -> Range { self.0.memory.read().unwrap().wasm_accessible() } + + fn memory_may_move(&self) -> bool { + debug_assert!(!self.0.memory.read().unwrap().memory_may_move()); + false + } } diff --git a/crates/wasmtime/src/runtime/vm/threads/shared_memory_disabled.rs b/crates/wasmtime/src/runtime/vm/threads/shared_memory_disabled.rs index 204463aabf2f..74e7c8725c8e 100644 --- a/crates/wasmtime/src/runtime/vm/threads/shared_memory_disabled.rs +++ b/crates/wasmtime/src/runtime/vm/threads/shared_memory_disabled.rs @@ -12,7 +12,6 @@ pub enum SharedMemory {} impl SharedMemory { pub fn wrap( _ty: &wasmtime_environ::Memory, - _tunables: &Tunables, _memory: Box, ) -> Result { bail!("support for shared memories was disabled at compile time"); @@ -101,4 +100,8 @@ impl RuntimeLinearMemory for SharedMemory { fn wasm_accessible(&self) -> Range { match *self {} } + + fn memory_may_move(&self) -> bool { + match *self {} + } } diff --git a/tests/disas/readonly-heap-base-pointer1.wat b/tests/disas/readonly-heap-base-pointer1.wat new file mode 100644 index 000000000000..7ac2dbe43512 --- /dev/null +++ b/tests/disas/readonly-heap-base-pointer1.wat @@ -0,0 +1,32 @@ +;;! test = "optimize" +;;! target = "x86_64" +;;! flags = ["-Omemory-reservation=0x20000", "-Omemory-may-move=n"] + +(module + (memory 1) + (func $load (param i32) (result i32) + (i32.load (local.get 0))) +) +;; function u0:0(i64 vmctx, i64, i32) -> i32 tail { +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly gv0+8 +;; gv2 = load.i64 notrap aligned gv1 +;; gv3 = vmctx +;; gv4 = load.i64 notrap aligned gv3+104 +;; gv5 = load.i64 notrap aligned readonly checked gv3+96 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i32): +;; @0020 v4 = uextend.i64 v2 +;; @0020 v5 = iconst.i64 0x0001_fffc +;; @0020 v6 = icmp ugt v4, v5 ; v5 = 0x0001_fffc +;; @0020 v9 = iconst.i64 0 +;; @0020 v7 = load.i64 notrap aligned readonly checked v0+96 +;; @0020 v8 = iadd v7, v4 +;; @0020 v10 = select_spectre_guard v6, v9, v8 ; v9 = 0 +;; @0020 v11 = load.i32 little heap v10 +;; @0023 jump block1 +;; +;; block1: +;; @0023 return v11 +;; } diff --git a/tests/disas/readonly-heap-base-pointer2.wat b/tests/disas/readonly-heap-base-pointer2.wat new file mode 100644 index 000000000000..7fd8673aad87 --- /dev/null +++ b/tests/disas/readonly-heap-base-pointer2.wat @@ -0,0 +1,34 @@ +;;! test = "optimize" +;;! target = "x86_64" +;;! flags = ["-Omemory-reservation=0x20000"] + +(module + (memory 1 200 shared) + (func $load (param i32) (result i32) + (i32.load (local.get 0))) +) +;; function u0:0(i64 vmctx, i64, i32) -> i32 tail { +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly gv0+8 +;; gv2 = load.i64 notrap aligned gv1 +;; gv3 = vmctx +;; gv4 = load.i64 notrap aligned readonly gv3+88 +;; gv5 = load.i64 notrap aligned gv4+8 +;; gv6 = load.i64 notrap aligned readonly checked gv4 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i32): +;; @0022 v12 = load.i64 notrap aligned readonly v0+88 +;; @0022 v5 = load.i64 notrap aligned v12+8 +;; @0022 v4 = uextend.i64 v2 +;; @0022 v6 = icmp ugt v4, v5 +;; @0022 v9 = iconst.i64 0 +;; @0022 v7 = load.i64 notrap aligned readonly checked v12 +;; @0022 v8 = iadd v7, v4 +;; @0022 v10 = select_spectre_guard v6, v9, v8 ; v9 = 0 +;; @0022 v11 = load.i32 little heap v10 +;; @0025 jump block1 +;; +;; block1: +;; @0025 return v11 +;; } diff --git a/tests/disas/readonly-heap-base-pointer3.wat b/tests/disas/readonly-heap-base-pointer3.wat new file mode 100644 index 000000000000..801f6e31a839 --- /dev/null +++ b/tests/disas/readonly-heap-base-pointer3.wat @@ -0,0 +1,31 @@ +;;! test = "optimize" +;;! target = "x86_64" +;;! flags = ["-Wmemory64", "-Omemory-may-move=n"] + +(module + (memory i64 1) + (func $load (param i64) (result i32) + (i32.load (local.get 0))) +) +;; function u0:0(i64 vmctx, i64, i64) -> i32 tail { +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly gv0+8 +;; gv2 = load.i64 notrap aligned gv1 +;; gv3 = vmctx +;; gv4 = load.i64 notrap aligned gv3+104 +;; gv5 = load.i64 notrap aligned readonly checked gv3+96 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i64): +;; @0020 v4 = iconst.i64 0xffff_fffc +;; @0020 v5 = icmp ugt v2, v4 ; v4 = 0xffff_fffc +;; @0020 v8 = iconst.i64 0 +;; @0020 v6 = load.i64 notrap aligned readonly checked v0+96 +;; @0020 v7 = iadd v6, v2 +;; @0020 v9 = select_spectre_guard v5, v8, v7 ; v8 = 0 +;; @0020 v10 = load.i32 little heap v9 +;; @0023 jump block1 +;; +;; block1: +;; @0023 return v10 +;; } diff --git a/tests/misc_testsuite/memory-combos.wast b/tests/misc_testsuite/memory-combos.wast new file mode 100644 index 000000000000..0eb968fa7a02 --- /dev/null +++ b/tests/misc_testsuite/memory-combos.wast @@ -0,0 +1,288 @@ +;;! multi_memory = true +;;! threads = true +;;! memory64 = true +;;! custom_page_sizes = true + +;; generate the cross product of: +;; +;; * maximum listed: {no, yes} +;; * page size: {1<<16, 1} +;; * shared: {no, yes} +;; * index type: {i32, i64} +(module + (memory $m1 1) + (memory $m2 1 65536) + (memory $m3 1 (pagesize 1)) + (memory $m4 1 0xffff_ffff (pagesize 1)) + + ;; (memory $m5 1 shared) - skipped since shared requires max size + (memory $m6 1 65536 shared) + ;; (memory $m7 1 (pagesize 1)) - skipped since shared requires max size + (memory $m8 1 0xffff_ffff shared (pagesize 1)) + + (memory $m9 i64 1) + (memory $m10 i64 1 65536) + (memory $m11 i64 1 (pagesize 1)) + (memory $m12 i64 1 0xffff_ffff (pagesize 1)) + + ;; (memory $m13 1 shared) - skipped since shared requires max size + (memory $m14 i64 1 65536 shared) + ;; (memory $m16 1 (pagesize 1)) - skipped since shared requires max size + (memory $m16 i64 1 0xffff_ffff shared (pagesize 1)) + + (func (export "load_m1") (param i32) (result i32) (i32.load8_u $m1 (local.get 0))) + (func (export "load_m2") (param i32) (result i32) (i32.load8_u $m2 (local.get 0))) + (func (export "load_m3") (param i32) (result i32) (i32.load8_u $m3 (local.get 0))) + (func (export "load_m4") (param i32) (result i32) (i32.load8_u $m4 (local.get 0))) + (func (export "load_m6") (param i32) (result i32) (i32.load8_u $m6 (local.get 0))) + (func (export "load_m8") (param i32) (result i32) (i32.load8_u $m8 (local.get 0))) + (func (export "load_m9") (param i64) (result i32) (i32.load8_u $m9 (local.get 0))) + (func (export "load_m10") (param i64) (result i32) (i32.load8_u $m10 (local.get 0))) + (func (export "load_m11") (param i64) (result i32) (i32.load8_u $m11 (local.get 0))) + (func (export "load_m12") (param i64) (result i32) (i32.load8_u $m12 (local.get 0))) + (func (export "load_m14") (param i64) (result i32) (i32.load8_u $m14 (local.get 0))) + (func (export "load_m16") (param i64) (result i32) (i32.load8_u $m16 (local.get 0))) + + (func (export "store_m1") (param i32 i32) (i32.store8 $m1 (local.get 0) (local.get 1))) + (func (export "store_m2") (param i32 i32) (i32.store8 $m2 (local.get 0) (local.get 1))) + (func (export "store_m3") (param i32 i32) (i32.store8 $m3 (local.get 0) (local.get 1))) + (func (export "store_m4") (param i32 i32) (i32.store8 $m4 (local.get 0) (local.get 1))) + (func (export "store_m6") (param i32 i32) (i32.store8 $m6 (local.get 0) (local.get 1))) + (func (export "store_m8") (param i32 i32) (i32.store8 $m8 (local.get 0) (local.get 1))) + (func (export "store_m9") (param i64 i32) (i32.store8 $m9 (local.get 0) (local.get 1))) + (func (export "store_m10") (param i64 i32) (i32.store8 $m10 (local.get 0) (local.get 1))) + (func (export "store_m11") (param i64 i32) (i32.store8 $m11 (local.get 0) (local.get 1))) + (func (export "store_m12") (param i64 i32) (i32.store8 $m12 (local.get 0) (local.get 1))) + (func (export "store_m14") (param i64 i32) (i32.store8 $m14 (local.get 0) (local.get 1))) + (func (export "store_m16") (param i64 i32) (i32.store8 $m16 (local.get 0) (local.get 1))) + + (func (export "grow_m1") (param i32) (result i32) (memory.grow $m1 (local.get 0))) + (func (export "grow_m2") (param i32) (result i32) (memory.grow $m2 (local.get 0))) + (func (export "grow_m3") (param i32) (result i32) (memory.grow $m3 (local.get 0))) + (func (export "grow_m4") (param i32) (result i32) (memory.grow $m4 (local.get 0))) + (func (export "grow_m6") (param i32) (result i32) (memory.grow $m6 (local.get 0))) + (func (export "grow_m8") (param i32) (result i32) (memory.grow $m8 (local.get 0))) + (func (export "grow_m9") (param i64) (result i64) (memory.grow $m9 (local.get 0))) + (func (export "grow_m10") (param i64) (result i64) (memory.grow $m10 (local.get 0))) + (func (export "grow_m11") (param i64) (result i64) (memory.grow $m11 (local.get 0))) + (func (export "grow_m12") (param i64) (result i64) (memory.grow $m12 (local.get 0))) + (func (export "grow_m14") (param i64) (result i64) (memory.grow $m14 (local.get 0))) + (func (export "grow_m16") (param i64) (result i64) (memory.grow $m16 (local.get 0))) + + (func (export "size_m1") (result i32) (memory.size $m1)) + (func (export "size_m2") (result i32) (memory.size $m2)) + (func (export "size_m3") (result i32) (memory.size $m3)) + (func (export "size_m4") (result i32) (memory.size $m4)) + (func (export "size_m6") (result i32) (memory.size $m6)) + (func (export "size_m8") (result i32) (memory.size $m8)) + (func (export "size_m9") (result i64) (memory.size $m9)) + (func (export "size_m10") (result i64) (memory.size $m10)) + (func (export "size_m11") (result i64) (memory.size $m11)) + (func (export "size_m12") (result i64) (memory.size $m12)) + (func (export "size_m14") (result i64) (memory.size $m14)) + (func (export "size_m16") (result i64) (memory.size $m16)) +) + +(assert_return (invoke "load_m1" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "store_m1" (i32.const 0) (i32.const 1))) +(assert_return (invoke "load_m1" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "size_m1") (i32.const 1)) +(assert_trap (invoke "load_m1" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "store_m1" (i32.const 65536) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "load_m1" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m1" (i32.const -1) (i32.const 0)) "out of bounds memory access") +(assert_return (invoke "grow_m1" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "size_m1") (i32.const 2)) +(assert_return (invoke "grow_m1" (i32.const -1)) (i32.const -1)) +(assert_return (invoke "load_m1" (i32.const 65536)) (i32.const 0)) +(assert_return (invoke "store_m1" (i32.const 65536) (i32.const 1))) +(assert_return (invoke "load_m1" (i32.const 65536)) (i32.const 1)) +(assert_trap (invoke "load_m1" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m1" (i32.const -1) (i32.const 0)) "out of bounds memory access") + +(assert_return (invoke "load_m2" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "store_m2" (i32.const 0) (i32.const 1))) +(assert_return (invoke "load_m2" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "size_m2") (i32.const 1)) +(assert_trap (invoke "load_m2" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "store_m2" (i32.const 65536) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "load_m2" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m2" (i32.const -1) (i32.const 0)) "out of bounds memory access") +(assert_return (invoke "grow_m2" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "size_m2") (i32.const 2)) +(assert_return (invoke "grow_m2" (i32.const -1)) (i32.const -1)) +(assert_return (invoke "load_m2" (i32.const 65536)) (i32.const 0)) +(assert_return (invoke "store_m2" (i32.const 65536) (i32.const 1))) +(assert_return (invoke "load_m2" (i32.const 65536)) (i32.const 1)) +(assert_trap (invoke "load_m2" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m2" (i32.const -1) (i32.const 0)) "out of bounds memory access") + +(assert_return (invoke "load_m3" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "store_m3" (i32.const 0) (i32.const 1))) +(assert_return (invoke "load_m3" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "size_m3") (i32.const 1)) +(assert_trap (invoke "load_m3" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "store_m3" (i32.const 1) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "load_m3" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m3" (i32.const -1) (i32.const 0)) "out of bounds memory access") +(assert_return (invoke "grow_m3" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "size_m3") (i32.const 2)) +(assert_return (invoke "grow_m3" (i32.const -1)) (i32.const -1)) +(assert_return (invoke "load_m3" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "store_m3" (i32.const 1) (i32.const 1))) +(assert_return (invoke "load_m3" (i32.const 1)) (i32.const 1)) +(assert_trap (invoke "load_m3" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m3" (i32.const -1) (i32.const 0)) "out of bounds memory access") + +(assert_return (invoke "load_m4" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "store_m4" (i32.const 0) (i32.const 1))) +(assert_return (invoke "load_m4" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "size_m4") (i32.const 1)) +(assert_trap (invoke "load_m4" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "store_m4" (i32.const 1) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "load_m4" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m4" (i32.const -1) (i32.const 0)) "out of bounds memory access") +(assert_return (invoke "grow_m4" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "size_m4") (i32.const 2)) +(assert_return (invoke "grow_m4" (i32.const -1)) (i32.const -1)) +(assert_return (invoke "load_m4" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "store_m4" (i32.const 1) (i32.const 1))) +(assert_return (invoke "load_m4" (i32.const 1)) (i32.const 1)) +(assert_trap (invoke "load_m4" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m4" (i32.const -1) (i32.const 0)) "out of bounds memory access") + +(assert_return (invoke "load_m6" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "store_m6" (i32.const 0) (i32.const 1))) +(assert_return (invoke "load_m6" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "size_m6") (i32.const 1)) +(assert_trap (invoke "load_m6" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "store_m6" (i32.const 65536) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "load_m6" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m6" (i32.const -1) (i32.const 0)) "out of bounds memory access") +(assert_return (invoke "grow_m6" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "size_m6") (i32.const 2)) +(assert_return (invoke "grow_m6" (i32.const -1)) (i32.const -1)) +(assert_return (invoke "load_m6" (i32.const 65536)) (i32.const 0)) +(assert_return (invoke "store_m6" (i32.const 65536) (i32.const 1))) +(assert_return (invoke "load_m6" (i32.const 65536)) (i32.const 1)) +(assert_trap (invoke "load_m6" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m6" (i32.const -1) (i32.const 0)) "out of bounds memory access") + +(assert_return (invoke "load_m8" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "store_m8" (i32.const 0) (i32.const 1))) +(assert_return (invoke "load_m8" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "size_m8") (i32.const 1)) +(assert_trap (invoke "load_m8" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "store_m8" (i32.const 1) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "load_m8" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m8" (i32.const -1) (i32.const 0)) "out of bounds memory access") +(assert_return (invoke "grow_m8" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "size_m8") (i32.const 2)) +(assert_return (invoke "grow_m8" (i32.const -1)) (i32.const -1)) +(assert_return (invoke "load_m8" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "store_m8" (i32.const 1) (i32.const 1))) +(assert_return (invoke "load_m8" (i32.const 1)) (i32.const 1)) +(assert_trap (invoke "load_m8" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m8" (i32.const -1) (i32.const 0)) "out of bounds memory access") + +(assert_return (invoke "load_m9" (i64.const 0)) (i32.const 0)) +(assert_return (invoke "store_m9" (i64.const 0) (i32.const 1))) +(assert_return (invoke "load_m9" (i64.const 0)) (i32.const 1)) +(assert_return (invoke "size_m9") (i64.const 1)) +(assert_trap (invoke "load_m9" (i64.const 65536)) "out of bounds memory access") +(assert_trap (invoke "store_m9" (i64.const 65536) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "load_m9" (i64.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m9" (i64.const -1) (i32.const 0)) "out of bounds memory access") +(assert_return (invoke "grow_m9" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "size_m9") (i64.const 2)) +(assert_return (invoke "grow_m9" (i64.const -1)) (i64.const -1)) +(assert_return (invoke "load_m9" (i64.const 65536)) (i32.const 0)) +(assert_return (invoke "store_m9" (i64.const 65536) (i32.const 1))) +(assert_return (invoke "load_m9" (i64.const 65536)) (i32.const 1)) +(assert_trap (invoke "load_m9" (i64.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m9" (i64.const -1) (i32.const 0)) "out of bounds memory access") + +(assert_return (invoke "load_m10" (i64.const 0)) (i32.const 0)) +(assert_return (invoke "store_m10" (i64.const 0) (i32.const 1))) +(assert_return (invoke "load_m10" (i64.const 0)) (i32.const 1)) +(assert_return (invoke "size_m10") (i64.const 1)) +(assert_trap (invoke "load_m10" (i64.const 65536)) "out of bounds memory access") +(assert_trap (invoke "store_m10" (i64.const 65536) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "load_m10" (i64.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m10" (i64.const -1) (i32.const 0)) "out of bounds memory access") +(assert_return (invoke "grow_m10" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "size_m10") (i64.const 2)) +(assert_return (invoke "grow_m10" (i64.const -1)) (i64.const -1)) +(assert_return (invoke "load_m10" (i64.const 65536)) (i32.const 0)) +(assert_return (invoke "store_m10" (i64.const 65536) (i32.const 1))) +(assert_return (invoke "load_m10" (i64.const 65536)) (i32.const 1)) +(assert_trap (invoke "load_m10" (i64.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m10" (i64.const -1) (i32.const 0)) "out of bounds memory access") + +(assert_return (invoke "load_m11" (i64.const 0)) (i32.const 0)) +(assert_return (invoke "store_m11" (i64.const 0) (i32.const 1))) +(assert_return (invoke "load_m11" (i64.const 0)) (i32.const 1)) +(assert_return (invoke "size_m11") (i64.const 1)) +(assert_trap (invoke "load_m11" (i64.const 1)) "out of bounds memory access") +(assert_trap (invoke "store_m11" (i64.const 1) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "load_m11" (i64.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m11" (i64.const -1) (i32.const 0)) "out of bounds memory access") +(assert_return (invoke "grow_m11" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "size_m11") (i64.const 2)) +(assert_return (invoke "grow_m11" (i64.const -1)) (i64.const -1)) +(assert_return (invoke "load_m11" (i64.const 1)) (i32.const 0)) +(assert_return (invoke "store_m11" (i64.const 1) (i32.const 1))) +(assert_return (invoke "load_m11" (i64.const 1)) (i32.const 1)) +(assert_trap (invoke "load_m11" (i64.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m11" (i64.const -1) (i32.const 0)) "out of bounds memory access") + +(assert_return (invoke "load_m12" (i64.const 0)) (i32.const 0)) +(assert_return (invoke "store_m12" (i64.const 0) (i32.const 1))) +(assert_return (invoke "load_m12" (i64.const 0)) (i32.const 1)) +(assert_return (invoke "size_m12") (i64.const 1)) +(assert_trap (invoke "load_m12" (i64.const 1)) "out of bounds memory access") +(assert_trap (invoke "store_m12" (i64.const 1) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "load_m12" (i64.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m12" (i64.const -1) (i32.const 0)) "out of bounds memory access") +(assert_return (invoke "grow_m12" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "size_m12") (i64.const 2)) +(assert_return (invoke "grow_m12" (i64.const -1)) (i64.const -1)) +(assert_return (invoke "load_m12" (i64.const 1)) (i32.const 0)) +(assert_return (invoke "store_m12" (i64.const 1) (i32.const 1))) +(assert_return (invoke "load_m12" (i64.const 1)) (i32.const 1)) +(assert_trap (invoke "load_m12" (i64.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m12" (i64.const -1) (i32.const 0)) "out of bounds memory access") + +(assert_return (invoke "load_m14" (i64.const 0)) (i32.const 0)) +(assert_return (invoke "store_m14" (i64.const 0) (i32.const 1))) +(assert_return (invoke "load_m14" (i64.const 0)) (i32.const 1)) +(assert_return (invoke "size_m14") (i64.const 1)) +(assert_trap (invoke "load_m14" (i64.const 65536)) "out of bounds memory access") +(assert_trap (invoke "store_m14" (i64.const 65536) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "load_m14" (i64.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m14" (i64.const -1) (i32.const 0)) "out of bounds memory access") +(assert_return (invoke "grow_m14" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "size_m14") (i64.const 2)) +(assert_return (invoke "grow_m14" (i64.const -1)) (i64.const -1)) +(assert_return (invoke "load_m14" (i64.const 65536)) (i32.const 0)) +(assert_return (invoke "store_m14" (i64.const 65536) (i32.const 1))) +(assert_return (invoke "load_m14" (i64.const 65536)) (i32.const 1)) +(assert_trap (invoke "load_m14" (i64.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m14" (i64.const -1) (i32.const 0)) "out of bounds memory access") + +(assert_return (invoke "load_m16" (i64.const 0)) (i32.const 0)) +(assert_return (invoke "store_m16" (i64.const 0) (i32.const 1))) +(assert_return (invoke "load_m16" (i64.const 0)) (i32.const 1)) +(assert_return (invoke "size_m16") (i64.const 1)) +(assert_trap (invoke "load_m16" (i64.const 1)) "out of bounds memory access") +(assert_trap (invoke "store_m16" (i64.const 1) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "load_m16" (i64.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m16" (i64.const -1) (i32.const 0)) "out of bounds memory access") +(assert_return (invoke "grow_m16" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "size_m16") (i64.const 2)) +(assert_return (invoke "grow_m16" (i64.const -1)) (i64.const -1)) +(assert_return (invoke "load_m16" (i64.const 1)) (i32.const 0)) +(assert_return (invoke "store_m16" (i64.const 1) (i32.const 1))) +(assert_return (invoke "load_m16" (i64.const 1)) (i32.const 1)) +(assert_trap (invoke "load_m16" (i64.const -1)) "out of bounds memory access") +(assert_trap (invoke "store_m16" (i64.const -1) (i32.const 0)) "out of bounds memory access") diff --git a/tests/misc_testsuite/missing-atomics.wast b/tests/misc_testsuite/missing-atomics.wast deleted file mode 100644 index bdc355102223..000000000000 --- a/tests/misc_testsuite/missing-atomics.wast +++ /dev/null @@ -1 +0,0 @@ -(assert_invalid (module (memory 1 1 shared)) "threads must be enabled for shared memories")