吉里吉里2 TJS 2 VMのdispatch loop
18258 ワード
少し吉里吉里2.28のソースコードの中でTJS 2 VMの実行メカニズムを探して、主にdispatch loopの実現に着目して、しかも以下のコードを見つけました:
kirikiri2\src\core\tjs2\tjsInterCodeExec.cpp/972:
やはりかなり典型的で直感的な解釈器です.このような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ライセンスに基づいて発表する
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ライセンスに基づいて発表する