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 into
icmp slt %x, %y, this is not the case if either of the sext operations has been converted into a
- Unlike most other targets, RISC-V prefers
zext. 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).
The proposal is to add a new
nneg flag to
zext, with the following semantics:
nnegflag is set, and the
zextargument is negative, the result is a poison value.
A corollary is that replacing a
zext nneg with
sext is a refinement.
Passes that convert
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).
There are a number of minor and major variations of the basic idea:
The same flag could be supported on
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.
In a previous discussion it was suggested to instead add
nsw flags to
sext. One of the flags is implied by the operation and always set:
zext nsw(same as
sext nuw(same as
sext nnegfrom previous section)
This is nice in that it reuses the existing
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).
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
A way to avoid this would be to combine
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.
@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.