Repository: bytecodealliance/wasmtime
Pull request #8173 (2024-03-18 20:29:38)

PCC: add facts to all relevant iconsts to handle propagation through opts.
We have various constant-propagation/folding rules in the mid-end that generate new `iconst`s in place of other expressions. We got a fuzzbug with PCC wherein it was not able to verify that an iadd-iadd-uextend combination generating a Wasm address was in-range when rules reassociated the iadds to put constants together. Rather than carefully augment all rules to propagate constant facts only where they exist on the inputs, I opted to add a hook to the optimizer to generate brand-new assertions on *all* iconsts that we insert. This adds a little more work during verification (not too much hopefully: it's pretty low-overhead to check that `mov $1, %rax` puts `1` in `rax`) but should provide broader coverage of interesting corner-cases where optimization breaks the PCC chain.

Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=67432.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/RELEASES.md b/RELEASES.md
index 30e58bfdc..9a554aed0 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -8,6 +8,10 @@ Unreleased.
 
 ### Changed
 
+* The `-S common` flag is renamed to `-S cli`, to better reflect that it provides
+  the wasi-cli APIs. `-S common` is still accepted for now, and will be deprecated
+  in the future.
+
 --------------------------------------------------------------------------------
 
 ## 19.0.0
diff --git a/cranelift/codegen/src/context.rs b/cranelift/codegen/src/context.rs
index cc9719e5f..c5efafaee 100644
--- a/cranelift/codegen/src/context.rs
+++ b/cranelift/codegen/src/context.rs
@@ -365,6 +365,7 @@ impl Context {
             "About to optimize with egraph phase:\n{}",
             self.func.display()
         );
+        let fisa = fisa.into();
         self.compute_loop_analysis();
         let mut alias_analysis = AliasAnalysis::new(&self.func, &self.domtree);
         let mut pass = EgraphPass::new(
@@ -372,6 +373,7 @@ impl Context {
             &self.domtree,
             &self.loop_analysis,
             &mut alias_analysis,
+            &fisa.flags,
             ctrl_plane,
         );
         pass.run();
diff --git a/cranelift/codegen/src/egraph.rs b/cranelift/codegen/src/egraph.rs
index 8fa09e522..b1be475ac 100644
--- a/cranelift/codegen/src/egraph.rs
+++ b/cranelift/codegen/src/egraph.rs
@@ -7,12 +7,15 @@ use crate::dominator_tree::{DominatorTree, DominatorTreePreorder};
 use crate::egraph::elaborate::Elaborator;
 use crate::fx::FxHashSet;
 use crate::inst_predicates::{is_mergeable_for_egraph, is_pure_for_egraph};
+use crate::ir::pcc::Fact;
 use crate::ir::{
-    Block, DataFlowGraph, Function, Inst, InstructionData, Type, Value, ValueDef, ValueListPool,
+    Block, DataFlowGraph, Function, Inst, InstructionData, Opcode, Type, Value, ValueDef,
+    ValueListPool,
 };
 use crate::loop_analysis::LoopAnalysis;
 use crate::opts::IsleContext;
 use crate::scoped_hash_map::{Entry as ScopedEntry, ScopedHashMap};
+use crate::settings::Flags;
 use crate::trace;
 use crate::unionfind::UnionFind;
 use core::cmp::Ordering;
@@ -55,6 +58,8 @@ pub struct EgraphPass<'a> {
     /// Loop analysis results, used for built-in LICM during
     /// elaboration.
     loop_analysis: &'a LoopAnalysis,
+    /// Compiler flags.
+    flags: &'a Flags,
     /// Chaos-mode control-plane so we can test that we still get
     /// correct results when our heuristics make bad decisions.
     ctrl_plane: &'a mut ControlPlane,
@@ -91,6 +96,7 @@ where
     domtree: &'opt DominatorTreePreorder,
     pub(crate) alias_analysis: &'opt mut AliasAnalysis<'analysis>,
     pub(crate) alias_analysis_state: &'opt mut LastStores,
+    flags: &'opt Flags,
     ctrl_plane: &'opt mut ControlPlane,
     // Held locally during optimization of one node (recursively):
     pub(crate) rewrite_depth: usize,
@@ -203,6 +209,8 @@ where
                 }
             };
 
+            self.attach_constant_fact(inst, result);
+
             self.available_block[result] = self.get_available_block(inst);
             let opt_value = self.optimize_pure_enode(inst);
 
@@ -473,6 +481,22 @@ where
             false
         }
     }
