2011-12-03 10 views
8

Uso Freescale Kinetis K60 e utilizzo dell'ID CodeWarrior (che credo utilizzi GCC per il compilatore).Matematica a punti fissi con ARM Cortex-M4 e compilatore gcc

Voglio moltiplicare due numeri a 32 bit (che si traduce in un numero a 64 bit) e mantenere solo i 32 bit superiori.

Penso che le istruzioni di assemblaggio corrette per l'ARM Cortex-M4 siano le istruzioni SMMUL. Preferirei accedere a questa istruzione dal codice C piuttosto che dall'assemblaggio. Come faccio a fare questo?

immagino che il codice sarebbe idealmente essere qualcosa di simile:

int a,b,c; 

a = 1073741824; // 0x40000000 = 0.5 as a D0 fixed point number 
b = 1073741824; // 0x40000000 = 0.5 as a D0 fixed point number 

c = ((long long)a*b) >> 31; // 31 because there are two sign bits after the multiplication 
          // so I can throw away the most significant bit 

Quando provo questo CodeWarrior, ottengo il risultato corretto per c (536.870.912 = 0.25 come numero D0 FP). Non vedo l'istruzione SMMUL da nessuna parte e la moltiplicazione è di 3 istruzioni (UMULL, MLA e MLA - Non capisco perché sta usando un multiplo senza segno, ma questa è un'altra domanda). Ho anche provato uno spostamento verso destra di 32, poiché ciò potrebbe avere più senso per l'istruzione SMMUL, ma ciò non fa nulla di diverso.

+0

Si sta compilando l'ottimizzazione? Se il compilatore non sta generando il codice ottimale, è possibile scrivere una piccola funzione di assemblaggio o utilizzare un assembly inline da C. – TJD

risposta

6

Il problema che si ottiene con l'ottimizzazione che il codice è:

08000328 <mul_test01>: 
8000328: f04f 5000 mov.w r0, #536870912 ; 0x20000000 
800032c: 4770  bx lr 
800032e: bf00  nop 

codice tuo doesnt fare nulla runtime così l'ottimizzatore può solo calcolare la risposta finale.

questo:

.thumb_func 
.globl mul_test02 
mul_test02: 
    smull r2,r3,r0,r1 
    mov r0,r3 
    bx lr 

chiamato con questo:

c = mul_test02(0x40000000,0x40000000); 

dà 0x10000000

UMULL dà lo stesso risultato perché si sta utilizzando numeri positivi, gli operandi ei risultati sono tutti positivi così non entra nelle differenze firmate/non firmate.

Hmm, beh, mi hai preso su questo. Leggerò il tuo codice come dicendo al compilatore di promuovere la moltiplicazione a 64 bit. smull è due operandi a 32 bit che danno un risultato a 64 bit, che non è quello che il tuo codice sta chiedendo .... ma sia gcc che clang hanno usato comunque il smull, anche se l'ho lasciato come una funzione non richiamata, quindi non lo sapeva a compilare il tempo in cui gli operandi non avevano cifre significative al di sopra dei 32, continuavano a usare lo smull.

Forse lo spostamento era la ragione.

Sì, era così ..

int mul_test04 (int a, int b) 
{ 
    int c; 
    c = ((long long)a*b) >> 31; 
    return(c); 
} 

sia gcc e clang (ben clang ricicla r0 e r1 invece di usare r2 e r3)

08000340 <mul_test04>: 
8000340: fb81 2300 smull r2, r3, r1, r0 
8000344: 0fd0  lsrs r0, r2, #31 
8000346: ea40 0043 orr.w r0, r0, r3, lsl #1 
800034a: 4770  bx lr 

ma questo

int mul_test04 (int a, int b) 
{ 
    int c; 
    c = ((long long)a*b); 
    return(c); 
} 

dà questo

gcc:

08000340 <mul_test04>: 
8000340: fb00 f001 mul.w r0, r0, r1 
8000344: 4770  bx lr 
8000346: bf00  nop 

clang:

0800048c <mul_test04>: 
800048c: 4348  muls r0, r1 
800048e: 4770  bx lr 

Così con il bit spostare i compilatori rendono conto che si è interessati solo nella porzione superiore del risultato in modo che possano scartare la porzione superiore degli operandi che significa Smull può essere usato.

Ora, se si esegue questa operazione:

int mul_test04 (int a, int b) 
{ 
    int c; 
    c = ((long long)a*b) >> 32; 
    return(c); 
} 

entrambi i compilatori si fanno ancora più intelligente, in particolare clang:

0800048c <mul_test04>: 
800048c: fb81 1000 smull r1, r0, r1, r0 
8000490: 4770  bx lr 

gcc:

08000340 <mul_test04>: 
8000340: fb81 0100 smull r0, r1, r1, r0 
8000344: 4608  mov r0, r1 
8000346: 4770  bx lr 

posso vedere che 0x40000000 considerato un galleggiante in cui si tiene traccia della posizione decimale e tale posizione è una posizione fissa. 0x20000000 avrebbe senso come risposta. Non posso ancora decidere se quel turno a 31 bit funziona universalmente o solo per questo caso.

