私の実装したForth、moiforthは、内部インタプリタをスイッチスレッディングで実装している。
switch (prog[prog_cnt]) { case CODE_PUSH: 処理 break; case CODE_PLUS: 処理 break;
これを、コールスレッディングに変える。コールスレッディングに変えると、ワード処理は個別の関数に分けられ、それを呼び出すのは次のような処理になる:
while (1) { (*prog[prog_cnt])(); }
なにがうれしいかと言うと、
- 内部インタプリタがコンパクトになる
- 処理が個々の関数に分けられ、すっきりする
ただ、うれしくない部分もあり、
- 遅くなる。スイッチスレッディングは分岐だが、コールスレッディングはサブルーチンコールになるため
いろいろ悩んだが、コールスレッディングにした。なぜなら、まだ書いたことがない、から。
スレッディングについては、以下に詳しく書かれている。
上記のコールスレッディングは、直接スレッディングなので、ジャンプ先アドレスがそのまま入っているが、これから作るのは間接スレッディングなので、以下のようになる。
while (1) { (*jump_table[prog[prog_cnt++]])(); }
当然直接スレッディングより遅い。しかし、prog[]のコードを番地のサイズより小さくすれば、メモリ効率が良くなる。PCではそんなことは関係ないが、組み込み用途だと意味がある。かも。
実際のコードは、こうなった。
while (prog[prog_cnt] != CODE_END) { (*jump_table[prog[prog_cnt++]])(); }
ループを抜けるには、プログラムの終了判定をしなければならない。アセンブラならループの外にジャンプするだけで済むが、コールスレッディングだと、こうせざるを得ない。そのぶん遅くなる。
つぎに、ワードの処理を関数単位に分ける。doを例にすると、こうなる。
/** * ワード'do'を実行する */ void call_do(void) { loop_pos--; /* スタックの内容をループスタックに入れる */ loop_cnt_stack[loop_pos] = pop(); loop_end_stack[loop_pos] = pop(); }
さらに、ジャンプ先となる関数ポインタの配列を作った。
void (*jump_table[])(void) = { /**< ワードのジャンプ先テーブル */ NULL, call_push, plus, print, word_if, word_else, word_then, call_do, call_loop, call_i, call_cr, call_word, proc_ret };
新しいスレッディングのmoiforthを、テストする。testfile以下のプログラムを次々テストしていくが、問題ない。というわけで、スイッチスレッディングからコールスレッディングの変更が終わった。
moiforth-0.8.zip