I’m looking at making a
u16 shl_unchecked(u16 value, u32 shift_amount) {
return value << shift_amount;
}
where it’s not allowed to call it with shift_amount >= 16
(either UB or poison would be fine).
What’s the best way to encode that in LLVM IR?
For example, I could assume the truncation is a nop
%inrange = icmp ult i32 %shift_amount, 65536
tail call void @llvm.assume(i1 %inrange)
%trunc = trunc i32 %shift_amount to i16
%r = shl i16 %value, %trunc
ret i16 %r
or I could assume something stricter, expanding it to include the check that shl
already includes
%inrange = icmp ult i32 %shift_amount, 16 ; <-- note different constant
tail call void @llvm.assume(i1 %inrange)
%trunc = trunc i32 %shift_amount to i16
%r = shl i16 %value, %trunc
ret i16 %r
or I could shift in i32
instead, like
%wide = zext i16 %value to i32
%shifted = shl nuw i32 %wide, %shift_amount
%r = trunc i32 %shifted to i16
ret i16 %r
although that’s not quite the same poison
condition. (shl_unchecked(-1, 16)
would still be poison thanks to the nuw
, but shl_unchecked(16, 16)
would be non-poison.)
I also tried putting the poison
in there explicitly, like
%inrange = icmp ult i32 %shift_amount, 65536
%trunc = trunc i32 %shift_amount to i16
%shifted = shl i16 %value, %trunc
%r = select i1 %inrange, i16 %shifted, i16 poison
ret i16 %r
but that select seems to disappear in opt
, so seems fruitless.
Are any of these better that the others? On x86 I think they’re all just emit shl
s, so it’s hard for me to judge any impact.
As a meta-point for the future, I wish I could just emit this as
%trunc = trunc nuw i32 %shift_amount to i16 ; <-- note wrap keyword
%r = shl i16 %value, %trunc
ret i16 %r
Would that be something plausible for me to attempt to add to the IR?
(After, of course, further discussion in the IR & Optimizations topic. Just wondering if that’s obviously bad for some reason and thus isn’t worth me spending additional effort on it.)