1

To learn assembly I am viewing the assembly generated by GCC using the -S command for some simple C programs on Linux.

I write a C function foo.c

long shift_left4_rightn(long x, long n)
{
        x <<= 4;
        x >>= n;
        return x;
}

When I run gcc -Og -S foo.c

I got foo.s . Below is the part about this function

shift_left4_rightn:
        movq    %rdi, %rax
        salq    $4, %rax
        movl    %esi, %ecx
        sarq    %cl, %rax
        ret

The function parameter x uses the register %rdi, which is normal. What confuses me is why the other parameter n uses the register %esi instead of %rsi. What am I missing? What would happen if I replace movl %esi, %ecx with movq %rsi, %rcx.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
yokino
  • 21
  • 3
  • 3
    Only the lower 8 bits are actually used for anything (actually only the lower 6 bits), so it doesn't matter. – Michael Aug 10 '21 at 08:46
  • You can try it and see what happens by editing the `.s` output, then build and single-step it with GDB. (Be careful with this approach in general, though: in asm it's often possible for things to happen to work, depending on details of the caller they're not supposed to). But yes, more literally translating C to asm by using a 64-bit `mov` would be 100% fine. x86 shift instructions mask the count anyway, so high bits are irrelevant. – Peter Cordes Aug 10 '21 at 09:41

1 Answers1

2

It's undefined behavior if you try to shift by more than the number of bits in a value. If long is 64 bits, this means that the maximum possible value of n is 64, even though it's declared as a long. So we don't need all 8 bytes of n, the low 4 bytes are enough. In fact, even %sil (1 byte) would be OK, but maybe there's a performance reason why it prefers %esi.

I think it would still work if you use %rsi.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • The probable reason is that 32-bit operations often have the shortest opcodes. – ElderBug Aug 10 '21 at 08:48
  • 1
    @ElderBug no, 8-bit and 32-bit instructions usually have the same length. It's just that 32-bit versions avoid the partial register update – phuclv Aug 10 '21 at 09:11
  • 2
    @phuclv: Yes, avoiding partial registers is why GCC chooses 32-bit in this case. But it's also shorter: `mov %sil, %cl` would need a REX prefix. (But as you say, not because of the opcode.) There's actually already a canonical Q&A for this: [Why does GCC chose dword movl to copy a long shift count to CL?](https://stackoverflow.com/q/63571651) based on the same example code (apparently from CS:APP) – Peter Cordes Aug 10 '21 at 09:31
  • 1
    32-bit operand-size is shorter than 64, though, if that's what @ElderBug was getting at. (But again, that's due to not needing a REX prefix, not strictly the opcode itself.) [The advantages of using 32bit registers/instructions in x86-64](https://stackoverflow.com/q/38303333) – Peter Cordes Aug 10 '21 at 09:38
  • As mentioned, I meant that 32-bit ops will often (maybe always ? I'm not sure) have the shortest instruction size, including prefixes. 8-bit can have the same shortest size but not here. – ElderBug Aug 10 '21 at 09:41
  • @ElderBug: Yes, 32-bit operand-size has encodings that are at least as small as any other operand-size (in 32 and 64-bit mode), except for stuff like `test r/m8, imm8` (because there is no `test r/m32, sign_extended_imm8`), and for the AL, imm special cases like 2-byte `cmp $1, %al`. [Tips for golfing in x86/x64 machine code](https://codegolf.stackexchange.com/q/132981) – Peter Cordes Dec 08 '21 at 03:43