Stream: wasmtime

Topic: Turning on GC changes codegen for `call_indirect`


view this post on Zulip Alex Crichton (Jun 04 2026 at 19:50):

@fitzgen (he/him) I was poking at somethings and noticed that there's a few instances in wasm translation where features.gc() affects what CLIF is generated. Notably checking type signatures in call-indirect (and another here) changes whether or not GC is enabled.

I think this is probably something we're going to want to fix prior to enable-by-default (or before we ship on-by-default) because otherwise this looks like it's de-optimizing the call_indirect path with gc enabled. I think this is probably an easy fix (specialize funcref tables specifically) by my read, but wanted to flag this with you

view this post on Zulip fitzgen (he/him) (Jun 04 2026 at 23:17):

Alex Crichton said:

because otherwise this looks like it's de-optimizing the call_indirect path with gc enabled.

The fast path for call-indirect with GC is the same as call-indirect without GC, and the slow path with GC will always result in a trap without GC, so I don't think this is actually de-optimizing anything (other than a very slight bit of code size bloat, which is acceptable in this case IMO)

view this post on Zulip Alex Crichton (Jun 04 2026 at 23:30):

The codegen definitely changes though, and notably with -Wgc I see:

 cmp esi, edx
 sete al
 movzx eax, al
...
 test eax, eax
 je 0xa2 <wasm[0]::function[0]+0xa2>

view this post on Zulip Chris Fallin (Jun 04 2026 at 23:38):

 cmp esi, edx
 sete al
 movzx eax, al
 cmp esi, edx
 je 0x64 <wasm[0]::function[0]+0x64>

cries in peephole optimization and path-sensitive constant propagation pain

(eax will always be constant 1 when taken, constant 0 when not -- and after N years of effort our flags-related codegen is still decidedly Not Great. tangential to topic but we definitely still have low-hanging fruit here)

view this post on Zulip Alex Crichton (Jun 04 2026 at 23:41):

I can never get sightglass to do what I want...

execution
  ./benchmarks/spidermonkey/spidermonkey-markdown.wasm
    cycles
      [115443777 126864658.04 195022945] ../wasmtime/target/release/libwasmtime_bench_api.so
      [122040249 130580372.04 194436680] ../wasmtime/target/release/libwasmtime_bench_api.so (-Wgc)

is the best I can get out of sightglass

view this post on Zulip Chris Fallin (Jun 04 2026 at 23:45):

so ~3% slowdown

view this post on Zulip fitzgen (he/him) (Jun 05 2026 at 13:24):

hrm okay I can investigate

view this post on Zulip fitzgen (he/him) (Jun 05 2026 at 13:25):

Alex Crichton said:

I can never get sightglass to do what I want...

execution
  ./benchmarks/spidermonkey/spidermonkey-markdown.wasm
    cycles
      [115443777 126864658.04 195022945] ../wasmtime/target/release/libwasmtime_bench_api.so
      [122040249 130580372.04 194436680] ../wasmtime/target/release/libwasmtime_bench_api.so (-Wgc)

is the best I can get out of sightglass

fwiw, sightglass will currently only do its effect-size test when there are exactly two different engines, we haven't implemented the logic to do that when there is one engine but two different sets of engine flags, but should be pretty straightforward to do

view this post on Zulip Paul Osborne (Jun 05 2026 at 19:13):

One option available now: you can run multiple benchmark runs with different flags and then get the analysis results using the report command; this will generate a report including effect size analysis relative to the baseline (first input specified unless you override).

With the "raw" results recorded, which with my latest round of updates does capture flags, you can then use report for an html report and can also use the summarize and effect-size commands on the results.

view this post on Zulip Paul Osborne (Jun 05 2026 at 19:15):

This is mac-specific as written (but easy to adapt) to show one way of wiring this up for an experiment.

#!/usr/bin/env bash

set -x

OUTDIR=${OUTDIR:-"bench-$(date +%Y%m%d-%H%M%S)"}
REVISION=${REVISION:-"v45.0.1"}
mkdir -p "$OUTDIR"

sightglass() {
    cargo run --release -- "$@"
}

build_engine() {
    engine_dir="$PWD/engines/wasmtime/$REVISION"

    # early exit if the directory exists; assume built
    if [ -f "$engine_dir/libengine.dylib" ]; then
        return 0
    fi

    mkdir -p "$engine_dir"
    (
        cd engines/wasmtime || exit
        rustc build.rs
        REVISION="$REVISION" ./build "$engine_dir"
    )
}

benchmark() {
    flags="$1"
    variant="$2"

    build_engine
    sightglass benchmark \
        --raw \
        -e "engines/wasmtime/$REVISION/libengine.dylib" \
        --engine-flags="$flags" \
        -o "$OUTDIR/$variant.json"
}

benchmark "" "baseline"
benchmark "-Wgc" "gc"

(
    cd "$OUTDIR" || exit
    sightglass report -o report.html ./*.json
    sightglass summarize -f ./*.json
)

view this post on Zulip Alex Crichton (Jun 05 2026 at 19:59):

Oh that's what I ended up doing yeah but the summarize at the end didn't print out %-changes

view this post on Zulip Alex Crichton (Jun 05 2026 at 19:59):

it also took me embarassingly long to figure out that I needed to use --raw to emit json

view this post on Zulip Paul Osborne (Jun 05 2026 at 20:46):

Yeah, I think we should change that. I think if an output file is specified or format, that should just infer you want structured output rather than the summary.

view this post on Zulip Paul Osborne (Jun 05 2026 at 20:52):

I think there's a collect/analyze flow that is probably going to be used enough that there's probably some command (maybe collect or record) that takes as input some sort of experiment description.

That's hand-wavy and coming up with an "experiment description" format that's powerful enough and concise is the hard part.


Last updated: Jul 29 2026 at 05:03 UTC