- gcc(Cortex-M3)において、関数呼び出し後のSPの位置は必ず8byte境界にアライメントされていなければならないようだ。
これまでは4byteでアライメントされていればよいと思っていた。しかし、可変長引数としてfloatやdoubleを渡そうとすると、その値は8byte境界にアライメントされて関数に引き渡されることが判明した。例えば、int型5つとfloat型1つを関数に渡すと仮定すると、int型の最初の4つはレジスタR0、R1、R2、R3に、5つ目のintはスタックにpushされ、次のfloatはdouble型に変換された上でスタックを1つ(=1ワード=4バイト)飛ばして、次の8byte境界となるスタック上に8バイト値(double型に変換されているので)がpushされることが分かった。
そういえば、以前Cortex-M3関係で割り込み動作を調べているとき、ISRは常に8byteアライメントを要求していたような記憶が・・・・。もう、どこで知った情報か忘れてしまったけど。 - 上記のように、可変長引数を持つ関数にfloatを渡そうとすると、doubleに変換された上で8byteにアライメントされてpushされる(レジスタ渡しの場合は[R0,R1]ペアか、[R2,R3]ペアに入る)。ということで、渡すfloat型の引数より前にある引数が奇数個であった場合はレジスタなりスタックにパディングが入る可能性がある点に注意が必要だった。
なお、va_arg()で引数を一つずつ取り出すとき、コンパイラはdouble型を取り出す場合には自動的にパディングの存在を調べてスタックをずらして読み出すので(va_argで読み出す型と順番が間違っていない限り)問題は出ないようになっているようだ。パディングの存在自体はSPの末尾3bitを調べて調整する方法をとっているようだ。なので、もし万一、関数を呼び出すときのスタックが8byteアライメントになっておらずに4バイトずれていたとしたら、上のような可変長引数を持つ関数+float/double型引数を持つ呼び出し処理においてはおかしな動作になってしまう。 - 余談だけれど、↑のようなことに気がついたのはたまたま使っていたFreeRTOS v4.9.2に不具合があったからだ。
FreeRTOSのCortex-M3用portルーチンでタスクの初期化のところに不備があり、タスク関数が呼び出されたときのスタックポインタは8byteアライメントになっておらず、常に+4バイトずれていた。そのため、そのタスクから呼び出される関数はすべて+4バイトずれたスタックポインタを持っていて、通常の関数呼び出しでは問題はでないのだけれど、上のようなケースでは問題になることがあった。
ちなみに、FreeRTOS最新版(v7.1.1)では問題のportルーチンは対策されていた(タスク起動時の初期スタック状態を初期化する際に、余分にパディングワードを挿入するようになっていた)。
ああ、やっぱり組み込みは難しいなぁ。
0 件のコメント:
コメントを投稿