-2

I'm trying to convert code from C++ to ASM based on AT&T code to convert:

void func() {
    int num = 1;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
                       /* code to swap */
            std::cout <<"before: " << num;
            num = num << 3;
            std::cout << " after: " << num << std::endl;
                      /* end code to swap */
        }
    }
}

After the change the code looks like this:

void func() {
    int num = 1;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
            std::cout <<"before: " << num;
            asm (
                "mov -4(%rbp), %eax \n"
                "sall $3, %eax \n"
                "mov %eax, -4(%rbp) \n"
            );
            std::cout << " after: " << num << std::endl;
        }
    }
}

The same example without the use of both loops works correctly, but when you add them, it does not work anymore. While 1 example gives correct results, the second code after changing the code prints only number 1, I assume that after adding 2 loops, the location of the variable changes from -4(%rbp) to another address.

My assumptions:

int num is in  -4(%rbp)
int i is in -8(%rbp)
int j is in -12(%rbp)

Im using GCC Compiler with Dev C++

ira1denu
  • 1
  • 2
  • @JeffSpencer I forgot to add the compiler name outside the tag, but I already updated the post – ira1denu Dec 17 '20 at 20:46
  • You need to let the compiler know that you’re changing num within the asm and also that you’re trashing rax. There are many questions here on stackoverflow covering these issues. – prl Dec 17 '20 at 21:31
  • 2
    As people have (correctly) pointed out, there's no guarantee about how the compiler may order your variables, or even whether it will allocate memory for them at all (esp w/optimizations). Also you are violating the rules of inline asm by modifying a register (eax) without informing the compiler. That said, gcc has the ability to associate c variables with asm: `asm("mov %0, %%eax\n sall $3, %%eax\n mov %%eax, %0\n" : "+rm" (num): : "eax")` or even just `asm("sall $3, %0" : "+rm" (num))`. But you might want to consider why you're doing this. Inline asm is hard to get right. – David Wohlferd Dec 17 '20 at 21:32
  • If you want to add your own asm instructions to compiler-generated code, compile to asm once, and then use that as the starting point for an asm source file that you edit hand. If you want to use GNU C inline asm, use it properly (Extended asm). – Peter Cordes Dec 18 '20 at 23:18
  • Duplicate of your previous question. The current version of the answer on your previous question now explains how to use inline asm properly. (And so do duplicates). Although to be fair, both those changes on your previous question came after you posted this; at the time the answer didn't correct the fundamental mistake. I thought there had been some comments about that not being the right way at all to use inline asm. Maybe a mod nuked them so you missed getting that info, or else I'm misremembering. So asking another question with the same problem isn't really your fault. – Peter Cordes Dec 18 '20 at 23:27

3 Answers3

3

Well, on godbolt your code "seems" to be running correctly. However, what you are doing is undefined behavior: the code will break if something changes in the environment. You should use proper (extended) GCC assembly that refers to local variables.

void func() {
    int num = 1;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
            std::cout <<"before: " << num;
            asm (
                "sal $3, %[num];"
                :[num] "+r" (num)
            );
            std::cout << " after: " << num << std::endl;
        }
    }
}

i.e. you directly use (num) as an input-output register +r and assign it a label [num]. You then only have to run the shift arithmetic left command (not necessary to add the 'l') which uses that register and writes back to it.

JHBonarius
  • 10,824
  • 3
  • 22
  • 41
0

I'm not familiar with AT&T code you are referring to. It seems to be your motivation for doing this. In general, hand coding ASM is avoided for general functions like this. ASM is incredibly error prone.

That said, since the compiler is free to store the local variables anywhere it wants the code as written likely won't work. The compiler may provide syntax to solve this. But it would be entirely compiler specific. I believe Visual Studio compilers do, but I haven't written a line of assembly language in at least 10 years.

If the goal is performance, generally introducing a three instruction assembly language change won't improve performance. Unless of course the compiler is not very good at optimization. Though there are exceptions I suppose for instructions looping over large data sets in parallel. But personally I'll let the compiler developers figure out how to implement those reliably.

Of course, if the goal is learning about doing things like this. Then that's cool, many new devs now days don't really understand what the language is doing at the assembly language level. If that's the case then take a look at this link. If it applies to your compiler it should allow you to have the compiler pre-position the items you need in the registers you want. GNU GCC Asm

  • You're right, this is completely the wrong way to use GNU C inline asm. And it only has any hope of working in a debug build (the GCC / clang default, `-O0`), [where everything is basically `volatile`](https://stackoverflow.com/questions/53366394/why-does-clang-produce-inefficient-asm-with-o0-for-this-simple-floating-point) so the compiler's already storing everything back to memory between every C++ statement. So this asm is just as inefficient as what the compiler is doing in those cases, but that''you're totally correct that it's really bad. – Peter Cordes Dec 18 '20 at 23:22
  • But no, MSVC doesn't support inline asm for x86-64, only for 32-bit x86. The compiler internals of their implementation were so brittle they just abandoned it. – Peter Cordes Dec 18 '20 at 23:23
-1

Your assumptions are most likely wrong, the compiler is free to order those variables in the stack frame in any way it wants (and even not put them in the stack frame in some cases).

Assuming you want to use assembler code, you should be able to find the actual location of num by putting num++ in you code and seeing what the compiler generates.

However, keep in mind this freedom the compiler has also extends to the freedom to change its mind based on your code changes, or even the phase of the moon or what time of the day you decide to compile your code :-) That means it may be at a different in your checking code than it is in your non-checking code.


For example, entering this code into godbolt using x86-64 gcc 10.2:

void func() {
    int num = 42;
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 2; j++)
            asm (
                "mov -4(%rbp), %eax \n"
                "sall $3, %eax \n"
                "mov %eax, -4(%rbp) \n"
            );
}

starts off with num being at -12(%rbp):

pushq   %rbp
movq    %rsp, %rbp
movl    $42, -12(%rbp)

When you comment out the outer loop and then the inner loop, it moves (respectively) to -8(%rbp) and -4(%rbp).

Now there are certainly ways in which you can get the correct address of a C variable into your assembler code but, like asm itself, they're not necessarily portable. If you really want to have full control over where things are located, you may want to consider writing your code in assembler fully.

But, to be honest, I can't really see what you're gaining by doing so in this case. Other than education, possibly, which you're certainly getting with these issues :-)

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • If I use asm in the c++ code, or more precisely in a function, then theoretically 1 local variable should be in the address -4(%rbp) (which can be confirmed by removing both loops, then the program and the code are executed correctly) and I have no idea why adding these loops changes the address of 1 variable – ira1denu Dec 17 '20 at 20:42
  • 1
    @ira1denu: because, as stated, there is no *requirement* on the part of the compiler to order those variables in any specific way. The order could be `num, i, j`, `j, i, num`, or even weirder ones like `i, 16 bytes of seemingly useless space, num, j`. – paxdiablo Dec 17 '20 at 20:46
  • 1
    I've worked with gcc very little. But the compiler may have compiler flags that will optimize this code beyond what your change is doing here. For instance it may keep virtually all the local variables in registers rather than memory. – Jeff Spencer Dec 17 '20 at 21:02