Repository: bytecodealliance/wasmtime
Pull request #2613 (2021-01-28 03:50:41)

Optimize EntityList::extend and add EntityList::from_iter


Patch:
diff --git a/cranelift/codegen/src/isa/x64/inst/emit.rs b/cranelift/codegen/src/isa/x64/inst/emit.rs
index 095256ab4..0a029301a 100644
--- a/cranelift/codegen/src/isa/x64/inst/emit.rs
+++ b/cranelift/codegen/src/isa/x64/inst/emit.rs
@@ -2094,7 +2094,9 @@ pub(crate) fn emit(
                 SseOpcode::Pextrd => (LegacyPrefixes::_66, 0x0F3A16, 3),
                 SseOpcode::Pshufd => (LegacyPrefixes::_66, 0x0F70, 2),
                 SseOpcode::Roundps => (LegacyPrefixes::_66, 0x0F3A08, 3),
+                SseOpcode::Roundss => (LegacyPrefixes::_66, 0x0F3A0A, 3),
                 SseOpcode::Roundpd => (LegacyPrefixes::_66, 0x0F3A09, 3),
+                SseOpcode::Roundsd => (LegacyPrefixes::_66, 0x0F3A0B, 3),
                 _ => unimplemented!("Opcode {:?} not implemented", op),
             };
             let rex = if *is64 {
diff --git a/cranelift/codegen/src/isa/x64/inst/mod.rs b/cranelift/codegen/src/isa/x64/inst/mod.rs
index bab28f2aa..76bde8e13 100644
--- a/cranelift/codegen/src/isa/x64/inst/mod.rs
+++ b/cranelift/codegen/src/isa/x64/inst/mod.rs
@@ -566,7 +566,7 @@ impl Inst {
 
 impl Inst {
     pub(crate) fn nop(len: u8) -> Self {
-        debug_assert!(len <= 16);
+        debug_assert!(len <= 15);
         Self::Nop { len }
     }
 
@@ -1823,7 +1823,7 @@ impl fmt::Debug for Inst {
 fn x64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) {
     // This is a bit subtle. If some register is in the modified set, then it may not be in either
     // the use or def sets. However, enforcing that directly is somewhat difficult. Instead,
-    // regalloc.rs will "fix" this for us by removing the the modified set from the use and def
+    // regalloc.rs will "fix" this for us by removing the modified set from the use and def
     // sets.
     match inst {
         Inst::AluRmiR { src, dst, .. } => {
@@ -1895,6 +1895,10 @@ fn x64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) {
                 || *op == SseOpcode::Pextrw
                 || *op == SseOpcode::Pextrd
                 || *op == SseOpcode::Pshufd
+                || *op == SseOpcode::Roundss
+                || *op == SseOpcode::Roundsd
+                || *op == SseOpcode::Roundps
+                || *op == SseOpcode::Roundpd
             {
                 src.get_regs_as_uses(collector);
                 collector.add_def(*dst);
@@ -2236,6 +2240,10 @@ fn x64_map_regs<RUM: RegUsageMapper>(inst: &mut Inst, mapper: &RUM) {
                 || *op == SseOpcode::Pextrw
                 || *op == SseOpcode::Pextrd
                 || *op == SseOpcode::Pshufd
+                || *op == SseOpcode::Roundss
+                || *op == SseOpcode::Roundsd
+                || *op == SseOpcode::Roundps
+                || *op == SseOpcode::Roundpd
             {
                 src.map_uses(mapper);
                 map_def(mapper, dst);
@@ -2594,11 +2602,11 @@ impl MachInst for Inst {
     }
 
     fn gen_zero_len_nop() -> Inst {
-        Inst::Nop { len: 0 }
+        Inst::nop(0)
     }
 
     fn gen_nop(preferred_size: usize) -> Inst {
-        Inst::nop((preferred_size % 16) as u8)
+        Inst::nop(std::cmp::min(preferred_size, 15) as u8)
     }
 
     fn maybe_direct_reload(&self, _reg: VirtualReg, _slot: SpillSlot) -> Option<Inst> {
diff --git a/cranelift/codegen/src/isa/x64/lower.rs b/cranelift/codegen/src/isa/x64/lower.rs
index 45014fca1..6c002dd6c 100644
--- a/cranelift/codegen/src/isa/x64/lower.rs
+++ b/cranelift/codegen/src/isa/x64/lower.rs
@@ -8,7 +8,7 @@ use crate::ir::{
 use crate::isa::x64::abi::*;
 use crate::isa::x64::inst::args::*;
 use crate::isa::x64::inst::*;
-use crate::isa::{x64::X64Backend, CallConv};
+use crate::isa::{x64::settings as x64_settings, x64::X64Backend, CallConv};
 use crate::machinst::lower::*;
 use crate::machinst::*;
 use crate::result::CodegenResult;
@@ -1330,6 +1330,7 @@ fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
     ctx: &mut C,
     insn: IRInst,
     flags: &Flags,
+    isa_flags: &x64_settings::Flags,
     triple: &Triple,
 ) -> CodegenResult<()> {
     let op = ctx.data(insn).opcode();
@@ -4211,11 +4212,29 @@ fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
         }
 
         Opcode::Ceil | Opcode::Floor | Opcode::Nearest | Opcode::Trunc => {
-            // TODO use ROUNDSS/ROUNDSD after sse4.1.
-
-            // Lower to VM calls when there's no access to SSE4.1.
             let ty = ty.unwrap();
-            if !ty.is_vector() {
+            if isa_flags.use_sse41() {
+                let mode = match op {
+                    Opcode::Ceil => RoundImm::RoundUp,
+                    Opcode::Floor => RoundImm::RoundDown,
+                    Opcode::Nearest => RoundImm::RoundNearest,
+                    Opcode::Trunc => RoundImm::RoundZero,
+                    _ => panic!("unexpected opcode {:?} in Ceil/Floor/Nearest/Trunc", op),
+                };
+                let op = match ty {
+                    types::F32 => SseOpcode::Roundss,
+                    types::F64 => SseOpcode::Roundsd,
+                    types::F32X4 => SseOpcode::Roundps,
+                    types::F64X2 => SseOpcode::Roundpd,
+                    _ => panic!("unexpected type {:?} in Ceil/Floor/Nearest/Trunc", ty),
+                };
+                let src = input_to_reg_mem(ctx, inputs[0]);
+                let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap();
+                ctx.emit(Inst::xmm_rm_r_imm(op, src, dst, mode.encode(), false));
+            } else {
+                // Lower to VM calls when there's no access to SSE4.1.
+                // Note, for vector types on platforms that don't support sse41
+                // the execution will panic here.
                 let libcall = match (op, ty) {
                     (Opcode::Ceil, types::F32) => LibCall::CeilF32,
                     (Opcode::Ceil, types::F64) => LibCall::CeilF64,
@@ -4231,28 +4250,6 @@ fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
                     ),
                 };
                 emit_vm_call(ctx, flags, triple, libcall, insn, inputs, outputs)?;
-            } else {
-                let (op, mode) = match (op, ty) {
-                    (Opcode::Ceil, types::F32X4) => (SseOpcode::Roundps, RoundImm::RoundUp),
-                    (Opcode::Ceil, types::F64X2) => (SseOpcode::Roundpd, RoundImm::RoundUp),
-                    (Opcode::Floor, types::F32X4) => (SseOpcode::Roundps, RoundImm::RoundDown),
-                    (Opcode::Floor, types::F64X2) => (SseOpcode::Roundpd, RoundImm::RoundDown),
-                    (Opcode::Trunc, types::F32X4) => (SseOpcode::Roundps, RoundImm::RoundZero),
-                    (Opcode::Trunc, types::F64X2) => (SseOpcode::Roundpd, RoundImm::RoundZero),
-                    (Opcode::Nearest, types::F32X4) => (SseOpcode::Roundps, RoundImm::RoundNearest),
-                    (Opcode::Nearest, types::F64X2) => (SseOpcode::Roundpd, RoundImm::RoundNearest),
-                    _ => panic!("Unknown op/ty combination (vector){:?}", ty),
-                };
-                let src = put_input_in_reg(ctx, inputs[0]);
-                let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap();
-                ctx.emit(Inst::gen_move(dst, src, ty));
-                ctx.emit(Inst::xmm_rm_r_imm(
-                    op,
-                    RegMem::from(dst),
-                    dst,
-                    mode.encode(),
-                    false,
-                ));
             }
         }
 
@@ -5389,7 +5386,7 @@ impl LowerBackend for X64Backend {
     type MInst = Inst;
 
     fn lower<C: LowerCtx<I = Inst>>(&self, ctx: &mut C, ir_inst: IRInst) -> CodegenResult<()> {
-        lower_insn_to_regs(ctx, ir_inst, &self.flags, &self.triple)
+        lower_insn_to_regs(ctx, ir_inst, &self.flags, &self.x64_flags, &self.triple)
     }
 
     fn lower_branch_group<C: LowerCtx<I = Inst>>(
diff --git a/cranelift/codegen/src/machinst/mod.rs b/cranelift/codegen/src/machinst/mod.rs
index 297d53195..d65fd9b3c 100644
--- a/cranelift/codegen/src/machinst/mod.rs
+++ b/cranelift/codegen/src/machinst/mod.rs
@@ -178,7 +178,7 @@ pub trait MachInst: Clone + Debug {
     /// request a NOP of that size, or as close to it as possible. The machine
     /// backend may return a NOP whose binary encoding is smaller than the
     /// preferred size, but must not return a NOP that is larger. However,
-    /// the instruction must have a nonzero size.
+    /// the instruction must have a nonzero size if preferred_size is nonzero.
     fn gen_nop(preferred_size: usize) -> Self;
 
     /// Get the register universe for this backend.
diff --git a/cranelift/entity/src/list.rs b/cranelift/entity/src/list.rs
index 68cab8166..dfb8fac64 100644
--- a/cranelift/entity/src/list.rs
+++ b/cranelift/entity/src/list.rs
@@ -91,17 +91,20 @@ type SizeClass = u8;
 
 /// Get the size of a given size class. The size includes the length field, so the maximum list
 /// length is one less than the class size.
+#[inline]
 fn sclass_size(sclass: SizeClass) -> usize {
     4 << sclass
 }
 
 /// Get the size class to use for a given list length.
 /// This always leaves room for the length element in addition to the list elements.
+#[inline]
 fn sclass_for_length(len: usize) -> SizeClass {
     30 - (len as u32 | 3).leading_zeros() as SizeClass
 }
 
 /// Is `len` the minimum length in its size class?
+#[inline]
 fn is_sclass_min_length(len: usize) -> bool {
     len > 3 && len.is_power_of_two()
 }
@@ -387,14 +390,34 @@ impl<T: EntityRef + ReservedValue> EntityList<T> {
         &mut pool.data[block + 1..block + 1 + new_len]
     }
 
+    /// Constructs a list from an iterator.
+    pub fn from_iter<I>(elements: I, pool: &mut ListPool<T>) -> Self
+    where
+        I: IntoIterator<Item = T>,
+    {
+        let mut list = Self::new();
+        list.extend(elements, pool);
+        list
+    }
+
     /// Appends multiple elements to the back of the list.
     pub fn extend<I>(&mut self, elements: I, pool: &mut ListPool<T>)
     where
         I: IntoIterator<Item = T>,
     {
-        // TODO: use `size_hint()` to reduce reallocations.
-        for x in elements {
-            self.push(x, pool);
+        let iterator = elements.into_iter();
+        let (len, upper) = iterator.size_hint();
+        // On most iterators this check is optimized down to `true`.
+        if upper == Some(len) {
+            let data = self.grow(len, pool);
+            let offset = data.len() - len;
+            for (src, dst) in iterator.zip(data[offset..].iter_mut()) {
+                *dst = src;
+            }
+        } else {
+            for x in iterator {
+                self.push(x, pool);
+            }
         }
     }
 
@@ -630,6 +653,10 @@ mod tests {
             list.as_slice(pool),
             &[i1, i2, i3, i4, i1, i1, i2, i2, i3, i3, i4, i4]
         );
+
+        let list2 = EntityList::from_iter([i1, i1, i2, i2, i3, i3, i4, i4].iter().cloned(), pool);
+        assert_eq!(list2.len(pool), 8);
+        assert_eq!(list2.as_slice(pool), &[i1, i1, i2, i2, i3, i3, i4, i4]);
     }
 
     #[test]
diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs
index 1d0e2dcd6..8923b4c78 100644
--- a/crates/bench-api/src/lib.rs
+++ b/crates/bench-api/src/lib.rs
@@ -21,10 +21,16 @@
 //! # Example
 //!
 //! ```
+//! use std::ptr;
 //! use wasmtime_bench_api::*;
 //!
-//! let engine = unsafe { wasm_bench_create() };
-//! assert!(!engine.is_null());
+//! let working_dir = std::env::current_dir().unwrap().display().to_string();
+//! let mut bench_api = ptr::null_mut();
+//! unsafe {
+//!     let code = wasm_bench_create(working_dir.as_ptr(), working_dir.len(), &mut bench_api);
+//!     assert_eq!(code, OK);
+//!     assert!(!bench_api.is_null());
+//! };
 //!
 //! let wasm = wat::parse_bytes(br#"
 //!     (module
@@ -42,7 +48,7 @@
 //! "#).unwrap();
 //!
 //! // Start your compilation timer here.
-//! let code = unsafe { wasm_bench_compile(engine, wasm.as_ptr(), wasm.len()) };
+//! let code = unsafe { wasm_bench_compile(bench_api, wasm.as_ptr(), wasm.len()) };
 //! // End your compilation timer here.
 //! assert_eq!(code, OK);
 //!
@@ -57,24 +63,25 @@
 //! }
 //!
 //! // Start your instantiation timer here.
-//! let code = unsafe { wasm_bench_instantiate(engine, bench_start, bench_stop) };
+//! let code = unsafe { wasm_bench_instantiate(bench_api, bench_start, bench_stop) };
 //! // End your instantiation timer here.
 //! assert_eq!(code, OK);
 //!
 //! // No need to start timers for the execution since, by convention, the timer
 //! // functions we passed during instantiation will be called by the benchmark
 //! // at the appropriate time (before and after the benchmarked section).
-//! let code = unsafe { wasm_bench_execute(engine) };
+//! let code = unsafe { wasm_bench_execute(bench_api) };
 //! assert_eq!(code, OK);
 //!
 //! unsafe {
-//!     wasm_bench_free(engine);
+//!     wasm_bench_free(bench_api);
 //! }
 //! ```
 
 use anyhow::{anyhow, Context, Result};
 use std::env;
 use std::os::raw::{c_int, c_void};
+use std::path::Path;
 use std::slice;
 use wasi_common::WasiCtxBuilder;
 use wasmtime::{Config, Engine, Instance, Linker, Module, Store};
@@ -95,12 +102,32 @@ static ALLOC: shuffling_allocator::ShufflingAllocator<std::alloc::System> =
 /// Exposes a C-compatible way of creating the engine from the bytes of a single
 /// Wasm module.
 ///
-/// This function returns a pointer to a structure that contains the engine's
-/// initialized state.
+/// On success, the `out_bench_ptr` is initialized to a pointer to a structure
+/// that contains the engine's initialized state, and `0` is returned. On
+/// failure, a non-zero status code is returned and `out_bench_ptr` is left
+/// untouched.
 #[no_mangle]
-pub extern "C" fn wasm_bench_create() -> *mut c_void {
-    let state = Box::new(BenchState::new());
-    Box::into_raw(state) as _
+pub extern "C" fn wasm_bench_create(
+    working_dir_ptr: *const u8,
+    working_dir_len: usize,
+    out_bench_ptr: *mut *mut c_void,
+) -> ExitCode {
+    let result = (|| -> Result<_> {
+        let working_dir = unsafe { std::slice::from_raw_parts(working_dir_ptr, working_dir_len) };
+        let working_dir = std::str::from_utf8(working_dir)
+            .context("given working directory is not valid UTF-8")?;
+        let state = Box::new(BenchState::new(working_dir)?);
+        Ok(Box::into_raw(state) as _)
+    })();
+
+    if let Ok(bench_ptr) = result {
+        unsafe {
+            assert!(!out_bench_ptr.is_null());
+            *out_bench_ptr = bench_ptr;
+        }
+    }
+
+    to_exit_code(result.map(|_| ()))
 }
 
 /// Free the engine state allocated by this library.
@@ -164,27 +191,49 @@ fn to_exit_code<T>(result: impl Into<Result<T>>) -> ExitCode {
 /// to manage the Wasmtime engine between calls.
 struct BenchState {
     engine: Engine,
-    store: Store,
+    linker: Linker,
     module: Option<Module>,
     instance: Option<Instance>,
     did_execute: bool,
 }
 
 impl BenchState {
-    fn new() -> Self {
+    fn new(working_dir: impl AsRef<Path>) -> Result<Self> {
         let mut config = Config::new();
         config.wasm_simd(true);
         // NB: do not configure a code cache.
 
         let engine = Engine::new(&config);
         let store = Store::new(&engine);
-        Self {
+
+        let mut linker = Linker::new(&store);
+
+        // Create a WASI environment.
+
+        let mut cx = WasiCtxBuilder::new();
+        cx.inherit_stdio();
+        // Allow access to the working directory so that the benchmark can read
+        // its input workload(s).
+        let working_dir = wasi_common::preopen_dir(working_dir)
+            .context("failed to preopen the working directory")?;
+        cx.preopened_dir(working_dir, ".");
+        // Pass this env var along so that the benchmark program can use smaller
+        // input workload(s) if it has them and that has been requested.
+        if let Ok(val) = env::var("WASM_BENCH_USE_SMALL_WORKLOAD") {
+            cx.env("WASM_BENCH_USE_SMALL_WORKLOAD", &val);
+        }
+
+        let cx = cx.build()?;
+        let wasi = Wasi::new(&store, cx);
+        wasi.add_to_linker(&mut linker)?;
+
+        Ok(Self {
             engine,
-            store,
+            linker,
             module: None,
             instance: None,
             did_execute: false,
-        }
+        })
     }
 
     fn compile(&mut self, bytes: &[u8]) -> Result<()> {
@@ -210,34 +259,11 @@ impl BenchState {
             .as_mut()
             .expect("compile the module before instantiating it");
 
-        let mut linker = Linker::new(&self.store);
-
-        // Create a WASI environment.
-
-        let mut cx = WasiCtxBuilder::new();
-        cx.inherit_stdio();
-        // Allow access to the current working directory so that the benchmark
-        // can read its input workload(s). The sightglass benchmark runner will
-        // make sure that this process is spawned with its current directory set
-        // to the current benchmark's directory.
-        let cwd = wasi_common::preopen_dir(".")
-            .context("failed to open the current working directory")?;
-        cx.preopened_dir(cwd, ".");
-        // Pass this env var along so that the benchmark program can use smaller
-        // input workload(s) if it has them and that has been requested.
-        if let Ok(val) = env::var("WASM_BENCH_USE_SMALL_WORKLOAD") {
-            cx.env("WASM_BENCH_USE_SMALL_WORKLOAD", &val);
-        }
-
-        let cx = cx.build()?;
-        let wasi = Wasi::new(linker.store(), cx);
-        wasi.add_to_linker(&mut linker)?;
-
         // Import the specialized benchmarking functions.
-        linker.func("bench", "start", move || bench_start())?;
-        linker.func("bench", "end", move || bench_end())?;
+        self.linker.func("bench", "start", move || bench_start())?;
+        self.linker.func("bench", "end", move || bench_end())?;
 
-        self.instance = Some(linker.instantiate(&module)?);
+        self.instance = Some(self.linker.instantiate(&module)?);
         Ok(())
     }
 
diff --git a/crates/fuzzing/src/generators.rs b/crates/fuzzing/src/generators.rs
index d31eba04c..a35bd186a 100644
--- a/crates/fuzzing/src/generators.rs
+++ b/crates/fuzzing/src/generators.rs
@@ -75,7 +75,7 @@ pub struct Config {
 impl Config {
     /// Converts this to a `wasmtime::Config` object
     pub fn to_wasmtime(&self) -> wasmtime::Config {
-        let mut cfg = wasmtime::Config::new();
+        let mut cfg = crate::fuzz_default_config(wasmtime::Strategy::Auto).unwrap();
         cfg.debug_info(self.debug_info)
             .static_memory_maximum_size(self.static_memory_maximum_size.unwrap_or(0).into())
             .static_memory_guard_size(self.static_memory_guard_size.unwrap_or(0).into())
diff --git a/crates/fuzzing/src/lib.rs b/crates/fuzzing/src/lib.rs
index 7ef338241..d131149dd 100644
--- a/crates/fuzzing/src/lib.rs
+++ b/crates/fuzzing/src/lib.rs
@@ -39,6 +39,9 @@ pub fn fuzz_default_config(strategy: wasmtime::Strategy) -> anyhow::Result<wasmt
         .wasm_bulk_memory(true)
         .wasm_reference_types(true)
         .wasm_module_linking(true)
+        .max_instances(100)
+        .max_tables(100)
+        .max_memories(100)
         .strategy(strategy)?;
     Ok(config)
 }
diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs
index 6581f9e0d..093ffbacd 100644
--- a/crates/fuzzing/src/oracles.rs
+++ b/crates/fuzzing/src/oracles.rs
@@ -47,12 +47,11 @@ fn log_wasm(wasm: &[u8]) {
 ///
 /// You can control which compiler is used via passing a `Strategy`.
 pub fn instantiate(wasm: &[u8], known_valid: bool, strategy: Strategy) {
-    instantiate_with_config(
-        wasm,
-        known_valid,
-        crate::fuzz_default_config(strategy).unwrap(),
-        None,
-    );
+    // Explicitly disable module linking for now since it's a breaking change to
+    // pre-module-linking modules due to imports
+    let mut cfg = crate::fuzz_default_config(strategy).unwrap();
+    cfg.wasm_module_linking(false);
+    instantiate_with_config(wasm, known_valid, cfg, None);
 }
 
 /// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
@@ -101,6 +100,9 @@ pub fn instantiate_with_config(
         Ok(_) => {}
         // Allow traps which can happen normally with `unreachable`
         Err(e) if e.downcast_ref::<Trap>().is_some() => {}
+        // Allow resource exhaustion since this is something that our wasm-smith
+        // generator doesn't guarantee is forbidden.
+        Err(e) if e.to_string().contains("resource limit exceeded") => {}
         Err(e) => panic!("failed to instantiate {}", e),
     }
 }
diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs
index 942bdef0f..e554e54c3 100644
--- a/crates/runtime/src/traphandlers.rs
+++ b/crates/runtime/src/traphandlers.rs
@@ -432,7 +432,7 @@ pub unsafe trait TrapInfo {
 
     /// Returns whether the given program counter lies within wasm code,
     /// indicating whether we should handle a trap or not.
-    fn is_wasm_code(&self, pc: usize) -> bool;
+    fn is_wasm_trap(&self, pc: usize) -> bool;
 
     /// Uses `call` to call a custom signal handler, if one is specified.
     ///
@@ -635,7 +635,7 @@ impl<'a> CallThreadState<'a> {
         }
 
         // If this fault wasn't in wasm code, then it's not our problem
-        if !self.trap_info.is_wasm_code(pc as usize) {
+        if !self.trap_info.is_wasm_trap(pc as usize) {
             return ptr::null();
         }
 
diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs
index 80b10548e..3edbfebb2 100644
--- a/crates/wasmtime/src/config.rs
+++ b/crates/wasmtime/src/config.rs
@@ -34,6 +34,8 @@ pub struct Config {
     pub(crate) features: WasmFeatures,
     pub(crate) wasm_backtrace_details_env_used: bool,
     pub(crate) max_instances: usize,
+    pub(crate) max_tables: usize,
+    pub(crate) max_memories: usize,
 }
 
 impl Config {
@@ -81,6 +83,8 @@ impl Config {
                 ..WasmFeatures::default()
             },
             max_instances: 10_000,
+            max_tables: 10_000,
+            max_memories: 10_000,
         };
         ret.wasm_backtrace_details(WasmBacktraceDetails::Environment);
         return ret;
@@ -655,11 +659,35 @@ impl Config {
     /// this `Store`.
     ///
     /// Instantiation will fail with an error if this limit is exceeded.
+    ///
+    /// This value defaults to 10,000.
     pub fn max_instances(&mut self, instances: usize) -> &mut Self {
         self.max_instances = instances;
         self
     }
 
+    /// Configures the maximum number of tables which can be created within
+    /// this `Store`.
+    ///
+    /// Instantiation will fail with an error if this limit is exceeded.
+    ///
+    /// This value defaults to 10,000.
+    pub fn max_tables(&mut self, tables: usize) -> &mut Self {
+        self.max_tables = tables;
+        self
+    }
+
+    /// Configures the maximum number of memories which can be created within
+    /// this `Store`.
+    ///
+    /// Instantiation will fail with an error if this limit is exceeded.
+    ///
+    /// This value defaults to 10,000.
+    pub fn max_memories(&mut self, memories: usize) -> &mut Self {
+        self.max_memories = memories;
+        self
+    }
+
     pub(crate) fn target_isa(&self) -> Box<dyn TargetIsa> {
         self.isa_flags
             .clone()
diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs
index 02ad1a1b9..47115d90a 100644
--- a/crates/wasmtime/src/instance.rs
+++ b/crates/wasmtime/src/instance.rs
@@ -257,7 +257,7 @@ impl<'a> Instantiator<'a> {
     ///   defined here.
     fn step(&mut self) -> Result<Option<(StoreInstanceHandle, Option<RuntimeInstance>)>> {
         if self.cur.initializer == 0 {
-            self.store.bump_instance_count()?;
+            self.store.bump_resource_counts(&self.cur.module)?;
         }
 
         // Read the current module's initializer and move forward the
diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs
index be4aaf156..cc86011fb 100644
--- a/crates/wasmtime/src/store.rs
+++ b/crates/wasmtime/src/store.rs
@@ -67,8 +67,11 @@ pub(crate) struct StoreInner {
     /// Set of all compiled modules that we're holding a strong reference to
     /// the module's code for. This includes JIT functions, trampolines, etc.
     modules: RefCell<HashSet<ArcModuleCode>>,
-    /// The number of instantiated instances in this store.
+
+    // Numbers of resources instantiated in this store.
     instance_count: Cell<usize>,
+    memory_count: Cell<usize>,
+    table_count: Cell<usize>,
 }
 
 struct HostInfoKey(VMExternRef);
@@ -112,6 +115,8 @@ impl Store {
                 frame_info: Default::default(),
                 modules: Default::default(),
                 instance_count: Default::default(),
+                memory_count: Default::default(),
+                table_count: Default::default(),
             }),
         }
     }
@@ -191,8 +196,9 @@ impl Store {
             None => return,
         };
         // Only register this module if it hasn't already been registered.
-        if !self.is_wasm_code(first_pc) {
-            self.inner.frame_info.borrow_mut().register(module);
+        let mut info = self.inner.frame_info.borrow_mut();
+        if !info.contains_pc(first_pc) {
+            info.register(module);
         }
     }
 
@@ -216,12 +222,40 @@ impl Store {
         }
     }
 
-    pub(crate) fn bump_instance_count(&self) -> Result<()> {
-        let n = self.inner.instance_count.get();
-        self.inner.instance_count.set(n + 1);
-        if n >= self.engine().config().max_instances {
-            bail!("instance limit of {} exceeded", n);
+    pub(crate) fn bump_resource_counts(&self, module: &Module) -> Result<()> {
+        let config = self.engine().config();
+
+        fn bump(slot: &Cell<usize>, max: usize, amt: usize, desc: &str) -> Result<()> {
+            let new = slot.get().saturating_add(amt);
+            if new > max {
+                bail!(
+                    "resource limit exceeded: {} count too high at {}",
+                    desc,
+                    new
+                );
+            }
+            slot.set(new);
+            Ok(())
         }
+
+        let module = module.env_module();
+        let memories = module.memory_plans.len() - module.num_imported_memories;
+        let tables = module.table_plans.len() - module.num_imported_tables;
+
+        bump(
+            &self.inner.instance_count,
+            config.max_instances,
+            1,
+            "instance",
+        )?;
+        bump(
+            &self.inner.memory_count,
+            config.max_memories,
+            memories,
+            "memory",
+        )?;
+        bump(&self.inner.table_count, config.max_tables, tables, "table")?;
+
         Ok(())
     }
 
@@ -400,8 +434,8 @@ unsafe impl TrapInfo for Store {
         self
     }
 
-    fn is_wasm_code(&self, addr: usize) -> bool {
-        self.frame_info().borrow().contains_pc(addr)
+    fn is_wasm_trap(&self, addr: usize) -> bool {
+        self.frame_info().borrow().lookup_trap_info(addr).is_some()
     }
 
     fn custom_signal_handler(&self, call: &dyn Fn(&SignalHandler) -> bool) -> bool {


Test patch:
diff --git a/tests/all/module_linking.rs b/tests/all/module_linking.rs
index b81a9deb3..829ddd9b8 100644
--- a/tests/all/module_linking.rs
+++ b/tests/all/module_linking.rs
@@ -218,6 +218,81 @@ fn limit_instances() -> Result<()> {
     )?;
     let store = Store::new(&engine);
     let err = Instance::new(&store, &module, &[]).err().unwrap();
-    assert!(err.to_string().contains("instance limit of 10 exceeded"));
+    assert!(
+        err.to_string().contains("resource limit exceeded"),
+        "bad error: {}",
+        err
+    );
+    Ok(())
+}
+
+#[test]
+fn limit_memories() -> Result<()> {
+    let mut config = Config::new();
+    config.wasm_module_linking(true);
+    config.wasm_multi_memory(true);
+    config.max_memories(10);
+    let engine = Engine::new(&config);
+    let module = Module::new(
+        &engine,
+        r#"
+            (module
+              (module $m0
+                (memory 1 1)
+                (memory 1 1)
+                (memory 1 1)
+                (memory 1 1)
+                (memory 1 1)
+              )
+
+              (instance (instantiate $m0))
+              (instance (instantiate $m0))
+              (instance (instantiate $m0))
+              (instance (instantiate $m0))
+            )
+        "#,
+    )?;
+    let store = Store::new(&engine);
+    let err = Instance::new(&store, &module, &[]).err().unwrap();
+    assert!(
+        err.to_string().contains("resource limit exceeded"),
+        "bad error: {}",
+        err
+    );
+    Ok(())
+}
+
+#[test]
+fn limit_tables() -> Result<()> {
+    let mut config = Config::new();
+    config.wasm_module_linking(true);
+    config.max_tables(10);
+    let engine = Engine::new(&config);
+    let module = Module::new(
+        &engine,
+        r#"
+            (module
+              (module $m0
+                (table 1 1 funcref)
+                (table 1 1 funcref)
+                (table 1 1 funcref)
+                (table 1 1 funcref)
+                (table 1 1 funcref)
+              )
+
+              (instance (instantiate $m0))
+              (instance (instantiate $m0))
+              (instance (instantiate $m0))
+              (instance (instantiate $m0))
+            )
+        "#,
+    )?;
+    let store = Store::new(&engine);
+    let err = Instance::new(&store, &module, &[]).err().unwrap();
+    assert!(
+        err.to_string().contains("resource limit exceeded"),
+        "bad error: {}",
+        err
+    );
     Ok(())
 }