EvanCarroll opened issue #14848:
Cranelift x64: a
shufflewhose mask reads one operand still depends on the otherSummary
On x64, a
shufflegets a singlepshufbonly when both operands are the same
value (shuffle a a). If the operands differ but the mask only selects lanes from
one of them (every index < 16, or every index >= 16), lowering falls through to:
vpermi2b mask, a, bwhen AVX512VL + AVX512VBMI are available, orpshufb a+pshufb b(zeroing mask) +porotherwise.Both read the operand the mask never selects. That costs instructions, and it
also adds a false data dependency on that operand.Wasm producers emit this pattern. In
core::arch::wasm32::i8x16_shuffle::<0, 4, 1, 5, …>(x, x), LLVM marks the second operand undefined, and the wasm backend fills
it with whatever local is handy. In the case below that local is the loop's
accumulator, so the shuffle waits on the previous iteration's add and the loop
runs serially. V8 lowers the same module to a singlepshufb.Reproducer
CLIF (
test compile precise-output,target x86_64 ssse3 has_avx512vl has_avx512vbmi):function %shuffle_lhs_only(i8x16, i8x16) -> i8x16 { block0(v0: i8x16, v1: i8x16): v2 = shuffle v0, v1, [0 4 1 5 2 6 3 7 8 12 9 13 10 14 11 15] return v2 }Observed (
mainat dcfbea188d):movdqa %xmm0, %xmm5 movdqu (%rip), %xmm0 movdqa %xmm5, %xmm6 vpermi2b %xmm1, %xmm6, %xmm0With
target x86_64 sse41it is twopshufbs and apor. Expected in both
cases:pshufb (%rip), %xmm0. The mirror case, every index >= 16, behaves the
same way withv1andv0swapped.Wasm loop showing the false dependency (
shuffle-false-dep.wat, attached):(loop $l (local.set $v (i8x16.shuffle 0 4 1 5 2 6 3 7 8 12 9 13 10 14 11 15 (v128.load …) (local.get $acc))) ;; $acc is never selected (local.set $acc (i32x4.add (local.get $acc) (i32x4.dot_i16x8_s …))) (br_if $l …))Impact
shuffle-false-dep.wat, i7-1165G7 (has AVX512VBMI):
wasmtime time 49.0.3 3.37 s (3.4 ns/iteration) with a single-operand lowering 0.85 s (0.85 ns/iteration), 4.0× faster
n = 1_000_000_000,wasmtime run --invoke run shuffle-false-dep.wat 1000000000,
pinned to one core; two runs each agree to the hundredth of a second. The loop
on 49.0.3:vmovdqu (%rax, %rcx), %xmm1 subl $1, %edx vmovdqu 0x38(%rip), %xmm2 vpermi2b %xmm0, %xmm1, %xmm2 ; %xmm0 is $acc, never selected vpmovsxbw %xmm2, %xmm1 vpalignr $8, %xmm2, %xmm2, %xmm2 vpmovsxbw %xmm2, %xmm2 vpmaddwd %xmm2, %xmm1, %xmm1 vpaddd %xmm1, %xmm0, %xmm0 ; $accWith
vpshufb 0x27(%rip), %xmm1, %xmm2in place of thevmovdqu 0x38(%rip)/
vpermi2bpair, the shuffle no longer waits on the previous iteration'svpaddd.In a real int8 matrix-vector kernel (tract's
wasm_i32_4x1, the inner loop of a
transformer decoder's weight matmuls), patching only this operand in the module
(passing the loaded vector twice, so thatshuffle a amatches) takes a
512×512 call from 56.7 µs to 27.2 µs on wasmtime 49.0.3: 2.1×, level with V8
(26 µs).Where it comes from
cranelift/codegen/src/isa/x64/lower.isle,shufflerules: rule 3 is
(shuffle _ a a …). Rules 2 and 1 (vpermi2b) and theporfallback take
(shuffle _ a b …)with no check on which operands the mask reads. Several
special cases already match "lhs only" or "rhs only" masks (pshufd_lhs_imm/
pshufd_rhs_imm,pshuflw_*,pshufhw_*), but no general byte shuffle does.
Neither the mid-end nor the wasm translator canonicalizesshuffle a bto
shuffle a a.Related
#2531: shuffle optimizations needed. Closed once the common patterns got
single-instruction lowerings; single-operand masks with distinct operands were
not among them.#2930: added the
vpermi2blowering for shuffles of two different registers.
It is the rule that now also catches these single-operand masks.#4772: port of the x64 shuffle lowering to ISLE (the rules above).
#5930, #5999: more
shufflespecializations (pshufd/pshuf{l,h}wlhs/rhs,
palignr); they show the lhs/rhs-only pattern this needs.#6606: non-SSSE3
pshufblowering (theX86Pshufblibcall).
alexcrichton added the cranelift:goal:optimize-speed label to Issue #14848.
alexcrichton added the cranelift:area:x64 label to Issue #14848.
alexcrichton closed issue #14848:
Cranelift x64: a
shufflewhose mask reads one operand still depends on the otherSummary
On x64, a
shufflegets a singlepshufbonly when both operands are the same
value (shuffle a a). If the operands differ but the mask only selects lanes from
one of them (every index < 16, or every index >= 16), lowering falls through to:
vpermi2b mask, a, bwhen AVX512VL + AVX512VBMI are available, orpshufb a+pshufb b(zeroing mask) +porotherwise.Both read the operand the mask never selects. That costs instructions, and it
also adds a false data dependency on that operand.Wasm producers emit this pattern. In
core::arch::wasm32::i8x16_shuffle::<0, 4, 1, 5, …>(x, x), LLVM marks the second operand undefined, and the wasm backend fills
it with whatever local is handy. In the case below that local is the loop's
accumulator, so the shuffle waits on the previous iteration's add and the loop
runs serially. V8 lowers the same module to a singlepshufb.Reproducer
CLIF (
test compile precise-output,target x86_64 ssse3 has_avx512vl has_avx512vbmi):function %shuffle_lhs_only(i8x16, i8x16) -> i8x16 { block0(v0: i8x16, v1: i8x16): v2 = shuffle v0, v1, [0 4 1 5 2 6 3 7 8 12 9 13 10 14 11 15] return v2 }Observed (
mainat dcfbea188d):movdqa %xmm0, %xmm5 movdqu (%rip), %xmm0 movdqa %xmm5, %xmm6 vpermi2b %xmm1, %xmm6, %xmm0With
target x86_64 sse41it is twopshufbs and apor. Expected in both
cases:pshufb (%rip), %xmm0. The mirror case, every index >= 16, behaves the
same way withv1andv0swapped.Wasm loop showing the false dependency (
shuffle-false-dep.wat, attached):(loop $l (local.set $v (i8x16.shuffle 0 4 1 5 2 6 3 7 8 12 9 13 10 14 11 15 (v128.load …) (local.get $acc))) ;; $acc is never selected (local.set $acc (i32x4.add (local.get $acc) (i32x4.dot_i16x8_s …))) (br_if $l …))Impact
shuffle-false-dep.wat, i7-1165G7 (has AVX512VBMI):
wasmtime time 49.0.3 3.37 s (3.4 ns/iteration) with a single-operand lowering 0.85 s (0.85 ns/iteration), 4.0× faster
n = 1_000_000_000,wasmtime run --invoke run shuffle-false-dep.wat 1000000000,
pinned to one core; two runs each agree to the hundredth of a second. The loop
on 49.0.3:vmovdqu (%rax, %rcx), %xmm1 subl $1, %edx vmovdqu 0x38(%rip), %xmm2 vpermi2b %xmm0, %xmm1, %xmm2 ; %xmm0 is $acc, never selected vpmovsxbw %xmm2, %xmm1 vpalignr $8, %xmm2, %xmm2, %xmm2 vpmovsxbw %xmm2, %xmm2 vpmaddwd %xmm2, %xmm1, %xmm1 vpaddd %xmm1, %xmm0, %xmm0 ; $accWith
vpshufb 0x27(%rip), %xmm1, %xmm2in place of thevmovdqu 0x38(%rip)/
vpermi2bpair, the shuffle no longer waits on the previous iteration'svpaddd.In a real int8 matrix-vector kernel (tract's
wasm_i32_4x1, the inner loop of a
transformer decoder's weight matmuls), patching only this operand in the module
(passing the loaded vector twice, so thatshuffle a amatches) takes a
512×512 call from 56.7 µs to 27.2 µs on wasmtime 49.0.3: 2.1×, level with V8
(26 µs).Where it comes from
cranelift/codegen/src/isa/x64/lower.isle,shufflerules: rule 3 is
(shuffle _ a a …). Rules 2 and 1 (vpermi2b) and theporfallback take
(shuffle _ a b …)with no check on which operands the mask reads. Several
special cases already match "lhs only" or "rhs only" masks (pshufd_lhs_imm/
pshufd_rhs_imm,pshuflw_*,pshufhw_*), but no general byte shuffle does.
Neither the mid-end nor the wasm translator canonicalizesshuffle a bto
shuffle a a.Related
#2531: shuffle optimizations needed. Closed once the common patterns got
single-instruction lowerings; single-operand masks with distinct operands were
not among them.#2930: added the
vpermi2blowering for shuffles of two different registers.
It is the rule that now also catches these single-operand masks.#4772: port of the x64 shuffle lowering to ISLE (the rules above).
#5930, #5999: more
shufflespecializations (pshufd/pshuf{l,h}wlhs/rhs,
palignr); they show the lhs/rhs-only pattern this needs.#6606: non-SSSE3
pshufblowering (theX86Pshufblibcall).
Last updated: Oct 31 2026 at 23:03 UTC