5

Why is permitted shift value of LSL [0, 31] and permitted shift value of LSR is [1, 32]?

How is shifting 0 bits in LSL useful for any application? Why is 0 shift not allowed in LSR? Why doesn't LSL allow 32-bit shifts?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • If you shift to the right 32 times you have shifted all bits out leaving `0`. If you shift left 32 times, you overflow. – David C. Rankin Mar 05 '21 at 06:41
  • @DavidC.Rankin: If your shift count was from a register, a left shift by 32 or more also shifts all the bits out (because ARM shifts saturate their count, unlike x86 for example that masks the shift count like `x << (n&31)`). Any time you shift out a `1` you can call that "overflow", but the overflow behaviour is well-defined to wrap mod 2^32, i.e. truncate to 32 bits. I'd assume the real answer is in machine-code design for how to encode these. – Peter Cordes Mar 05 '21 at 06:45
  • @DavidC.Rankin: Are you talking about C where signed overflow is undefined behaviour? In ARM assembly, everything about shifting is well-defined. (Like `gcc -fwrapv`). The interpretation of the bits in the register as an integer value that wraps or whatever is not really important or relevant here, just the raw shift operation. – Peter Cordes Mar 05 '21 at 06:47
  • Yes, sorry read past the [arm] tag completely. – David C. Rankin Mar 05 '21 at 06:49
  • The answer is: because the instructions have been specified that way. – fuz Mar 05 '21 at 09:29
  • @fuz: Is there any sensible machine-code reason for that, like using a 2's complement shift count where negative = right, non-negative = left? If so, that's the answer. But since other shift modes include ASR and rotate, IDK if that's right, and don't already know exactly which ARM PDF to look in for machine-code details like I would for x86. – Peter Cordes Mar 05 '21 at 11:27
  • 2
    @PeterCordes In Thumb, `lsl Rd, Rn, #0` is how `MOVS Rd, Rn` is encoded. For the other shifts, a shift amount of zero would not be useful as it would do the same as `MOVS`, so an immediate of 0 was defined to mean 32. No idea about A32 mode. – fuz Mar 05 '21 at 11:31
  • @fuz: Ah right, you need an encoding for one of the shifts that means "no shift", but for others "32" can be useful to zero a reg (or broadcast the sign bit with ASR) and set the C flag according to the last bit shifted out. That sounds like an answer. (I'd guess it's basically the same for A32 mode where every instruction has room for barrel-shifter bits; you'd still need an encoding that means no-shift.) – Peter Cordes Mar 05 '21 at 11:37
  • @PeterCordes In A32 mode it's a bit different in that `ROR` by `#0` encodes `RRX`. This doesn't happen in T32 because `rors Rd, Rn, #imm5` is not encodable in Thumb1. – fuz Mar 05 '21 at 12:06

1 Answers1

3

This restriction applies to shifts by immediates. Shifts by registers are not subject to such restrictions.

LSL by 0 is permitted to indicate “no shift.” This is a special case in that the C flag of a flag setting instruction is not modified.

LSR and ASR by 0 would behave the same way as LSL by 0, so the CPU designers decided to make an immediate operand of 0 indicate a shift by 32, enabling additional functionality. For the case of ROR, a shift by 0 instead indicates the special “rotate right extended” instruction RRX as a rotate by 32 would not be very useful.

Refer e.g. to the ARM7TDMI datasheet where it says:

Note LSL #0 is a special case, where the shifter carry out is the old value of the CPSR C flag. The contents of Rm are used directly as the second operand.

The form of the shift field which might be expected to correspond to LSR #0 is used to encode LSR #32, which has a zero result with bit 31 of Rm as the carry output. Logical shift right zero is redundant as it is the same as logical shift left zero, so the assembler will convert LSR #0 (and ASR #0 and ROR #0) into LSL #0, and allow LSR #32 to be specified.

The form of the shift field which might be expected to give ASR #0 is used to encode ASR #32. Bit 31 of Rm is again used as the carry output, and each bit of operand 2 is also equal to bit 31 of Rm. The result is therefore all ones or all zeros, according to the value of bit 31 of Rm.

The form of the shift field which might be expected to give ROR #0 is used to encode a special function of the barrel shifter, rotate right extended (RRX). This is a rotate right by one bit position of the 33 bit quantity formed by appending the CPSR C flag to the most significant end of the contents of Rm [...].

In Thumb (T32) mode, the convention was adapted. Thumb lacks a distinct MOVS Rd, Rn instruction, so LSLS Rd, Rn, #0 is used for this purpose. LSR Rd, Rn, #imm5 and ASR Rd, Rn, #imm5 behave the same way they do in A32 mode. An immediate of #32 is encoded by setting the five-bit immediate field to zero. The hypothetical instruction ROR Rd, Rn, #imm5 does not exist since the opcode space it would be found at is used to encode

ADDS Rd, Rn, Rm
SUBS Rd, Rn, Rm
ADDS Rd, Rn, #imm3
SUBS Rd, Rn, #imm3

instead.

fuz
  • 88,405
  • 25
  • 200
  • 352
  • 2
    *Thumb lacks a distinct MOVS Rd, Rn instruction* - It's not totally different from ARM mode. In ARM mode, `lsl` is just `mov` with a non-zero shift count. It's just a matter of which one's the "base" operation and which is an application of it / pseudo-instruction. I guess in ARM mode it makes the most sense to say that MOV is the base instruction, and `lsl r0, r1, #12` is an alias for `mov r0, r1, lsl #12`, but in Thumb mode instructions don't *always* have room to encode a shifted source operand so yeah it makes sense to call `lsls` a first-class mnemonic / opcode. – Peter Cordes Mar 05 '21 at 12:32
  • this is all documented by arm. you should be able to see gnu assembler to encode the specific mov into an lsl with a zero shift. – old_timer Mar 05 '21 at 18:58