Un esempio completo utilizzato per quanto sopra è qui

https://github.com/dwelch67/stm32vld/tree/master/stm32f4d/sample01

e ho eseguito su uno STM32F4 per verificare il funzionamento ed i risultati.

EDIT:

Se si passa i parametri nella funzione invece di hardcoding all'interno della funzione:

int myfun (int a, int b) 
{ 
    return(a+b); 
} 

Il compilatore è costretto a rendere il codice runtime invece di ottimizzare la risposta in fase di compilazione.

Ora, se si chiama quella funzione da un'altra funzione con i numeri hardcoded:

... 
c=myfun(0x1234,0x5678); 
... 

In questa funzione di chiamata al compilatore può scegliere di calcolare la risposta e solo metterlo lì al momento della compilazione. Se la funzione myfun() è globale (non dichiarata come statica) il compilatore non sa se un altro codice da collegare in seguito lo userà in modo tale che anche vicino al punto di chiamata in questo file si ottimizzi una risposta che deve ancora produrre la funzione effettiva e lasciatelo nell'oggetto per chiamare un altro codice in altri file, quindi è ancora possibile esaminare cosa fa il compilatore/ottimizzatore con quel codice C. A meno che non si usi llvm, ad esempio, dove è possibile ottimizzare l'intero progetto (attraverso i file), il codice esterno che chiama questa funzione utilizzerà la funzione reale e non una risposta calcolata in base al tempo di compilazione.

gcc e clang hanno fatto quello che sto descrivendo, ha lasciato il codice runtime per la funzione come funzione globale, ma all'interno del file ha calcolato la risposta al momento della compilazione e ha inserito la risposta codificata nel codice invece di chiamare la funzione:

int mul_test04 (int a, int b) 
{ 
    int c; 
    c = ((long long)a*b) >> 31; 
    return(c); 
} 

in un'altra funzione nello stesso file:

hexstring(mul_test04(0x40000000,0x40000000),1); 

la funzione stessa è implementato nel codice:

0800048c <mul_test04>: 
800048c: fb81 1000 smull r1, r0, r1, r0 
8000490: 0fc9  lsrs r1, r1, #31 
8000492: ea41 0040 orr.w r0, r1, r0, lsl #1 
8000496: 4770  bx lr 

ma dove è chiamato hanno hardcoded la risposta perché avevano tutte le informazioni necessarie per farlo:

8000520: f04f 5000 mov.w r0, #536870912 ; 0x20000000 
8000524: 2101  movs r1, #1 
8000526: f7ff fe73 bl 8000210 <hexstring> 

Se non volete la risposta hardcoded è necessario utilizzare una funzione che non è nella stesso passaggio di ottimizzazione.

Manipolare il compilatore e l'ottimizzatore si riduce a un sacco di pratica e non è una scienza esatta poiché i compilatori e gli ottimizzatori sono in continua evoluzione (nel bene o nel male).
Isolando un piccolo bit di codice in una funzione si stanno causando problemi in un altro modo, è più probabile che funzioni più grandi necessitino di uno stack frame e rimuovano variabili dai registri allo stack mentre le funzioni più piccole potrebbero non essere necessarie. e gli ottimizzatori possono cambiare il modo in cui il codice viene implementato come risultato. Testando il frammento di codice in un modo per vedere cosa sta facendo il compilatore, quindi utilizzarlo in una funzione più ampia e non ottenere il risultato desiderato. Se c'è un'istruzione esatta o una sequenza di istruzioni che si desidera implementare .... Implementarle in assembler. Se avessi preso di mira una serie specifica di istruzioni in un set/processore di istruzioni specifico, evita il gioco, evita che il codice cambi quando cambi computer/compilatori/etc e usi semplicemente l'assemblatore per quella destinazione. se necessario ifdef o altrimenti usa le opzioni di compilazione condizionale per costruire per target diversi senza l'assemblatore.

+0

Ho utilizzato il compilatore gcc precedentemente noto come codici-libreria e clang da llvm versione 3.0. –

+2

Sembra che mi sia sbagliato. CodeWarrior non sembra utilizzare GCC. Fa riferimento a un eseguibile chiamato mwccarm, che sembra essere un compilatore specifico di Freescale. Ho provato a definire la moltiplicazione in una funzione e ancora non posso fare in modo che il compilatore usi il comando smull - in effetti il ​​comando umull è ancora lì. Proverò a scaricare gcc o clang e vedere cosa succede. – EpicAdv

+1

Non penso di riuscire a compilare una funzione autonoma con il compilatore. Sembra guardare indietro dove (e se) viene chiamato e ottimizzato. Come posso costringerlo a compilare la funzione generica come stai facendo qui? – EpicAdv

-1

GCC supporta i tipi di punto fisso effettivi: http://gcc.gnu.org/onlinedocs/gcc/Fixed_002dPoint.html

Non sono sicuro di quale istruzione verrà utilizzato, ma potrebbe rendere la vita più facile.

+1

Dalla pagina collegata: "Non tutti i target supportano i tipi a virgola fissa". Nella mia esperienza, pochissimi bersagli supportano i tipi a virgola fissa. –

Problemi correlati