2011-07-20 8 views
9

Próbuję napisać inline ASM GCC dla CMPXCHG8B dla ia32. Nie, nie mogę używać __sync_bool_compare_and_swap. Musi działać z opcją -fPIC i bez niej.Prawidłowy sposób na zawijanie CMPXCHG8B w zespole inline GCC, 32 bity

tej pory najlepsze jakie (EDIT: nie działa po wszystkim, zobacz moje własne odpowiedzi poniżej) jest

register int32 ebx_val asm("ebx")= set & 0xFFFFFFFF; 
asm ("lock; cmpxchg8b %0;" 
    "setz %1;" 
    : "+m" (*a), "=q" (ret), "+A" (*cmp) 
    : "r" (ebx_val), "c" ((int32)(set >> 32)) 
    : "flags") 

Jednak nie jestem pewien, czy to jest w rzeczywistości prawidłowego .

Nie mogę wykonać "b" ((int32)(set & 0xFFFFFFFF)) dla ebx_val z powodu PIC, ale podobno register asm("ebx") zmienna jest akceptowana przez kompilator.

BONUS: zmienna ret służy do rozgałęzienia, więc kod kończy się wyglądać jak ten:

cmpxchg8b [edi]; 
setz cl; 
cmp cl, 0; 
je foo; 

Każdy pomysł jak opisać argumenty wyjściowe tak, że staje się:

cmpxchg8b [edi] 
jz foo 

?

Dziękuję.

+3

Fakt, że wewnętrzna kompilator nie działa z flagą -fPIC jest tylko płaska-out kompilator bug: http: //gcc.gnu.org/bugzilla/show_bug.cgi?id=37651 To jest do bani, gdy musisz obejść uszkodzony kompilator, więc możesz chcieć umieścić się na liście CC dla tego błędu. – Crashworks

+0

Używasz '-fPIC' na IA32? Jestem ciekawy dlaczego. – Gabe

+4

@Gabe - Najważniejsze przy pisaniu obiektów z biblioteki współdzielonej. Ulrich Drepper ma dobry artykuł na ten temat: http://www.akkadia.org/drepper/dsohowto.pdf – Crashworks

Odpowiedz

2

Jak o następujących, które wydaje się działać dla mnie w małym teście:

int sbcas(uint64_t* ptr, uint64_t oldval, uint64_t newval) 
{ 
    int changed = 0; 
    __asm__ (
     "push %%ebx\n\t" // -fPIC uses ebx, so save it 
     "mov %5, %%ebx\n\t" // load ebx with needed value 
     "lock\n\t" 
     "cmpxchg8b %0\n\t" // perform CAS operation 
     "setz %%al\n\t" // eax potentially modified anyway 
     "movzx %%al, %1\n\t" // store result of comparison in 'changed' 
     "pop %%ebx\n\t" // restore ebx 
     : "+m" (*ptr), "=r" (changed) 
     : "d" ((uint32_t)(oldval >> 32)), "a" ((uint32_t)(oldval & 0xffffffff)), "c" ((uint32_t)(newval >> 32)), "r" ((uint32_t)(newval & 0xffffffff)) 
     : "flags", "memory" 
     ); 
    return changed; 
} 

Jeśli ta również zostanie miscompiled mógłbyś to niewielki fragment, który wyzwala to zachowanie?

Jeśli chodzi o pytanie o bonus, nie sądzę, że możliwe jest rozgałęzienie po bloku asemblera przy użyciu kodu warunku z instrukcji cmpxchg8b (chyba że używasz asm goto lub podobnej funkcjonalności).Od GNU C Language Extensions:

Naturalnym pomysłem jest znalezienie sposobu na udostępnienie kodu warunku pozostawionego przez instrukcję assemblera. Jednak, gdy próbowaliśmy to wdrożyć, nie znaleźliśmy sposobu, aby sprawić, by działał on niezawodnie. Problem polega na tym, że operandy wyjściowe mogą wymagać ponownego załadowania, co skutkowałoby dodatkowymi instrukcjami "składowania". Na większości komputerów instrukcje te zmieniłyby kod warunków, zanim zdążyliby go przetestować. Ten problem nie występuje w przypadku zwykłych instrukcji "test" i "porównaj", ponieważ nie mają żadnych argumentów wyjściowych.

