Simon Richter
Simon Richter

Reputation: 29586

Disable optimization on specific MSVC version

I have a project that uses CMake, and which fails to compile this code on VS 2015 due to a compiler bug (interaction between template resolution, optimization and exception handling).

The bug can be avoided by disabling optimization -- while that gives suboptimal code, at least the project compiles.

How can I change the default optimization level for MSVC 2015 to /O0 in Release and RelWithDebInfo builds?

My naïve implementation would be a conditional substitution in CMAKE_CXX_FLAGS -- is that future safe?

An alternative approach would be to use a #pragma in the offending header.

Upvotes: 3

Views: 2252

Answers (3)

Florian
Florian

Reputation: 42852

Just to complement the answers utilizing C++ based workarounds, here is an example for a CMake based implementation utilizing CMake's CHECK_CXX_SOURCE_COMPILES() macro:

cmake_minimum_required(VERSION 3.2)

include(CheckCXXSourceCompiles)

project(TestCompile CXX)

if (MSVC AND MSVC_VERSION GREATER 1899)
    set(CMAKE_TRY_COMPILE_CONFIGURATION "Release")
    CHECK_CXX_SOURCE_COMPILES(
        "
        #include <limits>

        template<typename T>
        struct one
        {
            typedef T type;
            T t1, t2;
        };

        template<typename T>
        struct two
        {
            T t;
            void func()
            {
                typedef typename T::type type;
                t.t1 = t.t2 = std::numeric_limits<type>::min() / 2 + std::numeric_limits<type>::epsilon();
            }
        };

        void main()
        {
            two<one<int> > obj;
            obj.func();
        }
        "
        CAN_LINK_MY_TEMPLATES_EXAMPLE
    )
    if (NOT CAN_LINK_MY_TEMPLATES_EXAMPLE)
        add_compile_options("/Od")
    endif()
endif()

I think you wouldn't have to changes the flags for specific build configurations, it would be enough to generally disable it with add_compile_options("/Od").

If you want to do it per configuration you could replace/remove the /O2 flags in the CMAKE_CXX_FLAGS_... variables (like here or here).

Upvotes: 0

JVene
JVene

Reputation: 1759

This compiler bug vexes the linker with garbage, as best as I can track down the problem (VS2015 is new, no patches/updates have been released as of this writing). The crash happens in the linker. The compiler seems to think all went well.

When this bug is "excited", the problem is within the code which uses the generated template object. Put another way, even if you disable optimization in the header, but turn optimization back on in the body, it still crashes the linker. What "works" is to disable optimization on code which instantiates and uses member functions of the template object (you can leave optimization on for all code outside this target).

For example, in the code posted in the question, leave optimizations on throughout the headers. On the function which USES the template, do:

#pragma optimize( "", off )


void test()
{
    two<one<int> > obj;
    obj.func();
}

#pragma optimize( "", on )

This isolates the loss of optimization to that code which ignites the problem, and the linker succeeds.

Of course, the pragma's themselves can be wrapped with conditional defines, or some other mechanism, which you can disable once a patch to VS2015 is released that fixes the problem.

In this way the code can be used without concern for build configurations (meaning, it would work for both CMAKE building and IDE building) without having to burden subsequent users of the code (with anything more than a define to control whether or not optimization is disabled).

In case it happens to be practicable for your situation, you could also try something like:

template<typename T>
struct two
{
    T t;

    void func()
    {
        typedef typename T::type type;

        type i = std::numeric_limits<type>::min();
        type j = std::numeric_limits<type>::epsilon();

        t.t1 = i / 2 + j;
        t.t2 = t.t1;
    }
};

This kind of restating the code bypasses the bug, and compiles without crashing the linker.

Also, this bypasses the problem, too:

   void func()
    {
        typedef typename T::type type;

        t.t1 = std::numeric_limits<type>::min() / 2 + std::numeric_limits<type>::epsilon();
        t.t2 = t.t1;
    }

Also, which is curious to me, and represents slightly better design (since it does not require one class to fiddle with the members of another)

template<typename T>
struct one
{
    typedef T type;

    T t1, t2;

    void set( const T & i ) { t1 = t2 = i; }
};



template<typename T>
struct two
{
    T t;

    void func()
    {
        typedef typename T::type type;

        t.set( std::numeric_limits<type>::min() / 2 + std::numeric_limits<type>::epsilon() );

    }
};

The good news being that there IS a solution that doesn't involve changing the project build rules, leaves optimization enabled and is effectively identical.

It appears the culprit is actually the t.t1 = t.t2 = ... clause. Why such an assignment triggers a linker crash, but equivalent expressions otherwise don't is a mystery for Microsoft to dive into, but really it appears the solution results in very slightly better looking code.

Upvotes: 1

Chaz
Chaz

Reputation: 339

This will isolate the #pragma for only Release and VS 2015

#if ( NDEBUG && _MSC_VER == 1900 )
#pragma optimize ("d", on) // same as /Od
#endif

Upvotes: 0

Related Questions