Logical bitshift on `WordN` wraps around

Today I’ve observed the rather peculiar behaviour where logical bitshifts on WordN wrap around after N bitshifts. Consider the following code & output:

let b : Word8 = 0xFF;
for (i in Iter.range(0, 10)) {
  let k : Word8 = Word8.fromNat(i);
  Debug.print(debug_show(b) # " >> " # debug_show(k) # " = " # debug_show(b >> k));

0xFF >> 0x0 = 0xFF                                                                                        
0xFF >> 0x1 = 0x7F
0xFF >> 0x2 = 0x3F
0xFF >> 0x3 = 0x1F
0xFF >> 0x4 = 0xF 
0xFF >> 0x5 = 0x7 
0xFF >> 0x6 = 0x3 
0xFF >> 0x7 = 0x1 
0xFF >> 0x8 = 0xFF
0xFF >> 0x9 = 0x7F
0xFF >> 0xA = 0x3F

This does not align with my understanding of logical bitshifts, nor do other languages (eg Go) seem to implement logical shifts this way.
I was unable to find any hint on that in the Motoko documentation. Is this behaviour on purpose? If so, what is the reason for implementing shifts this way?

1 Like

Some googling later - this does seems to be one of these topics where people’s opinion diverges :joy:
Eg the x86 SHL instruction also behaves the same - Winter: x86 Instruction Set Reference (albeit in their case the discussion obviously applies to 32-bit numbers):

[…] do mask the shift count to 5 bits, resulting in a maximum count of 31. This masking is done […] to reduce the maximum execution time of the instructions.

While I acknowledge that there may be reasons to do this when it comes to hardware design, it does feel a bit… unintuitive to do so within a semi-high-level language. :slight_smile: