そのコードを最適化する際に発生する問題は次のとおりです。
08000328 <mul_test01>:
8000328: f04f 5000 mov.w r0, #536870912 ; 0x20000000
800032c: 4770 bx lr
800032e: bf00 nop
あなたのコードは実行時に何もしないので、オプティマイザーは最終的な答えを計算するだけです。
これ:
.thumb_func
.globl mul_test02
mul_test02:
smull r2,r3,r0,r1
mov r0,r3
bx lr
これで呼び出されます:
c = mul_test02(0x40000000,0x40000000);
0x10000000 を与える
正の数を使用しているため、UMULL は同じ結果を返します。オペランドと結果はすべて正であるため、符号付き/符号なしの違いにはなりません。
うーん、あなたはこれで私を手に入れました。私はあなたのコードを、コンパイラに乗算を 64 ビットに昇格するように指示していると読みます。smull は、64 ビットの結果を与える 2 つの 32 ビット オペランドです。これは、コードが求めているものではありません....しかし、gcc と clang の両方がとにかく smull を使用していました。コンパイル時にオペランドに 32 を超える有効数字がない場合でも、まだ smull が使用されていました。
シフトが原因だったのかもしれません。
うん、そうだった..
int mul_test04 ( int a, int b )
{
int c;
c = ((long long)a*b) >> 31;
return(c);
}
与える
gcc と clang の両方 (clang は r2 と r3 を使用する代わりに r0 と r1 をリサイクルします)
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
でもこれは
int mul_test04 ( int a, int b )
{
int c;
c = ((long long)a*b);
return(c);
}
これを与える
gcc:
08000340 <mul_test04>:
8000340: fb00 f001 mul.w r0, r0, r1
8000344: 4770 bx lr
8000346: bf00 nop
クラン:
0800048c <mul_test04>:
800048c: 4348 muls r0, r1
800048e: 4770 bx lr
したがって、ビットシフトにより、コンパイラは結果の上部のみに関心があることを認識し、オペランドの上部を破棄できます。つまり、smull を使用できます。
これを行う場合:
int mul_test04 ( int a, int b )
{
int c;
c = ((long long)a*b) >> 32;
return(c);
}
どちらのコンパイラもよりスマートになり、特に 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
0x40000000 は、小数点以下を追跡している浮動小数点数と見なされ、その場所は固定された場所であることがわかります。0x20000000 は答えとして理にかなっています。その31ビットシフトが普遍的に機能するのか、それともこの1つのケースだけで機能するのかはまだわかりません.
上記に使用される完全な例はこちら
https://github.com/dwelch67/stm32vld/tree/master/stm32f4d/sample01
stm32f4で実行して、動作と結果を確認しました。
編集:
関数内でパラメーターをハードコーディングする代わりに、パラメーターを関数に渡す場合:
int myfun ( int a, int b )
{
return(a+b);
}
コンパイラは、コンパイル時に答えを最適化するのではなく、ランタイム コードを作成することを余儀なくされます。
ハードコードされた数値を使用して別の関数からその関数を呼び出すと、次のようになります。
...
c=myfun(0x1234,0x5678);
...
この呼び出し関数では、コンパイラは答えを計算し、コンパイル時にそこに配置することを選択できます。myfun() 関数がグローバル (静的として宣言されていない) である場合、コンパイラは、後でリンクされる他のコードがそれを使用するかどうかを認識しないため、このファイルの呼び出しポイントの近くでも応答を最適化し、実際の関数を生成する必要があります。他のファイルの他のコードが呼び出されるようにオブジェクトに残しておくと、コンパイラ/オプティマイザがそのCコードで何をするかを調べることができます。たとえば、プロジェクト全体を (ファイル間で) 最適化できる llvm を使用しない限り、この関数を呼び出す外部コードは、コンパイル時に計算された答えではなく、実際の関数を使用します。
gcc と clang の両方が私が説明していることを実行し、関数の実行時コードをグローバル関数として残しましたが、ファイル内ではコンパイル時に答えを計算し、関数を呼び出す代わりにコードにハードコーディングされた答えを配置しました。
int mul_test04 ( int a, int b )
{
int c;
c = ((long long)a*b) >> 31;
return(c);
}
同じファイル内の別の関数で:
hexstring(mul_test04(0x40000000,0x40000000),1);
関数自体はコードに実装されています。
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
しかし、それが呼ばれる場所では、必要なすべての情報を持っていたので、答えをハードコーディングしました:
8000520: f04f 5000 mov.w r0, #536870912 ; 0x20000000
8000524: 2101 movs r1, #1
8000526: f7ff fe73 bl 8000210 <hexstring>
ハードコーディングされた回答が必要ない場合は、同じ最適化パスにない関数を使用する必要があります。
コンパイラーとオプティマイザーの操作は多くの実践に帰着しますが、コンパイラーとオプティマイザーは常に (良くも悪くも) 進化しているため、正確な科学ではありません。
別の方法で問題を引き起こしている関数内の小さなコードを分離することにより、大きな関数はスタック フレームを必要とする可能性が高くなり、変数がレジスタからスタックに追い出されます。小さな関数はそれを行う必要がない可能性があり、オプティマイザは、結果としてコードの実装方法を変更する場合があります。コードフラグメントを 1 つの方法でテストして、コンパイラが何をしているかを確認してから、それをより大きな関数で使用すると、必要な結果が得られません。実装したい正確な命令または一連の命令がある場合....それらをアセンブラで実装します。特定の命令セット/プロセッサの特定の命令セットをターゲットにしている場合は、ゲームを避け、コンピューター/コンパイラ/などを変更するときにコードを変更しないようにし、そのターゲットにはアセンブラを使用してください。