2017-01-11 6 views
4

Wenn einige Codes mit Klirren 3.9.1 und Optimierungen kompilieren (-O2) Ich lief in einig unerwartetes Verhalten zur Laufzeit, die ich nicht mit anderen Compilern (Klirren 3.8 und gcc 6.3) gesehen.Mapping C++ zu Montage

Ich dachte, ich könnte ein unbeabsichtigtes undefiniertes Verhalten haben (das Kompilieren mit Ubsan entfernt das unerwartete Verhalten), also habe ich versucht, das Programm zu vereinfachen und festgestellt, dass eine bestimmte Funktion die Unterschiede im Verhalten verursacht.

Nun, ich Abbilden der Baugruppe c zurück ++, um zu sehen, wo es schief geht, um zu versuchen und herauszufinden, warum dies geschieht und es gibt ein paar Teile ich Schwierigkeiten Mapping zurück habe.

Godbolt link

C++:

#include <atomic> 
#include <cstdint> 
#include <cstdlib> 
#include <thread> 
#include <cstdio> 

enum class FooState { A, B }; 

struct Foo { 
    std::atomic<std::int64_t> counter{0}; 
    std::atomic<std::int64_t> counter_a{0}; 
    std::atomic<std::int64_t> counter_b{0}; 
}; 

//__attribute__((noinline)) 
FooState to_state(const std::int64_t c) { 
    return c >= 0 ? FooState::A : FooState::B; 
} 

static const int NUM_MODIFIES = 100; 

int value_a = 0, value_b = 0; 
Foo foo; 
std::atomic<std::int64_t> total_sum{0}; 

void test_function() { 
    bool done = false; 
    while (!done) { 
    const std::int64_t count = 
     foo.counter.fetch_add(1, std::memory_order_seq_cst); 
    const FooState state = to_state(count); 

    int &val = FooState::A == state ? value_a : value_b; 
    if (val == NUM_MODIFIES) { 
     total_sum += val; 
     done = true; 
    } 

    std::atomic<std::int64_t> &c = 
     FooState::A == state ? foo.counter_a : foo.counter_b; 
    c.fetch_add(1, std::memory_order_seq_cst); 
    } 
} 

Montage:

test_function():      # @test_function() 
     test rax, rax 
     setns al 
     lock 
     inc  qword ptr [rip + foo] 
     mov  ecx, value_a 
     mov  edx, value_b 
     cmovg rdx, rcx 
     cmp  dword ptr [rdx], 100 
     je  .LBB1_3 
     mov  ecx, foo+8 
     mov  edx, value_a 
.LBB1_2:        # =>This Inner Loop Header: Depth=1 
     test al, 1 
     mov  eax, foo+16 
     cmovne rax, rcx 
     lock 
     inc  qword ptr [rax] 
     test rax, rax 
     setns al 
     lock 
     inc  qword ptr [rip + foo] 
     mov  esi, value_b 
     cmovg rsi, rdx 
     cmp  dword ptr [rsi], 100 
     jne  .LBB1_2 
.LBB1_3: 
     lock 
     add  qword ptr [rip + total_sum], 100 
     test al, al 
     mov  eax, foo+8 
     mov  ecx, foo+16 
     cmovne rcx, rax 
     lock 
     inc  qword ptr [rcx] 
     ret 

ich gefunden habe, dass to_state als noinline Markierung oder das Ändern done global zu sein scheint das unerwartete Verhalten zu "reparieren".

Das unerwartete Verhalten, das ich gesehen habe ist, dass, wenn der Zähler ist> = 0, dann sollte counter_a sonst erhöht wird counter_b erhöht werden soll. Von dem, was ich manchmal sagen kann, passiert das nicht, sondern genau zu bestimmen, wann/warum es schwierig war.

Ein Teil der Baugruppe, die ich verwenden könnte, sind die test rax, rax; setns al und die test al, 1 Teile. Es sieht so aus, als würde der Anfangstest al nicht deterministisch setzen und dann wird dieser Wert verwendet, um zu bestimmen, welcher Zähler erhöht werden soll, aber vielleicht verstehe ich etwas falsch.

Unten ist ein eher kleines Beispiel dieses Problem zu demonstrieren. Es bleibt normalerweise für immer hängen, wenn es mit clang 3.9 und -O2 kompiliert wird und läuft sonst bis zum Abschluss.

#include <atomic> 
#include <cstdint> 
#include <cstdlib> 
#include <thread> 
#include <cstdio> 

enum class FooState { A, B }; 

struct Foo { 
    std::atomic<std::int64_t> counter{0}; 
    std::atomic<std::int64_t> counter_a{0}; 
    std::atomic<std::int64_t> counter_b{0}; 
}; 

//__attribute__((noinline)) 
FooState to_state(const std::int64_t c) { 
    return c >= 0 ? FooState::A : FooState::B; 
} 

//__attribute__((noinline)) 
FooState to_state2(const std::int64_t c) { 
    return c >= 0 ? FooState::A : FooState::B; 
} 

static const int NUM_MODIFIES = 100; 

