2011-12-12 19 views
9

Próbuję zapoznać się z montażem x86 za pomocą wbudowanego asemblera GCC. Próbuję dodać dwie liczby (a i b) i zapisać wynik w c. Mam cztery nieznacznie różne próby, z których trzy działają; ostatni nie daje oczekiwanego rezultatu.Dodawanie dwóch liczb

Pierwsze dwa przykłady wykorzystują rejestr pośredni, a oba działają dobrze. Trzecie i czwarte przykłady próbują dodać dwie wartości bezpośrednio bez rejestru pośredniego, ale wyniki różnią się w zależności od poziomu optymalizacji i kolejności dodawania wartości wejściowych. Co robię źle?

środowisko jest:

i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3) 

pierwsze zmienne są uznane w następujący sposób:

int a = 4; 
int b = 7; 
int c; 

Przykład 1:

asm(" movl %1,%%eax;" 
    " addl %2,%%eax;" 
    " movl %%eax,%0;" 
    : "=r" (c) 
    : "r" (a), "r" (b) 
    : "%eax" 
    ); 
printf("a=%d, b=%d, c=%d\n", a, b, c); 
// output: a=4, b=7, c=11 

Przykład 2:

asm(" movl %2,%%eax;" 
    " addl %1,%%eax;" 
    " movl %%eax,%0;" 
    : "=r" (c) 
    : "r" (a), "r" (b) 
    : "%eax" 
    ); 
printf("a=%d, b=%d, c=%d\n", a, b, c); 
// output: a=4, b=7, c=11 

Przykład 3:

asm(" movl %2,%0;" 
    " addl %1,%0;" 
    : "=r" (c) 
    : "r" (a), "r" (b) 
    ); 
printf("a=%d, b=%d, c=%d\n", a, b, c); 
// output with -O0: a=4, b=7, c=11 
// output with -O3: a=4, b=7, c=14 

Przykład 4:

// this one appears to calculate a+a instead of a+b 
asm(" movl %1,%0;" 
    " addl %2,%0;" 
    : "=r" (c) 
    : "r" (a), "r" (b) 
    ); 
printf("a=%d, b=%d, c=%d\n", a, b, c); 
// output with -O0: a=4, b=7, c=8 
// output with -O3: a=4, b=7, c=11 

rozwiązania.Matthew Slattery's answer jest poprawna. Przed, to starałem się ponowne eax zarówno b i c:

movl -4(%rbp), %edx 
movl -8(%rbp), %eax 
movl %edx, %eax 
addl %eax, %eax 

Z Mateusza sugerowane poprawki w miejscu, to teraz wykorzystuje ecx trzymać c oddzielnie.

movl -4(%rbp), %edx 
movl -8(%rbp), %eax 
movl %edx, %ecx 
addl %eax, %ecx 
+2

Działa dobrze dla mnie, z włączoną optymalizacją lub bez niej. Spróbuj skompilować z opcją -S, aby uzyskać listę języków asemblerowych. Wtedy możesz zobaczyć, które rejestry są używane. – TonyK

+0

Zauważyłem, że otrzymuję różne wyniki w zależności od poziomu optymalizacji. Próbki kodu zaktualizowano o nowe dane wyjściowe. –

+0

Co zawiera informacja o montażu? – TonyK

Odpowiedz

7

Domyślnie gcc zakłada, że ​​wbudowany blok asm zakończy działanie z argumentami wejściowymi przed zaktualizowaniem argumentów wyjściowych. Oznacza to, że zarówno dane wejściowe, jak i wyjściowe mogą być przypisane do tego samego rejestru.

jednak, że nie jest to regułą w swoich przykładach 3 i 4.

np w przykładzie 3:

asm(" movl %2,%0;" 
    " addl %1,%0;" 
    : "=r" (c) 
    : "r" (a), "r" (b) 
    ); 

... zaktualizowaniu c (%0) przed przeczytaniem a (%1). Jeśli gcc stanie przypisać ten sam rejestr zarówno %0 i %1, następnie obliczy c = b; c += c, a więc nie powiedzie się dokładnie w ten sposób można zaobserwować:

printf("a=%d, b=%d, c=%d\n", a, b, c); 
// output with -O0: a=4, b=7, c=11 
// output with -O3: a=4, b=7, c=14 

Można go naprawić mówiąc gcc że operand wyjściowy może można stosować przed wejścia są spożywane przez dodanie „&” modyfikator argumentu, na przykład:

asm(" movl %2,%0;" 
    " addl %1,%0;" 
    : "=&r" (c) 
    : "r" (a), "r" (b) 
    ); 

(Patrz "Constraint Modifier Characters" w gcc dokumentów.)

+0

To właśnie to, dzięki. Zaktualizowano pytanie do listy zespołów przed i po poprawce. –

+0

Dzięki za to! Jest to coś, co muszę zrozumieć, ale dokumentacja jest raczej zakazana. Takie porady są bardzo cenne. – TonyK

0

Hoi, nie widzę tu problemu, który kompiluje i działa dobrze tutaj. Jednak mała podpowiedź: trochę się pomyliłem z nienazwanymi zmiennymi/rejestrami, więc zdecydowałem się na użycie nazwanych. Dodatek cienias można na przykład realizować tak:

static inline void atomicAdd32(volInt32 *dest, int32_t source) { 
// IMPLEMENTS: add m32, r32 
__asm__ __volatile__(
     "lock; addl %[in], %[out]" 
     : [out] "+m"(*dest) 
     : [in] "ir"(source)//, "[out]" "m"(*dest) 
     ); 
return; 
    } 

(można po prostu zignorować atomowych rzeczy/lock na razie), który sprawia, że ​​jasne, co się dzieje:

1) co rejestry są do zapisu, czytelny lub oba: co jest używane (pamięć, rejestry), co może być ważne, jeśli chodzi o wydajność i cykle zegara, ponieważ operacje rejestrowania są szybsze niż te, które uzyskują dostęp do pamięci.

Cheers, G.

P.S .: Czy można sprawdzić, czy kompilator przestawia kodu?

Powiązane problemy