Repository: bytecodealliance/wasmtime
Pull request #8427 (2024-04-22 01:45:37)

cranelift/x64: Optimize i128 comparisons
Inequality comparisons between i128 values were previously eight instructions and this reduces them to two, plus one move if one of the inputs is still live afterward.

Equality comparisons were six instructions and are now three, plus up to two moves if both inputs are still live afterward.

This removes 45 instructions from the test in x64/i128.clif that generates all possible i128 comparisons. In addition to using fewer instructions for each comparison, it also reduces register pressure enough that the function no longer spills.

Conditional branches on i128 values are a special case but similar optimizations shrink them from six instructions to two.

This brings Cranelift in line with what rustc+LLVM generates for equivalent 128-bit comparisons.

This PR probably conflicts with #8421 in the filetest output; I'll rebase whichever one doesn't land first.


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 37086e8af..2c83fae08 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -141,7 +141,7 @@ jobs:
     if: |
       needs.determine.outputs.audit
       && github.event_name == 'pull_request'
-      && needs.cargo_vet.outputs.outcome == 'failed'
+      && needs.cargo_vet.outputs.outcome == 'failure'
     runs-on: ubuntu-latest
     steps:
     # NB: this message ideally would link back to the previous step, but I'm not
@@ -464,6 +464,7 @@ jobs:
       working-directory: ./fuzz
     - run: cargo fuzz build --dev -s none
     - run: cargo fuzz build --dev -s none --fuzz-dir ./cranelift/isle/fuzz
+    - run: cargo fuzz build --dev -s none --fuzz-dir ./crates/environ/fuzz --features component-model
 
     # Verify the "min platform" example still works.
     #
diff --git a/RELEASES.md b/RELEASES.md
index 5b232971f..2ab587864 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -12,7 +12,7 @@ Unreleased.
 
 ## 20.0.0
 
-Unreleased.
+Released 2024-04-22
 
 ### Added
 
diff --git a/cranelift/codegen/src/isa/x64/inst.isle b/cranelift/codegen/src/isa/x64/inst.isle
index 41d036152..79fd09218 100644
--- a/cranelift/codegen/src/isa/x64/inst.isle
+++ b/cranelift/codegen/src/isa/x64/inst.isle
@@ -2514,6 +2514,15 @@
                         dst)
          dst)))
 
