25

I expect the following program to return 0 all of the time. However with Visual Studio 2013 (Update 4), the program exits 1 in release builds. I'm not sure if this is a bug or if the compiler's optimizer is correct and is relying on some edge behavior. If the CONST macro is turned off, the release exe returns 0. If the optimizer is indeed correct, could I get the reason why it is allowed to emit the code it does?

#if 1
#   define CONST const
#else
#   define CONST
#endif


class TypeId {
public:
    bool operator== (TypeId const & other) const
    {
        return id == other.id;
    }

private:
    TypeId (void const * id)
        : id(id)
    {}

public:
    template <typename T>
    static TypeId Get ()
    {
        static char CONST uniqueMemLoc = 0;
        return TypeId(&uniqueMemLoc);
    }

private:
    void const * id;
};


int main(int, char **)
{
    typedef int A;
    typedef unsigned int B;

    if (TypeId::Get<A>() == TypeId::Get<B>()) {
        return 1;
    }
    return 0;
}
usr
  • 168,620
  • 35
  • 240
  • 369
Thomas Eding
  • 35,312
  • 13
  • 75
  • 106
  • 1
    I think optimizer is incorrect. – Neil Kirk Mar 15 '15 at 03:31
  • 1
    Probably something to do with COMDAT folding in MSVC. – T.C. Mar 15 '15 at 03:35
  • 1
    Yep, `/opt:noicf` fixes it. – T.C. Mar 15 '15 at 03:41
  • 1
    This does not seem valid to me and as @T.C. points out this looks like a folding issue, perhaps it is related to [Do distinct functions have distinct addresses?](http://stackoverflow.com/q/26533740/1708801) which is still an open question. – Shafik Yaghmour Mar 15 '15 at 04:13
  • does /Za imply /opt:noicf? (if not then perhaps it should) – M.M Mar 15 '15 at 04:30
  • 4
    @ShafikYaghmour the [MSDN documentation](https://msdn.microsoft.com/en-us/library/bxwfs976.aspx) seems to imply that it is intentional: "Because /OPT:ICF can cause the same address to be assigned to different functions or read-only data members (const variables compiled by using /Gy), it can break a program that depends on unique addresses for functions or read-only data members. For more information, see /Gy (Enable Function-Level Linking)." – M.M Mar 15 '15 at 04:30
  • @MattMcNabb perhaps you are correct, it would be nice to get a confirmation that it is indeed expected behavior. – Shafik Yaghmour Mar 15 '15 at 04:37
  • This optimization is opt-in because according to an MS blog "it broke an important internal customer". That customer was the CLR. They were using a dummy static variable to ensure that function pointers to identical/empty functions are unique. This is nasty. Don't do this. – usr Mar 15 '15 at 11:14
  • @usr is it possible to link to that entry? – Shafik Yaghmour Mar 15 '15 at 13:40
  • @ShafikYaghmour how much more "confirmation" do you need than the MSDN documentation saying that this is what the flag *does*? – jalf Mar 15 '15 at 14:02
  • 1
    @ShafikYaghmour http://blogs.msdn.com/b/vcblog/archive/2013/09/11/introducing-gw-compiler-switch.aspx – usr Mar 15 '15 at 14:09
  • @jalf I personally don't think it is obvious that the documentation means *non-conforming* but since the strong consensus is that this is indeed what it means then I will accept that. – Shafik Yaghmour Mar 15 '15 at 16:29
  • @MattMcNabb: The linker can't fold what aren't in individual COMDATs. So I do expect `/Za` to avoid the broken behavior, regardless of whether it sets `/OPT:NOICF` (which is only important is `/OPT:REF` is specified, otherwise the default of no `/OPT` options will have folding disabled as well). – Ben Voigt Mar 15 '15 at 18:23
  • @ShafikYaghmour For what it's worth, I agree with you in disagreeing with the strong consensus. I think this is worth filing as a bug. – bogdan Mar 15 '15 at 19:02
  • 3
    @usr wait a second, I just fully read the article and it said `Please note, the ICF optimization will only be applied for identical COMDATs where their address is not taken` which indicates it does not apply to this case. – Shafik Yaghmour Mar 16 '15 at 03:46
  • @ShafikYaghmour that makes more sense. Under that clause this would be a conforming optimization. – usr Mar 16 '15 at 10:03

2 Answers2

15

This does not seem like a valid optimization according to the draft C++11 standard section 14.8 [temp.fct.spec] says (emphasis mine going forward):

Each function template specialization instantiated from a template has its own copy of any static variable. [ Example:

template<class T> void f(T* p) {
static T s;
};
void g(int a, char* b) {
    f(&a); // calls f<int>(int*)
    f(&b); // calls f<char*>(char**)
}

Here f(int*) has a static variable s of type int and f<char*>(char**) has a static variable s of type char*. —end example ]

Since your taking the address of the variable folding them effects observable behavior which would violate the as-if rule.

T.C. points out that /opt:noicf prevents the non-conforming behavior.

Matt McNabb points out that the /OPT (Optimizations) documentation contains the following note:

Because /OPT:ICF can cause the same address to be assigned to different functions or read-only data members (const variables compiled by using /Gy), it can break a program that depends on unique addresses for functions or read-only data members. For more information, see /Gy (Enable Function-Level Linking).

Which suggests this could be intentional non-conforming behavior. Ben Voigt says in a comment now moved to chat that this indeed means the optimizations can be non-conforming but this points is debatable.

User usr linked to an MS blog post: Introducing ‘/Gw’ Compiler Switch and it says:

Please note, the ICF optimization will only be applied for identical COMDATs where their address is not taken, and they are read only. If a data is not address taken, then breaking address uniqueness by ICF won't lead to any observable difference, thus it is valid and conformant to the standard.

and a later comment says:

Even though it's on it's own completely standards complaint, when combined with /Gy potentially breaking behavior can result.

From what I can tell in order for /Gy to effect const variables __declspec(selectany) has to be used but it could be clearer in the documentation.

At minimum we can see that /Gw should not introduce non-conforming behavior but /Gy in combination with /Gw may.

Community
  • 1
  • 1
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackoverflow.com/rooms/73053/discussion-on-answer-by-shafik-yaghmour-is-visual-studio-2013-optimizing-correct). – Flexo Mar 16 '15 at 07:42
7

No, this optimization does not conform to the C++ standard. The declaration of uniqueMemLoc defines a unique object for each instance of the template, and each object has its own address.

(If you had used a string literal, it would be a different story. The optimization would be valid in that case.)

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421