BREW で MicroThread(2)

MicroThread は、スタック切り替えで関数を実行途中で停止出来るようにすることによって実現出来ます。
id:yaneurao さんのやね本2にこの実装が詳しく書かれています。

自分もほとんど同じ実装だったり……。


当然スタック切り替えなんて C++ では出来ないので、ARM アセンブラで書くことになります。

    AREA ||i.SwitchThread||, CODE, READONLY
    EXPORT SwitchThread
SwitchThread
    ; r0 には MicroThread へのポインタが入っている
    STMFD           sp!,{r0-r12,lr}     ; sp と pc 以外のレジスタをバックアップ
    
    ADD             r4,r0,#4            ; r0 は仮想テーブルの位置なので、
                                        ; +4 してスタックへのポインタを取得する
    SWP             sp,sp,[r4]          ; スタックポインタを交換(`・ω・´)
    
    LDMFD           sp!,{r0-r12,lr}     ; sp と pc 以外のレジスタをリストア
    
    MOV             pc,lr               ; (・∀・)カエレ!!

確かバックアップを取る必要のないレジスタもあったと思うんですが、どうせ1命令なので(スタックポインタとプログラムカウンタ以外)全部保存してしまってます。
で、SWP でスタックポインタを交換して、バックアップしておいたレジスタを復元するだけです。
あと、最初は組み込みアセンブラを使おうと思っていたんですが、どうやら RVCT for BREW v1.2 だと対応してないっぽいです。
なので結局普通の(?)アセンブラです(;´Д`)


以下が現在の実装。

class Runnable{
public:
    virtual ~Runnable(){}
    virtual void run() = 0;
};

class MicroThread : public Runnable{
public:
    MicroThread();
    virtual ~MicroThread();
    
    virtual void start();
    virtual void suspend();
    virtual void resume();
    
    virtual bool isExecute();
    virtual bool isSuspend();
private:
    byte*       _pStack;        // スタックへのポインタ
    int         _bExecute;      // ARM asm で書きやすいので int 型
    byte        _stack[ 1024 * 32 ];
    bool        _bSuspend;
    
    static void StartCallback( MicroThread* pThread );
};
extern "C" void StartThread( MicroThread* , void (*callback)( MicroThread* ) );
extern "C" void SwitchThread( MicroThread* );

MicroThread::MicroThread() : _pStack( null ) , _bSuspend( false ) , _bExecute( 0 ){
}

MicroThread::~MicroThread(){
}

void MicroThread::start(){
    if( isExecute() ) return;
    
    _pStack = &_stack[ 1024 * 32 ];
    
    StartThread( this , &StartCallback );
}

void MicroThread::suspend(){
    if( isSuspend() || !isExecute() ) return;
    
    _bSuspend = true;
    
    SwitchThread( this );
}

void MicroThread::resume(){
    if( !isSuspend() ) return;
    
    _bSuspend = false;
    
    SwitchThread( this );
}

bool MicroThread::isExecute(){
    return (_bExecute != 0);
}

bool MicroThread::isSuspend(){
    return _bSuspend;
}

void MicroThread::StartCallback( MicroThread* pThread ){
    pThread->run();
}
    AREA ||i.StartThread||, CODE, READONLY
    EXPORT StartThread
StartThread
    ; r0 には MicroThread へのポインタが入っている
    ; r1 にはコールバック関数へのポインタが入っている
    
    STMFD           sp!,{r0-r12,lr}     ; sp と pc 以外のレジスタをバックアップ
    
    ADD             r4,r0,#4            ; r0 は仮想テーブルの位置なので、
                                        ; +4 してスタックへのポインタを取得する
    SWP             sp,sp,[r4]          ; スタックポインタを交換(`・ω・´)
    
    MOV             r5,#1               ; 実行状態を実行中に変更(_bExecute = 1)
    STR             r5,[r0,#8]          ; r0 + 8 の場所に _bExecute(b を付けてるけどint型) がある
    
    STR             r0,[sp,#-4]!        ; スタックに r0 をプッシュ
    
    MOV             lr,pc               ; BLX が未サポートらしいので、
    BX              r1                  ; lr を保存して BX でジャンプ
                                        ; 引数は r0
    
    ; ここから下は正常に run() を抜けた場合しか通らない
    ; なぜなら、run() を実行して1回目の suspend() で SwitchThread を実行して、
    ; 最後の MOV で StartThread の呼び出し元へジャンプするから。
    
    LDR             r0,[sp],#4          ; スタックから r0 をポップ
    
    MOV             r5,#0               ; 実行状態を非実行中に変更(_bExecute = 0)
    STR             r5,[r0,#8]          ; r0 + 8 の場所に _bExecute(b を付けてるけどint型) がある
    
    ADD             r4,r0,#4            ; r0 は仮想テーブルの位置なので、
                                        ; +4 してスタックへのポインタを取得する
    SWP             sp,sp,[r4]          ; スタックポインタを交換(`・ω・´)
    
    LDMFD           sp!,{r0-r12,lr}     ; sp と pc 以外のレジスタをリストア
    
    MOV             pc,lr               ; (・∀・)カエレ!!



    AREA ||i.SwitchThread||, CODE, READONLY
    EXPORT SwitchThread
SwitchThread
    ; r0 には MicroThread へのポインタが入っている
    
    STMFD           sp!,{r0-r12,lr}     ; sp と pc 以外のレジスタをバックアップ
    
    ADD             r4,r0,#4            ; r0 は仮想テーブルの位置なので、
                                        ; +4 してスタックへのポインタを取得する
    SWP             sp,sp,[r4]          ; スタックポインタを交換(`・ω・´)
    
    LDMFD           sp!,{r0-r12,lr}     ; sp と pc 以外のレジスタをリストア
    
    MOV             pc,lr               ; (・∀・)カエレ!!



    END

MALLOC() してやるのが面倒なので(ぇ、メンバに MicroThread 用のスタックを持っています。
で、使ってない方のスタックポインタを _pStack に保存しておいて、suspend() や resume() が呼び出されるたびに _pStack とスタックポインタを交換してやってます。


短いソースなので実装はそんなに難しくないんですが、どっちかっていうとアセンブラの書き方がわからなくて苦戦したという罠(;´Д`)


あとは昨日のデストラクタが呼ばれない問題か……ARM は例外が使えないし、どうしよう(;´Д`)

2009/06/27 追記

Boost.Coroutine を BREW で使えるようにしてみました。

これは例外を有効にする必要がありますが……。