2011-12-12 14 views
9

Sto cercando di familiarizzare con l'assembly x86 utilizzando l'assemblatore inline di GCC. Sto cercando di aggiungere due numeri (a e b) e memorizzare il risultato in c. Ho quattro tentativi leggermente diversi, tre dei quali funzionano; l'ultimo non produce il risultato atteso.Aggiunta di due numeri

I primi due esempi utilizzano un registro intermedio e entrambi funzionano correttamente. Il terzo e il quarto esempio cercano di aggiungere i due valori direttamente senza il registro intermedio, ma i risultati variano a seconda del livello di ottimizzazione e dell'ordine in cui aggiungo i valori di input. Cosa sto sbagliando?

Ambiente è:

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

In primo luogo, le variabili sono dichiarate come segue:

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

Esempio 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 

Esempio 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 

Esempio 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 

Esempio 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 

risolto.Matthew Slattery's answer è corretto. Prima, stava cercando di riutilizzare eax sia per b e c:

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

Con correzione suggerito di Matteo in luogo, esso utilizza ora ecx per tenere c separatamente.

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

Funziona bene per me, con o senza ottimizzazione abilitata. Prova a compilare con -S, per ottenere un elenco di linguaggio assembly. Quindi puoi vedere quali registri vengono utilizzati. – TonyK

+0

Ho appena notato che ottengo risultati diversi a seconda del livello di ottimizzazione. Campioni di codice aggiornati con nuovo output. –

+0

Allora, cosa ti dice la lista di montaggio? – TonyK

risposta

7

Per impostazione predefinita, gcc presuppone che un blocco in linea asm terminerà utilizzando gli operandi di input prima di aggiornare gli operandi di output. Ciò significa che sia un input sia un output possono essere assegnati allo stesso registro.

Ma, che non è necessariamente il caso nei vostri esempi 3 e 4.

esempio nell'esempio 3:

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

... si è aggiornato c (%0) prima di leggere a (%1). Se gcc accade di assegnare lo stesso registro sia %0 e %1, allora calcolerà c = b; c += c, e quindi non riuscirà esattamente nel modo in cui si osserva:

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 

si può risolvere dicendo gcc che l'operando di uscita può essere utilizzati prima che i fattori produttivi sono utilizzati, con l'aggiunta del modificatore "&" per l'operando, in questo modo:

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

(Vedi "Constraint Modifier Characters" nelle gcc docs.)

+0

Questo è esattamente, grazie. Aggiornato la domanda con la lista di assemblaggio da prima e dopo la correzione. –

+0

Grazie per questo! È qualcosa che devo capire, ma la documentazione è piuttosto proibita. Suggerimenti come questo sono molto preziosi. – TonyK

0

Hoi, non vedo un problema lì e compila e funziona bene qui. Tuttavia, un piccolo suggerimento: mi sono confuso abbastanza presto con le variabili/registri senza nome, così ho deciso di usare quelle nominative. L'add thingy si potrebbe per esempio implementare in questo modo:

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; 
    } 

(si può semplicemente ignorare le cose atomiche/blocco per ora), che rende chiaro ciò che accade:

1) quali registri sono scrivibili, leggibili o entrambi

2) quello che viene utilizzato (memoria, registri), che può essere importante quando si tratta di cicli di clock e di prestazioni, come operazioni di registro sono più veloci di quelli accesso alla memoria.

Cheers, G.

P.S .: Avete controllato se il vostro compilatore riorganizza codice?