Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
606 views
in Technique[技术] by (71.8m points)

c - How to write a short block of inline gnu extended assembly to swap the values of two integer variables?

For entertainment, I am learning gnu extended assembly using AT&T syntax for x86 with a 32bit Linux target. I have just spent the last three hours coding two possible solutions to my challenge of swapping the values of two integer variables a and b, and neither of my solutions completely solved my problem. First, let's look at my TODO obstacle in some more detail:

int main()
{
    int a = 2, b = 1;
    printf("a is %d, b is %d
", a, b);
    // TODO: swap a and b using extended assembly, and do not modify the program in any other way
    printf("a is %d, b is %d
", a, b);
}

After reading this HOWTO, I wrote the following inline extended assembler code. Here is my first attempt at swapping the integers:

asm volatile("movl %0, %%eax;"
    "movl %1, %%ecx;"
    "movl %%ecx, %0;"
  : "=r" (a)
  : "r" (b)
  : "%eax", "%ecx");

asm volatile("movl %%eax, %0;"
  : "=r" (b)
  : "r" (a)
  : "%eax", "%ecx");

My reasoning was that to set a = b, I needed an extended assembly call that was separated from the assembly to set b = a. So I wrote the two extended assembly calls, compiled my code, i.e., gcc -m32 asmPractice.c, and ran a.out. The results were as follows:

a is 2, b is 1

a is 1, b is 1

Seeing how that did not work properly, I then decided to combine the two extended assembler calls, and wrote this:

asm volatile("movl %0, %%eax;"
    "movl %1, %%ecx;"
    "movl %%ecx, %0;"
    "movl %%eax, %1;"
  : "=r" (a)
  : "r" (b));

After recompiling and linking, my code still does not correctly swap both values. See for yourself. Here are my results:

a is 2, b is 1

a is 1, b is 1

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Here are some solutions from the comments:

Solution #0 (best option): https://gcc.gnu.org/wiki/DontUseInlineAsm
Even the zero-instruction solution defeats constant-propagation, and any other optimization that involves gcc knowing anything about the value. It also forces the compiler to have both variables in registers at the same time at that point. Always keep these downsides in mind when considering using inline-asm instead of builtins / intrinsics.

Solution #1: x86 xchg, no scratch regs, and works in both AT&T and Intel-syntax modes. Costs about the same as 3 mov instructions on most Intel CPUs, or only 2 uops on some AMD.

asm("xchg %0, %1;" : "+r" (a), "+r" (b));

Solution #2: purely using GNU C inline asm constraints. (Bonus: portable to all architectures)

asm("" : "=r" (a), "=r" (b) : "1" (a), "0" (b));

See all three solutions in action on the Godbolt compiler explorer, including examples of them defeating optimization:

int swap_constraints(int a, int b) {
    asm("" : "=r" (a), "=r" (b) : "1" (a), "0" (b));
    return a;
}

// Demonstrate the optimization-defeating behaviour:
int swap_constraints_constants(void) {
  int a = 10, b = 20;
  return swap_constraints(a, b) + 15;
}

swap_constraints_constants:
    movl    $10, %edx
    movl    $20, %eax
    addl    $15, %eax
    ret

vs. with a pure C swap:

swap_noasm_constants:
    movl    $35, %eax    # the add is done at compile-time, and `a` is optimized away as unused.
    ret

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...