吉里吉里2 TJS 2 VMのdispatch loop


少し吉里吉里2.28のソースコードの中でTJS 2 VMの実行メカニズムを探して、主にdispatch loopの実現に着目して、しかも以下のコードを見つけました:
kirikiri2\src\core\tjs2\tjsInterCodeExec.cpp/972:

tjs_int tTJSInterCodeContext::ExecuteCode(tTJSVariant *ra_org, tjs_int startip,
    tTJSVariant **args, tjs_int numargs, tTJSVariant *result)
{
    // execute VM codes
    tjs_int32 *codesave;
    try
    {
        tjs_int32 *code = codesave = CodeArea + startip;

        if(TJSStackTracerEnabled()) TJSStackTracerSetCodePointer(CodeArea, &codesave);

        tTJSVariant *ra = ra_org;
        tTJSVariant *da = DataArea;

        bool flag = false;

        while(true)
        {
            codesave = code;
            switch(*code)
            {
            case VM_NOP:
                code ++;
                break;

            case VM_CONST:
                TJS_GET_VM_REG(ra, code[1]).CopyRef(TJS_GET_VM_REG(da, code[2]));
                code += 3;
                break;

            case VM_CP:
                TJS_GET_VM_REG(ra, code[1]).CopyRef(TJS_GET_VM_REG(ra, code[2]));
                code += 3;
                break;

            case VM_CL:
                TJS_GET_VM_REG(ra, code[1]).Clear();
                code += 2;
                break;

            case VM_CCL:
                ContinuousClear(ra, code);
                code += 3;
                break;

            case VM_TT:
                flag = TJS_GET_VM_REG(ra, code[1]).operator bool();
                code += 2;
                break;

            case VM_TF:
                flag = !(TJS_GET_VM_REG(ra, code[1]).operator bool());
                code += 2;
                break;

            case VM_CEQ:
                flag = TJS_GET_VM_REG(ra, code[1]).NormalCompare(
                    TJS_GET_VM_REG(ra, code[2]));
                code += 3;
                break;

            case VM_CDEQ:
                flag = TJS_GET_VM_REG(ra, code[1]).DiscernCompare(
                    TJS_GET_VM_REG(ra, code[2]));
                code += 3;
                break;

            case VM_CLT:
                flag = TJS_GET_VM_REG(ra, code[1]).GreaterThan(
                    TJS_GET_VM_REG(ra, code[2]));
                code += 3;
                break;

            case VM_CGT:
                flag = TJS_GET_VM_REG(ra, code[1]).LittlerThan(
                    TJS_GET_VM_REG(ra, code[2]));
                code += 3;
                break;

            case VM_SETF:
                TJS_GET_VM_REG(ra, code[1]) = flag;
                code += 2;
                break;

            case VM_SETNF:
                TJS_GET_VM_REG(ra, code[1]) = !flag;
                code += 2;
                break;

            case VM_LNOT:
                TJS_GET_VM_REG(ra, code[1]).logicalnot();
                code += 2;
                break;

            case VM_NF:
                flag = !flag;
                code ++;
                break;

            case VM_JF:
                if(flag)
                    TJS_ADD_VM_CODE_ADDR(code, code[1]);
                else
                    code += 2;
                break;

            case VM_JNF:
                if(!flag)
                    TJS_ADD_VM_CODE_ADDR(code, code[1]);
                else
                    code += 2;
                break;

            case VM_JMP:
                TJS_ADD_VM_CODE_ADDR(code, code[1]);
                break;

            case VM_INC:
                TJS_GET_VM_REG(ra, code[1]).increment();
                code += 2;
                break;

            case VM_INCPD:
                OperatePropertyDirect0(ra, code, TJS_OP_INC);
                code += 4;
                break;

            case VM_INCPI:
                OperatePropertyIndirect0(ra, code, TJS_OP_INC);
                code += 4;
                break;

            case VM_INCP:
                OperateProperty0(ra, code, TJS_OP_INC);
                code += 3;
                break;

            case VM_DEC:
                TJS_GET_VM_REG(ra, code[1]).decrement();
                code += 2;
                break;

            case VM_DECPD:
                OperatePropertyDirect0(ra, code, TJS_OP_DEC);
                code += 4;
                break;

            case VM_DECPI:
                OperatePropertyIndirect0(ra, code, TJS_OP_DEC);
                code += 4;
                break;

            case VM_DECP:
                OperateProperty0(ra, code, TJS_OP_DEC);
                code += 3;
                break;

#define TJS_DEF_VM_P(vmcode, rope) \
            case VM_##vmcode: \
                TJS_GET_VM_REG(ra, code[1]).rope(TJS_GET_VM_REG(ra, code[2])); \
                code += 3; \
                break; \
            case VM_##vmcode##PD: \
                OperatePropertyDirect(ra, code, TJS_OP_##vmcode); \
                code += 5; \
                break; \
            case VM_##vmcode##PI: \
                OperatePropertyIndirect(ra, code, TJS_OP_##vmcode); \
                code += 5; \
                break; \
            case VM_##vmcode##P: \
                OperateProperty(ra, code, TJS_OP_##vmcode); \
                code += 4; \
                break

                TJS_DEF_VM_P(LOR, logicalorequal);
                TJS_DEF_VM_P(LAND, logicalandequal);
                TJS_DEF_VM_P(BOR, operator |=);
                TJS_DEF_VM_P(BXOR, operator ^=);
                TJS_DEF_VM_P(BAND, operator &=);
                TJS_DEF_VM_P(SAR, operator >>=);
                TJS_DEF_VM_P(SAL, operator <<=);
                TJS_DEF_VM_P(SR, rbitshiftequal);
                TJS_DEF_VM_P(ADD, operator +=);
                TJS_DEF_VM_P(SUB, operator -=);
                TJS_DEF_VM_P(MOD, operator %=);
                TJS_DEF_VM_P(DIV, operator /=);
                TJS_DEF_VM_P(IDIV, idivequal);
                TJS_DEF_VM_P(MUL, operator *=);

#undef TJS_DEF_VM_P

            case VM_BNOT:
                TJS_GET_VM_REG(ra, code[1]).bitnot();
                code += 2;
                break;

            case VM_ASC:
                CharacterCodeOf(TJS_GET_VM_REG(ra, code[1]));
                code += 2;
                break;

            case VM_CHR:
                CharacterCodeFrom(TJS_GET_VM_REG(ra, code[1]));
                code += 2;
                break;

            case VM_NUM:
                TJS_GET_VM_REG(ra, code[1]).tonumber();
                code += 2;
                break;

            case VM_CHS:
                TJS_GET_VM_REG(ra, code[1]).changesign();
                code += 2;
                break;

            case VM_INV:
                TJS_GET_VM_REG(ra, code[1]) =
                    (TJS_GET_VM_REG(ra,
                    code[1]).AsObjectClosureNoAddRef().Invalidate(0,
                    NULL, NULL, ra[-1].AsObjectNoAddRef()) == TJS_S_TRUE);
                code += 2;
                break;

            case VM_CHKINV:
                TJS_GET_VM_REG(ra, code[1]) =
                    TJSIsObjectValid(TJS_GET_VM_REG(ra,
                    code[1]).AsObjectClosureNoAddRef().IsValid(0,
                    NULL, NULL, ra[-1].AsObjectNoAddRef()));
                code += 2;
                break;

            case VM_INT:
                TJS_GET_VM_REG(ra, code[1]).ToInteger();
                code += 2;
                break;

            case VM_REAL:
                TJS_GET_VM_REG(ra, code[1]).ToReal();
                code += 2;
                break;

            case VM_STR:
                TJS_GET_VM_REG(ra, code[1]).ToString();
                code += 2;
                break;

            case VM_OCTET:
                TJS_GET_VM_REG(ra, code[1]).ToOctet();
                code += 2;
                break;

            case VM_TYPEOF:
                TypeOf(TJS_GET_VM_REG(ra, code[1]));
                code += 2;
                break;

            case VM_TYPEOFD:
                TypeOfMemberDirect(ra, code, TJS_MEMBERMUSTEXIST);
                code += 4;
                break;

            case VM_TYPEOFI:
                TypeOfMemberIndirect(ra, code, TJS_MEMBERMUSTEXIST);
                code += 4;
                break;

            case VM_EVAL:
                Eval(TJS_GET_VM_REG(ra, code[1]),
                    TJSEvalOperatorIsOnGlobal ? NULL : ra[-1].AsObjectNoAddRef(),
                    true);
                code += 2;
                break;

            case VM_EEXP:
                Eval(TJS_GET_VM_REG(ra, code[1]),
                    TJSEvalOperatorIsOnGlobal ? NULL : ra[-1].AsObjectNoAddRef(),
                    false);
                code += 2;
                break;

            case VM_CHKINS:
                InstanceOf(TJS_GET_VM_REG(ra, code[2]),
                    TJS_GET_VM_REG(ra, code[1]));
                code += 3;
                break;

            case VM_CALL:
            case VM_NEW:
                code += CallFunction(ra, code, args, numargs);
                break;

            case VM_CALLD:
                code += CallFunctionDirect(ra, code, args, numargs);
                break;

            case VM_CALLI:
                code += CallFunctionIndirect(ra, code, args, numargs);
                break;

            case VM_GPD:
                GetPropertyDirect(ra, code, 0);
                code += 4;
                break;

            case VM_GPDS:
                GetPropertyDirect(ra, code, TJS_IGNOREPROP);
                code += 4;
                break;

            case VM_SPD:
                SetPropertyDirect(ra, code, 0);
                code += 4;
                break;

            case VM_SPDE:
                SetPropertyDirect(ra, code, TJS_MEMBERENSURE);
                code += 4;
                break;

            case VM_SPDEH:
                SetPropertyDirect(ra, code, TJS_MEMBERENSURE|TJS_HIDDENMEMBER);
                code += 4;
                break;

            case VM_SPDS:
                SetPropertyDirect(ra, code, TJS_MEMBERENSURE|TJS_IGNOREPROP);
                code += 4;
                break;

            case VM_GPI:
                GetPropertyIndirect(ra, code, 0);
                code += 4;
                break;

            case VM_GPIS:
                GetPropertyIndirect(ra, code, TJS_IGNOREPROP);
                code += 4;
                break;

            case VM_SPI:
                SetPropertyIndirect(ra, code, 0);
                code += 4;
                break;

            case VM_SPIE:
                SetPropertyIndirect(ra, code, TJS_MEMBERENSURE);
                code += 4;
                break;

            case VM_SPIS:
                SetPropertyIndirect(ra, code, TJS_MEMBERENSURE|TJS_IGNOREPROP);
                code += 4;
                break;

            case VM_GETP:
                GetProperty(ra, code);
                code += 3;
                break;

            case VM_SETP:
                SetProperty(ra, code);
                code += 3;
                break;

            case VM_DELD:
                DeleteMemberDirect(ra, code);
                code += 4;
                break;

            case VM_DELI:
                DeleteMemberIndirect(ra, code);
                code += 4;
                break;

            case VM_SRV:
                if(result) result->CopyRef(TJS_GET_VM_REG(ra, code[1]));
                code += 2;
                break;

            case VM_RET:
                return code+1-CodeArea;

            case VM_ENTRY:
                code = CodeArea + ExecuteCodeInTryBlock(ra, code-CodeArea + 3, args,
                    numargs, result, TJS_FROM_VM_CODE_ADDR(code[1])+code-CodeArea,
                    TJS_FROM_VM_REG_ADDR(code[2]));
                break;

            case VM_EXTRY:
                return code+1-CodeArea;  // same as ret

            case VM_THROW:
                ThrowScriptException(TJS_GET_VM_REG(ra, code[1]),
                    Block, CodePosToSrcPos(code-CodeArea));
                code += 2; // actually here not proceed...
                break;

            case VM_CHGTHIS:
                TJS_GET_VM_REG(ra, code[1]).ChangeClosureObjThis(
                    TJS_GET_VM_REG(ra, code[2]).AsObjectNoAddRef());
                code += 3;
                break;

            case VM_GLOBAL:
                TJS_GET_VM_REG(ra, code[1]) = Block->GetTJS()->GetGlobalNoAddRef();
                code += 2;
                break;

            case VM_ADDCI:
                AddClassInstanceInfo(ra, code);
                code+=3;
                break;

            case VM_REGMEMBER:
                RegisterObjectMember(ra[-1].AsObjectNoAddRef());
                code ++;
                break;

            case VM_DEBUGGER:
                TJSNativeDebuggerBreak();
                code ++;
                break;

            default:
                ThrowInvalidVMCode();
            }
        }
    }
    catch(eTJSSilent &e)
    {
        throw e;
    }
    catch(eTJSScriptException &e)
    {
        e.AddTrace(this, codesave-CodeArea);
        throw e;
    }
    catch(eTJSScriptError &e)
    {
        e.AddTrace(this, codesave-CodeArea);
        throw e;
    }
    catch(eTJS &e)
    {
        DisplayExceptionGeneratedCode(codesave - CodeArea, ra_org);
        TJS_eTJSScriptError(e.GetMessage(), this, codesave-CodeArea);
    }
    catch(exception &e)
    {
        DisplayExceptionGeneratedCode(codesave - CodeArea, ra_org);
        TJS_eTJSScriptError(e.what(), this, codesave-CodeArea);
    }
    catch(const wchar_t *text)
    {
        DisplayExceptionGeneratedCode(codesave - CodeArea, ra_org);
        TJS_eTJSScriptError(text, this, codesave-CodeArea);
    }
    catch(const char *text)
    {
        DisplayExceptionGeneratedCode(codesave - CodeArea, ra_org);
        TJS_eTJSScriptError(text, this, codesave-CodeArea);
    }
#ifdef TJS_SUPPORT_VCL
    catch(const EAccessViolation &e)
    {
        DisplayExceptionGeneratedCode(codesave - CodeArea, ra_org);
        TJS_eTJSScriptError(e.Message.c_str(), this, codesave-CodeArea);
    }
    catch(const Exception &e)
    {
        DisplayExceptionGeneratedCode(codesave - CodeArea, ra_org);
        TJS_eTJSScriptError(e.Message.c_str(), this, codesave-CodeArea);
    }
#endif

    return codesave-CodeArea;
}

やはりかなり典型的で直感的な解釈器です.このようなVMコード(caseごとにcodeの増分を決定する)を読み込み続け、単一のswitch文でdispatchを完了する方法は、基本解釈器実装で最も直感的であるが、通常は最も遅い方法である.
RISCなどの比較的小さな命令セットの場合、threaded codeは、各命令ルーチンの最後にジャンプ操作を追加することで、現代のCPUのジャンプ予測エラーを低減することができるため、通常、より良い解決策である.indirectedとdirectedの2つのタイプのthreadedingもある.
Anton Ertl
threaded codeのいい文章について .にある
Virtual Machines: Versatile Platforms for Systems and Processesの本には、より詳細な説明があります.
TJS 2 VMの既存の実装にはツッコミを入れたくなる点が少なくありません.例えば、引用カウントに基づくGCや、読みにくいコードなど......そんなに大量のマクロを使うのはつらいです.
実はW.Dee氏が、既存の吉里吉里2のcodebaseで修正せずに吉里吉里3のRisse VMを直接実現し始めたのも、おそらくこのcodebaseが乱れているからだろう.新しいRisse VMは、もともとよくない参照カウントGCの代わりにBoehm GCを使用するなど、実質的な改善が少なくありません.中間表示(IR)をSSA形式に変更するなど、そのままではTJS 2 VMを捨てるのももったいないです.TJS 2 VMの中で改善できるところをゆっくり掘り出して、改善に適しているかどうか見てみたいです.吉里吉里3のRisse VMが完成する前にTJS 2 VMを少しでも変更できれば、まだ価値があります.
しかし、吉里吉里シリーズ内のVMには、実行時の外観全体が解釈器のように見えます.すなわち、内部実装はテキスト形式のスクリプトソースコードを中間表現にコンパイルすることです.その後、VMによって実行されます(ここではVMは本格的な解釈器です).これはコンパイルの部分に対する要求が高く、時間のかかる最適化がうまく行われません.W.Dee氏が本当に完全なコンパイルを受け入れ、VMに渡して実行すると、楽になります.
(考えてみてください.Java CompilerとJVMをパッケージ化し、解釈スクリプトのようにJavaソースファイルを実行します.SunのJDKを使用している場合は、おめでとうございます.HelloWorldをコンパイルするのに30分かかるかもしれません.またJDK 1.6.0シリーズにはNoClassDefFoundErrorがよくあります.1.6.0シリーズを敬遠するしかありません)
付注:吉里吉里2のソースコードはGPLライセンスに基づいて発表する