EDIT: Nie mogę znaleźć żadnego źródła, które określa jedną lub drugą stronę, czy to jest OK, aby zmodyfikować stos jednocześnie stosując wartości wejściowych %N (This starożytny Link mówi „Można nawet naciskać na swoich rejestrów stos, użyj ich i odłóż. "ale przykład nie ma wejścia).

Ale to powinno być możliwe do zrobienia bez ustalając wartości do innych rejestrów:

int sbcas(uint64_t* ptr, uint64_t oldval, uint64_t newval) 
{ 
    int changed = 0; 
    __asm__ (
     "push %%ebx\n\t" // -fPIC uses ebx 
     "mov %%edi, %%ebx\n\t" // load ebx with needed value 
     "lock\n\t" 
     "cmpxchg8b (%%esi)\n\t" 
     "setz %%al\n\t" // eax potentially modified anyway 
     "movzx %%al, %1\n\t" 
     "pop %%ebx\n\t" 
     : "+S" (ptr), "=a" (changed) 
     : "0" (ptr), "d" ((uint32_t)(oldval >> 32)), "a" ((uint32_t)(oldval & 0xffffffff)), "c" ((uint32_t)(newval >> 32)), "D" ((uint32_t)(newval & 0xffffffff)) 
     : "flags", "memory" 
     ); 
    return changed; 
} 
+1

Dziękujemy!Jeden problem, jaki widzę, jest taki sam jak w przypadku mojego fragmentu: kompilator może zaadresować% 0 do ESP i nie może stwierdzić, że ESP zmieniło się za pomocą push/pop. Również dziękuję za informacje re. kody warunków na wyjściu potwierdzają to, co podejrzewałem. –

+0

Nie sądzę, żebym kiedykolwiek widział GCC to zrobić. To (zawsze?) Robi to poprzez 'ebp', o ile widziałem. Zobaczę, czy uda mi się wykopać referencję, czy jest to zawsze gwarantowane (lub czy można to zrobić). – user786653

+0

Widziałem referencje, które robi to ICC. –

2

To jest to, co mam:

bool 
spin_lock(int64_t* lock, int64_t thread_id, int tries) 
{ 
    register int32_t pic_hack asm("ebx") = thread_id & 0xffffffff; 
retry: 
    if (tries-- > 0) { 
     asm goto ("lock cmpxchg8b %0; jnz %l[retry]" 
        : 
        : "m" (*lock), "A" ((int64_t) 0), 
        "c" ((int32_t) (thread_id >> 32)), "r" (pic_hack) 
        : 
        : retry); 
     return true; 
    } 
    return false; 
} 

Wykorzystuje funkcję asm goto, nowe z gcc 4.5, który umożliwia skoki z inline montażu na etykietach C. (Och, widzę twój komentarz o konieczności obsługi starych wersji gcc. No cóż, próbowałem. :-P)

+0

Dzięki! Podoba mi się, że mój kod jest już bardzo podobny do twojego, choć oczywiście Nie mogę używać asm goto, ale kilka pytań: 1) dlaczego * operand blokady jest tylko wejściowy, a nie wejściowy/wyjściowy? 2) dlaczego EFLAGS nie znajduje się na liście zablokowanych rejestrów? –

+0

@Laurynas: 1. 'asm goto' nie może mieć żadnych ograniczeń wyjściowych (ograniczenie prądu, które może być usunięte w późniejszych wersjach gcc); ponieważ nie "dbam" o aktualną wartość blokady (nie próbujemy wykonywać rekursywnego blokowania ;-)), co było do przyjęcia. 2. Ponieważ przykłady dla 'asm goto' również go nie zawierają (i tak, to też przeskakuje warunkowo), więc domyślam się, że' asm goto' domyślnie przyjmuje flagę clobbered. –

+0

Dla przyszłych czytelników: definicje maszyn gcc dla x86 i x86-64 sprawiają, że każda wbudowana instrukcja asm zawiera niejawnie '' cc "' clobber. Nigdy nie musisz pisać jednoznacznie dla x86 asm. –

1

Co zadziwiające, fragment kodu w pytaniu nadal jest niepoprawnie komplikowany w pewnych okolicznościach: jeśli zero Operator th asm pośrednio adresowalny przez EBX (PIC), zanim rejestr EBX zostanie skonfigurowany z register asm, następnie gcc kontynuuje ładowanie operandu przez EBX po przypisaniu go do set & 0xFFFFFFFF!

Jest to kod próbuję uczynić pracę teraz: (EDIT: unikać Push/pop)

asm ("movl %%edi, -4(%%esp);" 
    "leal %0, %%edi;" 
    "xchgl %%ebx, %%esi;" 
    "lock; cmpxchg8b (%%edi);" // Sets ZF 
    "movl %%esi, %%ebx;"  // Preserves ZF 
    "movl -4(%%esp), %%edi;" // Preserves ZF 
    "setz %1;"     // Reads ZF 
    : "+m" (*a), "=q" (ret), "+A" (*cmp) 
    : "S" ((int32)(set & 0xFFFFFFFF)), "c" ((int32)(set >> 32)) 
    : "flags") 

Chodzi o to, aby załadować argumentów przed przebijania się EBX, również unikać wszelkich pośrednie Odnosząc ustawienie wartości EBX dla CMPXCHG8B. Naprawiłem twardy rejestr ESI dla dolnej połowy argumentu operacji, ponieważ gdybym tego nie zrobił, GCC miałby ochotę ponownie wykorzystać każdy inny już zarejestrowany rejestr, jeśli mógłby udowodnić, że wartość była równa. Rejestr EDI jest zapisywany ręcznie, po prostu dodając go do dławionych list katalogowych dławi GCC z "niemożliwymi przeładowaniami", prawdopodobnie z powodu wysokiego ciśnienia rejestrowego. PUSH/POP jest unikany przy zapisywaniu EDI, ponieważ inne operandy mogą być adresowane przez ESP.

Powiązane problemy