2010-10-10 13 views
15

Mam trudności ze zrozumieniem ograniczeń roli w GCC inline assembly (x86). Mam read the manual, 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.Montaż inline w GCC: ograniczenia

Zdaję sobie sprawę, że jest to bardzo szeroki temat, więc mały przykład powinien pomóc zawęzić nacisk. Poniżej przedstawiono prostą procedurę asm, która dodaje tylko dwie liczby. Jeśli wystąpi przepełnienie liczby całkowitej, zapisuje wartość 1 na wyjściową zmienną 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 tym, że musiałem dowolnie manipulować ograniczeniami, dopóki nie zacząłem działać poprawnie. Początkowo używałem następujące ograniczenia:

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

Zauważ, że zamiast „0”, używam „M” ograniczenie dla b. Miało to dziwny skutek 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 "matching constraints", który pozwala ci określić, że zmienna ma być używana jako operand wejściowy i wyjściowy. Po zmianie "m"(b) na "0"(b) zadziałało.

Ale tak naprawdę nie rozumiem, dlaczego używałbyś jednego ograniczenia zamiast 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 rozumiem, co to jest wybór jednego z nich, ani 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 powoduje, że c jest zapisywane? 2) Czy jest jakiś tutorial lub zasób online, który bardziej szczegółowo opisuje ograniczenia?

Odpowiedz

13

Oto przykład, aby lepiej zilustrować, dlaczego należy wybrać ograniczeń starannie (taką samą funkcję jak ty, ale chyba napisany trochę 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; 
} 

Więc, ograniczenia stosowane były: q, r i g.

  • q oznacza jedynie eax, ecx, edx lub ebx może być wybrana. Dzieje się tak, ponieważ instrukcje set* muszą zapisywać do 8-bitowego rejestru adresowego (al, ah, ...). Zastosowanie b w %b0 oznacza użycie 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 dla obu wartości m lub g; użyj 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 (ogólne).

W powyższym przykładzie wybrałem używać g (zamiast r) dla a bo referencje są zazwyczaj realizowane jako wskaźniki pamięci, więc stosując r ograniczenie wymagałoby kopiowanie referent do rejestru, a potem kopiowanie z powrotem. Używając g, odnośnik mógłby zostać zaktualizowany bezpośrednio.


Jako dlaczego oryginalna wersja nadpisałeś swojej c z dodatkiem za wartość, to dlatego, że podałeś =m w szczelinie wyjściowej, 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, to znaczy dwa efekty (ponieważ sama lokalizacja pamięci użyto b i c):

  • Dodanie nie overflow: wtedy c dostał nadpisane wartości b (wynik dodania).
  • Dodatek spowodował przepełnienie: następnie c stał się 1 (i b może również stać się 1, w zależności od sposobu wygenerowania kodu).
+0

Dzięki - to doskonała odpowiedź. Tylko jedno wyjaśnienie: dlaczego modyfikator ograniczenia "=" (tylko do zapisu) daje kompilatorowi prawo do ponownego użycia tej samej lokalizacji pamięci, mimo że "b" i "c" to różne zmienne z różnymi lokalizacjami w pamięci? – Channel72

+0

@ Channel72: "nawet jeśli" b "i" c "są różnymi zmiennymi z różnymi lokalizacjami w pamięci" --- to jest rzeczywiście główne założenie, które często nie ma zastosowania. Jeśli "b" i "c" są zmiennymi lokalnymi, są szanse, że oba są faktycznie wspierane przez rejestry, a nie jako miejsce w pamięci. W takim przypadku lokalizacja pamięci jest po prostu tymczasowym miejscem przechowywania ustawionym wyłącznie w celu uwzględnienia ograniczenia 'm' --- w takim przypadku' b' i 'c' mogą bardzo dobrze używać tej samej tymczasowej lokalizacji. –

+0

Teraz, jeśli 'b' i' c' faktycznie były tak 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. –