組み込みの埋まってるとこ

システム寄りの組み込みCプログラミングBLOG

組み込みでlongjmp/setjmpは使えるか?

構造化言語でなくなる、よく分からない等の理由でなんとなく嫌われ者のlongjmp/setjmpですが、 その気になれば例外処理やスレッドも実装できる知る人ぞ知る便利な関数でもあります。 そんなlongjmp/setjmpが組み込みでも使用できるのかを調べてみました。

longjmp/setjmpの動作

簡単にlogjmp/setjmpの動作を説明すると、setjmpでラベルを付けて、longjmpでそのラベルへ分岐します。 C言語のgotoは関数の外側へ分岐することはできないのですが、longjmpはどこへでも分岐できるところが違います。

longjmp/setjmpの使用例としてC++の例外処理のようなことをするプログラムを示します。

#include <setjmp.h>

void func();
jmp_buf except;

int main()
{
    if (setjmp(except) == 0) { /* try */
        func();
    } else { /* catch */
        // ...
        return 0;
    }

    return 1;
}

void func()
{
    // ...
    if (fatal_error) {
        longjmp(except, 1); /* throw */
    }
    // ...
}

setjmpでの処理

longjmpで関数の外側へ分岐してしまうと関数は呼び出し元へ戻るものという構造が壊れてしまいます。 そこでsetjmpで戻り位置を決めたときタスクを切り替えるようにその場でレジスタを退避します。

実際にarm-none-eabi-gccのlongjmp/setjmpがどんな処理をしているかを調べた結果が次のアセンブラです。 setjmp関数の中でstm命令でスタックにレジスタを退避し、 longmp関数の中でldm命令で退避したレジスタを復旧していることが分かります。

0000802c <setjmp>:
    802c:   46ec        mov ip, sp
    802e:   e8a0 5ff0   stmia.w r0!, {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr}
    8032:   f04f 0000   mov.w   r0, #0
    8036:   4770        bx  lr

00008038 <longjmp>:
    8038:   e8b0 5ff0   ldmia.w r0!, {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr}
    803c:   46e5        mov sp, ip
    803e:   0008        movs    r0, r1
    8040:   bf08        it  eq
    8042:   2001        moveq   r0, #1
    8044:   4770        bx  lr
    8046:   bf00        nop

組み込みでlongjmp/setjmpは使えるか?

ライブラリにlongjmp/setjmpが存在しているようなので、組み込みでも使えないことはなさそうです。

しかしよく見ると浮動小数レジスタが退避されていません。 s0-s15は壊してもかまわない呼び出し規約になっているのですが、SIMD拡張を使うとs16-s31が使われるようになるのでこのとき問題になりそうです。

もしかするとPCで使うようなリッチなOSでは毎回浮動小数レジスタを退避しているとオーバーヘッドになるので、 浮動小数点命令が実行されたときだけCPU例外を起こしてOSが浮動小数レジスタの退避を代行するような仕組みがあるのかもしれません。

しかしRTOSでそういうことをしているのは見たことがありません。 タスクの属性にFPU例外が設定できるようになっているとか、 FPUにvfpv3-d16を明示してSIMD拡張を禁止していればまだましな方で、 最初から浮動小数点演算がハードでできるマイコンは考慮されていません、ましてやSIMD拡張なんて、というのがほとんどです。

まとめ

arm-none-eabi-gccでlongjmp/setjmpは使えそうです。 ですがSIMD拡張があるマイコン浮動小数レジスタをすべて使い切るようなヘビーなプログラムをするときは s16-s31が退避されないことを念頭において慎重にプログラミングしましょう。