ひとり勉強会

ひとり楽しく勉強会

set_sequence_stackcaching

YARV Maniacs【第8回】 によると「性能上の問題から、現時点では有効にできないようにしてあります」とのことです。一応ソースだけ読んでみました。

スタックの先頭付近最大2個分だけ特別な領域(AレジスタとBレジスタ)に持っておいて、スタック操作命令の数を減らしたりしようという最適化です。yarv-dev:354 のささださんの投稿がわかりやすかったです。
具体的には、各命令を「レジスタに値が載ってない場合バージョン」「Aレジスタにスタック先頭の値が載ってる場合バージョン」「Bレジスタに(以下同文」「Aに先頭、Bに2番目が載ってる場合バージョン」「Bに先頭、Aに2番目が載ってる場合バージョン」の5バージョンに増やします。

増やし方の定義テーブルは、ビルド時に"insns.def"(YARV命令の基本定義ファイル)から例によって "tool/insns2vm.rb" によって生成されます。(Makefileと同じディレクトリの、opt_sc.incというファイルです)。

static VALUE sc_insn_info[][SC_STATE_SIZE] = {
  {
SC_ERROR,
    BIN(nop_SC_xx_xx),
    BIN(nop_SC_ax_ax),
    BIN(nop_SC_bx_bx),
    BIN(nop_SC_ab_ab),
    BIN(nop_SC_ba_ba)},
  {
SC_ERROR,
    BIN(getlocal_SC_xx_ax),
    BIN(getlocal_SC_ax_ab),
    BIN(getlocal_SC_bx_ba),
    BIN(getlocal_SC_ab_ba),
    BIN(getlocal_SC_ba_ab)},
...

static VALUE sc_insn_next[] = {
  SCS_XX,
  SCS_XX,
  ...
  SCS_XX,
  SCS_XX,
  SCS_AX,
  SCS_BX,

sc_insn_infoが増えた命令テーブルで、sc_insn_nextは、あるレジスタ&スタック状態でどの命令を実行したらどういう状態に遷移するか、のテーブルです。この2つのテーブルを使って、全部の命令を書き換えていくのがset_sequence_stackcaching関数です。

static int
set_sequence_stackcaching(yarv_iseq_t *iseq, LINK_ANCHOR *anchor)
{
#if OPT_STACK_CACHING
  ...
      case BIN(pop):{
         switch (state) {
         case SCS_AX:
         case SCS_BX:
             state = SCS_XX;
             break;
         case SCS_AB:
             state = SCS_AX;
             break;
         case SCS_BA:
             state = SCS_BX;
             break;
         case SCS_XX:
             goto normal_insn;
         default:
             rb_bug("unreachable");
         }
         /* remove useless pop */
         REMOVE_ELEM(list);
         list = list->next;
         goto redo_point;
      }

詳細を見てもそのまんまで特にこれといった点もないので、テーブルを引いていない特別な場合、pop命令の部分だけ抜粋しました。状態の遷移がまず計算されています。popの場合、レジスタに値が載っている時は状態が変わるだけで何も実行時にすることがなくなるので、命令を丸ごと1個取り除けます。