+(decl x64_alurmi_flags_side_effect (AluRmiROpcode Type Gpr GprMemImm) ProducesFlags)
+(rule (x64_alurmi_flags_side_effect opc (fits_in_64 ty) src1 src2)
+      (ProducesFlags.ProducesFlagsSideEffect
+       (MInst.AluRmiR (raw_operand_size_of_type ty)
+                      opc
+                      src1
+                      src2
+                      (temp_writable_gpr))))
+
 ;; Should only be used for Adc and Sbb
 (decl x64_alurmi_with_flags_chained (AluRmiROpcode Type Gpr GprMemImm) ConsumesAndProducesFlags)
 (rule (x64_alurmi_with_flags_chained opc (fits_in_64 ty) src1 src2)
@@ -4790,62 +4799,42 @@
 
 ;; For I128 values (held in two GPRs), the instruction sequences depend on what
 ;; kind of condition is tested.
-(rule 5 (emit_cmp (IntCC.Equal) a @ (value_type $I128) b)
-      (let ((a_lo Gpr (value_regs_get_gpr a 0))
-            (a_hi Gpr (value_regs_get_gpr a 1))
-            (b_lo Gpr (value_regs_get_gpr b 0))
-            (b_hi Gpr (value_regs_get_gpr b 1))
-            (cmp_lo Reg (with_flags_reg (x64_cmp (OperandSize.Size64) a_lo b_lo) (x64_setcc (CC.Z))))
-            (cmp_hi Reg (with_flags_reg (x64_cmp (OperandSize.Size64) a_hi b_hi) (x64_setcc (CC.Z))))
-            ;; At this point, `cmp_lo` and `cmp_hi` contain either 0 or 1 in the
-            ;; lowest 8 bits--`SETcc` guarantees this. The upper bits may be
-            ;; unchanged so we must compare against 1 below; this instruction
-            ;; combines `cmp_lo` and `cmp_hi` for that final comparison.
-            (cmp Reg (x64_and $I64 cmp_lo cmp_hi)))
-        ;; We must compare one more time against the immediate value 1 to
-        ;; check if both `cmp_lo` and `cmp_hi` are true. If `cmp AND 1 == 0`
-        ;; then the `ZF` will be set (see `TEST` definition); if either of
-        ;; the halves `AND`s to 0, they were not equal, therefore we `SETcc`
-        ;; with `NZ`.
-        (icmp_cond_result
-          (x64_test (OperandSize.Size64) cmp (RegMemImm.Imm 1))
-          (CC.NZ))))
-
-(rule 5 (emit_cmp (IntCC.NotEqual) a @ (value_type $I128) b)
-      (let ((a_lo Gpr (value_regs_get_gpr a 0))
-            (a_hi Gpr (value_regs_get_gpr a 1))
-            (b_lo Gpr (value_regs_get_gpr b 0))
-            (b_hi Gpr (value_regs_get_gpr b 1))
-            (cmp_lo Reg (with_flags_reg (x64_cmp (OperandSize.Size64) a_lo b_lo) (x64_setcc (CC.NZ))))
-            (cmp_hi Reg (with_flags_reg (x64_cmp (OperandSize.Size64) a_hi b_hi) (x64_setcc (CC.NZ))))
-            ;; See comments for `IntCC.Equal`.
-            (cmp Reg (x64_or $I64 cmp_lo cmp_hi)))
-           (icmp_cond_result
-             (x64_test (OperandSize.Size64) cmp (RegMemImm.Imm 1))
-             (CC.NZ))))
-
-;; Result = (a_hi <> b_hi) ||
-;;          (a_hi == b_hi && a_lo <> b_lo)
 (rule 4 (emit_cmp cc a @ (value_type $I128) b)
       (let ((a_lo Gpr (value_regs_get_gpr a 0))
             (a_hi Gpr (value_regs_get_gpr a 1))
             (b_lo Gpr (value_regs_get_gpr b 0))
-            (b_hi Gpr (value_regs_get_gpr b 1))
-            (cmp_hi ValueRegs (with_flags (x64_cmp (OperandSize.Size64) a_hi b_hi)
-                                       (consumes_flags_concat
-                                                 (x64_setcc (intcc_without_eq cc))
-                                                 (x64_setcc (CC.Z)))))
-            (cc_hi Reg (value_regs_get cmp_hi 0))
-            (eq_hi Reg (value_regs_get cmp_hi 1))
-
-            (cmp_lo Reg (with_flags_reg (x64_cmp (OperandSize.Size64) a_lo b_lo)
-                                        (x64_setcc (intcc_unsigned cc))))
-
-            (res_lo Reg (x64_and $I64 eq_hi cmp_lo))
-            (res Reg (x64_or $I64 cc_hi res_lo)))
+            (b_hi Gpr (value_regs_get_gpr b 1)))
+      (emit_cmp_i128 cc a_hi a_lo b_hi b_lo)))
+
+(decl emit_cmp_i128 (CC Gpr Gpr Gpr Gpr) IcmpCondResult)
+;; Eliminate cases which compare something "or equal" by swapping arguments.
+(rule 2 (emit_cmp_i128 (CC.NLE) a_hi a_lo b_hi b_lo)
+        (emit_cmp_i128 (CC.L)   b_hi b_lo a_hi a_lo))
+(rule 2 (emit_cmp_i128 (CC.LE)  a_hi a_lo b_hi b_lo)
+        (emit_cmp_i128 (CC.NL)  b_hi b_lo a_hi a_lo))
+(rule 2 (emit_cmp_i128 (CC.NBE) a_hi a_lo b_hi b_lo)
+        (emit_cmp_i128 (CC.B)   b_hi b_lo a_hi a_lo))
+(rule 2 (emit_cmp_i128 (CC.BE)  a_hi a_lo b_hi b_lo)
+        (emit_cmp_i128 (CC.NB)  b_hi b_lo a_hi a_lo))
+
+;; 128-bit strict equality/inequality can't be easily tested using subtraction
+;; but we can quickly determine whether any bits are different instead.
+(rule 1 (emit_cmp_i128 (cc_nz_or_z cc) a_hi a_lo b_hi b_lo)
+        (let ((same_lo Reg (x64_xor $I64 a_lo b_lo))
+              (same_hi Reg (x64_xor $I64 a_hi b_hi)))
+          (icmp_cond_result
+            (x64_alurmi_flags_side_effect (AluRmiROpcode.Or) $I64 same_lo same_hi)
+            cc)))
+
+;; The only cases left are L/NL/B/NB which we can implement with a sub/sbb
+;; sequence. But since we don't care about anything but the flags we can
+;; replace the sub with cmp, which avoids clobbering one of the registers.
+(rule 0 (emit_cmp_i128 cc a_hi a_lo b_hi b_lo)
         (icmp_cond_result
-          (x64_test (OperandSize.Size64) res (RegMemImm.Imm 1))
-          (CC.NZ))))
+          (produces_flags_concat
+            (x64_cmp (OperandSize.Size64) a_lo b_lo)
+            (x64_alurmi_flags_side_effect (AluRmiROpcode.Sbb) $I64 a_hi b_hi))
+          cc))
 
 (type FcmpCondResult
       (enum
diff --git a/cranelift/codegen/src/isa/x64/lower.isle b/cranelift/codegen/src/isa/x64/lower.isle
index 15efff9ff..b05c6a614 100644
--- a/cranelift/codegen/src/isa/x64/lower.isle
+++ b/cranelift/codegen/src/isa/x64/lower.isle
@@ -3323,7 +3323,7 @@
 
 
 ;; Compare an I128 value to zero, returning a flags result suitable for making a
-;; jump decision. The comparison is implemented as `(hi == 0) && (low == 0)`,
+;; jump decision. The comparison is implemented as `(hi | low) == 0`,
 ;; and the result can be interpreted as follows
 ;; * CC.Z indicates that the value was non-zero, as one or both of the halves of
 ;;   the value were non-zero
@@ -3331,12 +3331,10 @@
 (decl cmp_zero_i128 (CC ValueRegs) IcmpCondResult)
 (rule (cmp_zero_i128 (cc_nz_or_z cc) val)
       (let ((lo Gpr (value_regs_get_gpr val 0))
-            (hi Gpr (value_regs_get_gpr val 1))
-            (lo_z Gpr (with_flags_reg (x64_cmp_imm (OperandSize.Size64) lo 0)
-                                      (x64_setcc (CC.Z))))
-            (hi_z Gpr (with_flags_reg (x64_cmp_imm (OperandSize.Size64) hi 0)
-                                      (x64_setcc (CC.Z)))))
-          (icmp_cond_result (x64_test (OperandSize.Size8) hi_z lo_z) cc)))
+            (hi Gpr (value_regs_get_gpr val 1)))
+          (icmp_cond_result
+            (x64_alurmi_flags_side_effect (AluRmiROpcode.Or) $I64 lo hi)
+            (cc_invert cc))))
 
 
 (decl cmp_zero_int_bool_ref (Value) ProducesFlags)
