思うだけで学ばない日記 2.0

思うだけで学ばない日記から移転しました☆!よろしくお願いします。

マイクロスレッド基本おさらい(2)

上記仮定の下で、exec(), suspend(), resume()は約こんなコードでいいはず。

void exec(TCtx *p_ctx, void (*bar)()) {
  PUSH S1
  PUSH S2
  ...
  PUSH Sm
  PUSH PR
  p_ctx->ssp = p_ctx->stk;
  swap(SP, &(p_ctx->ssp));
  PR = bar;
  J PR
}

void suspend(TCtx *p_ctx) {
  PUSH S1
  PUSH S2
  ...
  PUSH Sm
  PUSH PR
  swap(SP, &(p_ctx->ssp));
  POP PR
  POP Sm
  ...
  POP S2
  POP S1
  J PR
}

void resume(TCtx* p_ctx) {
  (suspend()と同じ)
}

ここで、

  • TCtx: ctxの型(sspとstk[]をメンバとしてもつ)
  • SP: スタックポインタ
  • PR: 関数からの戻り値を保持するレジスタ(既出)
  • S1〜Sm: 汎用レジスタR1〜Rnのうち、関数呼び出し規約により、関数呼び出し前後で保存される(せねばならない)もの
  • PUSH : レジスタの値をスタックにpushする
  • POP : スタックに退避された値をスタックからpopしてレジスタにロードする
  • J : レジスタに格納されたアドレスにジャンプする
  • swap(SP, p): pをSPの型へのポインタとして、SPと*pの値を互いに交換する

Cとアセンブリ言語がちゃんぽんになったような怪しい仮想コードだが、意味は多分明白だと思う、、
(ので構文要素についてのこれ以上の説明はしない。)

果たしてこんなロジックで動くのか?
(つづく)

…いやちょっとその前に、p_ctx->ssp = p_ctx->stkやswap()の実現方法と“J PR”についてちゃんと考えとこう、、

【p_ctx->ssp = p_ctx->stkの実現方法】

これは、空いてる汎用レジスタRt,Ruを使って次のように書けるはず。

Rt = &(p_ctx->ssp)  /* (1) */
Ru = &(p_ctx->stk)  /* (2) */
Rt = Ru;            /* (3) */

(1)の時点でp_ctxの値はレジスタに乗っている(∵仮定2.)ので、(1)はRt=p_ctx+(定数)という単なるadd命令で済む。(2)も同様。(3)はメモリへのレジスタ内容のストア。実現性は問題ないはず。レジスタRt, Ruが破壊される件については、そのタイミングがPUSH後かつPOP前なので問題ないはず。

【swap()の実現方法】

swap()は、空いてる汎用レジスタRtを使って次のように書けるはず。

Rt = p_ctx->ssp     /* (1) */
p_ctx->ssp = SP     /* (2) */
SP = Rt             /* (3) */

(1)は、p_ctxをベースアドレスとする固定ディスプレースメントなインデックス参照、かつ、p_ctxの値はレジスタに乗っている(∵仮定2.)。(2)も同様。(3)は単なるレジスタ間のmoveとしてそれぞれ実現される。いずれもCが走るまっとうなCPUなら備えてるはずの命令の範囲内なので、実現性は問題ないはず。レジスタRtが破壊される件については、そのタイミングがPUSH後かつPOP前なので問題ないはず。

【“J PR”を使う関数呼び出し規約】

仮定3.により、今考えている呼び出し規約では、“J PR”が関数からのreturnとして働く(通常は)。この規約の下で、関数呼び出しは次のロジックにより実現される(bar()を呼び出す例)。

Rt = (bar()のアドレス)
PR = PC + s         /* (4) */
J Rt                /* (5) */

(4)の直後、PRは(5)の“J Rt”の直後(+CPUによっては遅延スロットサイズ)を指し、(5)の“J Rt”でbar()が呼ばれる*1

で、多重呼び出し(と呼び出し先からのリターン)も併せると、結局次のようなロジックとなる。

foo() {
  ...
  Rt = (bar()のアドレス)
  PR = PC + s
  J Rt
  ...
}

bar() {
  PUSH PR
  ...
  Rt = (baz()のアドレス)
  PR = PC + s
  J Rt
  ...
  POP PR
  J PR
}

baz() {
  ...
  J PR
}

このように多重呼び出しも矛盾無くできる。

(つづく)

*1:MIPSやSHでは(4)〜(5)を行う単一命令が用意されているのを確か見た!