D106 - The lane model refuses a mask write; Auto reads the shader to choose, and says so
Status: decided (confirmed with input, 2026-08-20)
Model::write_mask is a required trait method that
returns a Result. The wavefront model writes both halves of
the mask as ordinary scalar registers. The per-lane model returns an
error.
Doing nothing was the alternative, and it is the dangerous one: that model has one invocation per lane and no way to represent an inactive one, so a shader that disables lanes would run every lane regardless. Plausible output, wrong answer, nothing in it to indicate the problem - the exact failure D098 keeps three levels to avoid.
resolve now takes the decode.
Fidelity::Auto picks the wavefront model for a shader that
touches the mask and the lane model otherwise. It previously answered
the lane model unconditionally, and the function said why: any shader
needing more would contain an instruction the translator refused anyway,
so the wrong level could not be chosen - "not by analysis, but because
the translator stops first". The same comment noted this was safety by
accident and would expire when those instructions were built. It expired
in the change that built them.
This is not the silent substitution D098 forbids. Auto
is a request to be told what the shader needs; asking for a level
explicitly and getting it is unaffected, and a shader that needs a mask
is refused loudly by the model that has none.
touches_mask inspects operands, not
opcodes. s_mov_b64 needs a mask when its
destination is exec and not when it is an ordinary register
pair, so a table keyed on the opcode would have to say yes to both and
push every shader containing any 64-bit move onto the slow model.
Confirmed, and the fallback is now a warning
The open question was whether Auto should silently
upgrade to a much slower model or refuse and make the caller ask.
It upgrades - and it says so.
Refusing was never really available: a translator that refuses
whenever a shader masks would refuse nearly every real shader, which is
not a useful default. But the cost is a factor of sixty four, and one
instruction touching exec anywhere is enough to trigger it,
so leaving the only evidence in a field was the wrong side of it. A
field is something a caller has to know to look for; a warning is
something a caller has to decide to ignore.
Translated::warnings carries it, the submission report
collects it, and the message says what forced the fallback.
It also carries the width a subgroup would need,
because that is the actionable part. Fidelity::Subgroup
(D146) is as fast as the per-lane model and has a mask, so it
is the right answer whenever it fits - and whether it fits is a property
of the device, not of the shader. The translator has never seen
a device, so it reports the requirement and leaves the comparison to
whoever knows. That is also why Auto does not simply choose
the subgroup level itself: it cannot, without knowing what it is running
on.
A shader that needs no mask warns about nothing, so the warning stays worth reading rather than becoming noise every caller learns to skip past.