6

I encountered something weird in the MSVC compiler.

it puts function template definition in assembly while optimization eliminates the need for them. It seems that Clang and GCC successfully remove function definition at all but MSVC does not.

Can it be fixed?

main.cpp:

#include <iostream>

template <int n> int value() noexcept
{
    return n;
}

int main()
{
    return value<5>() + value<10>();
}

assembly:

int value<5>(void) PROC                                ; value<5>, COMDAT
        mov     eax, 5
        ret     0
int value<5>(void) ENDP                                ; value<5>

int value<10>(void) PROC                                ; value<10>, COMDAT
        mov     eax, 10
        ret     0
int value<10>(void) ENDP                                ; value<10>

main    PROC                                            ; COMDAT
        mov     eax, 15
        ret     0
main    ENDP

Sample code on godbolt

Nima Ghorab
  • 309
  • 1
  • 3
  • 13

3 Answers3

7

The /FA switch generates the listing file for each translation unit. Since this is before the linking stage, MSVC does not determine if those two functions are required anywhere else within the program, and are thus still included in the generated .asm file (Note: this may be for simplicity on MS's part, since it can treat templates the same as regular functions in the generated .obj file, though realistically there's no actual need to store them in the .obj file, as user17732522 points out in the comments).

During linking, MSVC determines that those functions are in fact not actually used / needed anywhere else, and thus can be eliminated (even if they were used elsewhere, since the result can be determined at compile time, they'd still be eliminated) from the compiled executable.

In order to see what's in the final compiled executable, you can view the executable through a disassembler. Example for using MSVC to do this, is put a breakpoint in the main function, run it, then when the breakpoint is hit, right click and "View Disassembly". In this, you will see that the two functions don't exist anymore.

You can also generate the Mapfile using /MAP option, which also shows it does not exist.


If I am reading the documentation correctly, it seems as those MS chose to include explicit instantiations of templates classes and functions because it "is useful" when creating libraries. Uninstantiated templates are not put into the obj files though.

ChrisMM
  • 8,448
  • 13
  • 29
  • 48
  • Correct. This is done in the linker with the ``/OPT:REF`` switch. – Chuck Walbourn Apr 02 '22 at 19:11
  • But implicit template instantiations which aren't used in a object file (because they have been inlined) do not need to be emitted into the object file. They behave like `inline` functions and it is guaranteed that their definitions will be available in each other translation unit requiring it. The compiler could remove them already before the linking phase and thereby reduce time and storage required for the linking procedure. That's how GCC and Clang behave. – user17732522 Apr 02 '22 at 19:13
  • @user17732522, you're right, was thinking normal functions, as opposed to templates with the comment of not being able to remove them before linking. I'll try to rephrase. – ChrisMM Apr 02 '22 at 19:41
4

Just add /Zc:inline to your compile statement and it does the same thing as clang/GCC if you also wrap the template in an anonymous namespace to ensure it does not have external visibility.

#include <iostream>

namespace
{
    template <int n> int value() noexcept
    {
        return n;
    }
}

or if you mark the template function inline

template <int n> inline int value() noexcept
{
    return n;
}

Both result in:

main    PROC
        mov     eax, 15
        ret     0
main    ENDP

The /Zc:inline (Remove unreferenced COMDAT) switch was added in VS 2015 Update 2 as part of the C++11 Standard conformance which allows this optimization.

It is off-by-default in command-line builds. In MSBuild, <RemoveUnreferencedCodeData> defaults to true.

See Microsoft Docs

OTHERWISE It will be cleaned up in the linker phase with /OPT:REF.

Chuck Walbourn
  • 38,259
  • 2
  • 58
  • 81
  • I tested -Zc:inline but still definitions exist in main.asm! – Nima Ghorab Apr 02 '22 at 19:23
  • Interesting. The documentation talks only about `inline` functions, but presumably implicit template instantiations are implemented the same way since they effectively follow the same rules as `inline` functions. However as mentioned above this doesn't seem to work, at least on godbolt. – user17732522 Apr 02 '22 at 19:25
  • Sorry, I forgot the detail that it must have 'local' scope. – Chuck Walbourn Apr 02 '22 at 19:25
  • @ChuckWalbourn In your example the template has static linkage. Now there is obviously no reason to emit it since other translation units can't use the template at all. The question is about external linkage templates. – user17732522 Apr 02 '22 at 19:27
  • 1
    It works correctly if you make the template `inline`: https://godbolt.org/z/na4dPWWf9 So it really seems they apply this behavior only to inline functions, not implicit template instantiations, which is weird since `inline` has basically no effect on a function template by the standard specification. – user17732522 Apr 02 '22 at 19:29
  • I think the answer to the original question is: "No, it can't be 'fixed' because it's By Design" per [Microsoft Docs](https://learn.microsoft.com/en-us/cpp/cpp/function-template-instantiation). – Chuck Walbourn Apr 02 '22 at 19:36
1

I compiled your code as given on my vs2022 in release mode. I get

    return value<5>() + value<10>();
00007FF65CD21000  mov         eax,0Fh  
}
00007FF65CD21005  ret  
pm100
  • 48,078
  • 23
  • 82
  • 145