Background
LLVM currently canonicalizes sext
operations to zext
operations if the argument is known to be non-negative. This enables better analysis, redundancy elimination and is preferred on most targets.
However, the conversion to zext is not always beneficial, for two main reasons:
- If the extension is used in a signed context, sext may be preferable. For example, while
icmp slt sext(%x), sext(%y)
can be converted intoicmp slt %x, %y
, this is not the case if either of the sext operations has been converted into azext
. - Unlike most other targets, RISC-V prefers
sext
overzext
. The latter may require additional instructions.
This problem can be addressed to some degree by undoing the canonicalization in places where sext is preferred. However, this does not work reliably, because the necessary information may have been lost or is not readily available (e.g. if the canonicalization was performed by IPSCCP or CVP, but the undo transform only has access to VT).
Proposal
The proposal is to add a new nneg
flag to zext
, with the following semantics:
If the
nneg
flag is set, and thezext
argument is negative, the result is a poison value.
A corollary is that replacing a zext nneg
with sext
is a refinement.
Passes that convert sext
to zext
would add this flag, which would enable conversion back to sext
to be more reliable. Additionally, the flag can be inferred based on different analysis passes (IPSCCP, LVI, SCEV, VT).
Alternatives
There are a number of minor and major variations of the basic idea:
Also support sext nneg
The same flag could be supported on sext
. sext nneg
and zext nneg
would be equivalent.
This is arguably more consistent, but as we would canonicalize any sext nneg
to zext nneg
anyway, the value of having such a flag (and associated cost) is dubious.
Call the flags nuw
and nsw
In a previous discussion it was suggested to instead add nuw
and nsw
flags to zext
and sext
. One of the flags is implied by the operation and always set:
zext nuw
(implied)sext nsw
(implied)zext nsw
(same aszext nneg
)sext nuw
(same assext nneg
from previous section)
This is nice in that it reuses the existing nuw
/nsw
flags, but I find their use here rather non-obvious and awkward, especially as one of the flags is always implicit. I don’t think we actually gain anything from using the same name (especially as the existing flags are fairly hard-coded to binary operators).
Introduce a general ext
instruction
A disadvantage of zext nneg
is that it still leaves zext
as the privileged operation, because it is our canonicalization target. Transforms that require zero extension can just look for zext
. Transforms that require sign extension would have to look for sext
or zext nneg
.
A way to avoid this would be to combine zext
and sext
into a single instruction, as follows:
ext zero
: Zero-extend.ext sign
: Sign-extend.ext zero sign
: Both zero-extend and sign-extend. Returns poison when these conflict, i.e. when the argument is negative.
This removes any asymmetry between zext/sext. The sext to zext canonicalization is replaced with inferring that a sign ext is also a zero ext.
The zero/sign flags here are not standard poison-generating flags, so some additional handling would be needed in relation to flag manipulation.
This is arguably the most principled approach, but also requires a lot more implementation effort and IR churn. This is probably not justified at this point.
Patches
@karouzakisp has implemented a patch series for introducing the nneg flag starting with ⚙ D156444 [llvm][RISCV][IR] Zext flag in IR for RISC-V. I’ve submitted a slightly cleaned up version of the first step in https://github.com/llvm/llvm-project/pull/67982.