Best IR for mixed-width shift?

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 shls, 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.)

Adding nuw and nsw support to the trunc instruction sounds good to me.

Personally I think it was a bad idea for LLVM IR shift instructions to insist that their operands have the same type - but that ship sailed a very long time ago.

1 Like