diff --git a/crates/environ/fuzz/Cargo.toml b/crates/environ/fuzz/Cargo.toml
index d4a68fee4..97ff78986 100644
--- a/crates/environ/fuzz/Cargo.toml
+++ b/crates/environ/fuzz/Cargo.toml
@@ -26,4 +26,8 @@ doc = false
 required-features = ["component-model"]
 
 [features]
-component-model = ["wasmtime-environ/component-model", "dep:component-fuzz-util"]
+component-model = [
+  "wasmtime-environ/component-model",
+  "wasmtime-environ/compile",
+  "dep:component-fuzz-util",
+]
diff --git a/crates/environ/fuzz/fuzz_targets/fact-valid-module.rs b/crates/environ/fuzz/fuzz_targets/fact-valid-module.rs
index d6b323285..cb7d85e02 100644
--- a/crates/environ/fuzz/fuzz_targets/fact-valid-module.rs
+++ b/crates/environ/fuzz/fuzz_targets/fact-valid-module.rs
@@ -9,35 +9,79 @@
 
 #![no_main]
 
-use arbitrary::Arbitrary;
-use component_fuzz_util::TestCase;
+use arbitrary::Unstructured;
+use component_fuzz_util::{TestCase, Type, MAX_TYPE_DEPTH};
 use libfuzzer_sys::fuzz_target;
+use wasmparser::types::ComponentAnyTypeId;
 use wasmparser::{Parser, Payload, Validator, WasmFeatures};
 use wasmtime_environ::component::*;
 use wasmtime_environ::fact::Module;
 
-#[derive(Arbitrary, Debug)]
-struct GenAdapterModule {
+const TYPE_COUNT: usize = 50;
+const MAX_ARITY: u32 = 5;
+const TEST_CASE_COUNT: usize = 20;
+
+#[derive(Debug)]
+struct GenAdapterModule<'a> {
     debug: bool,
-    adapters: Vec<GenAdapter>,
+    adapters: Vec<GenAdapter<'a>>,
 }
 
-#[derive(Arbitrary, Debug)]
-struct GenAdapter {
+#[derive(Debug)]
+struct GenAdapter<'a> {
     post_return: bool,
     lift_memory64: bool,
     lower_memory64: bool,
-    test: TestCase,
+    test: TestCase<'a>,
 }
 
