Warum schiebt diese Funktion RAX als erste Operation auf den Stack?

Lesezeit: 3 Minuten

Benutzer-Avatar
JCx

In der Assemblierung der C++-Quelle unten. Warum wird RAX auf den Stack gepusht?

RAX, wie ich es von der ABI verstehe, könnte alles von der aufrufenden Funktion enthalten. Aber wir speichern es hier und verschieben den Stack später um 8 Bytes zurück. Also das RAX auf dem Stack ist, denke ich nur für den relevant std::__throw_bad_function_call() Betrieb … ?

Der Code:-

#include <functional> 

void f(std::function<void()> a) 
{
  a(); 
}

Ausgang, ab gcc.godbolt.orgmit Clang 3.7.1 -O3:

f(std::function<void ()>):                  # @f(std::function<void ()>)
        push    rax
        cmp     qword ptr [rdi + 16], 0
        je      .LBB0_1
        add     rsp, 8
        jmp     qword ptr [rdi + 24]    # TAILCALL
.LBB0_1:
        call    std::__throw_bad_function_call()

Ich bin sicher, der Grund ist offensichtlich, aber ich habe Mühe, es herauszufinden.

Hier ist ein Tailcall ohne die std::function<void()> Verpackung zum Vergleich:

void g(void(*a)())
{
  a(); 
}

Das Triviale:

g(void (*)()):             # @g(void (*)())
        jmp     rdi        # TAILCALL

  • Ah – ich habe falsch darüber nachgedacht und nicht wirklich geglaubt, dass RAX ein Müllwert war 🙂 Rätsel gelöst!

    – JCx

    12. Juni 2016 um 13:20 Uhr


  • Eigentlich ist es umgekehrt. Vor dem Aufruf muss der Stack ausgerichtet werden, so dass er nach dem Aufruf nicht ausgerichtet ist und neu ausgerichtet werden muss.

    – Dani

    12. Juni 2016 um 13:54 Uhr

  • Da muss ich @Dani zustimmen. Wenn die Übertragung über die Call-to-Funktion gesteuert wird f, RSP ist bereits um 8 falsch ausgerichtet, da die Rücksendeadresse auf dem Stack abgelegt wurde. Es wurde an einer 16-Byte-Grenze ausgerichtet, kurz bevor die Steuerung übertragen wurde f. Möglicherweise meinten Sie das nachher push rax der Stack wird wieder an einer 16-Byte-Grenze ausgerichtet. Der Code addiert tatsächlich 8 zurück zu UVP wenn die Verzweigung nicht kurz vor dem genommen wird JMP. Es ist ineffizienter Code, um diese Aufgabe zu erledigen.

    – Michael Petsch

    12. Juni 2016 um 15:55 Uhr


  • @Gen: sub rsp, 8 erfordert eine zusätzliche uop für die Stack-Engine, um ihren Offset-Wert von rsp mit dem Wert im Out-of-Order-Kern zu synchronisieren. Auf modernen Intel-CPUs (aber nicht AMD) ist es also tatsächlich effizienter, eine zu machen push von Müll, als manuell zu ändern rsp um nur 8. (Die Stack-Engine macht es möglich push / pop um Single-Uop-Anweisungen zu sein, anstatt ein zusätzliches Uop zum Ändern zu benötigen rsp). Sehen Mikroarch von Agner Fog.pdf für die Einzelheiten. Die Stack-Engine war neu im Pentium-M, aber AMD hat sie dank ihrer Patentteilungsvereinbarung auch.

    – Peter Cordes

    13. Juni 2016 um 1:15 Uhr


  • @PeterCordes: Auf GCC scheint es sich zwischen den Versionen zu ändern. GCC 5.3 (und einige frühe Versionen) scheinen zu verwenden push rax im generierten Code (für diesen Fall) und dann in 6.1 verwendet sub. Ich verstehe immer noch nicht ganz, warum CLANG das Stack-Alignment nicht vor dem durchführt call std::__throw_bad_function_call() anstatt einen Push/Pop im Pfad auszuführen, der wahrscheinlicher ausgeführt wird.

    – Michael Petsch

    13. Juni 2016 um 2:00 Uhr


  • In der Tat ist das, was Sie im letzten Absatz beschreiben genau das, was GCC tut entweder bei -O2 oder -O3. Clang und ICC richten beide den Stack am Anfang der Funktion aus. Dies ist einer der seltenen Fälle, in denen der Optimierer von GCC effektiver zu sein scheint als der von Clang.

    – Cody Grey

    12. Juni 2016 um 13:30 Uhr


  • @CodyGray jetzt, wo ich Kaffee getrunken habe, habe ich ihn auf Godbolt geworfen und du hast Recht GCC versucht, in dieser Situation besseren Code zu generieren. Ich habe meine Antwort geändert, um diese Feststellung widerzuspiegeln. Das bestätigte auch meinen Kommentar, wie ich es erwartet hätte, optimiert zu werden.

    – Michael Petsch

    12. Juni 2016 um 13:45 Uhr


  • @daniel: Ihre andere Bearbeitung einer Antwort auf diese Frage wurde abgelehnt, da Sie die Antwort mit der Bearbeitung drastisch ändern. Wenn Sie Ihre eigene Antwort geben möchten, können Sie dies gerne tun, aber Sie sollten solche drastischen Änderungen vermeiden, es sei denn, die Antwort ist eine Antwort vom Typ Community-Wiki.

    – Hovercraft voller Aale

    4. Januar 2020 um 16:45 Uhr


1013440cookie-checkWarum schiebt diese Funktion RAX als erste Operation auf den Stack?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy