2016-09-27 24 views
5

W kodzie nasz Znalazłem ten fragment do szybkiego, w kierunku ujemnym nieskończoności zaokrąglenia na x87:Jak określić spłaszczone dno stosu FPU x87 z rozszerzonym zestawem gcc?

inline int my_int(double x) 
{ 
    int r; 
#ifdef _GCC_ 
    asm ("fldl %1\n" 
     "fistpl %0\n" 
     :"=m"(r) 
     :"m"(x)); 
#else 
    // ... 
#endif 
    return r; 
} 

nie jestem bardzo obeznany ze składnią montażowej GCC przedłużony, ale z tego co zbierać z dokumentacji:

  • r musi być miejscem pamięci, w którym piszę;
  • x musi być także miejscem w pamięci, skąd pochodzą dane.
  • nie ma specyfikacji typu clobber, więc kompilator może mieć pewność, że na końcu fragmentu rejestru znajdują się tak, jak je zostawił.

Teraz, aby odpowiedzieć na moje pytanie: to prawda, że ​​na końcu stos FPU jest zrównoważony, ale co, jeśli wszystkie 8 lokalizacji było już w użyciu i przepełniało mnie to? W jaki sposób kompilator może wiedzieć, że nie może zaufać ST(7), aby był tam, gdzie go zostawił? Czy powinien zostać dodany jakiś clobber?

Edit starałem się określić st(7) na liście sprać i wydaje się wpływać na Codegen, teraz będę czekać na jakiegoś potwierdzenia tego faktu.


Na marginesie: patrząc na realizację w podstawowe lrint zarówno w glibc oraz w MinGW widzę coś

__asm__ __volatile__ ("fistpl %0" 
         : "=m" (retval) 
         : "t" (x) 
         : "st"); 

gdzie jesteśmy z prośbą o wejście do umieszczenia bezpośrednio w ST(0) (co pozwala uniknąć tego potencjalnie bezużytecznego fldl); co to jest "st" Clobber? Dokumenty wydają się wskazywać tylko na t (tj. Na górę stosu).


  1. tak, to zależy od aktualnego trybu zaokrąglania, która w naszej aplikacji powinna być zawsze „do minus nieskończoności”.
+1

'st' to skrót od' st0' lub 'st (0)' –

+0

@MichaelPetch: ok, który usuwa ostatni fragment, niestety, dokumentacja dotycząca tych rzeczy jest nieco trudna do osiągnięcia "z zewnątrz" ", szczególnie części specyficzne dla platformy. –

+0

Mogłoby to zostać napisane bez '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '". GCC zwykle (nie zawsze) musi zrobić coś takiego jak 'fstp% st (0)', gdy twój szablon jest kompletny, aby usunąć to, co pchnął przy wejściu do twojego szablonu. Posiadanie kodu assemblera w szablonie powoduje, że wartość na górze stosu z 'fistpl' i lista' st' jako clobber oznacza, że ​​szablon nie musi dodawać dodatkowej instrukcji, aby zakończyć szablon. –

Odpowiedz

4

patrząc na realizację w podstawowe lrint zarówno w glibc oraz w MinGW widzę coś

__asm__ __volatile__ ("fistpl %0" 
        : "=m" (retval) 
        : "t" (x) 
        : "st"); 

gdzie jesteśmy z prośbą o wejście do umieszczenia bezpośrednio w ST(0) (który unika że potencjalnie bezużyteczny)

To jest właściwie poprawny sposób przedstawienia kodu, który ma być wbudowany.

Aby uzyskać najbardziej optymalny możliwy kod, należy użyć wejść i wyjść. Zamiast sztywnego kodowania niezbędnych instrukcji ładowania/przechowywania, pozwól, aby kompilator je wygenerował.Nie tylko wprowadza to możliwość eliminowania potencjalnie zbędnych instrukcji, ale także oznacza, że ​​kompilator może lepiej wykonywać te instrukcje, gdy są one wymagane (to znaczy, może przeplatać instrukcję w ramach wcześniejszej sekwencji kodu, często minimalizując jej koszt).

co to jest "st" clobber? Dokumenty wydają się wskazywać tylko na t (tj. Na górę stosu).

"st" sprać odnosi się do rejestru st(0), tj, na górze stosu x87 FPU. Co notacja Intel/MASM nazywa st(0), AT & Notacja T/GAS ogólnie odnosi się po prostu jako st. I, zgodnie z dokumentacją GCC dla clobbers, pozycje na liście clobbera są "albo nazwami rejestrów albo specjalnymi klobkami" ("cc" (kody warunków/flagi) i "memory"). Oznacza to po prostu, że wbudowany zespół blokuje (nadpisuje) rejestr st(0). Powód, dla którego ten clobber jest konieczny, polega na tym, że instrukcja fistpl wyskakuje z góry stosu, a więc podkręca oryginalną zawartość st(0).