int value_a = 0, value_b = 0; 
Foo foo; 
std::atomic<std::int64_t> total_sum{0}; 

void test_function() { 
    bool done = false; 
    while (!done) { 
    const std::int64_t count = 
     foo.counter.fetch_add(1, std::memory_order_seq_cst); 
    const FooState state = to_state(count); 

    int &val = FooState::A == state ? value_a : value_b; 
    if (val == NUM_MODIFIES) { 
     total_sum += val; 
     done = true; 
    } 

    std::atomic<std::int64_t> &c = 
     FooState::A == state ? foo.counter_a : foo.counter_b; 
    c.fetch_add(1, std::memory_order_seq_cst); 
    } 
} 

int main() { 
    std::thread thread = std::thread(test_function); 

    for (std::size_t i = 0; i <= NUM_MODIFIES; ++i) { 
    const std::int64_t count = 
     foo.counter.load(std::memory_order_seq_cst); 
    const FooState state = to_state2(count); 

    unsigned log_count = 0; 

    auto &inactive_val = FooState::A == state ? value_b : value_a; 
    inactive_val = i; 

    if (FooState::A == state) { 
     foo.counter_b.store(0, std::memory_order_seq_cst); 
     const auto accesses_to_wait_for = 
      foo.counter.exchange((std::numeric_limits<std::int64_t>::min)(), 
           std::memory_order_seq_cst); 
     while (accesses_to_wait_for != 
      foo.counter_a.load(std::memory_order_seq_cst)) { 
     std::this_thread::yield(); 

     if(++log_count <= 10) { 
      std::printf("#1 wait_for=%ld, val=%ld\n", accesses_to_wait_for, 
      foo.counter_a.load(std::memory_order_seq_cst)); 
     } 
     } 
    } else { 
     foo.counter_a.store(0, std::memory_order_seq_cst); 

     auto temp = foo.counter.exchange(0, std::memory_order_seq_cst); 
     std::int64_t accesses_to_wait_for = 0; 
     while (temp != INT64_MIN) { 
     ++accesses_to_wait_for; 
     --temp; 
     } 

     while (accesses_to_wait_for != 
      foo.counter_b.load(std::memory_order_seq_cst)) { 
     std::this_thread::yield(); 

     if (++log_count <= 10) { 
      std::printf("#2 wait_for=%ld, val=%ld\n", accesses_to_wait_for, 
      foo.counter_b.load(std::memory_order_seq_cst)); 
     } 
     } 
    } 

    std::printf("modify #%lu complete\n", i); 
    } 

    std::printf("modifies complete\n"); 

    thread.join(); 

    const std::size_t expected_result = NUM_MODIFIES; 
    std::printf("%s\n", total_sum == expected_result ? "ok" : "fail"); 
} 
+0

Warum betrachten Sie Assemblersprache Debugcode? Erstelle mvce und benutze einen Debugger? –

+4

Sie sagen immer "unerwartetes Verhalten" *, aber ich bin immer noch unsicher, welches Verhalten Sie nicht erwarten? Kannst du das bitte klären? – UnholySheep

+0

@UnholySheep Tut mir leid. Ich habe den Beitrag mit weiteren Informationen aktualisiert. – CTT

Antwort

1

Ich bin nicht 100% sicher (habe es nicht debuggen, nur Simulation in Kopf), aber ich denke, die beide Paare test rax,rax + setns al etwas falsch testen.

Ergebnis erster hängt davon ab, ob rax < 0 auf die Funktion (UB) aufrufen, und die andere Test Inneren Schleife (in rax Test 32b address => SF = 0 =>al = 1) immer "NS" sein, so behoben al == 1 für die verbleibenden Schleifen wählt immer die counter_a.

Nun lese ich Ihre Frage, und Sie haben den gleichen Verdacht (ich nur zuerst einen Blick auf Code nehmen tat).

+0

Ja, das war der Teil, bei dem ich auch misstrauisch war. Siehst du irgendwo im Code, dass dies möglicherweise generiert wird? – CTT

+0

@CTT nicht wirklich. Wenn es "Zustand" selbst wäre, würde ich die entgegengesetzte Logik erwarten (enum sei A = 0, B = 1, während "al" stattdessen auf 1/0 gesetzt wird). Ich dachte, dass es Teil sein kann "nach Erhalt von Rückgabewert von to_state()", bevor Sie es wegen Inline entfernen ", aber die inverse Logik unterstützt dies nicht. Es ist wahrscheinlich viel komplizierter. Ich bin immer noch nicht einmal 100% sicher, dass ich es richtig entziffert habe. Außerdem sehe ich in C++ - Quellen nichts UB-ähnliches, aber ich bin kein C++ - Guru. Eine Sache zu prüfen, ob das Aliasing durch Referenz gültig ist, aber AFAIG (g = raten), ist es. (Also ich glaube, Sie haben Compiler Bug .. 50%?) – Ped7g

+1

Dies erwies sich als ein Compiler-Fehler. Das Update ist hier: https://reviews.llvm.org/rL291630 – CTT