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);
}
dà
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.
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