Proposal
Add the nuw
(no unsigned wrap) and nsw
(no signed wrap) poison-generating flags to the trunc
instruction, with the following semantics:
trunc nuw iN %x to iM
returns poison if any of the truncated bits are non-zero.trunc nsw iN %x to iM
returns poison if any of the truncated bits are not the same as the top bit of the truncation result.
A corollary is that both zext iM (trunc nuw iN %x to iM)
and sext iM (trunc nsw iN %x to iM)
can be refined to %x
.
Another corollary is that trunc nuw nsw
implies that the truncation result is non-negative.
While sharing the same name, these would be separate flags on TruncInst
and not share logic with OverflowingBinaryOperator
(as trunc is not a binary operator).
Motivation
The motivation for these flags is similar to the recently introduced zext nneg
and or disjoint
flags. We have certain transforms that may introduce no-op truncations, but this knowledge is later lost.
The prime example of this is IV widening. When an induction variable is replaced with a wider one, widening tries hard to also widen its users. However, if it fails to do this and inserts a truncation, the fact that it is always either nuw or nsw is lost, and likely not recoverable by later transforms (that do not use SCEV). For example, this currently happens for widenable LCSSA phi users.
Another motivation is to support a proper IR encoding for non-byte-sized memory accesses. For example, the following IR
%v = load i1, ptr %p
store i1 %v, ptr %p2
has unspecified behavior, but in practice behaves like:
%v.load = load i8, ptr %p
%v = trunc nuw i8 %v.load to i1
%v.store = zext i1 %v to i8
store i8 %v.store, ptr %p2
We strongly prefer only having byte-sized accesses on the IR level, but the lack of trunc nuw
makes it hard to encode them without losing important information (namely, that the top bits of the non-byte-sized access must be zero).
With trunc nuw
, the above truncation and extension can be omitted, without introducing a masking operation:
%v = load i8, ptr %p
store i8 %v, ptr %p2
Alternatives
Don’t add flags
Adding new poison-generating flags adds some additional degree of complexity, which may not be justified.
The IV widening problem could be partially addressed by trying even harder to widen users, even those located outside the loop. Ultimately, this will never catch all cases due to mismatches in analysis capabilities.
The problem of non-byte-sized accesses can (and already is) partially addressed by using range metadata instead:
%v.load = load i8, ptr %p, !range !{i8 0, i8 2}
%v = trunc i8 %v.load to i1
%v.store = zext i1 %v to i8
store i8 %v.store, ptr %p2
Even without trunc nuw
, the trunc/zext pair can be optimized away here. However, the !range
metadata is more easily lost, e.g. because the memory access was eliminated.
Use different flag names
This proposal reuses the nuw and nsw flags that are already familiar from the overflowing binary operators add, sub and mul.
I think the flag names are quite fitting for trunc as well, but the reuse might cause confusion.
The main alternative I can think of would be something like trunc zero
and trunc sign
.