Pytanie GCC inline assembly: constraints


Mam trudności ze zrozumieniem ograniczeń roli w GCC wbudowany zestaw (x86). Ja przeczytaj instrukcję, który dokładnie wyjaśnia, co każde ograniczenie ma. Problem polega na tym, że chociaż rozumiem, co robi każde ograniczenie, bardzo niewiele rozumiem, dlaczego używałbyś jednego ograniczenia w stosunku do drugiego lub jakie mogą być tego konsekwencje.

Rozumiem, że jest to bardzo szeroki temat, więc mały przykład powinien pomóc w zawężeniu zainteresowania. Poniżej przedstawiono prostą procedurę asm, która dodaje tylko dwie liczby. Jeśli wystąpi przepełnienie liczby całkowitej, zapisuje wartość 1 do wyjściowej zmiennej C.

 int32_t a = 10, b = 5;
 int32_t c = 0; // overflow flag

 __asm__
 (
  "addl %2,%3;"        // Do a + b (the result goes into b)
  "jno 0f;"            // Jump ahead if an overflow occurred
  "movl $1, %1;"       // Copy 1 into c
  "0:"                 // We're done.

  :"=r"(b), "=m"(c)    // Output list
  :"r"(a), "0"(b)     // Input list
 );

Teraz to działa dobrze, z wyjątkiem tego, że musiałem dowolnie manipulować przy ograniczeniach, dopóki nie zacząłem działać poprawnie. Pierwotnie użyłem następujących ograniczeń:

  :"=r"(b), "=m"(c)    // Output list
  :"r"(a), "m"(b)     // Input list

Zauważ, że zamiast "0", używam ograniczenia "m" dla b. To miało dziwny efekt uboczny, gdy skompilowałem z flagami optymalizacyjnymi i dwukrotnie wywołałem funkcję, z jakiegoś powodu wynik operacji dodawania również zostałby zapisany w c. W końcu przeczytałem o "pasujące ograniczenia", która pozwala ci określić, że zmienna ma być używana jako operand wejściowy i wyjściowy "m"(b) do "0"(b) zadziałało.

Ale tak naprawdę nie rozumiem, dlaczego używałbyś jednego ograniczenia w stosunku do drugiego. Chodzi mi o to, rozumiem, że "r" oznacza, że ​​zmienna powinna znajdować się w rejestrze, a "m" oznacza, że ​​powinna być w pamięci - ale ja nie naprawdę zrozumieć, jakie są konsekwencje wyboru jednego nad drugim lub dlaczego operacja dodawania nie działa poprawnie, jeśli wybiorę pewną kombinację ograniczeń.

Pytania: 1) W powyższym przykładzie kodu, dlaczego ograniczenie "m" na b przyczyna c napisać do? 2) Czy jest jakiś tutorial lub zasób online, który bardziej szczegółowo opisuje ograniczenia?


16
2017-10-10 02:03


pochodzenie




Odpowiedzi:


Oto przykład, który lepiej ilustruje, dlaczego powinieneś ostrożnie wybierać ograniczenia (ta sama funkcja co Twoja, ale być może napisana nieco bardziej zwięźle):

bool add_and_check_overflow(int32_t& a, int32_t b)
{
    bool result;
    __asm__("addl %2, %1; seto %b0"
            : "=q" (result), "+g" (a)
            : "r" (b));
    return result;
}

Tak więc stosowane ograniczenia to: q, r, i g.

  • q oznacza tylko eax, ecx, edx, lub ebx można wybrać. To dlatego, że set* instrukcje muszą wpisywać do rejestru 8-bitowego adresowalnego (al, ah, ...). Sposób użycia b w %b0 oznacza, użyj najniższej 8-bitowej części (al, cl, ...).
  • W przypadku większości instrukcji z dwoma operandami przynajmniej jeden z operandów musi być rejestrem. Więc nie używaj m lub g dla obu; posługiwać się r dla co najmniej jednego z operandów.
  • Dla końcowego argumentu nie ma znaczenia, czy jest to rejestr, czy pamięć, więc użyj g (generał).

W powyższym przykładzie wybrałem opcję użycia g (zamiast r) dla a ponieważ referencje są zwykle implementowane jako wskaźniki pamięci, więc przy użyciu r ograniczenie wymagałoby najpierw skopiowania odniesienia do rejestru, a następnie skopiowania z powrotem. Za pomocą g, referent mógł być aktualizowany bezpośrednio.


Dlaczego oryginalna wersja zastąpiła twoje c z wartością dodawania, to dlatego, że podałeś =m w gnieździe wyjściowym zamiast (powiedzmy) +m; oznacza to, że kompilator może ponownie użyć tej samej lokalizacji pamięci dla wejścia i wyjścia.

W twoim przypadku oznacza to dwa wyniki (ponieważ użyto tej samej lokalizacji pamięci b i c):

  • Dodatek nie przepełnił się: wtedy, c został nadpisany wartością b (wynik dodania).
  • Dodatek się przepełnił: wtedy, c stał się 1 (i b może również stać się 1, w zależności od tego, w jaki sposób wygenerowano kod).

13
2017-10-10 02:15



Dzięki - to doskonała odpowiedź. Tylko jedno wyjaśnienie: dlaczego = (tylko do zapisu) modyfikator ograniczenia daje kompilatorowi prawo do ponownego użycia tej samej lokalizacji pamięci, nawet jeśli b i c są różne zmienne z różnymi lokalizacjami w pamięci? - Channel72
@ Channel72: "mimo że b i c są różne zmienne z różnymi lokalizacjami w pamięci "--- to w rzeczywistości główne założenie, które często nie ma zastosowania b i c są zmienne lokalne, szanse są dobre, ponieważ są one faktycznie wspierane przez rejestry, a nie lokalizacja pamięci. W takim przypadku miejsce w pamięci jest po prostu tymczasowym miejscem przechowywania, które jest ustawione wyłącznie w celu dostosowania się do twojego m ograniczenie - w takim przypadku b i c może bardzo dobrze wykorzystać tę samą tymczasową lokalizację. - Chris Jester-Young
Teraz jeśli b i c faktycznie oba były naprawdę wspierane przez lokalizacje w pamięci, to masz rację, że normalnie nie powinny się w ogóle pokrywać. A jeśli jeden jest wspierany przez pamięć, a drugi jest wspierany przez rejestr ... to jeden z tych scenariuszy jest możliwy. - Chris Jester-Young