Jedyną rzeczą, która mnie martwi dotyczące tego kodu jest następujący ustęp z dokumentacji:

opisy sprać nie może w żaden sposób pokrywają się z argumentu wejściowego lub wyjściowego. Na przykład możesz nie mieć operandu opisującego klasę rejestru z jednym elementem podczas wpisywania tego rejestru na liście clobbera. Zmienne deklarowane do życia w określonych rejestrach (patrz Explicit Register Variables) i używane jako argumenty wejściowe lub wyjściowe asm nie mogą zawierać żadnej części w opisie clobbera. W szczególności nie ma możliwości określenia, że ​​operandy wejściowe zostaną zmodyfikowane bez określania ich jako argumentów wyjściowych.

Kiedy kompilator wybiera, które rejestry użyć do reprezentowania operandów wejściowych i wyjściowych, nie używa żadnego z rejestrów spiętrzonych. W wyniku tego rejestry z fleksami są dostępne do użycia w kodzie asemblera.

Jak już wiesz, tconstraint oznacza górę stosu FPU x87. Problem polega na tym, że jest taki sam jak rejestr st, a dokumentacja bardzo wyraźnie mówi, że nie możemy mieć clobbera, który określa ten sam rejestr jako jeden z operandów wejścia/wyjścia. Ponadto, ponieważ w dokumentacji stwierdza się, że kompilatorowi nie wolno używać żadnego z rejestrów z zagłodzeniem do reprezentowania operandów wejścia/wyjścia, ten wbudowany zespół uniemożliwia żądanie załadowania tej wartości na szczycie stosu FPU x87 bez umieszczania go w st!

Teraz, zakładam, że autorzy glibc wiedzą, co robią i są bardziej obeznani z implementacją wbudowanego kompilatora niż ty czy ja, więc ten kod jest prawdopodobnie legalny i uzasadniony.

Wygląda na to, że nietypowy przypadek rejestrów przypominających stosy x87 wymusza wyjątek w normalnych interakcjach między klobotami i operandami. Numer official documentation mówi:

W przypadku obiektów x86 istnieje kilka reguł dotyczących używania rejestrów podobnych do stosów w operandach obiektu ASM.Zasady te stosuje się wyłącznie do argumentów, które są stosu jak rejestry:

  1. Biorąc pod uwagę zestaw rejestrów wejściowych, które padły w ASM, konieczne jest, aby wiedzieć, które są niejawnie wpadł przez ASM, i które muszą być wyraźnie pobite przez GCC.

    Rejestr wejściowy, który został domyślnie pobity przez asm, musi być jawnie spętany, chyba że jest ograniczony do argumentu wynikowego.

To wpisuje się w naszą sprawę dokładnie.

Dalsze potwierdzenie jest przez przykład znajdującej się w dolnej części (the official documentation połączonego odcinka):

ASM ten trwa dwa wejścia, które zostały zdjęte ze stosu przez fyl2xp1 kodu operacji i zastępuje je z jednym wyjściem. Konieczne jest, aby kompilator st(1) wiedział, że fyl2xp1 wysyła oba wejścia.

asm ("fyl2xp1" : "=t" (result) : "0" (x), "u" (y) : "st(1)"); 

Tutaj clobber st(1) jest taka sama jak przy ograniczeniu wprowadzania u, który wydaje się naruszać cytowany dokumentację dotyczącą clobbers, ale jest używany i uzasadnione dokładnie z tego samego powodu, że "st" służy jako clobber w twoim oryginalnym kodzie, ponieważ fistpl wyskakuje z wejścia.


Wszystko, co powiedział, a teraz, że wiesz jak prawidłowo napisać kod w inline montażu, muszę echo poprzednich komentujących, którzy sugerowali, że najlepszym rozwiązaniem byłoby nie używać inline zespół w ogóle . Wystarczy zadzwonić pod numer lrint, który nie tylko ma dokładnie określoną semantykę, ale także może być lepiej zoptymalizowany przez kompilator w pewnych okolicznościach (, np., przekształcając go w pojedynczą instrukcję cvtsd2si, gdy docelowa architektura obsługuje SSE).

+0

Dziękuję za poświęcenie czasu na udzielenie właściwej odpowiedzi na moje stare pytanie :-); bit o rejestrach wejściowych, które są również na liście clobber wydaje się być kluczem, spróbuję później znaleźć kilka różnych przypadków ('fchs' => wejście w st0, wyjście w st0, no clobber;' fdivp' => wejście w st0 i st1, wyjście w st0, wyskakuje st1; i co najważniejsze, 'fxtract' => wejście w st0, wyjście w st0 *, a następnie * wypchnięcie innego wyjścia) i zobacz czy nadal czegoś brakuje. –