-fuzz_target!(|module: GenAdapterModule| {
-    target(module);
+fuzz_target!(|data: &[u8]| {
+    let _ = target(data);
 });
 
-fn target(module: GenAdapterModule) {
+fn target(data: &[u8]) -> arbitrary::Result<()> {
     drop(env_logger::try_init());
 
-    let mut types = ComponentTypesBuilder::default();
+    let mut u = Unstructured::new(data);
+
+    // First generate a set of type to select from.
+    let mut type_fuel = 1000;
+    let mut types = Vec::new();
+    for _ in 0..u.int_in_range(1..=TYPE_COUNT)? {
+        // Only discount fuel if the generation was successful,
+        // otherwise we'll get more random data and try again.
+        types.push(Type::generate(&mut u, MAX_TYPE_DEPTH, &mut type_fuel)?);
+    }
+
+    // Next generate a set of static API test cases driven by the above
+    // types.
+    let mut ret = GenAdapterModule {
+        debug: u.arbitrary()?,
+        adapters: Vec::new(),
+    };
+    for _ in 0..u.int_in_range(1..=TEST_CASE_COUNT)? {
+        let mut params = Vec::new();
+        let mut results = Vec::new();
+        for _ in 0..u.int_in_range(0..=MAX_ARITY)? {
+            params.push(u.choose(&types)?);
+        }
+        for _ in 0..u.int_in_range(0..=MAX_ARITY)? {
+            results.push(u.choose(&types)?);
+        }
+
+        let test = TestCase {
+            params,
+            results,
+            encoding1: u.arbitrary()?,
+            encoding2: u.arbitrary()?,
+        };
+        ret.adapters.push(GenAdapter {
+            test,
+            post_return: u.arbitrary()?,
+            lift_memory64: u.arbitrary()?,
+            lower_memory64: u.arbitrary()?,
+        });
+    }
 
     // Manufactures a unique `CoreDef` so all function imports get unique
     // function imports.
@@ -72,9 +116,11 @@ fn target(module: GenAdapterModule) {
             item: ExportItem::Name(String::new()),
         }
     };
+    let mut validator = Validator::new();
+    let mut types = ComponentTypesBuilder::new(&validator);
 
     let mut adapters = Vec::new();
-    for adapter in module.adapters.iter() {
+    for adapter in ret.adapters.iter() {
         let wat_decls = adapter.test.declarations();
         let wat = format!(
             "(component
@@ -87,9 +133,7 @@ fn target(module: GenAdapterModule) {
         );
         let wasm = wat::parse_str(&wat).unwrap();
 
-        let mut validator = Validator::new();
-
-        types.push_type_scope();
+        let mut type_index = 0;
         for payload in Parser::new(0).parse_all(&wasm) {
             let payload = payload.unwrap();
             validator.payload(&payload).unwrap();
@@ -97,13 +141,17 @@ fn target(module: GenAdapterModule) {
                 Payload::ComponentTypeSection(s) => s,
                 _ => continue,
             };
-            for ty in section {
-                let ty = types.intern_component_type(&ty.unwrap()).unwrap();
-                types.push_component_typedef(ty);
-                let ty = match ty {
-                    TypeDef::ComponentFunc(ty) => ty,
+            for _ in section {
+                let validator_types = validator.types(0).unwrap();
+                let id = validator_types.component_any_type_at(type_index);
+                type_index += 1;
+                let id = match id {
+                    ComponentAnyTypeId::Func(id) => id,
                     _ => continue,
                 };
+                let ty = types
+                    .convert_component_func_type(validator_types, id)
+                    .unwrap();
                 adapters.push(Adapter {
                     lift_ty: ty,
                     lower_ty: ty,
@@ -137,10 +185,10 @@ fn target(module: GenAdapterModule) {
                 });
             }
         }
-        types.pop_type_scope();
+        validator.reset();
     }
 
-    let mut fact_module = Module::new(&types, module.debug);
+    let mut fact_module = Module::new(&types, ret.debug);
     for (i, adapter) in adapters.iter().enumerate() {
         fact_module.adapt(&format!("adapter{i}"), adapter);
     }
@@ -149,11 +197,11 @@ fn target(module: GenAdapterModule) {
         .validate_all(&wasm);
 
     let err = match result {
-        Ok(_) => return,
+        Ok(_) => return Ok(()),
         Err(e) => e,
     };
     eprintln!("invalid wasm module: {err:?}");
-    for adapter in module.adapters.iter() {
+    for adapter in ret.adapters.iter() {
         eprintln!("adapter: {adapter:?}");
     }
     std::fs::write("invalid.wasm", &wasm).unwrap();
diff --git a/crates/wasmtime/src/runtime/func.rs b/crates/wasmtime/src/runtime/func.rs
index 17dfece9b..c0d5de6b1 100644
--- a/crates/wasmtime/src/runtime/func.rs
+++ b/crates/wasmtime/src/runtime/func.rs
@@ -1346,24 +1346,6 @@ impl Func {
         let (params, results) = val_vec.split_at_mut(nparams);
         func(caller.sub_caller(), params, results)?;
 
-        #[cfg(feature = "gc")]
-        {
-            // See the comment in `Func::call_impl_check_args`.
-            let num_gc_refs = ty.as_wasm_func_type().non_i31_gc_ref_returns_count();
-            if let Some(num_gc_refs) = NonZeroUsize::new(num_gc_refs) {
-                if caller
-                    .as_context()
-                    .0
-                    .gc_store()?
-                    .gc_heap
-                    .need_gc_before_entering_wasm(num_gc_refs)
-                {
-                    assert!(!caller.as_context().0.async_support());
-                    caller.as_context_mut().gc();
-                }
-            }
-        }
-
         // Unlike our arguments we need to dynamically check that the return
         // values produced are correct. There could be a bug in `func` that
         // produces the wrong number, wrong types, or wrong stores of


Test patch:
diff --git a/cranelift/filetests/filetests/isa/x64/i128.clif b/cranelift/filetests/filetests/isa/x64/i128.clif
index d7523724b..91753d088 100644
--- a/cranelift/filetests/filetests/isa/x64/i128.clif
+++ b/cranelift/filetests/filetests/isa/x64/i128.clif
@@ -316,116 +316,71 @@ block0(v0: i128, v1: i128):
 ; VCode:
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
-;   subq    %rsp, $64, %rsp
-;   movq    %rbx, 16(%rsp)
-;   movq    %r12, 24(%rsp)
-;   movq    %r13, 32(%rsp)
-;   movq    %r14, 40(%rsp)
-;   movq    %r15, 48(%rsp)
+;   subq    %rsp, $48, %rsp
+;   movq    %rbx, 0(%rsp)
+;   movq    %r12, 8(%rsp)
+;   movq    %r13, 16(%rsp)
+;   movq    %r14, 24(%rsp)
+;   movq    %r15, 32(%rsp)
 ; block0:
-;   cmpq    %rdx, %rdi
-;   setz    %r9b
-;   cmpq    %rcx, %rsi
-;   setz    %r10b
-;   andq    %r9, %r10, %r9
-;   testq   $1, %r9
-;   setnz   %al
-;   cmpq    %rdx, %rdi
-;   setnz   %r8b
-;   cmpq    %rcx, %rsi
-;   setnz   %r9b
+;   movq    %rdi, %rax
+;   xorq    %rax, %rdx, %rax
+;   movq    %rsi, %r8
+;   xorq    %r8, %rcx, %r8
+;   orq     %rax, %r8, %rax
+;   setz    %al
+;   movq    %rdi, %r8
+;   xorq    %r8, %rdx, %r8
+;   movq    %rsi, %r9
+;   xorq    %r9, %rcx, %r9
 ;   orq     %r8, %r9, %r8
-;   testq   $1, %r8
 ;   setnz   %r9b
-;   movq    %r9, rsp(0 + virtual offset)
-;   cmpq    %rcx, %rsi
-;   setl    %r8b
-;   setz    %r10b
-;   cmpq    %rdx, %rdi
-;   setb    %r11b
-;   andq    %r10, %r11, %r10
-;   orq     %r8, %r10, %r8
-;   testq   $1, %r8
-;   setnz   %r10b
-;   cmpq    %rcx, %rsi
-;   setl    %r11b
-;   setz    %r8b
 ;   cmpq    %rdx, %rdi
-;   setbe   %r15b
-;   andq    %r8, %r15, %r8
-;   orq     %r11, %r8, %r11
-;   testq   $1, %r11
-;   setnz   %r8b
-;   cmpq    %rcx, %rsi
-;   setnle  %r11b
-;   setz    %r12b
-;   cmpq    %rdx, %rdi
-;   setnbe  %r13b
-;   andq    %r12, %r13, %r12
-;   orq     %r11, %r12, %r11
-;   testq   $1, %r11
-;   setnz   %r11b
-;   cmpq    %rcx, %rsi
-;   setnle  %r15b
-;   setz    %bl
-;   cmpq    %rdx, %rdi
-;   setnb   %r12b
-;   andq    %rbx, %r12, %rbx
-;   orq     %r15, %rbx, %r15
-;   testq   $1, %r15
-;   setnz   %r13b
-;   cmpq    %rcx, %rsi
-;   setb    %r14b
-;   setz    %r15b
-;   cmpq    %rdx, %rdi
-;   setb    %bl
-;   andq    %r15, %rbx, %r15
-;   orq     %r14, %r15, %r14
-;   testq   $1, %r14
-;   setnz   %r14b
-;   cmpq    %rcx, %rsi
-;   setb    %bl
-;   setz    %r12b
+;   movq    %rsi, %r8
+;   sbbq    %r8, %rcx, %r8
+;   setl    %r8b
+;   cmpq    %rdi, %rdx
+;   movq    %rcx, %r10
+;   sbbq    %r10, %rsi, %r10
+;   setnl   %r11b
+;   cmpq    %rdi, %rdx
+;   movq    %rcx, %r10
+;   sbbq    %r10, %rsi, %r10
+;   setl    %r10b
 ;   cmpq    %rdx, %rdi
-;   setbe   %r15b
-;   andq    %r12, %r15, %r12
-;   orq     %rbx, %r12, %rbx
-;   testq   $1, %rbx
-;   setnz   %r15b
-;   cmpq    %rcx, %rsi
-;   setnbe  %bl
-;   setz    %r12b
+;   movq    %rsi, %rbx
+;   sbbq    %rbx, %rcx, %rbx
+;   setnl   %r14b
 ;   cmpq    %rdx, %rdi
-;   setnbe  %r9b
-;   andq    %r12, %r9, %r12
-;   orq     %rbx, %r12, %rbx
-;   testq   $1, %rbx
-;   setnz   %bl
-;   cmpq    %rcx, %rsi
-;   setnbe  %sil
-;   setz    %cl
+;   movq    %rsi, %r12
+;   sbbq    %r12, %rcx, %r12
+;   setb    %r13b
+;   cmpq    %rdi, %rdx
+;   movq    %rcx, %r15
+;   sbbq    %r15, %rsi, %r15
+;   setnb   %bl
+;   cmpq    %rdi, %rdx
+;   movq    %rcx, %r15
+;   sbbq    %r15, %rsi, %r15
+;   setb    %r15b
 ;   cmpq    %rdx, %rdi
+;   sbbq    %rsi, %rcx, %rsi
 ;   setnb   %dil
-;   andq    %rcx, %rdi, %rcx
-;   orq     %rsi, %rcx, %rsi
-;   testq   $1, %rsi
-;   setnz   %sil
-;   movq    rsp(0 + virtual offset), %rcx
-;   andl    %eax, %ecx, %eax
-;   andl    %r10d, %r8d, %r10d
-;   andl    %r11d, %r13d, %r11d
-;   andl    %r14d, %r15d, %r14d
-;   andl    %ebx, %esi, %ebx
+;   andl    %eax, %r9d, %eax
+;   andl    %r8d, %r11d, %r8d
+;   andl    %r10d, %r14d, %r10d
+;   andl    %r13d, %ebx, %r13d
+;   andl    %r15d, %edi, %r15d
+;   andl    %eax, %r8d, %eax
+;   andl    %r10d, %r13d, %r10d
 ;   andl    %eax, %r10d, %eax
-;   andl    %r11d, %r14d, %r11d
-;   andl    %eax, %r11d, %eax
-;   andl    %eax, %ebx, %eax
-;   movq    16(%rsp), %rbx
-;   movq    24(%rsp), %r12
-;   movq    32(%rsp), %r13
-;   movq    40(%rsp), %r14
-;   movq    48(%rsp), %r15
-;   addq    %rsp, $64, %rsp
+;   andl    %eax, %r15d, %eax
+;   movq    0(%rsp), %rbx
+;   movq    8(%rsp), %r12
+;   movq    16(%rsp), %r13
+;   movq    24(%rsp), %r14
+;   movq    32(%rsp), %r15
+;   addq    %rsp, $48, %rsp
 ;   movq    %rbp, %rsp
 ;   popq    %rbp
 ;   ret
@@ -434,116 +389,71 @@ block0(v0: i128, v1: i128):
 ; block0: ; offset 0x0
 ;   pushq %rbp
 ;   movq %rsp, %rbp
-;   subq $0x40, %rsp
-;   movq %rbx, 0x10(%rsp)
-;   movq %r12, 0x18(%rsp)
-;   movq %r13, 0x20(%rsp)
-;   movq %r14, 0x28(%rsp)
-;   movq %r15, 0x30(%rsp)
-; block1: ; offset 0x21
-;   cmpq %rdx, %rdi
-;   sete %r9b
-;   cmpq %rcx, %rsi
-;   sete %r10b
-;   andq %r10, %r9
-;   testq $1, %r9
-;   setne %al
-;   cmpq %rdx, %rdi
-;   setne %r8b
-;   cmpq %rcx, %rsi
-;   setne %r9b
+;   subq $0x30, %rsp
+;   movq %rbx, (%rsp)
+;   movq %r12, 8(%rsp)
+;   movq %r13, 0x10(%rsp)
+;   movq %r14, 0x18(%rsp)
+;   movq %r15, 0x20(%rsp)
+; block1: ; offset 0x20
+;   movq %rdi, %rax
+;   xorq %rdx, %rax
+;   movq %rsi, %r8
+;   xorq %rcx, %r8
+;   orq %r8, %rax
+;   sete %al
+;   movq %rdi, %r8
+;   xorq %rdx, %r8
+;   movq %rsi, %r9
+;   xorq %rcx, %r9
 ;   orq %r9, %r8
-;   testq $1, %r8
 ;   setne %r9b
-;   movq %r9, (%rsp)
-;   cmpq %rcx, %rsi
-;   setl %r8b
-;   sete %r10b
-;   cmpq %rdx, %rdi
-;   setb %r11b
-;   andq %r11, %r10
-;   orq %r10, %r8
-;   testq $1, %r8
-;   setne %r10b
-;   cmpq %rcx, %rsi
-;   setl %r11b
-;   sete %r8b
-;   cmpq %rdx, %rdi
-;   setbe %r15b
-;   andq %r15, %r8
-;   orq %r8, %r11
-;   testq $1, %r11
-;   setne %r8b
-;   cmpq %rcx, %rsi
-;   setg %r11b
-;   sete %r12b
-;   cmpq %rdx, %rdi
-;   seta %r13b
-;   andq %r13, %r12
-;   orq %r12, %r11
-;   testq $1, %r11
-;   setne %r11b
-;   cmpq %rcx, %rsi
-;   setg %r15b
-;   sete %bl
-;   cmpq %rdx, %rdi
-;   setae %r12b
-;   andq %r12, %rbx
-;   orq %rbx, %r15
-;   testq $1, %r15
-;   setne %r13b
-;   cmpq %rcx, %rsi
-;   setb %r14b
-;   sete %r15b
 ;   cmpq %rdx, %rdi
-;   setb %bl
-;   andq %rbx, %r15
-;   orq %r15, %r14
-;   testq $1, %r14
-;   setne %r14b
-;   cmpq %rcx, %rsi
-;   setb %bl
-;   sete %r12b
+;   movq %rsi, %r8
+;   sbbq %rcx, %r8
+;   setl %r8b
+;   cmpq %rdi, %rdx
+;   movq %rcx, %r10
+;   sbbq %rsi, %r10
+;   setge %r11b
+;   cmpq %rdi, %rdx
+;   movq %rcx, %r10
+;   sbbq %rsi, %r10
+;   setl %r10b
 ;   cmpq %rdx, %rdi
-;   setbe %r15b
-;   andq %r15, %r12
-;   orq %r12, %rbx
-;   testq $1, %rbx
-;   setne %r15b
-;   cmpq %rcx, %rsi
-;   seta %bl
-;   sete %r12b
+;   movq %rsi, %rbx
+;   sbbq %rcx, %rbx
+;   setge %r14b
 ;   cmpq %rdx, %rdi
-;   seta %r9b
-;   andq %r9, %r12
-;   orq %r12, %rbx
-;   testq $1, %rbx
-;   setne %bl
-;   cmpq %rcx, %rsi
-;   seta %sil
-;   sete %cl
+;   movq %rsi, %r12
+;   sbbq %rcx, %r12
+;   setb %r13b
+;   cmpq %rdi, %rdx
+;   movq %rcx, %r15
+;   sbbq %rsi, %r15
+;   setae %bl
+;   cmpq %rdi, %rdx
+;   movq %rcx, %r15
+;   sbbq %rsi, %r15
+;   setb %r15b
 ;   cmpq %rdx, %rdi
+;   sbbq %rcx, %rsi
 ;   setae %dil
-;   andq %rdi, %rcx
-;   orq %rcx, %rsi
-;   testq $1, %rsi
-;   setne %sil
-;   movq (%rsp), %rcx
-;   andl %ecx, %eax
-;   andl %r8d, %r10d
-;   andl %r13d, %r11d
-;   andl %r15d, %r14d
-;   andl %esi, %ebx
+;   andl %r9d, %eax
+;   andl %r11d, %r8d
+;   andl %r14d, %r10d
+;   andl %ebx, %r13d
+;   andl %edi, %r15d
+;   andl %r8d, %eax
+;   andl %r13d, %r10d
 ;   andl %r10d, %eax
-;   andl %r14d, %r11d
-;   andl %r11d, %eax
-;   andl %ebx, %eax
-;   movq 0x10(%rsp), %rbx
-;   movq 0x18(%rsp), %r12
-;   movq 0x20(%rsp), %r13
-;   movq 0x28(%rsp), %r14
-;   movq 0x30(%rsp), %r15
-;   addq $0x40, %rsp
+;   andl %r15d, %eax
+;   movq (%rsp), %rbx
+;   movq 8(%rsp), %r12
+;   movq 0x10(%rsp), %r13
+;   movq 0x18(%rsp), %r14
+;   movq 0x20(%rsp), %r15
+;   addq $0x30, %rsp
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
@@ -565,12 +475,8 @@ block2:
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   cmpq    $0, %rdi
-;   setz    %r9b
-;   cmpq    $0, %rsi
-;   setz    %sil
-;   testb   %r9b, %sil
-;   jz      label2; j label1
+;   orq     %rdi, %rsi, %rdi
+;   jnz     label2; j label1
 ; block1:
 ;   movl    $1, %eax
 ;   movq    %rbp, %rsp
@@ -587,18 +493,14 @@ block2:
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   cmpq $0, %rdi
-;   sete %r9b
-;   cmpq $0, %rsi
-;   sete %sil
-;   testb %r9b, %sil
-;   je 0x27
-; block2: ; offset 0x1d
+;   orq %rsi, %rdi
+;   jne 0x17
+; block2: ; offset 0xd
 ;   movl $1, %eax
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
-; block3: ; offset 0x27
+; block3: ; offset 0x17
 ;   movl $2, %eax
 ;   movq %rbp, %rsp
 ;   popq %rbp
@@ -621,12 +523,8 @@ block2:
 ;   pushq   %rbp
 ;   movq    %rsp, %rbp
 ; block0:
-;   cmpq    $0, %rdi
-;   setz    %r9b
-;   cmpq    $0, %rsi
-;   setz    %sil
-;   testb   %r9b, %sil
-;   jz      label2; j label1
+;   orq     %rdi, %rsi, %rdi
+;   jnz     label2; j label1
 ; block1:
 ;   movl    $2, %eax
 ;   movq    %rbp, %rsp
@@ -643,18 +541,14 @@ block2:
 ;   pushq %rbp
 ;   movq %rsp, %rbp
 ; block1: ; offset 0x4
-;   cmpq $0, %rdi
-;   sete %r9b
-;   cmpq $0, %rsi
-;   sete %sil
-;   testb %r9b, %sil
-;   je 0x27
-; block2: ; offset 0x1d
+;   orq %rsi, %rdi
+;   jne 0x17
+; block2: ; offset 0xd
 ;   movl $2, %eax
 ;   movq %rbp, %rsp
 ;   popq %rbp
 ;   retq
-; block3: ; offset 0x27
+; block3: ; offset 0x17
 ;   movl $1, %eax
 ;   movq %rbp, %rsp
 ;   popq %rbp
diff --git a/crates/test-programs/src/bin/preview2_ip_name_lookup.rs b/crates/test-programs/src/bin/preview2_ip_name_lookup.rs
index f62f48cbc..13ca9cbc4 100644
--- a/crates/test-programs/src/bin/preview2_ip_name_lookup.rs
+++ b/crates/test-programs/src/bin/preview2_ip_name_lookup.rs
@@ -5,7 +5,10 @@ fn main() {
     // Valid domains
     resolve("localhost").unwrap();
     resolve("example.com").unwrap();
-    resolve("münchen.de").unwrap();
+
+    // NB: this is an actual real resolution, so it might time out, might cause
+    // issues, etc. This result is ignored to prevent flaky failures in CI.
+    let _ = resolve("münchen.de");
 
     // Valid IP addresses
     assert_eq!(resolve_one("0.0.0.0").unwrap(), IpAddress::IPV4_UNSPECIFIED);
diff --git a/tests/all/async_functions.rs b/tests/all/async_functions.rs
index 5e1fbf17d..2eaca31f6 100644
--- a/tests/all/async_functions.rs
+++ b/tests/all/async_functions.rs
@@ -983,3 +983,65 @@ async fn gc_preserves_externref_on_historical_async_stacks() -> Result<()> {
 
     Ok(())
 }
+
+#[tokio::test]
+async fn async_gc_with_func_new_and_func_wrap() -> Result<()> {
+    let _ = env_logger::try_init();
+
+    let mut config = Config::new();
+    config.async_support(true);
+    let engine = Engine::new(&config)?;
+
+    let module = Module::new(
+        &engine,
+        r#"
+            (module $m1
+                (import "" "a" (func $a (result externref)))
+                (import "" "b" (func $b (result externref)))
+
+                (table 2 funcref)
+                (elem (i32.const 0) func $a $b)
+
+                (func (export "a")
+                    (call $call (i32.const 0))
+                )
+                (func (export "b")
+                    (call $call (i32.const 1))
+                )
+
+                (func $call (param i32)
+                    (local $cnt i32)
+                    (loop $l
+                        (drop (call_indirect (result externref) (local.get 0)))
+                        (local.set $cnt (i32.add (local.get $cnt) (i32.const 1)))
+
+                        (if (i32.lt_u (local.get $cnt) (i32.const 1000))
+                         (then (br $l)))
+                    )
+                )
+            )
+        "#,
+    )?;
+
+    let mut linker = Linker::new(&engine);
+    linker.func_wrap("", "a", |mut cx: Caller<'_, _>| {
+        Ok(Some(ExternRef::new(&mut cx, 100)?))
+    })?;
+    let ty = FuncType::new(&engine, [], [ValType::EXTERNREF]);
+    linker.func_new("", "b", ty, |mut cx, _, results| {
+        results[0] = ExternRef::new(&mut cx, 100)?.into();
+        Ok(())
+    })?;
+
+    let mut store = Store::new(&engine, ());
+    let instance = linker.instantiate_async(&mut store, &module).await?;
+    let a = instance.get_typed_func::<(), ()>(&mut store, "a")?;
+    a.call_async(&mut store, ()).await?;
+
+    let mut store = Store::new(&engine, ());
+    let instance = linker.instantiate_async(&mut store, &module).await?;
+    let b = instance.get_typed_func::<(), ()>(&mut store, "b")?;
+    b.call_async(&mut store, ()).await?;
+
+    Ok(())
+}