0

I am looking through assembly generated by my trivial C++ program. The full listing is:

test_func_0(int):
        add     edi, 15
        pxor    xmm0, xmm0
        cvtsi2ss        xmm0, edi
        divss   xmm0, DWORD PTR .LC0[rip]
        cvttss2si       eax, xmm0
        ret
test_func_1(int&):
        mov     eax, DWORD PTR [rdi]
        pxor    xmm0, xmm0
        add     eax, 15
        cvtsi2ss        xmm0, eax
        divss   xmm0, DWORD PTR .LC0[rip]
        cvttss2si       eax, xmm0
        ret
test_func_2(int*):
        mov     eax, DWORD PTR [rdi]
        pxor    xmm0, xmm0
        add     eax, 15
        cvtsi2ss        xmm0, eax
        divss   xmm0, DWORD PTR .LC0[rip]
        cvttss2si       eax, xmm0
        ret
.LC1:
        .string " "
main:
        push    r12
        mov     esi, 10
        mov     edi, OFFSET FLAT:_ZSt4cout
        push    rbp
        sub     rsp, 8
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     edx, 1
        mov     esi, OFFSET FLAT:.LC1
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     esi, 10
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     edx, 1
        mov     esi, OFFSET FLAT:.LC1
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     esi, 10
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     rbp, rax
        mov     rax, QWORD PTR [rax]
        mov     rax, QWORD PTR [rax-24]
        mov     r12, QWORD PTR [rbp+240+rax]
        test    r12, r12
        je      .L10
        cmp     BYTE PTR [r12+56], 0
        je      .L7
        movsx   esi, BYTE PTR [r12+67]
.L8:
        mov     rdi, rbp
        call    std::basic_ostream<char, std::char_traits<char> >::put(char)
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >::flush()
        add     rsp, 8
        xor     eax, eax
        pop     rbp
        pop     r12
        ret
.L7:
        mov     rdi, r12
        call    std::ctype<char>::_M_widen_init() const
        mov     rax, QWORD PTR [r12]
        mov     esi, 10
        mov     rdi, r12
        call    [QWORD PTR [rax+48]]
        movsx   esi, al
        jmp     .L8
.L10:
        call    std::__throw_bad_cast()
_GLOBAL__sub_I_test_func_0(int):
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit
.LC0:
        .long   1077936128

And it is geneaterd from

#include <iostream>

int test_func_0 (int my_int)
{
     return (my_int + 15) / 3.0f;
}

int test_func_1 (int& my_int)
{
     return (my_int + 15) / 3.0f;
}

int test_func_2 (int* my_int)
{
     return (*my_int + 15) / 3.0f;
}


int main()
{
    int a = 17;
  
    std::cout << test_func_0(a) << " ";
    std::cout << test_func_1(a) << " ";
    std::cout << test_func_2(&a) << std::endl;

    return 0;
}

I believe I have been able to understand a lot of this, but three things I'm not seeing are:

  1. Where are my functions being called? I see the long calls to std::cout (as well as a weird cout looking thing OFFSET FLAT:_ZSt4cout), but no explicit calls to test_func_1(int&).
  2. Where has my 17 gone?? I can understand it being taken as const and inlined somewhere.. but where?
  3. I thought my 17 might have been the .long 1077936128 at the bottom, but in a weird format, but I'm not convinced by this. So what is this 1077936128 number?

C++ Insights helps a little by showing me:

int main()
{
  int a = 17;
  std::operator<<(std::cout.operator<<(test_func_0(a)), " ");
  std::operator<<(std::cout.operator<<(test_func_1(a)), " ");
  std::cout.operator<<(test_func_2(&a)).operator<<(std::endl);
  return 0;
}

Doing various inlines, as I thought. But I still don't see in the assembly where the functions are called.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Cascades
  • 627
  • 7
  • 18
  • 1
    Semi-related re: optimization effects and making asm that's still interesting to look at despite that: [How to remove "noise" from GCC/clang assembly output?](https://stackoverflow.com/q/38552116) e.g. `__attribute__((noinline,noclone))`, or just don't provide a definition, only prototype, for the callees. – Peter Cordes Aug 17 '21 at 12:54
  • Oh yes! Only a declaration would have been a smarter way of attempting to stop it from inlining! Thanks again! – Cascades Aug 17 '21 at 17:56

1 Answers1

4
  1. Your functions were all inlined, so there aren't any calls to them in main. Instead, their result is used directly.
  2. The value 17 doesn't appear in your code because the compiler performed the calculations at compile time. All functions return int((17+15)/3.0f) which is 10. And you can see mov esi, 10 three times in the generated assembly, which is used to pass the value to basic:ostream::operator<<.
  3. 1077936128 is the representation of 3.0f when read as an integer from memory. (See Understanding GCC's floating point constants in assembly listing output for details). It is only used in the three functions and not in main (where constant-propagation resulted in a simple integer at compile-time).
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
interjay
  • 107,303
  • 21
  • 270
  • 254
  • Thanks for the response. That makes sense, but why are those functions even written out if I compiled with -O3 and they are not used at all? Is it in case this is being used as a library by something else or something? – Cascades Aug 17 '21 at 11:55
  • 3
    Yes, the compiler doesn't know if you will link this with another file which calls these functions. If you mark them as `static` then they probably won't appear in the assembly. – interjay Aug 17 '21 at 11:57
  • Yup, internal linkage through static got rid of them! Fantastic, thanks! – Cascades Aug 17 '21 at 12:06
  • 2
    @Cascades: `-fwhole-program` would probably also enable that optimization without `static`, telling GCC that the source file(s) on the command line are the entire program. Maybe also `-flto` (link-time whole-program / cross-file optimization.) – Peter Cordes Aug 17 '21 at 12:44
  • @PeterCordes: Oh, great to know! Thanks. – Cascades Aug 17 '21 at 17:53