Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does -O2 or greater optimization in clang break this code?

I checked similar questions on the site, but I couldn't find anything that matches my scenario here. This is the code I'm trying to run (requires C++14):

#include <iostream>
#include <chrono>
#include <thread>
using namespace std;

class countdownTimer {
public:
    using duration_t = chrono::high_resolution_clock::duration;

    countdownTimer(duration_t duration) : duration{ duration }, paused{ true } {}

    countdownTimer(const countdownTimer&)               = default;
    countdownTimer(countdownTimer&&)                    = default;
    countdownTimer& operator=(countdownTimer&&)         = default;
    countdownTimer& operator=(const countdownTimer&)    = default;

    void start() noexcept {
        if (started) return;
        startTime = chrono::high_resolution_clock::now();
        endTime = startTime + duration;
        started = true;
        paused = false;
    }

    void pause() noexcept {
        if (paused || !started) return;
        pauseBegin = chrono::high_resolution_clock::now();
        paused = true;
    }

    void resume() noexcept {
        if (!paused || !started) return;
        auto pauseDuration = chrono::high_resolution_clock::now() - pauseBegin;
        startTime += pauseDuration;
        endTime += pauseDuration;
        paused = false;
    }

    double remainingSeconds() const noexcept {
        auto ret = double{ 0.0 };
        if (!started) ret = chrono::duration_cast<chrono::duration<double>>(duration).count();
        else if (paused) ret = chrono::duration_cast<chrono::duration<double>>(duration - (pauseBegin - startTime)).count();
        else ret = chrono::duration_cast<chrono::duration<double>>(duration - (chrono::high_resolution_clock::now() - startTime)).count();
        return (ret < 0.0) ? 0.0 : ret;
    }

    duration_t remainingTime() const noexcept {
        auto ret = duration_t{ 0ms };
        if (!started) ret = chrono::duration_cast<duration_t>(duration);
        else if (paused) ret = chrono::duration_cast<duration_t>(duration - (pauseBegin - startTime));
        else ret = chrono::duration_cast<duration_t>(duration - (chrono::high_resolution_clock::now() - startTime));
        return (ret < 0ms) ? 0ms : ret;
    }

    bool isPaused() const noexcept { return paused; }

    bool hasFinished() const noexcept { return remainingTime() == 0s; }

    void reset() noexcept {
        started = false;
        paused = true;
    }

private:
    chrono::high_resolution_clock::time_point startTime;
    chrono::high_resolution_clock::time_point endTime;
    chrono::high_resolution_clock::time_point pauseBegin;
    duration_t duration;
    bool paused;
    bool started;
};

int main() {
    countdownTimer timer(10s);
    timer.start();

    while (!timer.hasFinished()) {
        cout << timer.remainingSeconds() << endl;
        this_thread::sleep_for(1s);
    }
}

It's a simple countdown timer class that I wrote for one of my projects. The client code in main() is pretty self-explanatory, it should output a countdown from 10 to 0, and then exit the program. With no optimization or -O/-O1, it does exactly that:

10
8.99495
7.98992
6.9849
5.97981
4.9748
3.96973
2.9687
1.9677
0.966752
Program ended with exit code: 0

But if I step up the optimization to >=-O2, the program just keeps outputting 10, and runs forever. The countdown simply doesn't work, it's stuck at the starting value.

I'm using the latest Xcode on OS X. clang --version says Apple LLVM version 7.3.0 (clang-703.0.31).

The strange part is that my code doesn't contain any weird self-written loops, undefined behavior, or anything like that, it's pretty much just standard library calls, so it's very strange that optimization breaks it.

Any ideas?

PS: I haven't tried it on other compilers, but I'm about to. I'll update the question with those results.

like image 254
notadam Avatar asked Aug 06 '16 14:08

notadam


People also ask

Do you need to be a compiler-writing Chain Gang member to optimize Clang?

In this blog post, I want to show that you don’t need to be a compiler-writing chain gang member to understand the optimization possibilities of Clang. My vision is to demystify clang optimization flags so that you’ll be able to make best use of them and use different Clang optimization flags.

Why demystify Clang optimization Flags?

My vision is to demystify clang optimization flags so that you’ll be able to make best use of them and use different Clang optimization flags. The post would use Clang in Windows environment (yes, Clang supports Windows compilations as mentioned in my previous blogs mentioned above).

What are the 10 reasons for code optimization?

10 Reasons Why You Need Code Optimization. 1 1. Cleaner Code Base. As a project matures, and more and more developers start to work on it, duplications and overlaps usually sooner or later ... 2 2. Higher Consistency. 3 3. Faster Sites. 4 4. Better Code Readability. 5 5. More Efficient Refactoring. More items

What is the difference between Clang and C++ compiler?

Clang compiler silently compiles it and creates an executable a.exe by default. Let us do a quick side-by-side comparison of the behavior of clang against the behavior of Microsoft C++ compiler cl. C:\Work\Temp>cl Example1.cpp Microsoft (R) C/C++ Optimizing Compiler Version 19.27.29111 for x86 Copyright (C) Microsoft Corporation.


2 Answers

bool started is not initialized. If you initialize it to false, it works with -O2:

live example

You can find errors like this using the Undefined behavior sanitizer:

$ g++ -std=c++14 -O2 -g -fsanitize=undefined -fno-omit-frame-pointer main.cpp && ./a.out

main.cpp:18:9: runtime error: load of value 106, which is not a valid value for type 'bool'
like image 111
m.s. Avatar answered Oct 24 '22 05:10

m.s.


The bug is in your constructor:

 countdownTimer(duration_t duration)
 : duration{ duration }, paused{ true } {}

You forgot to initialize started. This triggers undefined behavior when you call start().

No version of clang that I have convenient access to will diagnose this error, but GCC versions 5 and 6 (on Linux - I don't have GCC on my Mac anymore) will:

$ g++ -O2 -Wall -Wextra -std=c++14 test.cc
test.cc: In function ‘int main()’:
test.cc:18:13: warning: ‘*((void*)& timer +33)’ is used uninitialized in this function [-Wuninitialized]
         if (started) return;
             ^~~~~~~
test.cc:74:20: note: ‘*((void*)& timer +33)’ was declared here
     countdownTimer timer(10s);
                    ^~~~~

(My copy of Xcode seems to be a bit out of date, with Apple LLVM version 7.0.2 (clang-700.1.81); it does not change the behavior of the program at -O2. It's possible that your clang would diagnose this error if you turned on the warnings.)

(I have filed a bug report with GCC about the IR gobbledygook in the diagnostics.)

like image 32
zwol Avatar answered Oct 24 '22 05:10

zwol