+
+    /// Helper to propagate facts on constant values: if PCC is
+    /// enabled, then unconditionally add a fact attesting to the
+    /// Value's concrete value.
+    fn attach_constant_fact(&mut self, inst: Inst, value: Value) {
+        if self.flags.enable_pcc() {
+            if let InstructionData::UnaryImm {
+                opcode: Opcode::Iconst,
+                imm,
+            } = self.func.dfg.insts[inst]
+            {
+                let imm: i64 = imm.into();
+                self.func.dfg.facts[value] = Some(Fact::constant(64, imm as u64));
+            }
+        }
+    }
 }
 
 impl<'a> EgraphPass<'a> {
@@ -482,6 +506,7 @@ impl<'a> EgraphPass<'a> {
         raw_domtree: &'a DominatorTree,
         loop_analysis: &'a LoopAnalysis,
         alias_analysis: &'a mut AliasAnalysis<'a>,
+        flags: &'a Flags,
         ctrl_plane: &'a mut ControlPlane,
     ) -> Self {
         let num_values = func.dfg.num_values();
@@ -492,6 +517,7 @@ impl<'a> EgraphPass<'a> {
             domtree,
             loop_analysis,
             alias_analysis,
+            flags,
             ctrl_plane,
             stats: Stats::default(),
             eclasses: UnionFind::with_capacity(num_values),
@@ -662,6 +688,7 @@ impl<'a> EgraphPass<'a> {
                             domtree: &self.domtree,
                             alias_analysis: self.alias_analysis,
                             alias_analysis_state: &mut alias_analysis_state,
+                            flags: self.flags,
                             ctrl_plane: self.ctrl_plane,
                             optimized_values: Default::default(),
                         };
diff --git a/cranelift/codegen/src/isa/x64/lower.isle b/cranelift/codegen/src/isa/x64/lower.isle
index 9bb01775e..31cc17518 100644
--- a/cranelift/codegen/src/isa/x64/lower.isle
+++ b/cranelift/codegen/src/isa/x64/lower.isle
@@ -1485,20 +1485,12 @@
 
 ;;;; Rules for `insertlane` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(rule (lower (insertlane vec @ (value_type ty) val (u8_from_uimm8 idx)))
-      (vec_insert_lane ty vec val idx))
-
-;; Helper function used below for `insertlane` but also here for other
-;; lowerings.
-;;
-;; Note that the `Type` used here is the type of vector the insertion is
-;; happening into, or the type of the first `Reg` argument.
-(decl vec_insert_lane (Type Xmm RegMem u8) Xmm)
-
-;; i8x16.replace_lane
-(rule 1 (vec_insert_lane $I8X16 vec val idx)
-        (if-let $true (use_sse41))
-        (x64_pinsrb vec val idx))
+(rule 1 (lower (insertlane vec @ (value_type $I8X16) val (u8_from_uimm8 idx)))
+  (if-let $true (use_sse41))
+  (x64_pinsrb vec val idx))
+(rule 2 (lower (insertlane vec @ (value_type $I8X16) (sinkable_load_exact val) (u8_from_uimm8 idx)))
+  (if-let $true (use_sse41))
+  (x64_pinsrb vec val idx))
 
 ;; This lowering is particularly unoptimized and is mostly just here to work
 ;; rather than here to be fast. Requiring SSE 4.1 for the above lowering isn't
@@ -1524,7 +1516,7 @@
 ;; This all, laboriously, gets the `val` into the desired lane so it's then
 ;; `por`'d with the original vec-with-a-hole to produce the final result of the
 ;; insertion.
-(rule (vec_insert_lane $I8X16 vec val n)
+(rule (lower (insertlane vec @ (value_type $I8X16) val (u8_from_uimm8 n)))
       (let ((vec_with_hole Xmm (x64_pand vec (insert_i8x16_lane_hole n)))
             (val Gpr (x64_movzx (ExtMode.BL) val))
             (val Gpr (x64_shl $I32 val (Imm8Reg.Imm8 (u8_shl (u8_and n 3) 3))))
@@ -1540,102 +1532,108 @@
 (rule (insert_i8x16_lane_pshufd_imm 2) 0b01_00_01_01)
 (rule (insert_i8x16_lane_pshufd_imm 3) 0b00_01_01_01)
 
+
 ;; i16x8.replace_lane
-(rule (vec_insert_lane $I16X8 vec val idx)
-      (x64_pinsrw vec val idx))
+(rule (lower (insertlane vec @ (value_type $I16X8) val (u8_from_uimm8 idx)))
+  (x64_pinsrw vec val idx))
+(rule 1 (lower (insertlane vec @ (value_type $I16X8) (sinkable_load_exact val) (u8_from_uimm8 idx)))
+  (x64_pinsrw vec val idx))
 
 ;; i32x4.replace_lane
-(rule 1 (vec_insert_lane $I32X4 vec val idx)
+(rule 1 (lower (insertlane vec @ (value_type $I32X4) val (u8_from_uimm8 idx)))
         (if-let $true (use_sse41))
         (x64_pinsrd vec val idx))
 
-(rule (vec_insert_lane $I32X4 vec val 0)
-      (x64_movss_regmove vec (x64_movd_to_xmm val)))
+(rule (lower (insertlane vec @ (value_type $I32X4) val (u8_from_uimm8 0)))
+  (x64_movss_regmove vec (x64_movd_to_xmm val)))
 
 ;; tmp    = [ vec[1] vec[0] val[1] val[0] ]
 ;; result = [ vec[3] vec[2] tmp[0] tmp[2] ]
-(rule (vec_insert_lane $I32X4 vec val 1)
+(rule (lower (insertlane vec @ (value_type $I32X4) val (u8_from_uimm8 1)))
       (let ((val Xmm (x64_movd_to_xmm val))
             (vec Xmm vec))
         (x64_shufps (x64_punpcklqdq val vec) vec 0b11_10_00_10)))
 
 ;; tmp    = [ vec[0] vec[3] val[0] val[0] ]
 ;; result = [ tmp[2] tmp[0] vec[1] vec[0] ]
-(rule (vec_insert_lane $I32X4 vec val 2)
+(rule (lower (insertlane vec @ (value_type $I32X4) val (u8_from_uimm8 2)))
       (let ((val Xmm (x64_movd_to_xmm val))
             (vec Xmm vec))
         (x64_shufps vec (x64_shufps val vec 0b00_11_00_00) 0b10_00_01_00)))
 
 ;; tmp    = [ vec[3] vec[2] val[1] val[0] ]
 ;; result = [ tmp[0] tmp[2] vec[1] vec[0] ]
-(rule (vec_insert_lane $I32X4 vec val 3)
+(rule (lower (insertlane vec @ (value_type $I32X4) val (u8_from_uimm8 3)))
       (let ((val Xmm (x64_movd_to_xmm val))
             (vec Xmm vec))
         (x64_shufps vec (x64_shufps val vec 0b11_10_01_00) 0b00_10_01_00)))
 
 ;; i64x2.replace_lane
-(rule 1 (vec_insert_lane $I64X2 vec val idx)
+(rule 1 (lower (insertlane vec @ (value_type $I64X2) val (u8_from_uimm8 idx)))
         (if-let $true (use_sse41))
         (x64_pinsrq vec val idx))
-(rule (vec_insert_lane $I64X2 vec val 0)
+(rule (lower (insertlane vec @ (value_type $I64X2) val (u8_from_uimm8 0)))
       (x64_movsd_regmove vec (x64_movq_to_xmm val)))
-(rule (vec_insert_lane $I64X2 vec val 1)
+(rule (lower (insertlane vec @ (value_type $I64X2) val (u8_from_uimm8 1)))
       (x64_punpcklqdq vec (x64_movq_to_xmm val)))
 
+(rule 1 (lower (insertlane vec @ (value_type $F32X4) (sinkable_load val) (u8_from_uimm8 idx)))
+  (if-let $true (use_sse41))
+  (x64_insertps vec val (sse_insertps_lane_imm idx)))
+(rule (lower (insertlane vec @ (value_type $F32X4) val (u8_from_uimm8 idx)))
+  (f32x4_insertlane vec val idx))
+
+;; Helper function used below for `insertlane` but also here for other
+(decl f32x4_insertlane (Xmm Xmm u8) Xmm)
+
 ;; f32x4.replace_lane
-(rule 1 (vec_insert_lane $F32X4 vec val idx)
+(rule 1 (f32x4_insertlane vec val idx)
         (if-let $true (use_sse41))
         (x64_insertps vec val (sse_insertps_lane_imm idx)))
 
-;; f32x4.replace_lane 0 - without insertps
-(rule (vec_insert_lane $F32X4 vec (RegMem.Reg val) 0)
+;; External rust code used to calculate the immediate value to `insertps`.
+(decl sse_insertps_lane_imm (u8) u8)
+(extern constructor sse_insertps_lane_imm sse_insertps_lane_imm)
+
+;; f32x4.replace_lane 0
+(rule (f32x4_insertlane vec val 0)
       (x64_movss_regmove vec val))
 
-;; f32x4.replace_lane 1 - without insertps
+;; f32x4.replace_lane 1
 ;; tmp    = [ vec[1] vec[0] val[1] val[0] ]
 ;; result = [ vec[3] vec[2] tmp[0] tmp[2] ]
-(rule (vec_insert_lane $F32X4 vec (RegMem.Reg val) 1)
+(rule (f32x4_insertlane vec val 1)
       (let ((tmp Xmm (x64_movlhps val vec)))
         (x64_shufps tmp vec 0b11_10_00_10)))
 
-;; f32x4.replace_lane 2 - without insertps
+;; f32x4.replace_lane 2
 ;; tmp    = [ vec[0] vec[3] val[0] val[0] ]
 ;; result = [ tmp[2] tmp[0] vec[1] vec[0] ]
-(rule (vec_insert_lane $F32X4 vec (RegMem.Reg val) 2)
+(rule (f32x4_insertlane vec val 2)
       (let ((tmp Xmm (x64_shufps val vec 0b00_11_00_00)))
         (x64_shufps vec tmp 0b10_00_01_00)))
 
-;; f32x4.replace_lane 3 - without insertps
+;; f32x4.replace_lane 3
 ;; tmp    = [ vec[3] vec[2] val[1] val[0] ]
 ;; result = [ tmp[0] tmp[2] vec[1] vec[0] ]
-(rule (vec_insert_lane $F32X4 vec (RegMem.Reg val) 3)
+(rule (f32x4_insertlane vec val 3)
       (let ((tmp Xmm (x64_shufps val vec 0b11_10_01_00)))
         (x64_shufps vec tmp 0b00_10_01_00)))
 
-;; Recursively delegate to the above rules by loading from memory first.
-(rule (vec_insert_lane $F32X4 vec (RegMem.Mem addr) idx)
-      (vec_insert_lane $F32X4 vec (x64_movss_load addr) idx))
-
-;; External rust code used to calculate the immediate value to `insertps`.
-(decl sse_insertps_lane_imm (u8) u8)
-(extern constructor sse_insertps_lane_imm sse_insertps_lane_imm)
-
 ;; f64x2.replace_lane 0
 ;;
 ;; Here the `movsd` instruction is used specifically to specialize moving
 ;; into the fist lane where unlike above cases we're not using the lane
 ;; immediate as an immediate to the instruction itself.
-(rule (vec_insert_lane $F64X2 vec (RegMem.Reg val) 0)
+(rule (lower (insertlane vec @ (value_type $F64X2) val (u8_from_uimm8 0)))
       (x64_movsd_regmove vec val))
-(rule (vec_insert_lane $F64X2 vec (RegMem.Mem val) 0)
-      (x64_movsd_regmove vec (x64_movsd_load val)))
 
 ;; f64x2.replace_lane 1
 ;;
 ;; Here the `movlhps` instruction is used specifically to specialize moving
 ;; into the second lane where unlike above cases we're not using the lane
 ;; immediate as an immediate to the instruction itself.
-(rule (vec_insert_lane $F64X2 vec val 1)
+(rule (lower (insertlane vec @ (value_type $F64X2) val (u8_from_uimm8 1)))
       (x64_movlhps vec val))
 
 ;;;; Rules for `smin`, `smax`, `umin`, `umax` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -2804,9 +2802,9 @@
             (x64_pshufd y 3)
             (x64_pshufd z 3)))
 
-          (tmp Xmm (vec_insert_lane $F32X4 x0 x1 1))
-          (tmp Xmm (vec_insert_lane $F32X4 tmp x2 2))
-          (tmp Xmm (vec_insert_lane $F32X4 tmp x3 3))
+          (tmp Xmm (f32x4_insertlane x0 x1 1))
+          (tmp Xmm (f32x4_insertlane tmp x2 2))
+          (tmp Xmm (f32x4_insertlane tmp x3 3))
         )
         tmp))
 (rule (lower (has_type $F64X2 (fma x y z)))
@@ -2820,7 +2818,7 @@
             (x64_pshufd y 0xee)
             (x64_pshufd z 0xee)))
         )
-        (vec_insert_lane $F64X2 x0 x1 1)))
+        (x64_movlhps x0 x1)))
 
 
 ;; Special case for when the `fma` feature is active and a native instruction
@@ -4030,11 +4028,11 @@
           (libcall LibCall (round_libcall $F32 imm))
           (result Xmm (libcall_1 libcall a))
           (a1 Xmm (libcall_1 libcall (x64_pshufd a 1)))
-          (result Xmm (vec_insert_lane $F32X4 result a1 1))
+          (result Xmm (f32x4_insertlane result a1 1))
           (a2 Xmm (libcall_1 libcall (x64_pshufd a 2)))
-          (result Xmm (vec_insert_lane $F32X4 result a2 2))
+          (result Xmm (f32x4_insertlane result a2 2))
           (a3 Xmm (libcall_1 libcall (x64_pshufd a 3)))
-          (result Xmm (vec_insert_lane $F32X4 result a3 3))
+          (result Xmm (f32x4_insertlane result a3 3))
         )
         result))
 (rule (x64_round $F64X2 (RegMem.Reg a) imm)
@@ -4042,9 +4040,8 @@
           (libcall LibCall (round_libcall $F64 imm))
           (result Xmm (libcall_1 libcall a))
           (a1 Xmm (libcall_1 libcall (x64_pshufd a 0b00_00_11_10)))
-          (result Xmm (vec_insert_lane $F64X2 result a1 1))
         )
-        result))
+        (x64_movlhps result a1)))
 (rule (x64_round ty (RegMem.Mem addr) imm)
       (x64_round ty (RegMem.Reg (x64_load ty addr (ExtKind.ZeroExtend))) imm))
 
diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs
index bb3d6c21f..adbe7ea65 100644
--- a/cranelift/wasm/src/code_translator.rs
+++ b/cranelift/wasm/src/code_translator.rs
@@ -659,6 +659,13 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
                 callee,
                 state.peekn(num_args),
             )?;
+            let call = match call {
+                Some(call) => call,
+                None => {
+                    state.reachable = false;
+                    return Ok(());
+                }
+            };
             let inst_results = builder.inst_results(call);
             debug_assert_eq!(
                 inst_results.len(),
diff --git a/cranelift/wasm/src/environ/dummy.rs b/cranelift/wasm/src/environ/dummy.rs
index dac12cb1b..78ee7faf4 100644
--- a/cranelift/wasm/src/environ/dummy.rs
+++ b/cranelift/wasm/src/environ/dummy.rs
@@ -349,7 +349,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
         sig_ref: ir::SigRef,
         callee: ir::Value,
         call_args: &[ir::Value],
-    ) -> WasmResult<ir::Inst> {
+    ) -> WasmResult<Option<ir::Inst>> {
         // Pass the current function's vmctx parameter on to the callee.
         let vmctx = builder
             .func
@@ -376,10 +376,12 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
         args.extend(call_args.iter().cloned(), &mut builder.func.dfg.value_lists);
         args.push(vmctx, &mut builder.func.dfg.value_lists);
 
-        Ok(builder
-            .ins()
-            .CallIndirect(ir::Opcode::CallIndirect, INVALID, sig_ref, args)
-            .0)
+        Ok(Some(
+            builder
+                .ins()
+                .CallIndirect(ir::Opcode::CallIndirect, INVALID, sig_ref, args)
+                .0,
+        ))
     }
 
     fn translate_return_call_indirect(
diff --git a/cranelift/wasm/src/environ/spec.rs b/cranelift/wasm/src/environ/spec.rs
index 9d13f4e5a..d99696270 100644
--- a/cranelift/wasm/src/environ/spec.rs
+++ b/cranelift/wasm/src/environ/spec.rs
@@ -193,6 +193,8 @@ pub trait FuncEnvironment: TargetEnvironment {
     /// The signature `sig_ref` was previously created by `make_indirect_sig()`.
     ///
     /// Return the call instruction whose results are the WebAssembly return values.
+    /// Returns `None` if this statically traps instead of creating a call
+    /// instruction.
     fn translate_call_indirect(
         &mut self,
         builder: &mut FunctionBuilder,
@@ -201,7 +203,7 @@ pub trait FuncEnvironment: TargetEnvironment {
         sig_ref: ir::SigRef,
         callee: ir::Value,
         call_args: &[ir::Value],
-    ) -> WasmResult<ir::Inst>;
+    ) -> WasmResult<Option<ir::Inst>>;
 
     /// Translate a `return_call` WebAssembly instruction at the builder's
     /// current position.
diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs
index 5875e0b8d..d76dc9b23 100644
--- a/crates/cli-flags/src/lib.rs
+++ b/crates/cli-flags/src/lib.rs
@@ -252,7 +252,9 @@ wasmtime_option_group! {
 wasmtime_option_group! {
     #[derive(PartialEq, Clone)]
     pub struct WasiOptions {
-        /// Enable support for WASI common APIs
+        /// Enable support for WASI CLI APIs, including filesystems, sockets, clocks, and random.
+        pub cli: Option<bool>,
+        /// Deprecated alias for `cli`
         pub common: Option<bool>,
         /// Enable suport for WASI neural network API (experimental)
         pub nn: Option<bool>,
@@ -265,7 +267,7 @@ wasmtime_option_group! {
         pub listenfd: Option<bool>,
         /// Grant access to the given TCP listen socket
         pub tcplisten: Vec<String>,
-        /// Implement WASI with preview2 primitives (experimental).
+        /// Implement WASI CLI APIs with preview2 primitives (experimental).
         ///
         /// Indicates that the implementation of WASI preview1 should be backed by
         /// the preview2 implementation for components.
diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs
index a4d8c5344..d46b70cd6 100644
--- a/crates/cranelift/src/func_environ.rs
+++ b/crates/cranelift/src/func_environ.rs
@@ -13,15 +13,15 @@ use cranelift_entity::{EntityRef, PrimaryMap, SecondaryMap};
 use cranelift_frontend::FunctionBuilder;
 use cranelift_frontend::Variable;
 use cranelift_wasm::{
-    FuncIndex, FuncTranslationState, GlobalIndex, GlobalVariable, Heap, HeapData, HeapStyle,
-    MemoryIndex, TableData, TableIndex, TableSize, TargetEnvironment, TypeIndex, WasmHeapType,
-    WasmRefType, WasmResult, WasmValType,
+    EngineOrModuleTypeIndex, FuncIndex, FuncTranslationState, GlobalIndex, GlobalVariable, Heap,
+    HeapData, HeapStyle, MemoryIndex, TableData, TableIndex, TableSize, TargetEnvironment,
+    TypeIndex, WasmHeapType, WasmRefType, WasmResult, WasmValType,
 };
 use std::mem;
 use wasmparser::Operator;
 use wasmtime_environ::{
-    BuiltinFunctionIndex, MemoryPlan, MemoryStyle, Module, ModuleTranslation, ModuleTypesBuilder,
-    PtrSize, TableStyle, Tunables, TypeConvert, VMOffsets, WASM_PAGE_SIZE,
+    BuiltinFunctionIndex, MemoryPlan, MemoryStyle, Module, ModuleTranslation, ModuleType,
+    ModuleTypesBuilder, PtrSize, TableStyle, Tunables, TypeConvert, VMOffsets, WASM_PAGE_SIZE,
 };
 use wasmtime_environ::{FUNCREF_INIT_BIT, FUNCREF_MASK};
 
@@ -1046,6 +1046,16 @@ struct Call<'a, 'func, 'module_env> {
     tail: bool,
 }
 
+enum CheckIndirectCallTypeSignature {
+    Runtime,
+    StaticMatch {
+        /// Whether or not the funcref may be null or if it's statically known
+        /// to not be null.
+        may_be_null: bool,
+    },
+    StaticTrap,
+}
+
 impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
     /// Create a new `Call` site that will do regular, non-tail calls.
     pub fn new(
@@ -1147,67 +1157,160 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
         sig_ref: ir::SigRef,
         callee: ir::Value,
         call_args: &[ir::Value],
-    ) -> WasmResult<ir::Inst> {
-        let pointer_type = self.env.pointer_type();
-
+    ) -> WasmResult<Option<ir::Inst>> {
         // Get the funcref pointer from the table.
         let funcref_ptr =
             self.env
                 .get_or_init_func_ref_table_elem(self.builder, table_index, callee);
 
         // If necessary, check the signature.
-        match self.env.module.table_plans[table_index].style {
-            TableStyle::CallerChecksSignature => {
-                let sig_id_size = self.env.offsets.size_of_vmshared_type_index();
-                let sig_id_type = Type::int(u16::from(sig_id_size) * 8).unwrap();
-                let vmctx = self.env.vmctx(self.builder.func);
-                let base = self.builder.ins().global_value(pointer_type, vmctx);
-
-                // Load the caller ID. This requires loading the `*mut
-                // VMFuncRef` base pointer from `VMContext` and then loading,
-                // based on `SignatureIndex`, the corresponding entry.
-                let mem_flags = ir::MemFlags::trusted().with_readonly();
-                let signatures = self.builder.ins().load(
-                    pointer_type,
-                    mem_flags,
-                    base,
-                    i32::try_from(self.env.offsets.vmctx_type_ids_array()).unwrap(),
-                );
-                let sig_index = self.env.module.types[ty_index].unwrap_function();
-                let offset =
-                    i32::try_from(sig_index.as_u32().checked_mul(sig_id_type.bytes()).unwrap())
-                        .unwrap();
-                let caller_sig_id =
-                    self.builder
-                        .ins()
-                        .load(sig_id_type, mem_flags, signatures, offset);
+        let check = self.check_indirect_call_type_signature(table_index, ty_index, funcref_ptr);
+
+        let trap_code = match check {
+            // `funcref_ptr` is checked at runtime that its type matches,
+            // meaning that if code gets this far it's guaranteed to not be
+            // null. That means nothing in `unchecked_call` can fail.
+            CheckIndirectCallTypeSignature::Runtime => None,
+
+            // No type check was performed on `funcref_ptr` because it's
+            // statically known to have the right type. Note that whether or
+            // not the function is null is not necessarily tested so far since
+            // no type information was inspected.
+            //
+            // If the table may hold null functions, then further loads in
+            // `unchecked_call` may fail. If the table only holds non-null
+            // functions, though, then there's no possibility of a trap.
+            CheckIndirectCallTypeSignature::StaticMatch { may_be_null } => {
+                if may_be_null {
+                    Some(ir::TrapCode::IndirectCallToNull)
+                } else {
+                    None
+                }
+            }
 
-                // Load the callee ID.
+            // Code has already trapped, so return nothing indicating that this
+            // is now unreachable code.
+            CheckIndirectCallTypeSignature::StaticTrap => return Ok(None),
+        };
+        self.unchecked_call(sig_ref, funcref_ptr, trap_code, call_args)
+            .map(Some)
+    }
+
+    fn check_indirect_call_type_signature(
+        &mut self,
+        table_index: TableIndex,
+        ty_index: TypeIndex,
+        funcref_ptr: ir::Value,
+    ) -> CheckIndirectCallTypeSignature {
+        let pointer_type = self.env.pointer_type();
+        let table = &self.env.module.table_plans[table_index];
+        let sig_id_size = self.env.offsets.size_of_vmshared_type_index();
+        let sig_id_type = Type::int(u16::from(sig_id_size) * 8).unwrap();
+
+        // Generate a rustc compile error here if more styles are added in
+        // the future as the following code is tailored to just this style.
+        let TableStyle::CallerChecksSignature = table.style;
+
+        // Test if a type check is necessary for this table. If this table is a
+        // table of typed functions and that type matches `ty_index`, then
+        // there's no need to perform a typecheck.
+        match table.table.wasm_ty.heap_type {
+            // Functions do not have a statically known type in the table, a
+            // typecheck is required. Fall through to below to perform the
+            // actual typecheck.
+            WasmHeapType::Func => {}
+
+            // Functions that have a statically known type are either going to
+            // always succeed or always fail. Figure out by inspecting the types
+            // further.
+            WasmHeapType::Concrete(EngineOrModuleTypeIndex::Module(table_ty)) => {
+                // If `ty_index` matches `table_ty`, then this call is
+                // statically known to have the right type, so no checks are
+                // necessary.
+                let ModuleType::Function(specified_ty) = self.env.module.types[ty_index];
+                if specified_ty == table_ty {
+                    return CheckIndirectCallTypeSignature::StaticMatch {
+                        may_be_null: table.table.wasm_ty.nullable,
+                    };
+                }
+
+                // Otherwise if the types don't match then either (a) this is a
+                // null pointer or (b) it's a pointer with the wrong type.
+                // Figure out which and trap here.
                 //
-                // Note that the callee may be null in which case this load may
-                // trap. If so use the `IndirectCallToNull` trap code.
-                let mem_flags = ir::MemFlags::trusted().with_readonly();
-                let callee_sig_id = self.builder.ins().load(
-                    sig_id_type,
-                    mem_flags.with_trap_code(Some(ir::TrapCode::IndirectCallToNull)),
-                    funcref_ptr,
-                    i32::from(self.env.offsets.ptr.vm_func_ref_type_index()),
-                );
+                // If it's possible to have a null here then try to load the
+                // type information. If that fails due to the function being a
+                // null pointer, then this was a call to null. Otherwise if it
+                // succeeds then we know it won't match, so trap anyway.
+                if table.table.wasm_ty.nullable {
+                    let mem_flags = ir::MemFlags::trusted().with_readonly();
+                    self.builder.ins().load(
+                        sig_id_type,
+                        mem_flags.with_trap_code(Some(ir::TrapCode::IndirectCallToNull)),
+                        funcref_ptr,
+                        i32::from(self.env.offsets.ptr.vm_func_ref_type_index()),
+                    );
+                }
+                self.builder.ins().trap(ir::TrapCode::BadSignature);
+                return CheckIndirectCallTypeSignature::StaticTrap;
+            }
 
-                // Check that they match.
-                let cmp = self
-                    .builder
-                    .ins()
-                    .icmp(IntCC::Equal, callee_sig_id, caller_sig_id);
-                self.builder.ins().trapz(cmp, ir::TrapCode::BadSignature);
+            // Tables of `nofunc` can only be inhabited by null, so go ahead and
+            // trap with that.
+            WasmHeapType::NoFunc => {
+                assert!(table.table.wasm_ty.nullable);
+                self.builder.ins().trap(ir::TrapCode::IndirectCallToNull);
+                return CheckIndirectCallTypeSignature::StaticTrap;
+            }
+
+            // Engine-indexed types don't show up until runtime and it's a wasm
+            // validation error to perform a call through an `externref` table,
+            // so these cases are dynamically not reachable.
+            WasmHeapType::Concrete(EngineOrModuleTypeIndex::Engine(_)) | WasmHeapType::Extern => {
+                unreachable!()
             }
         }
 
-        // Note that `funcref_ptr` at this point is guaranteed to not be null as
-        // we've loaded its type which would have otherwise faulted. That means
-        // that further loads no longer need trap metadata, so use `None` here
-        // for the next trap code.
-        self.unchecked_call(sig_ref, funcref_ptr, None, call_args)
+        let vmctx = self.env.vmctx(self.builder.func);
+        let base = self.builder.ins().global_value(pointer_type, vmctx);
+
+        // Load the caller ID. This requires loading the `*mut VMFuncRef` base
+        // pointer from `VMContext` and then loading, based on `SignatureIndex`,
+        // the corresponding entry.
+        let mem_flags = ir::MemFlags::trusted().with_readonly();
+        let signatures = self.builder.ins().load(
+            pointer_type,
+            mem_flags,
+            base,
+            i32::try_from(self.env.offsets.vmctx_type_ids_array()).unwrap(),
+        );
+        let sig_index = self.env.module.types[ty_index].unwrap_function();
+        let offset =
+            i32::try_from(sig_index.as_u32().checked_mul(sig_id_type.bytes()).unwrap()).unwrap();
+        let caller_sig_id = self
+            .builder
+            .ins()
+            .load(sig_id_type, mem_flags, signatures, offset);
+
+        // Load the callee ID.
+        //
+        // Note that the callee may be null in which case this load may
+        // trap. If so use the `IndirectCallToNull` trap code.
+        let mem_flags = ir::MemFlags::trusted().with_readonly();
+        let callee_sig_id = self.builder.ins().load(
+            sig_id_type,
+            mem_flags.with_trap_code(Some(ir::TrapCode::IndirectCallToNull)),
+            funcref_ptr,
+            i32::from(self.env.offsets.ptr.vm_func_ref_type_index()),
+        );
+
+        // Check that they match.
+        let cmp = self
+            .builder
+            .ins()
+            .icmp(IntCC::Equal, callee_sig_id, caller_sig_id);
+        self.builder.ins().trapz(cmp, ir::TrapCode::BadSignature);
+        CheckIndirectCallTypeSignature::Runtime
     }
 
     /// Call a typed function reference.
@@ -2234,7 +2337,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
         sig_ref: ir::SigRef,
         callee: ir::Value,
         call_args: &[ir::Value],
-    ) -> WasmResult<ir::Inst> {
+    ) -> WasmResult<Option<ir::Inst>> {
         Call::new(builder, self).indirect_call(table_index, ty_index, sig_ref, callee, call_args)
     }
 
diff --git a/src/commands/run.rs b/src/commands/run.rs
index 10c97649a..1413874ee 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -607,7 +607,22 @@ impl RunCommand {
         store: &mut Store<Host>,
         module: &RunTarget,
     ) -> Result<()> {
-        if self.run.common.wasi.common != Some(false) {
+        let mut cli = self.run.common.wasi.cli;
+
+        // Accept -Scommon as a deprecated alias for -Scli.
+        if let Some(common) = self.run.common.wasi.common {
+            if cli.is_some() {
+                bail!(
+                    "The -Scommon option should not be use with -Scli as it is a deprecated alias"
+                );
+            } else {
+                // In the future, we may add a warning here to tell users to use
+                // `-S cli` instead of `-S common`.
+                cli = Some(common);
+            }
+        }
+
+        if cli != Some(false) {
             match linker {
                 CliLinker::Core(linker) => {
                     match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index e59f91839..c35182b59 100644
--- a/src/commands/serve.rs
+++ b/src/commands/serve.rs
@@ -191,16 +191,30 @@ impl ServeCommand {
     }
 
     fn add_to_linker(&self, linker: &mut Linker<Host>) -> Result<()> {
-        // Repurpose the `-Scommon` flag of `wasmtime run` for `wasmtime serve`
+        let mut cli = self.run.common.wasi.cli;
+
+        // Accept -Scommon as a deprecated alias for -Scli.
+        if let Some(common) = self.run.common.wasi.common {
+            if cli.is_some() {
+                bail!(
+                    "The -Scommon option should not be use with -Scli as it is a deprecated alias"
+                );
+            } else {
+                // In the future, we may add a warning here to tell users to use
+                // `-S cli` instead of `-S common`.
+                cli = Some(common);
+            }
+        }
+
+        // Repurpose the `-Scli` flag of `wasmtime run` for `wasmtime serve`
         // to serve as a signal to enable all WASI interfaces instead of just
-        // those in the `proxy` world. If `-Scommon` is present then add all
-        // `command` APIs which includes all "common" or base WASI APIs and then
-        // additionally add in the required HTTP APIs.
+        // those in the `proxy` world. If `-Scli` is present then add all
+        // `command` APIs and then additionally add in the required HTTP APIs.
         //
-        // If `-Scommon` isn't passed then use the `proxy::add_to_linker`
+        // If `-Scli` isn't passed then use the `proxy::add_to_linker`
         // bindings which adds just those interfaces that the proxy interface
         // uses.
-        if self.run.common.wasi.common == Some(true) {
+        if cli == Some(true) {
             wasmtime_wasi::command::add_to_linker(linker)?;
             wasmtime_wasi_http::proxy::add_only_http_to_linker(linker)?;
         } else {
diff --git a/src/old_cli.rs b/src/old_cli.rs
index f6bbe5ff4..c863fc1fd 100644
--- a/src/old_cli.rs
+++ b/src/old_cli.rs
@@ -981,7 +981,7 @@ impl CommonOptions {
             ret.wasi.http = wasi_http;
             ret.wasi.nn = wasi_nn;
             ret.wasi.threads = wasi_threads;
-            ret.wasi.common = wasi_common;
+            ret.wasi.cli = wasi_common;
         }
         ret
     }


Test patch:
diff --git a/cranelift/filetests/filetests/isa/x64/insertlane-avx.clif b/cranelift/filetests/filetests/isa/x64/insertlane-avx.clif
index 7b1368a9b..5f137321d 100644
--- a/cranelift/filetests/filetests/isa/x64/insertlane-avx.clif
+++ b/cranelift/filetests/filetests/isa/x64/insertlane-avx.clif
@@ -62,8 +62,8 @@ block0(v0: f64x2, v1: i64):
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   vmovsd  0(%rdi), %xmm3
-;   vmovsd  %xmm0, %xmm3, %xmm0
+;   vmovsd  0(%rdi), %xmm4
+;   vmovsd  %xmm0, %xmm4, %xmm0
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -73,8 +73,8 @@ block0(v0: f64x2, v1: i64):
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   vmovsd (%rdi), %xmm3 ; trap: heap_oob
-;   vmovsd %xmm3, %xmm0, %xmm0
+;   vmovsd (%rdi), %xmm4 ; trap: heap_oob
+;   vmovsd %xmm4, %xmm0, %xmm0
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
@@ -90,8 +90,7 @@ block0(v0: i8x16, v1: i64):
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   movzbq  0(%rdi), %rdx
-;   vpinsrb $1, %xmm0, %rdx, %xmm0
+;   vpinsrb $1, %xmm0, 0(%rdi), %xmm0
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -101,8 +100,7 @@ block0(v0: i8x16, v1: i64):
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   movzbq (%rdi), %rdx ; trap: heap_oob
-;   vpinsrb $1, %edx, %xmm0, %xmm0
+;   vpinsrb $1, (%rdi), %xmm0, %xmm0 ; trap: heap_oob
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
@@ -118,8 +116,7 @@ block0(v0: i16x8, v1: i64):
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   movzwq  0(%rdi), %rdx
-;   vpinsrw $1, %xmm0, %rdx, %xmm0
+;   vpinsrw $1, %xmm0, 0(%rdi), %xmm0
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -129,8 +126,7 @@ block0(v0: i16x8, v1: i64):
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   movzwq (%rdi), %rdx ; trap: heap_oob
-;   vpinsrw $1, %edx, %xmm0, %xmm0
+;   vpinsrw $1, (%rdi), %xmm0, %xmm0 ; trap: heap_oob
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
diff --git a/cranelift/filetests/filetests/isa/x64/insertlane.clif b/cranelift/filetests/filetests/isa/x64/insertlane.clif
index d15afb787..d27740f58 100644
--- a/cranelift/filetests/filetests/isa/x64/insertlane.clif
+++ b/cranelift/filetests/filetests/isa/x64/insertlane.clif
@@ -62,8 +62,8 @@ block0(v0: f64x2, v1: i64):
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   movsd   0(%rdi), %xmm3
-;   movsd   %xmm0, %xmm3, %xmm0
+;   movsd   0(%rdi), %xmm4
+;   movsd   %xmm0, %xmm4, %xmm0
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -73,8 +73,8 @@ block0(v0: f64x2, v1: i64):
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   movsd (%rdi), %xmm3 ; trap: heap_oob
-;   movsd %xmm3, %xmm0
+;   movsd (%rdi), %xmm4 ; trap: heap_oob
+;   movsd %xmm4, %xmm0
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
@@ -90,8 +90,7 @@ block0(v0: i8x16, v1: i64):
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   movzbq  0(%rdi), %rdx
-;   pinsrb  $1, %xmm0, %rdx, %xmm0
+;   pinsrb  $1, %xmm0, 0(%rdi), %xmm0
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -101,8 +100,7 @@ block0(v0: i8x16, v1: i64):
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   movzbq (%rdi), %rdx ; trap: heap_oob
-;   pinsrb $1, %edx, %xmm0
+;   pinsrb $1, (%rdi), %xmm0 ; trap: heap_oob
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
@@ -118,8 +116,7 @@ block0(v0: i16x8, v1: i64):
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   movzwq  0(%rdi), %rdx
-;   pinsrw  $1, %xmm0, %rdx, %xmm0
+;   pinsrw  $1, %xmm0, 0(%rdi), %xmm0
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -129,8 +126,7 @@ block0(v0: i16x8, v1: i64):
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   movzwq (%rdi), %rdx ; trap: heap_oob
-;   pinsrw $1, %edx, %xmm0
+;   pinsrw $1, (%rdi), %xmm0 ; trap: heap_oob
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
diff --git a/cranelift/filetests/filetests/isa/x64/simd-lane-access-compile.clif b/cranelift/filetests/filetests/isa/x64/simd-lane-access-compile.clif
index 3331c7765..5a84fa914 100644
--- a/cranelift/filetests/filetests/isa/x64/simd-lane-access-compile.clif
+++ b/cranelift/filetests/filetests/isa/x64/simd-lane-access-compile.clif
@@ -340,3 +340,135 @@ block0(v0: f32):
 ;   popq %rbp
 ;   retq
 
+
+
+function %load32_lane_coalesced(i64, i32x4) -> i32x4 {
+block0(v0: i64, v1: i32x4):
+    v2 = load.i32 v0
+    v3 = insertlane.i32x4 v1, v2, 3
+    return v3
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   pinsrd  $3, %xmm0, 0(%rdi), %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   pinsrd $3, (%rdi), %xmm0 ; trap: heap_oob
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %load16_lane_coalesced(i64, i16x8) -> i16x8 {
+block0(v0: i64, v1: i16x8):
+    v2 = load.i16 v0
+    v3 = insertlane.i16x8 v1, v2, 3
+    return v3
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   pinsrw  $3, %xmm0, 0(%rdi), %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   pinsrw $3, (%rdi), %xmm0 ; trap: heap_oob
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %load8_lane_coalesced(i64, i8x16) -> i8x16 {
+block0(v0: i64, v1: i8x16):
+    v2 = load.i8 v0
+    v3 = insertlane.i8x16 v1, v2, 3
+    return v3
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   pinsrb  $3, %xmm0, 0(%rdi), %xmm0
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   pinsrb $3, (%rdi), %xmm0 ; trap: heap_oob
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %store32_lane_coalesced(i64, i32x4) {
+block0(v0: i64, v1: i32x4):
+    v2 = extractlane.i32x4 v1, 3
+    store.i32 v2, v0
+    return
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   pextrd  $3, %xmm0, 0(%rdi)
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   pextrd $3, %xmm0, (%rdi) ; trap: heap_oob
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
+function %store8_lane_coalesced(i64, i16x8) {
+block0(v0: i64, v1: i16x8):
+    v2 = extractlane.i16x8 v1, 3
+    store.i16 v2, v0
+    return
+}
+
+; VCode:
+;   pushq   %rbp
+;   movq    %rsp, %rbp
+; block0:
+;   pextrw  $3, %xmm0, 0(%rdi)
+;   movq    %rbp, %rsp
+;   popq    %rbp
+;   ret
+;
+; Disassembled:
+; block0: ; offset 0x0
+;   pushq %rbp
+;   movq %rsp, %rbp
+; block1: ; offset 0x4
+;   pextrw $3, %xmm0, (%rdi) ; trap: heap_oob
+;   movq %rbp, %rsp
+;   popq %rbp
+;   retq
+
diff --git a/cranelift/filetests/filetests/pcc/succeed/uextend-add-iconst.clif b/cranelift/filetests/filetests/pcc/succeed/uextend-add-iconst.clif
new file mode 100644
index 000000000..9448298c8
--- /dev/null
+++ b/cranelift/filetests/filetests/pcc/succeed/uextend-add-iconst.clif
@@ -0,0 +1,119 @@
+test compile
+set enable_pcc=true
+set opt_level=speed
+target x86_64
+
+;; v110 is a uextend'd iconst; v111 is a memtype base; v112 is that
+;; base plus that fixed offset, and should be able to verify that its
+;; offset is in 0..4GiB. In the fuzzbug that inspired this test, we
+;; had a bare `iconst` with no range fact on it after optimization.
+
+function u0:2(i64 vmctx, i64, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32) -> i32, i32, i32, i32, i32, i32 fast {
+    gv0 = vmctx
+    gv1 = load.i64 notrap aligned readonly gv0+8
+    gv2 = load.i64 notrap aligned gv1
+    gv3 ! mem(mt0, 0x0, 0x0) = vmctx
+    gv4 ! mem(mt1, 0x0, 0x0) = load.i64 notrap aligned readonly checked gv3+96
+    mt0 = struct 104 { 96: i64 readonly ! mem(mt1, 0x0, 0x0) }
+    mt1 = memory 0x180000000
+    sig0 = (i64 vmctx, i32 uext) system_v
+    sig1 = (i64 vmctx, i32 uext, i32 uext, i32 uext) -> i32 uext system_v
+    sig2 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v
+    sig3 = (i64 vmctx, i32 uext) -> i32 uext system_v
+    stack_limit = gv2
+
+block0(v0 ! mem(mt0, 0x0, 0x0): i64, v1: i64, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32, v7: i32, v8: i32, v9: i32, v10: i32, v11: i32, v12: i32, v13: i32, v14: i32, v15: i32, v16: i32, v17: i32, v18: i32, v19: i32, v20: i32, v21: i32):
+    v77 -> v0
+    v81 -> v0
+    v85 -> v0
+    v87 -> v0
+    v91 -> v0
+    v95 -> v0
+    v97 -> v0
+    v130 -> v0
+    v78 = load.i32 notrap aligned table v77+176
+    v127 = iconst.i32 0
+    v79 = icmp eq v78, v127  ; v127 = 0
+    v80 = uextend.i32 v79
+    brif v80, block2, block3
+
+block2:
+    trap unreachable
+
+block3:
+    v82 = load.i32 notrap aligned table v81+176
+    v83 = iconst.i32 1
+    v84 = isub v82, v83  ; v83 = 1
+    store notrap aligned table v84, v85+176
+    jump block4
+
+block4:
+    v88 = load.i32 notrap aligned table v87+176
+    v128 = iconst.i32 0
+    v89 = icmp eq v88, v128  ; v128 = 0
+    v90 = uextend.i32 v89
+    brif v90, block6, block7
+
+block6:
+    trap unreachable
+
+block7:
+    v92 = load.i32 notrap aligned table v91+176
+    v93 = iconst.i32 1
+    v94 = isub v92, v93  ; v93 = 1
+    store notrap aligned table v94, v95+176
+    v96 = iconst.i32 0xffff_3234
+    v98 = load.i64 notrap aligned v97+104
+    v129 = iconst.i64 0x0001_0000
+    v99 = udiv v98, v129  ; v129 = 0x0001_0000
+    v100 = ireduce.i32 v99
+    v101 = iconst.i32 0x0001_0000
+    v102 = imul v100, v101  ; v101 = 0x0001_0000
+    v103 = iconst.i32 0x0005_ffee
+    v104 = iadd v103, v96  ; v103 = 0x0005_ffee, v96 = 0xffff_3234
+    v105 = icmp ule v102, v104
+    v106 = uextend.i32 v105
+    brif v106, block9, block10
+
+block10:
+    v107 = iconst.i32 0
+    v108 = icmp.i32 sle v96, v107  ; v96 = 0xffff_3234, v107 = 0
+    v109 = uextend.i32 v108
+    brif v109, block9, block11
+
+block11:
+    v110 ! range(64, 0x0, 0xffffffff) = uextend.i64 v96  ; v96 = 0xffff_3234
+    v111 ! mem(mt1, 0x0, 0x0) = load.i64 notrap aligned readonly checked v130+96
+    v112 ! mem(mt1, 0x0, 0xffffffff) = iadd v111, v110
+    v113 ! range(64, 0x5ffe6, 0x5ffe6) = iconst.i64 0x0005_ffe6
+    v114 ! mem(mt1, 0x5ffe6, 0x10005ffe5) = iadd v112, v113  ; v113 = 0x0005_ffe6
+    v115 = uload8.i64 little checked heap v114
+    jump block8(v115)
+
+block9:
+    v116 = iconst.i64 0
+    jump block8(v116)  ; v116 = 0
+
+block8(v117: i64):
+    v118 = fcvt_from_uint.f32 v117
+    v86 -> v118
+    jump block5
+
+block5:
+    v121 = iconst.i32 0
+    v22 -> v121
+    v122 = iconst.i32 0
+    v23 -> v122
+    v123 = iconst.i32 0
+    v24 -> v123
+    v124 = iconst.i32 0
+    v25 -> v124
+    v125 = iconst.i32 0
+    v26 -> v125
+    v126 = iconst.i32 0
+    v27 -> v126
+    jump block1
+
+block1:
+    return v22, v23, v24, v25, v26, v27  ; v22 = 0, v23 = 0, v24 = 0, v25 = 0, v26 = 0, v27 = 0
+}
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index 8fa270135..146f1dd72 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -1499,7 +1499,7 @@ mod test_programs {
             &[
                 "-Ccache=no",
                 "-Wcomponent-model",
-                "-Scommon,http,preview2",
+                "-Scli,http,preview2",
                 HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT,
             ],
             None,
diff --git a/tests/disas/typed-funcrefs.wat b/tests/disas/typed-funcrefs.wat
index 78eb51b3b..07f52e6aa 100644
--- a/tests/disas/typed-funcrefs.wat
+++ b/tests/disas/typed-funcrefs.wat
@@ -21,7 +21,8 @@
   (func $ic1 (param i32 i32 i32 i32) (result i32)
         local.get 0)
 
-  (func $call-ics (param i32 i32 i32 i32) (result i32)
+  ;; A function which uses ICs through `table.get` plus `call_ref`
+  (func $call-ics-with-call-ref (param i32 i32 i32 i32) (result i32)
         (local $sum i32)
 
         ;; IC callsite index 1 (arbitrary).
@@ -48,7 +49,69 @@
         i32.add
         local.set $sum
 
-        local.get $sum))
+        local.get $sum)
+
+  ;; Same as the above function, but uses `call_indirect` rather than
+  ;; `call_ref`.
+  (func $call-ics-with-call-indirect (param i32 i32 i32 i32) (result i32)
+        (local $sum i32)
+
+        ;; IC callsite index 1 (arbitrary).
+        local.get 0
+        local.get 1
+        local.get 2
+        local.get 3
+        i32.const 1
+        call_indirect $ic-sites (type $ic-stub)
+        local.get $sum
+        i32.add
+        local.set $sum
+
+        ;; IC callsite index 2 (arbitrary).
+        local.get 0
+        local.get 1
+        local.get 2
+        local.get 3
+        i32.const 2
+        call_indirect $ic-sites (type $ic-stub)
+        local.get $sum
+        i32.add
+        local.set $sum
+
+        local.get $sum)
+
+  (global $ic-site0 (mut (ref $ic-stub)) (ref.func $ic1))
+  (global $ic-site1 (mut (ref $ic-stub)) (ref.func $ic1))
+
+  ;; Sort of similar to the previous two functions, but uses globals instead of
+  ;; tables to store ICs. Mostly just here for comparison in terms of codegen.
+  (func $call-ics-with-global-get (param i32 i32 i32 i32) (result i32)
+        (local $sum i32)
+
+        ;; IC callsite index 1 (arbitrary).
+        local.get 0
+        local.get 1
+        local.get 2
+        local.get 3
+        global.get $ic-site0
+        call_ref $ic-stub
+        local.get $sum
+        i32.add
+        local.set $sum
+
+        ;; IC callsite index 2 (arbitrary).
+        local.get 0
+        local.get 1
+        local.get 2
+        local.get 3
+        global.get $ic-site1
+        call_ref $ic-stub
+        local.get $sum
+        i32.add
+        local.set $sum
+
+        local.get $sum)
+)
 ;; function u0:0(i64 vmctx, i64, i32, i32, i32, i32) -> i32 fast {
 ;;     gv0 = vmctx
 ;;     gv1 = load.i64 notrap aligned readonly gv0+8
@@ -59,10 +122,10 @@
 ;;
 ;;                                 block0(v0: i64, v1: i64, v2: i32, v3: i32, v4: i32, v5: i32):
 ;;                                     v6 -> v2
-;; @0027                               jump block1
+;; @0039                               jump block1
 ;;
 ;;                                 block1:
-;; @0027                               return v2
+;; @0039                               return v2
 ;; }
 ;;
 ;; function u0:1(i64 vmctx, i64, i32, i32, i32, i32) -> i32 fast {
@@ -87,68 +150,185 @@
 ;;                                     v32 -> v4
 ;;                                     v33 -> v5
 ;;                                     v62 = iconst.i8 0
-;; @0036                               brif v62, block6, block7  ; v62 = 0
+;; @0048                               brif v62, block6, block7  ; v62 = 0
+;;
+;;                                 block6 cold:
+;; @0048                               trap table_oob
+;;
+;;                                 block7:
+;; @0048                               v12 = load.i64 notrap aligned v0+72
+;;                                     v79 = iconst.i8 0
+;;                                     v70 = iconst.i64 8
+;; @0048                               v14 = iadd v12, v70  ; v70 = 8
+;; @0048                               v16 = select_spectre_guard v79, v12, v14  ; v79 = 0
+;; @0048                               v17 = load.i64 notrap aligned table v16
+;;                                     v58 = iconst.i64 -2
+;; @0048                               v18 = band v17, v58  ; v58 = -2
+;; @0048                               brif v17, block3(v18), block2
+;;
+;;                                 block2 cold:
+;; @005b                               v48 = load.i64 notrap aligned readonly v0+56
+;; @005b                               v49 = load.i64 notrap aligned readonly v48+72
+;; @003c                               v7 = iconst.i32 0
+;;                                     v28 -> v7
+;; @0046                               v8 = iconst.i32 1
+;; @0048                               v24 = call_indirect sig0, v49(v0, v7, v8)  ; v7 = 0, v8 = 1
+;; @0048                               jump block3(v24)
+;;
+;;                                 block3(v19: i64):
+;; @004a                               v25 = load.i64 null_reference aligned readonly v19+16
+;; @004a                               v26 = load.i64 notrap aligned readonly v19+32
+;; @004a                               v27 = call_indirect sig1, v25(v26, v0, v2, v3, v4, v5)
+;;                                     v80 = iconst.i8 0
+;; @005b                               brif v80, block8, block9  ; v80 = 0
+;;
+;;                                 block8 cold:
+;; @005b                               trap table_oob
+;;
+;;                                 block9:
+;; @005b                               v38 = load.i64 notrap aligned v0+72
+;;                                     v81 = iconst.i8 0
+;;                                     v78 = iconst.i64 16
+;; @005b                               v40 = iadd v38, v78  ; v78 = 16
+;; @005b                               v42 = select_spectre_guard v81, v38, v40  ; v81 = 0
+;; @005b                               v43 = load.i64 notrap aligned table v42
+;;                                     v82 = iconst.i64 -2
+;;                                     v83 = band v43, v82  ; v82 = -2
+;; @005b                               brif v43, block5(v83), block4
+;;
+;;                                 block4 cold:
+;;                                     v84 = load.i64 notrap aligned readonly v0+56
+;;                                     v85 = load.i64 notrap aligned readonly v84+72
+;;                                     v86 = iconst.i32 0
+;; @0059                               v34 = iconst.i32 2
+;; @005b                               v50 = call_indirect sig0, v85(v0, v86, v34)  ; v86 = 0, v34 = 2
+;; @005b                               jump block5(v50)
+;;
+;;                                 block5(v45: i64):
+;; @005d                               v51 = load.i64 null_reference aligned readonly v45+16
+;; @005d                               v52 = load.i64 notrap aligned readonly v45+32
+;; @005d                               v53 = call_indirect sig1, v51(v52, v0, v2, v3, v4, v5)
+;; @0066                               jump block1
+;;
+;;                                 block1:
+;; @0061                               v55 = iadd.i32 v53, v27
+;;                                     v6 -> v55
+;; @0066                               return v55
+;; }
+;;
+;; function u0:2(i64 vmctx, i64, i32, i32, i32, i32) -> i32 fast {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     gv4 = load.i64 notrap aligned gv3+72
+;;     sig0 = (i64 vmctx, i64, i32, i32, i32, i32) -> i32 fast
+;;     sig1 = (i64 vmctx, i32 uext, i32 uext) -> i64 system_v
+;;     sig2 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v
+;;     sig3 = (i64 vmctx, i32 uext) -> i32 uext system_v
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32, v3: i32, v4: i32, v5: i32):
+;;                                     v21 -> v0
+;;                                     v47 -> v0
+;;                                     v56 -> v0
+;;                                     v59 -> v0
+;;                                     v30 -> v2
+;;                                     v31 -> v3
+;;                                     v32 -> v4
+;;                                     v33 -> v5
+;;                                     v62 = iconst.i8 0
+;; @0075                               brif v62, block6, block7  ; v62 = 0
 ;;
 ;;                                 block6 cold:
-;; @0036                               trap table_oob
+;; @0075                               trap table_oob
 ;;
 ;;                                 block7:
-;; @0036                               v12 = load.i64 notrap aligned v0+72
+;; @0075                               v12 = load.i64 notrap aligned v0+72
 ;;                                     v79 = iconst.i8 0
 ;;                                     v70 = iconst.i64 8
-;; @0036                               v14 = iadd v12, v70  ; v70 = 8
-;; @0036                               v16 = select_spectre_guard v79, v12, v14  ; v79 = 0
-;; @0036                               v17 = load.i64 notrap aligned table v16
+;; @0075                               v14 = iadd v12, v70  ; v70 = 8
+;; @0075                               v16 = select_spectre_guard v79, v12, v14  ; v79 = 0
+;; @0075                               v17 = load.i64 notrap aligned table v16
 ;;                                     v58 = iconst.i64 -2
-;; @0036                               v18 = band v17, v58  ; v58 = -2
-;; @0036                               brif v17, block3(v18), block2
+;; @0075                               v18 = band v17, v58  ; v58 = -2
+;; @0075                               brif v17, block3(v18), block2
 ;;
 ;;                                 block2 cold:
-;; @0049                               v48 = load.i64 notrap aligned readonly v0+56
-;; @0049                               v49 = load.i64 notrap aligned readonly v48+72
-;; @002a                               v7 = iconst.i32 0
+;; @0087                               v48 = load.i64 notrap aligned readonly v0+56
+;; @0087                               v49 = load.i64 notrap aligned readonly v48+72
+;; @0069                               v7 = iconst.i32 0
 ;;                                     v28 -> v7
-;; @0034                               v8 = iconst.i32 1
-;; @0036                               v24 = call_indirect sig0, v49(v0, v7, v8)  ; v7 = 0, v8 = 1
-;; @0036                               jump block3(v24)
+;; @0073                               v8 = iconst.i32 1
+;; @0075                               v24 = call_indirect sig1, v49(v0, v7, v8)  ; v7 = 0, v8 = 1
+;; @0075                               jump block3(v24)
 ;;
 ;;                                 block3(v19: i64):
-;; @0038                               v25 = load.i64 null_reference aligned readonly v19+16
-;; @0038                               v26 = load.i64 notrap aligned readonly v19+32
-;; @0038                               v27 = call_indirect sig1, v25(v26, v0, v2, v3, v4, v5)
+;; @0075                               v25 = load.i64 icall_null aligned readonly v19+16
+;; @0075                               v26 = load.i64 notrap aligned readonly v19+32
+;; @0075                               v27 = call_indirect sig0, v25(v26, v0, v2, v3, v4, v5)
 ;;                                     v80 = iconst.i8 0
-;; @0049                               brif v80, block8, block9  ; v80 = 0
+;; @0087                               brif v80, block8, block9  ; v80 = 0
 ;;
 ;;                                 block8 cold:
-;; @0049                               trap table_oob
+;; @0087                               trap table_oob
 ;;
 ;;                                 block9:
-;; @0049                               v38 = load.i64 notrap aligned v0+72
+;; @0087                               v38 = load.i64 notrap aligned v0+72
 ;;                                     v81 = iconst.i8 0
 ;;                                     v78 = iconst.i64 16
-;; @0049                               v40 = iadd v38, v78  ; v78 = 16
-;; @0049                               v42 = select_spectre_guard v81, v38, v40  ; v81 = 0
-;; @0049                               v43 = load.i64 notrap aligned table v42
+;; @0087                               v40 = iadd v38, v78  ; v78 = 16
+;; @0087                               v42 = select_spectre_guard v81, v38, v40  ; v81 = 0
+;; @0087                               v43 = load.i64 notrap aligned table v42
 ;;                                     v82 = iconst.i64 -2
 ;;                                     v83 = band v43, v82  ; v82 = -2
-;; @0049                               brif v43, block5(v83), block4
+;; @0087                               brif v43, block5(v83), block4
 ;;
 ;;                                 block4 cold:
 ;;                                     v84 = load.i64 notrap aligned readonly v0+56
 ;;                                     v85 = load.i64 notrap aligned readonly v84+72
 ;;                                     v86 = iconst.i32 0
-;; @0047                               v34 = iconst.i32 2
-;; @0049                               v50 = call_indirect sig0, v85(v0, v86, v34)  ; v86 = 0, v34 = 2
-;; @0049                               jump block5(v50)
+;; @0085                               v34 = iconst.i32 2
+;; @0087                               v50 = call_indirect sig1, v85(v0, v86, v34)  ; v86 = 0, v34 = 2
+;; @0087                               jump block5(v50)
 ;;
 ;;                                 block5(v45: i64):
-;; @004b                               v51 = load.i64 null_reference aligned readonly v45+16
-;; @004b                               v52 = load.i64 notrap aligned readonly v45+32
-;; @004b                               v53 = call_indirect sig1, v51(v52, v0, v2, v3, v4, v5)
-;; @0054                               jump block1
+;; @0087                               v51 = load.i64 icall_null aligned readonly v45+16
+;; @0087                               v52 = load.i64 notrap aligned readonly v45+32
+;; @0087                               v53 = call_indirect sig0, v51(v52, v0, v2, v3, v4, v5)
+;; @0091                               jump block1
 ;;
 ;;                                 block1:
-;; @004f                               v55 = iadd.i32 v53, v27
+;; @008c                               v55 = iadd.i32 v53, v27
 ;;                                     v6 -> v55
-;; @0054                               return v55
+;; @0091                               return v55
+;; }
+;;
+;; function u0:3(i64 vmctx, i64, i32, i32, i32, i32) -> i32 fast {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     sig0 = (i64 vmctx, i64, i32, i32, i32, i32) -> i32 fast
+;;     sig1 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v
+;;     sig2 = (i64 vmctx, i32 uext) -> i32 uext system_v
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32, v3: i32, v4: i32, v5: i32):
+;;                                     v8 -> v0
+;;                                     v14 -> v0
+;; @009e                               v9 = load.i64 notrap aligned table v0+96
+;; @00a0                               v10 = load.i64 null_reference aligned readonly v9+16
+;; @00a0                               v11 = load.i64 notrap aligned readonly v9+32
+;; @00a0                               v12 = call_indirect sig0, v10(v11, v0, v2, v3, v4, v5)
+;; @00af                               v15 = load.i64 notrap aligned table v0+112
+;; @00b1                               v16 = load.i64 null_reference aligned readonly v15+16
+;; @00b1                               v17 = load.i64 notrap aligned readonly v15+32
+;; @00b1                               v18 = call_indirect sig0, v16(v17, v0, v2, v3, v4, v5)
+;; @00ba                               jump block1
+;;
+;;                                 block1:
+;; @00b5                               v19 = iadd.i32 v18, v12
+;;                                     v6 -> v19
+;; @00ba                               return v19
 ;; }
diff --git a/tests/misc_testsuite/function-references/call_indirect.wast b/tests/misc_testsuite/function-references/call_indirect.wast
new file mode 100644
index 000000000..60fabf9c6
--- /dev/null
+++ b/tests/misc_testsuite/function-references/call_indirect.wast
@@ -0,0 +1,54 @@
+(module
+  (table $t1 2 funcref)
+  (elem (table $t1) (i32.const 0) func $nop)
+  (func $nop)
+
+  (func (export "t1") (param i32)
+    local.get 0
+    call_indirect $t1)
+  (func (export "t1-wrong-type") (param i32)
+    i32.const 0
+    local.get 0
+    call_indirect $t1 (param i32))
+
+  (type $empty (func))
+  (table $t2 2 (ref null $empty))
+  (elem (table $t2) (i32.const 0) (ref null $empty) (ref.func $nop))
+
+  (func (export "t2") (param i32)
+    local.get 0
+    call_indirect $t2)
+  (func (export "t2-wrong-type") (param i32)
+    i32.const 0
+    local.get 0
+    call_indirect $t2 (param i32))
+
+  (table $t3 2 (ref $empty) (ref.func $nop))
+
+  (func (export "t3") (param i32)
+    local.get 0
+    call_indirect $t3)
+  (func (export "t3-wrong-type") (param i32)
+    i32.const 0
+    local.get 0
+    call_indirect $t3 (param i32))
+)
+
+(assert_return (invoke "t1" (i32.const 0)))
+(assert_trap (invoke "t1" (i32.const 1)) "uninitialized element")
+(assert_trap (invoke "t1" (i32.const 2)) "out of bounds")
+(assert_trap (invoke "t1-wrong-type" (i32.const 0)) "call type mismatch")
+(assert_trap (invoke "t1-wrong-type" (i32.const 1)) "uninitialized element")
+(assert_trap (invoke "t1-wrong-type" (i32.const 2)) "out of bounds")
+(assert_return (invoke "t2" (i32.const 0)))
+(assert_trap (invoke "t2" (i32.const 1)) "uninitialized element")
+(assert_trap (invoke "t2" (i32.const 2)) "out of bounds")
+(assert_trap (invoke "t2-wrong-type" (i32.const 0)) "call type mismatch")
+(assert_trap (invoke "t2-wrong-type" (i32.const 1)) "uninitialized element")
+(assert_trap (invoke "t2-wrong-type" (i32.const 2)) "out of bounds")
+(assert_return (invoke "t3" (i32.const 0)))
+(assert_return (invoke "t3" (i32.const 1)))
+(assert_trap (invoke "t3" (i32.const 2)) "out of bounds")
+(assert_trap (invoke "t3-wrong-type" (i32.const 0)) "call type mismatch")
+(assert_trap (invoke "t3-wrong-type" (i32.const 1)) "call type mismatch")
+(assert_trap (invoke "t3-wrong-type" (i32.const 2)) "out of bounds")