Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. The Lounge
  3. I thought I knew C++ *sob* It has been inserting extra code on me this whole time.

I thought I knew C++ *sob* It has been inserting extra code on me this whole time.

Scheduled Pinned Locked Moved The Lounge
c++wpfperformance
38 Posts 8 Posters 0 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • honey the codewitchH Offline
    honey the codewitchH Offline
    honey the codewitch
    wrote on last edited by
    #1

    #include template class foo {
    constexpr const static int pin = Pin;
    public:
    constexpr inline static char test() __attribute((always_inline)) {
    if(Pin==-1) {
    return 'A';
    } else {
    return 'B';
    }
    }
    static_assert(test()!=0,"test");
    };
    int main(int argc, char** argv) {
    // mov eax,65
    // movsx eax, al
    // mov esi, eax
    printf("%c\n",foo<-1>::test());
    // move esi, 65
    printf("%c\n",65);
    return 0;
    }

    I'd like someone smarter than I am to explain to me why the first printf does not generate a mov esi, 65 or even movsx esi, 65, but rather, 3 instructions that are seemingly redundant and yet don't get removed by the peephole optimizer, but I don't think that's going to happen. The worst part is, I have a dozen libraries using a bus framework I wrote that relies on my bad assumptions about the code that is generated. The upshot is the code is slow, and the only way to speed it up is to A) rewrite it to not use templates B) nix the ability to run multiple displays at once But IT SHOULD NOT BE THIS WAY. I feel misled by the C++ documentation. But it was my fault for not checking my assumptions. :~ :(

    To err is human. Fortune favors the monsters.

    P L C P 4 Replies Last reply
    0
    • honey the codewitchH honey the codewitch

      #include template class foo {
      constexpr const static int pin = Pin;
      public:
      constexpr inline static char test() __attribute((always_inline)) {
      if(Pin==-1) {
      return 'A';
      } else {
      return 'B';
      }
      }
      static_assert(test()!=0,"test");
      };
      int main(int argc, char** argv) {
      // mov eax,65
      // movsx eax, al
      // mov esi, eax
      printf("%c\n",foo<-1>::test());
      // move esi, 65
      printf("%c\n",65);
      return 0;
      }

      I'd like someone smarter than I am to explain to me why the first printf does not generate a mov esi, 65 or even movsx esi, 65, but rather, 3 instructions that are seemingly redundant and yet don't get removed by the peephole optimizer, but I don't think that's going to happen. The worst part is, I have a dozen libraries using a bus framework I wrote that relies on my bad assumptions about the code that is generated. The upshot is the code is slow, and the only way to speed it up is to A) rewrite it to not use templates B) nix the ability to run multiple displays at once But IT SHOULD NOT BE THIS WAY. I feel misled by the C++ documentation. But it was my fault for not checking my assumptions. :~ :(

      To err is human. Fortune favors the monsters.

      P Offline
      P Offline
      PIEBALDconsult
      wrote on last edited by
      #2

      Is it because printf returns a value which has to be moved somewhere?

      honey the codewitchH 1 Reply Last reply
      0
      • P PIEBALDconsult

        Is it because printf returns a value which has to be moved somewhere?

        honey the codewitchH Offline
        honey the codewitchH Offline
        honey the codewitch
        wrote on last edited by
        #3

        Nah, the printf is just forcing the code that I need to be put inside the binary. That code executes before printf is called, and the register printf uses for its parameter is esi Really, the second example generates the appropriate code mov esi, 65 which is all that is needed.

        To err is human. Fortune favors the monsters.

        1 Reply Last reply
        0
        • honey the codewitchH honey the codewitch

          #include template class foo {
          constexpr const static int pin = Pin;
          public:
          constexpr inline static char test() __attribute((always_inline)) {
          if(Pin==-1) {
          return 'A';
          } else {
          return 'B';
          }
          }
          static_assert(test()!=0,"test");
          };
          int main(int argc, char** argv) {
          // mov eax,65
          // movsx eax, al
          // mov esi, eax
          printf("%c\n",foo<-1>::test());
          // move esi, 65
          printf("%c\n",65);
          return 0;
          }

          I'd like someone smarter than I am to explain to me why the first printf does not generate a mov esi, 65 or even movsx esi, 65, but rather, 3 instructions that are seemingly redundant and yet don't get removed by the peephole optimizer, but I don't think that's going to happen. The worst part is, I have a dozen libraries using a bus framework I wrote that relies on my bad assumptions about the code that is generated. The upshot is the code is slow, and the only way to speed it up is to A) rewrite it to not use templates B) nix the ability to run multiple displays at once But IT SHOULD NOT BE THIS WAY. I feel misled by the C++ documentation. But it was my fault for not checking my assumptions. :~ :(

          To err is human. Fortune favors the monsters.

          L Offline
          L Offline
          Lost User
          wrote on last edited by
          #4

          This is defined in the language spec. https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf Page 71, paragraph 6 The code generator uses the movsx instruction to sign extend because of the ellipsis. int printf ( const char * format, ... );

          honey the codewitchH 1 Reply Last reply
          0
          • L Lost User

            This is defined in the language spec. https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf Page 71, paragraph 6 The code generator uses the movsx instruction to sign extend because of the ellipsis. int printf ( const char * format, ... );

            honey the codewitchH Offline
            honey the codewitchH Offline
            honey the codewitch
            wrote on last edited by
            #5

            Yeah, that's not really the issue I'm having though. I guess I wasn't clear. Let me see if I can explain it better. See, if I put a constant in as per the second example of calling printf, it simply does mov esi, 65

            int main(int argc, char** argv) {
            constexpr const char a = 'A';
            // mov eax,65
            // movsx eax, al
            // mov esi, eax
            printf("%c\n",foo<-1>::test());
            // mov esi, 65
            printf("%c\n",a);
            return 0;
            }

            That's what I'd think it should do in the first example as well, printf or no. It's using eax in the intermediary for reasons I can't fathom, and only when I call a forced inline function that should resolve to a compile time constant - and indeed *it does!* but it still generates extra instructions around it (fiddling with eax and al) To bottom line it, why is the first example generating more code than the second example?

            To err is human. Fortune favors the monsters.

            L 1 Reply Last reply
            0
            • honey the codewitchH honey the codewitch

              Yeah, that's not really the issue I'm having though. I guess I wasn't clear. Let me see if I can explain it better. See, if I put a constant in as per the second example of calling printf, it simply does mov esi, 65

              int main(int argc, char** argv) {
              constexpr const char a = 'A';
              // mov eax,65
              // movsx eax, al
              // mov esi, eax
              printf("%c\n",foo<-1>::test());
              // mov esi, 65
              printf("%c\n",a);
              return 0;
              }

              That's what I'd think it should do in the first example as well, printf or no. It's using eax in the intermediary for reasons I can't fathom, and only when I call a forced inline function that should resolve to a compile time constant - and indeed *it does!* but it still generates extra instructions around it (fiddling with eax and al) To bottom line it, why is the first example generating more code than the second example?

              To err is human. Fortune favors the monsters.

              L Offline
              L Offline
              Lost User
              wrote on last edited by
              #6

              honey the codewitch wrote:

              Yeah, that's not really the issue I'm having though.

              :laugh: That's why the code there is being generated. It's promoting the char to 32 bits. The language spec calls it "default argument promotion" I have nothing more to add. Good luck

              honey the codewitchH L 2 Replies Last reply
              0
              • L Lost User

                honey the codewitch wrote:

                Yeah, that's not really the issue I'm having though.

                :laugh: That's why the code there is being generated. It's promoting the char to 32 bits. The language spec calls it "default argument promotion" I have nothing more to add. Good luck

                honey the codewitchH Offline
                honey the codewitchH Offline
                honey the codewitch
                wrote on last edited by
                #7

                But why is it only promoting it in one case? Sorry, you don't have to answer. I know you said you have nothing left to add. It's just I'm still confused. :confused:

                To err is human. Fortune favors the monsters.

                L D 2 Replies Last reply
                0
                • honey the codewitchH honey the codewitch

                  But why is it only promoting it in one case? Sorry, you don't have to answer. I know you said you have nothing left to add. It's just I'm still confused. :confused:

                  To err is human. Fortune favors the monsters.

                  L Offline
                  L Offline
                  Lost User
                  wrote on last edited by
                  #8

                  No clue, you haven't even mentioned what compiler you are using. I can only point to the language standard documents. All I can say is that it's [well documented](https://www.google.com/search?q="default+argument+promotion"). Read through the language spec, this was changed in C++11 and maybe the later specs, I don't feel like looking for you right now.

                  honey the codewitchH 2 Replies Last reply
                  0
                  • honey the codewitchH honey the codewitch

                    But why is it only promoting it in one case? Sorry, you don't have to answer. I know you said you have nothing left to add. It's just I'm still confused. :confused:

                    To err is human. Fortune favors the monsters.

                    D Offline
                    D Offline
                    Daniel Pfeffer
                    wrote on last edited by
                    #9

                    honey the codewitch wrote:

                    But why is it only promoting it in one case?

                    Perhaps because 65 is already an int? Try this with printf("%c\n", 'A') and see what happens. (Yes, a clever enough compiler could optimize the first assembly-language sequence to mov esi,65. Obviously, optimizers still have a way to go...)

                    Freedom is the freedom to say that two plus two make four. If that is granted, all else follows. -- 6079 Smith W.

                    honey the codewitchH 1 Reply Last reply
                    0
                    • D Daniel Pfeffer

                      honey the codewitch wrote:

                      But why is it only promoting it in one case?

                      Perhaps because 65 is already an int? Try this with printf("%c\n", 'A') and see what happens. (Yes, a clever enough compiler could optimize the first assembly-language sequence to mov esi,65. Obviously, optimizers still have a way to go...)

                      Freedom is the freedom to say that two plus two make four. If that is granted, all else follows. -- 6079 Smith W.

                      honey the codewitchH Offline
                      honey the codewitchH Offline
                      honey the codewitch
                      wrote on last edited by
                      #10

                      Same result. *scratches head*

                      To err is human. Fortune favors the monsters.

                      1 Reply Last reply
                      0
                      • L Lost User

                        No clue, you haven't even mentioned what compiler you are using. I can only point to the language standard documents. All I can say is that it's [well documented](https://www.google.com/search?q="default+argument+promotion"). Read through the language spec, this was changed in C++11 and maybe the later specs, I don't feel like looking for you right now.

                        honey the codewitchH Offline
                        honey the codewitchH Offline
                        honey the codewitch
                        wrote on last edited by
                        #11

                        Tried on clang x86, gcc x86, gcc xtensa, gcc AVR.

                        To err is human. Fortune favors the monsters.

                        K CPalliniC 2 Replies Last reply
                        0
                        • L Lost User

                          No clue, you haven't even mentioned what compiler you are using. I can only point to the language standard documents. All I can say is that it's [well documented](https://www.google.com/search?q="default+argument+promotion"). Read through the language spec, this was changed in C++11 and maybe the later specs, I don't feel like looking for you right now.

                          honey the codewitchH Offline
                          honey the codewitchH Offline
                          honey the codewitch
                          wrote on last edited by
                          #12

                          I get that promotion is a thing. Again what I don't get is why it's only doing it in one case. The thing is, all the documentation you've pointed me to suggests it should be doing the same thing in both cases. But whatever, it doesn't matter because you're obviously tired of this.

                          To err is human. Fortune favors the monsters.

                          L 1 Reply Last reply
                          0
                          • honey the codewitchH honey the codewitch

                            I get that promotion is a thing. Again what I don't get is why it's only doing it in one case. The thing is, all the documentation you've pointed me to suggests it should be doing the same thing in both cases. But whatever, it doesn't matter because you're obviously tired of this.

                            To err is human. Fortune favors the monsters.

                            L Offline
                            L Offline
                            Lost User
                            wrote on last edited by
                            #13

                            i think you should see the rule at [C++11 7.16.1.4 paragraph 4](https://www.google.com/search?q="7.16.1.4"+c%2B%2B11) You are passing a function as an argument to a variadic function. That falls under the 'undefined behavior' description. This is why you get the default argument promotion. As I said, just look it up and read the language spec.

                            honey the codewitch wrote:

                            The thing is, all the documentation you've pointed me to suggests it should be doing the same thing in both cases.

                            Well, there are two things you need to look at - The rules of default argument promotion. - The rules of passing arguments to variadic functions. The spec clearly says that passing a function as an argument to a variadic function is undefined behavior. This is likely why you see the 'default argument promotion'. It also mentions addressable object types and register storage.

                            printf("%c\n",65);

                            This is passed in a register, this is defined behavior. ...

                            printf("%c\n",foo<-1>::test());

                            This is passed as a function. C11 7.16.1.4 paragraph 4 says this is undefined behavior. I do see a [proposal for C23 in the pipeline](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2975.pdf) but it hasn't been voted on yet as far as I can tell. I was wrong, looks like N2975 passed with 17 Yes, 0 No and two abstains. You still haven't said what language version you are using or compiler.

                            honey the codewitchH 1 Reply Last reply
                            0
                            • L Lost User

                              i think you should see the rule at [C++11 7.16.1.4 paragraph 4](https://www.google.com/search?q="7.16.1.4"+c%2B%2B11) You are passing a function as an argument to a variadic function. That falls under the 'undefined behavior' description. This is why you get the default argument promotion. As I said, just look it up and read the language spec.

                              honey the codewitch wrote:

                              The thing is, all the documentation you've pointed me to suggests it should be doing the same thing in both cases.

                              Well, there are two things you need to look at - The rules of default argument promotion. - The rules of passing arguments to variadic functions. The spec clearly says that passing a function as an argument to a variadic function is undefined behavior. This is likely why you see the 'default argument promotion'. It also mentions addressable object types and register storage.

                              printf("%c\n",65);

                              This is passed in a register, this is defined behavior. ...

                              printf("%c\n",foo<-1>::test());

                              This is passed as a function. C11 7.16.1.4 paragraph 4 says this is undefined behavior. I do see a [proposal for C23 in the pipeline](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2975.pdf) but it hasn't been voted on yet as far as I can tell. I was wrong, looks like N2975 passed with 17 Yes, 0 No and two abstains. You still haven't said what language version you are using or compiler.

                              honey the codewitchH Offline
                              honey the codewitchH Offline
                              honey the codewitch
                              wrote on last edited by
                              #14

                              I'm using C++17 gcc 12.2 or whatever. relatively recent i think. i've tried it on several platforms. clearly you're better at poring over that stuff than I am. It reads like japanese stereo instructions to me, and I get lost pretty easily. I didn't realize taking the return value of a function to a function that takes variadic arguments is undefined behavior. That's very weird to me, as I would have thought it would simply evaluate the function and then stick the return value in the register. Particularly since this is a constexpr function that's const all the way through I figured it would be optimized out. And it is kind of, in that it never calls anything. Anyway, thanks. I'll leave it there, as you seem impatient. Sorry to bother you.

                              To err is human. Fortune favors the monsters.

                              L 2 Replies Last reply
                              0
                              • honey the codewitchH honey the codewitch

                                I'm using C++17 gcc 12.2 or whatever. relatively recent i think. i've tried it on several platforms. clearly you're better at poring over that stuff than I am. It reads like japanese stereo instructions to me, and I get lost pretty easily. I didn't realize taking the return value of a function to a function that takes variadic arguments is undefined behavior. That's very weird to me, as I would have thought it would simply evaluate the function and then stick the return value in the register. Particularly since this is a constexpr function that's const all the way through I figured it would be optimized out. And it is kind of, in that it never calls anything. Anyway, thanks. I'll leave it there, as you seem impatient. Sorry to bother you.

                                To err is human. Fortune favors the monsters.

                                L Offline
                                L Offline
                                Lost User
                                wrote on last edited by
                                #15

                                I'm not on a PC tonight, it takes longer to type on my TV onscreen keyboard. It's not easy! :sigh: Anyways, I found some better material for you to read. [Variadic arguments - cppreference.com](https://en.cppreference.com/w/cpp/language/variadic\_arguments#Default\_conversions)

                                honey the codewitchH 1 Reply Last reply
                                0
                                • honey the codewitchH honey the codewitch

                                  I'm using C++17 gcc 12.2 or whatever. relatively recent i think. i've tried it on several platforms. clearly you're better at poring over that stuff than I am. It reads like japanese stereo instructions to me, and I get lost pretty easily. I didn't realize taking the return value of a function to a function that takes variadic arguments is undefined behavior. That's very weird to me, as I would have thought it would simply evaluate the function and then stick the return value in the register. Particularly since this is a constexpr function that's const all the way through I figured it would be optimized out. And it is kind of, in that it never calls anything. Anyway, thanks. I'll leave it there, as you seem impatient. Sorry to bother you.

                                  To err is human. Fortune favors the monsters.

                                  L Offline
                                  L Offline
                                  Lost User
                                  wrote on last edited by
                                  #16

                                  Sorry, I was [wrong, about N2975](https://www.open-std.org/jtc1/sc22/WG14/www/docs/n3044.txt). It passed back in July with 17 Yes, 0 No and two abstains. I updated my post.

                                  1 Reply Last reply
                                  0
                                  • honey the codewitchH honey the codewitch

                                    Tried on clang x86, gcc x86, gcc xtensa, gcc AVR.

                                    To err is human. Fortune favors the monsters.

                                    K Offline
                                    K Offline
                                    k5054
                                    wrote on last edited by
                                    #17

                                    What release of gcc/clang are you using? According to [Compiler Explorer](https://godbolt.org/) I get the following with clang 5.0 with -O1 -std=C++17:

                                    main: # @main
                                    push rax
                                    mov edi, .L.str
                                    mov esi, 65
                                    xor eax, eax
                                    call printf
                                    mov edi, .L.str
                                    mov esi, 65
                                    xor eax, eax
                                    call printf
                                    xor eax, eax
                                    pop rcx
                                    ret
                                    .L.str:
                                    .asciz "%c\n"

                                    And x86-64 gcc 5.1 with the same flags gives:

                                    .LC0:
                                    .string "%c\n"
                                    main:
                                    sub rsp, 8
                                    mov esi, 65
                                    mov edi, OFFSET FLAT:.LC0
                                    mov eax, 0
                                    call printf
                                    mov esi, 65
                                    mov edi, OFFSET FLAT:.LC0
                                    mov eax, 0
                                    call printf
                                    mov eax, 0
                                    add rsp, 8
                                    ret

                                    Those are both pretty old compilers - the first of their lines to support C++17 AFAICT. Both produce the same code for each call. So maybe something in the compiler flags you're passing?

                                    Keep Calm and Carry On

                                    honey the codewitchH 1 Reply Last reply
                                    0
                                    • K k5054

                                      What release of gcc/clang are you using? According to [Compiler Explorer](https://godbolt.org/) I get the following with clang 5.0 with -O1 -std=C++17:

                                      main: # @main
                                      push rax
                                      mov edi, .L.str
                                      mov esi, 65
                                      xor eax, eax
                                      call printf
                                      mov edi, .L.str
                                      mov esi, 65
                                      xor eax, eax
                                      call printf
                                      xor eax, eax
                                      pop rcx
                                      ret
                                      .L.str:
                                      .asciz "%c\n"

                                      And x86-64 gcc 5.1 with the same flags gives:

                                      .LC0:
                                      .string "%c\n"
                                      main:
                                      sub rsp, 8
                                      mov esi, 65
                                      mov edi, OFFSET FLAT:.LC0
                                      mov eax, 0
                                      call printf
                                      mov esi, 65
                                      mov edi, OFFSET FLAT:.LC0
                                      mov eax, 0
                                      call printf
                                      mov eax, 0
                                      add rsp, 8
                                      ret

                                      Those are both pretty old compilers - the first of their lines to support C++17 AFAICT. Both produce the same code for each call. So maybe something in the compiler flags you're passing?

                                      Keep Calm and Carry On

                                      honey the codewitchH Offline
                                      honey the codewitchH Offline
                                      honey the codewitch
                                      wrote on last edited by
                                      #18

                                      It probably has to do with the fact that I can't convince godbolt.org to allow me to remove their default compiler options and replace them with my own. I'm stuck with -o -whole-program or whatever. I used to be able to change it there somehow. Maybe someone exploited it and they turned off the feature.

                                      To err is human. Fortune favors the monsters.

                                      K 1 Reply Last reply
                                      0
                                      • L Lost User

                                        I'm not on a PC tonight, it takes longer to type on my TV onscreen keyboard. It's not easy! :sigh: Anyways, I found some better material for you to read. [Variadic arguments - cppreference.com](https://en.cppreference.com/w/cpp/language/variadic\_arguments#Default\_conversions)

                                        honey the codewitchH Offline
                                        honey the codewitchH Offline
                                        honey the codewitch
                                        wrote on last edited by
                                        #19

                                        Well it's tomorrow. In case you're curious, I took the variadic arguments out of the code. I replaced printf with putchar. Same result.

                                            push    rbp
                                            mov     rbp, rsp
                                            sub     rsp, 16
                                            mov     DWORD PTR \[rbp-4\], edi
                                            mov     QWORD PTR \[rbp-16\], rsi
                                            mov     eax, 65 ; \*\*\*
                                            movsx   eax, al ; \*\*\*
                                            mov     edi, eax ;\*\*\*
                                            call    putchar
                                            mov     edi, 65  ;\*\*\*
                                            call    putchar
                                            mov     eax, 0
                                            leave
                                            ret
                                        

                                        To err is human. Fortune favors the monsters.

                                        L 1 Reply Last reply
                                        0
                                        • honey the codewitchH honey the codewitch

                                          It probably has to do with the fact that I can't convince godbolt.org to allow me to remove their default compiler options and replace them with my own. I'm stuck with -o -whole-program or whatever. I used to be able to change it there somehow. Maybe someone exploited it and they turned off the feature.

                                          To err is human. Fortune favors the monsters.

                                          K Offline
                                          K Offline
                                          k5054
                                          wrote on last edited by
                                          #20

                                          I get the same results using g++ 5.5.0 on my local linux box. That would be a CentOS 7 system on which I compiled g++-5.5.0 from source. So, still wondering if its maybe the flags you're using.

                                          Keep Calm and Carry On

                                          honey the codewitchH 1 Reply Last reply
                                          0
                                          Reply
                                          • Reply as topic
                                          Log in to reply
                                          • Oldest to Newest
                                          • Newest to Oldest
                                          • Most Votes


                                          • Login

                                          • Don't have an account? Register

                                          • Login or register to search.
                                          • First post
                                            Last post
                                          0
                                          • Categories
                                          • Recent
                                          • Tags
                                          • Popular
                                          • World
                                          • Users
                                          • Groups