Inline asm: missing `vzeroupper` insertion for value-less `out("ymmN") _` / `out("zmmN") _` clobbers
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 28/100
Research direction
Start from the repro: compile it with rustc -O --crate-type=lib --emit=asm,llvm-ir repro.rs and compare clobber with typed. The difference is in how the explicit out("ymm0") _ operand is lowered to an LLVM constraint, so the register-to-constraint mapping for value-less outputs is the place to read. Done when the clobber emits {ymm0} or equivalent and LLVM inserts vzeroupper before ext(), matching the typed and clang cases.
Written by the indexing model from the issue text.
Description
I wrote this code:
use std::arch::asm;
use std::arch::x86_64::__m256i;
extern "C" {
fn ext();
}
// Clobber declared as `out("ymm0") _`: no vzeroupper before the call.
#[no_mangle]
#[target_feature(enable = "avx")]
pub unsafe fn clobber() {
asm!("vpcmpeqd ymm0, ymm0, ymm0", out("ymm0") _);
ext();
}
// Same, but the output is bound to a 256-bit value: vzeroupper is inserted.
#[no_mangle]
#[target_feature(enable = "avx")]
pub unsafe fn typed() {
let _x: __m256i;
asm!("vpcmpeqd ymm0, ymm0, ymm0", out("ymm0") _x);
ext();
}
After running:
rustc -O --crate-type=lib --emit=asm,llvm-ir repro.rs
I expected the LLVM IR to be the same, but it is not:
define void @clobber()
asm sideeffect alignstack inteldialect "vpcmpeqd ymm0, ymm0, ymm0", "=&{xmm0},~{dirflag},~{fpsr},~{flags},~{memory}"()
define void @typed()
asm sideeffect alignstack inteldialect "vpcmpeqd ymm0, ymm0, ymm0", "=&{ymm0},~{dirflag},~{fpsr},~{flags},~{memory}"()
I expected the generated assembly to be the same, but it is not:
clobber:
pushq %rax
vpcmpeqd %ymm0, %ymm0, %ymm0
popq %rax
jmpq *ext@GOTPCREL(%rip)
typed:
pushq %rax
vpcmpeqd %ymm0, %ymm0, %ymm0
popq %rax
vzeroupper
jmpq *ext@GOTPCREL(%rip)
Explanation
On x86_64, an asm! output operand naming a ymm or zmm register with _ as its place (ie. a clobber) is passed to LLVM as an {xmmN} constraint. LLVM then does not know the upper bits of the register were written. Its vzeroupper insertion pass (VZeroUpperInserter) does not treat the block as dirtying the upper state, and emits no vzeroupper before following calls or returns.
If the same operand is bound to a 256-bit value, rustc emits {ymmN} and LLVM inserts vzeroupper as expected. Clang passes "ymm0" clobbers through as ~{ymm0}, and also gets a vzeroupper.
I tried this with:
- x86_64-unknown-linux-gnu
- rustc 1.99.1 (LLVM 23.1.1) and 1.101.0-nightly (92b5b8c87 2026-10-29)
- also every release back to 1.60
Clang, for comparison
void ext(void);
__attribute__((target("avx"))) void clobber(void) {
__asm__ volatile("vpcmpeqd %%ymm0, %%ymm0, %%ymm0" ::: "ymm0");
ext();
}
This gives "~{ymm0},~{dirflag},~{fpsr},~{flags}", and the code is:
clobber:
vpcmpeqd %ymm0, %ymm0, %ymm0
vzeroupper
jmp ext@PLT
Other forms with the same behaviour
out("zmm0") _withavx512fis lowered to=&{xmm0}; novzeroupper.out(ymm_reg) _(a register class, not an explicit register) is lowered to=&x; novzeroupper.
Expected
When the user names ymmN/zmmN (or uses ymm_reg/zmm_reg), a value-less operand should reach LLVM with that register's full width, such as ~{ymmN} or =&{ymmN}.
In other words: a manually-written clobber for a specific register should make its way to LLVM as-is, allowing LLVM VZeroUpperInserter to do its thing (as with clang).
Impact
This is a minor performance impact on older Intel CPUs who had a AVX transition penalty, and later ones it causes false dependencies. I don't believe AMD CPUs have this problem. It is not a correctness problem, as the ISA ensures that uses of a narrow register class is unaffected if the next class up has set bits.
- Dominant language
- Rust
- Stars
- 121k
- Forks
- 17.3k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 531
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from rust-lang/rust
-
A-run-make relnotes relnotes-needs-review relnotes-tracking-issue T-lang T-libs
Difficulty 1/5 Under an hour Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
relnotes relnotes-needs-review relnotes-tracking-issue T-compiler T-libs
Difficulty 1/5 Under an hour Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
A-LLVM O-Arm O-UEFI O-x86_64 relnotes relnotes-needs-review relnotes-tracking-issue T-compiler
Difficulty 1/5 Under an hour Newbie friendliness 72/100
rust-lang/rust#165504 · 1 comment ·
Maintainers usually reply within 1 day
-
relnotes relnotes-tracking-issue T-libs
Difficulty 1/5 Under an hour Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
unclear wording in vec macroPossibly taken @Suhird claimed this 5 days ago. OpenA-docs A-slice-vec T-libs
Difficulty 1/5 1-3 hours Newbie friendliness 70/100
rust-lang/rust#165360 · 6 comments · 1 assignee ·
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 69/100
bytecodealliance/rustix#1736 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
longbridge/gpui-kit#3871 ·
Maintainers usually reply within 1 day
-
area:models enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
Maintainers usually reply within 1 day
-
good first issue help wanted
Difficulty 1/5 Under an hour Newbie friendliness 84/100
servo/rust-smallvec#864 ·