Come accedere a C struct / variables da inline asm?

Considera il seguente codice:

int bn_div(bn_t *bn1, bn_t *bn2, bn_t *bnr) { uint32 q, m; /* Division Result */ uint32 i; /* Loop Counter */ uint32 j; /* Loop Counter */ /* Check Input */ if (bn1 == NULL) return(EFAULT); if (bn1->dat == NULL) return(EFAULT); if (bn2 == NULL) return(EFAULT); if (bn2->dat == NULL) return(EFAULT); if (bnr == NULL) return(EFAULT); if (bnr->dat == NULL) return(EFAULT); #if defined(__i386__) || defined(__amd64__) __asm__ (".intel_syntax noprefix"); __asm__ ("pushl %eax"); __asm__ ("pushl %edx"); __asm__ ("pushf"); __asm__ ("movl %eax, (bn1->dat[i])"); __asm__ ("xorl %edx, %edx"); __asm__ ("divl (bn2->dat[j])"); __asm__ ("movl (q), %eax"); __asm__ ("movl (m), %edx"); __asm__ ("popf"); __asm__ ("popl %edx"); __asm__ ("popl %eax"); #else q = bn->dat[i] / bn->dat[j]; m = bn->dat[i] % bn->dat[j]; #endif /* Return */ return(0); } 

I tipi di dati uint32 sono fondamentalmente unsigned long int o uint32_t unsigned 32-bit integer. Il tipo bnint è un int breve non firmato int (uint16_t) o uint32_t a seconda che i tipi di dati a 64 bit siano disponibili o meno. Se 64-bit è disponibile, allora bnint è un uint32, altrimenti è un uint16. Questo è stato fatto al fine di acquisire carry / overflow in altre parti del codice. La struttura bn_t è definita come segue:

 typedef struct bn_data_t bn_t; struct bn_data_t { uint32 sz1; /* Bit Size */ uint32 sz8; /* Byte Size */ uint32 szw; /* Word Count */ bnint *dat; /* Data Array */ uint32 flags; /* Operational Flags */ }; 

La funzione inizia sulla riga 300 nel mio codice sorgente. Quindi quando provo a compilarlo / farlo, ottengo i seguenti errori:

 system:/home/user/c/m3/bn 1036 $$$ ->make clang -I. -I/home/user/c/m3/bn/.. -I/home/user/c/m3/bn/../include -std=c99 -pedantic -Wall -Wextra -Wshadow -Wpointer-arith -Wcast-align -Wstrict-prototypes -Wmissing-prototypes -Wnested-externs -Wwrite-strings -Wfloat-equal -Winline -Wunknown-pragmas -Wundef -Wendif-labels -c /home/user/c/m3/bn/bn.c /home/user/c/m3/bn/bn.c:302:12: warning: unused variable 'q' [-Wunused-variable] uint32 q, m; /* Division Result */ ^ /home/user/c/m3/bn/bn.c:302:15: warning: unused variable 'm' [-Wunused-variable] uint32 q, m; /* Division Result */ ^ /home/user/c/m3/bn/bn.c:303:12: warning: unused variable 'i' [-Wunused-variable] uint32 i; /* Loop Counter */ ^ /home/user/c/m3/bn/bn.c:304:12: warning: unused variable 'j' [-Wunused-variable] uint32 j; /* Loop Counter */ ^ /home/user/c/m3/bn/bn.c:320:14: error: unknown token in expression __asm__ ("movl %eax, (bn1->dat[i])"); ^ :1:18: note: instantiated into assembly here movl %eax, (bn1->dat[i]) ^ /home/user/c/m3/bn/bn.c:322:14: error: unknown token in expression __asm__ ("divl (bn2->dat[j])"); ^ :1:12: note: instantiated into assembly here divl (bn2->dat[j]) ^ 4 warnings and 2 errors generated. *** [bn.o] Error code 1 Stop in /home/user/c/m3/bn. system:/home/user/c/m3/bn 1037 $$$ -> 

Quello che so:

Mi considero abbastanza esperto nell’assembler x86 (come evidenziato dal codice che ho scritto sopra). Tuttavia, l’ultima volta che ho mixato un linguaggio e un assemblatore di alto livello utilizzavo Borland Pascal circa 15-20 anni fa quando scrivevo i driver grafici per i giochi (era pre-Windows 95). La mia familiarità è con la syntax Intel.

Quello che non so:

Come accedere ai membri di bn_t (in particolare * dat) da asm? Dato che * dat è un puntatore a uint32, sto accedendo agli elementi come una matrice (ad esempio bn1-> dat [i]).

Come posso accedere alle variabili locali dichiarate nello stack?

Sto usando push / pop per ripristinare i registri danneggiati ai loro valori precedenti in modo da non alterare il compilatore. Tuttavia, devo anche includere la parola chiave volatile sulle variabili locali?

O c’è un modo migliore di cui non sono a conoscenza? Non voglio mettere questo in una chiamata di funzione separata a causa del sovraccarico di chiamata in quanto questa funzione è critica dal punto di vista delle prestazioni.

Ulteriori:

In questo momento, sto appena iniziando a scrivere questa funzione, quindi non è completa. Mancano loop e altri tipi di supporto / codice colla. Ma il principale è l’accesso a variabili locali / elementi strutturali.

MODIFICA 1:

La syntax che sto usando sembra essere l’unica che supporta clang. Ho provato il seguente codice e clang mi ha dato tutti i tipi di errori:

 __asm__ ("pushl %%eax", "pushl %%edx", "pushf", "movl (bn1->dat[i]), %%eax", "xorl %%edx, %%edx", "divl ($0x0c + bn2 + j)", "movl %%eax, (q)", "movl %%edx, (m)", "popf", "popl %%edx", "popl %%eax" ); 

Vuole che metta una parentesi chiusa sulla prima riga, sostituendo la virgola. Sono passato a usare %% anziché% perché ho letto da qualche parte che l’assembly inline richiede %% per indicare i registri della CPU, e clang mi diceva che stavo usando una sequenza di escape non valida.

Se hai solo bisogno di 32b / 32b => divisione a 32 bit, lascia che il compilatore usi entrambi gli output di div , che gcc, clang e icc fanno tutti bene, come puoi vedere nel explorer del compilatore Godbolt :

 uint32_t q = bn1->dat[i] / bn2->dat[j]; uint32_t m = bn1->dat[i] % bn2->dat[j]; 

I compilatori sono abbastanza bravi in CSE e li inseriscono in un div . Assicurati solo di non archiviare il risultato della divisione da qualche parte che gcc non può dimostrare non influirà sull’input del resto.

es. *m = dat[i] / dat[j] potrebbe sovrapporsi (alias) dat[i] o dat[j] , quindi gcc dovrebbe ricaricare gli operandi e ripetere il div per l’operazione % . Vedi il link Godbolt per esempi cattivi / buoni.


Utilizzare in linea asm per 32 bit / 32 bit = 32 bit div non ti guadagna nulla, e in effetti rende il codice peggiore con clang (vedi il collegamento Godbolt).

Se hai bisogno di 64 bit / 32 bit = 32 bit, probabilmente hai bisogno di asm, però, se non c’è un compilatore integrato per questo. (GNU C non ne ha uno, AFAICT). Il modo ovvio in C (eseguire gli operandi su uint64_t ) genera una chiamata a una funzione libgcc a 64 bit / 64 bit = 64 bit, che ha diramazioni e istruzioni div multiple. gcc non è in grado di dimostrare che il risultato si adatta a 32 bit, quindi una singola istruzione div non causa un #DE .

Per molte altre istruzioni, è ansible evitare di scrivere in linea asm un sacco di volte con funzioni integrate per cose come il popcount . Con -mpopcnt , compila l’istruzione popcnt (e tiene conto della falsa dipendenza dell’operando di output che hanno le CPU Intel.) Senza, si compila in una chiamata di funzione libgcc.

Preferisci sempre i builtins, o pure C che compili a good asm, così il compilatore sa cosa fa il codice . Quando inlining rende alcuni degli argomenti noti in fase di compilazione, la C pura può essere ottimizzata o semplificata , ma il codice che utilizza in linea asm caricherà semplicemente le costanti nei registri e eseguirà un div in fase di esecuzione. In linea asm inoltre sconfigge CSE tra calcoli simili sugli stessi dati e, naturalmente, non può auto-vettorializzare.


Usare la syntax GNU C nel modo giusto

https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html spiega come dire all’assemblatore quali variabili si vogliono registrare e quali sono le uscite.

È ansible utilizzare syntax e mnemonici simili a Intel / MASM e nomi di registro non-% se lo si desidera , preferibilmente compilando con -masm=intel . Il bug di syntax AT & T ( fsub e fsubr mnemonics sono invertiti ) potrebbe essere ancora presente in modalità intel-syntax; Io dimentico.

La maggior parte dei progetti software che utilizzano GNU C in linea asm utilizzano solo la syntax AT & T.

Vedi anche il fondo di questa risposta per maggiori informazioni su asm GNU C in linea e il wiki per i tag x86 .


Un’istruzione asm accetta una stringa arg e 3 serie di vincoli. Il modo più semplice per renderlo multilinea è rendere ogni linea asm una stringa separata che termina con \n , e lasciare che il compilatore li concateni in modo implicito.

Inoltre, dici al compilatore che registra in cui vuoi inserire materiale. Quindi se le variabili sono già nei registri, il compilatore non deve versarle e caricarle e memorizzarle. Farlo ti sparerebbe davvero ai piedi. Il tutorial che Brett Hale ha collegato nei commenti si spera copra tutto questo.


Esempio corretto di div con GNU C in linea asm

Puoi vedere l’output del compilatore ASM per questo su Godbolt .

 uint32_t q, m; // this is unsigned int on every compiler that supports x86 inline asm with this syntax, but not when writing portable code. asm ("divl %[bn2dat_j]\n" : "=a" (q), "=d" (m) // results are in eax, edx registers : "d" (0), // zero edx for us, please "a" (bn1->dat[i]), // "a" means EAX / RAX [bn2dat_j] "mr" (bn2->dat[j]) // register or memory, compiler chooses which is more efficient : // no register clobbers, and we don't read/write "memory" other than operands ); 

"divl %4" avrebbe funzionato, ma gli input / output nominati non cambiano nome quando si aggiungono più vincoli di input / output.