# HG changeset patch # User anatofuz # Date 1555751195 -32400 # Node ID 1fc9d0bd924f3c2537e2308cca64906e5ee86c72 # Parent a176ea5c026416c542f17b48fb8b5ae745ae0e49 update diff -r a176ea5c0264 -r 1fc9d0bd924f Per6_rakudo_OSC2019.mm --- a/Per6_rakudo_OSC2019.mm Fri Apr 19 23:22:02 2019 +0900 +++ b/Per6_rakudo_OSC2019.mm Sat Apr 20 18:06:35 2019 +0900 @@ -1,6 +1,6 @@ - + - + @@ -10,6 +10,21 @@ + + + + + + + + + + + + + + + @@ -29,5 +44,6 @@ + diff -r a176ea5c0264 -r 1fc9d0bd924f fig/Perl6_num_convert.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/fig/Perl6_num_convert.svg Sat Apr 20 18:06:35 2019 +0900 @@ -0,0 +1,2 @@ + +
loc_3
[Not supported by viewer]
$n(Object)
[Not supported by viewer]
キャスト
[Not supported by viewer]
loc_4
[Not supported by viewer]
$n(Num)
[Not supported by viewer]
\ No newline at end of file diff -r a176ea5c0264 -r 1fc9d0bd924f fig/Rakudo_overview.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/fig/Rakudo_overview.svg Sat Apr 20 18:06:35 2019 +0900 @@ -0,0 +1,2 @@ + +
Perl6Program
Perl6Program
Rakudo
Rakudo
ByteCode
ByteCode
NQP
NQP
コンパイルを行う
コンパイルを行う
実装に利用
実装に利用
生成
生成
MoarVM
MoarVM
実行
実行
実行
実行
\ No newline at end of file diff -r a176ea5c0264 -r 1fc9d0bd924f fig/decont_perl6_loc3.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/fig/decont_perl6_loc3.svg Sat Apr 20 18:06:35 2019 +0900 @@ -0,0 +1,2 @@ + +
loc_0
[Not supported by viewer]
container
[Not supported by viewer]
1000($n)
[Not supported by viewer]
loc_3_obj
[Not supported by viewer]
\ No newline at end of file diff -r a176ea5c0264 -r 1fc9d0bd924f slide.html --- a/slide.html Fri Apr 19 23:22:02 2019 +0900 +++ b/slide.html Sat Apr 20 18:06:35 2019 +0900 @@ -139,7 +139,12 @@
  • 仕様と実装が分離しており, 現在はテストが仕様となっている
  • -
  • 実装は歴史上複数存在しているが,主流な実装はRakudo
  • +
  • 実装は歴史上複数存在しているが,主流な実装はRakudo +
      +
    • Haskellで実装されたPugs
    • +
    • Pythonとの共同基板を目指したParrot
    • +
    +
  • 言語的にはスクリプト言語であり, 漸進的型付き言語
  • 動作環境は、独自のVMのMoarVM, JVM、一部JavaScript上で動作する
  • @@ -384,7 +389,7 @@

    Rakudoの構成図

    -

    +

    (http://brrt-to-the-future.blogspot.com/2015/03/advancing-jit-compiler.html)

    @@ -749,7 +754,11 @@

    @@ -760,6 +769,61 @@
    +

    const_i64_16とcoerece_in

    + +
        while ( $n > 1) {
    +
    + +
    00009      const_i64_16       loc_2_int, 1
    +00010      coerce_in          loc_5_num, loc_2_int
    +
    +
      +
    • 64ビット整数として1をレジスタ loc_2 に設定する
    • +
    • その後、 numでの比較のためにキャストし, loc_5 レジスタに設定している
    • +
    + + + +
    + +
    + +

    比較とif文の判定

    + +
        while ( $n > 1) {
    +
    + +
    00011      gt_n               loc_2_int, loc_4_num, loc_5_num
    +00012      unless_i           loc_2_int, label_2(00031)
    +
    + +
      +
    • gt_nloc_4 (nが入っている) レジスタと loc_5 (1が入っているレジスタ)を比較する +
        +
      • 結果により loc_2 レジスタに真偽値がint型で代入される
      • +
      +
    • +
    • その結果を unless_i で読み取り、 値が偽であったら label_2(00031) にジャンプする
    • +
    + + + +
    + +
    + +

    C言語での実装へ

    +
      +
    • 今まではスクリプトレベルでの実装を見てきました
    • +
    • スクリプトが実行されるVMの実装をCレベルで見ていきましょう
    • +
    + + + +
    + +
    +

    MoarVMのバイトコードインタプリタ部分

    MoarVMなどの言語処理系のバイトコードインタプリタは次のことを繰り返している

      @@ -770,7 +834,7 @@
      -
    • この部分の実装は大体次のような処理をしている
    • +
    • この部分の実装は大体次のようなパターンで記述されている
    @@ -786,7 +850,16 @@
  • 実行のたびにループで先頭に戻り、次の命令を計算する必要があるので低速
  • -
    
    +
        while( pc != NULL) {
    +        switch(pc){
    +            case ADD_INSTRUCTION:
    +                // instruction....
    +                break;
    +            case SUBD_INSTRUCTION:
    +                // instruction....
    +                break;
    +        }
    +    }
     
    @@ -799,6 +872,11 @@
    • 巨大なcase文とループではなく、 次の命令の実行場所に直接jmpで移動する
    • +
    • GCC拡張でサポートしている場合「&&ラベル名 」でラベルのアドレスが取得できる +
        +
      • 取得したアドレスに対して「goto アドレス」でgoto文でjmpする
      • +
      +
    • 次の命令に対応するラベルを取得する必要があるが、 ループする必要がなく高速
    • ラベルgotoであり、 Cコンパイラの拡張機能として搭載されている
        @@ -807,7 +885,19 @@
      -
      
      +
          static const void *CODES[] = {&&ADD_INSTRUCTION, &&SUB_INSTRCUTION};
      +
      +    goto *CODES[pc];
      +
      +ADD_INSTRUCTION:
      +    // instruction...
      +    pc++;
      +    goto *CODES[pc];
      +
      +SUB_INSTRUCTION:
      +    // instruction...
      +    pc++;
      +    goto *CODES[pc];
       
      @@ -827,6 +917,326 @@
    • 一般的にはラベルgotoの方が高速である為、他のスクリプト言語でもラベルgotoが使われている
    + + +
    + +
    + +

    MoarVMのC言語での実装

    + +
      +
    • GitHub上にリポジトリがある
    • +
    • src/core/interp.c がバイトコードインタプリタの実装コード
    • +
    • この中でマクロを使いつつ、 バイトコードに対応した命令を処理している
    • +
    • MVM_interp_runが実際にバイトコードを解釈している
    • +
    + +
    /* This is the interpreter run loop. We have one of these per thread. */
    +void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContext *, void *), void *invoke_data) {
    +#if MVM_CGOTO
    +#include "oplabels.h"
    +#endif
    +
    +    /* Points to the place in the bytecode right after the current opcode. */
    +    /* See the NEXT_OP macro for making sense of this */
    +    MVMuint8 *cur_op = NULL;
    +
    +    /* The current frame's bytecode start. */
    +    MVMuint8 *bytecode_start = NULL;
    +
    +    /* Points to the base of the current register set for the frame we
    +     * are presently in. */
    +    MVMRegister *reg_base = NULL;
    +
    +    /* Points to the current compilation unit. */
    +    MVMCompUnit *cu = NULL;
    +
    +    /* The current call site we're constructing. */
    +    MVMCallsite *cur_callsite = NULL;
    +
    +    /* Stash addresses of current op, register base and SC deref base
    +     * in the TC; this will be used by anything that needs to switch
    +     * the current place we're interpreting. */
    +    tc->interp_cur_op         = &cur_op;
    +    tc->interp_bytecode_start = &bytecode_start;
    +    tc->interp_reg_base       = &reg_base;
    +    tc->interp_cu             = &cu;
    +
    +    /* With everything set up, do the initial invocation (exactly what this does
    +     * varies depending on if this is starting a new thread or is the top-level
    +     * program entry point). */
    +    initial_invoke(tc, invoke_data);
    +
    + + + +
    + +
    + +

    MoarVMのレジスタ構成

    + +
      +
    • レジスタはそれぞれの型を共用体のデータ構造で持っている
    • +
    • 型名は各命令の接尾辞に対応している
    • +
    + +
    /* Different views of a register. */
    +union MVMRegister {
    +    MVMObject         *o;
    +    MVMString *s;
    +    MVMint8            i8;
    +    MVMuint8           u8;
    +    MVMint16           i16;
    +    MVMuint16          u16;
    +    MVMint32           i32;
    +    MVMuint32          u32;
    +    MVMint64           i64;
    +    MVMuint64          u64;
    +    MVMnum32           n32;
    +    MVMnum64           n64;
    +};
    +
    + + + +
    + +
    + +

    MVM_interp_runの登場人物

    + +
      +
    • cur_op が読み取るのバイトコード列の先頭ポインタを保存している
    • +
    • op が現在実行する命令の数値が入っている
    • +
    • reg_base がMoarVMのレジスタの集合配列
    • +
    + +
        /* Points to the place in the bytecode right after the current opcode. */
    +    /* See the NEXT_OP macro for making sense of this */
    +    MVMuint8 *cur_op = NULL;
    +
    +    /* The current frame's bytecode start. */
    +    MVMuint8 *bytecode_start = NULL;
    +
    +    /* Points to the base of the current register set for the frame we
    +     * are presently in. */
    +    MVMRegister *reg_base = NULL;
    +
    + + + +
    + +
    + +

    MVM_interp_runメインループ

    + +
      +
    • runloopがメインのループで処理を行っている +
        +
      • DISPATCH部分で命令をバイトコード列から取り出している
      • +
      +
    • +
    + +
        /* Enter runloop. */
    +    runloop: {
    +        MVMuint16 op;
    +
    +#if MVM_TRACING
    +        if (tracing_enabled) {
    +            char *trace_line;
    +            trace_line = MVM_exception_backtrace_line(tc, tc->cur_frame, 0, cur_op);
    +            fprintf(stderr, "Op %d%s\n", (int)*((MVMuint16 *)cur_op), trace_line);
    +            /* slow tracing is slow. Feel free to speed it. */
    +            MVM_free(trace_line);
    +        }
    +#endif
    +
    +        /* The ops should be in the same order here as in the oplist file, so
    +         * the compiler can can optimise the switch properly. To check if they
    +         * are in the same order as the oplist use the
    +         * tools/compare-oplist-interp-order.sh helper script. */
    +        DISPATCH(NEXT_OP) {
    +            OP(no_op):
    +                goto NEXT;
    +            OP(const_i8):
    +            OP(const_i16):
    +            OP(const_i32):
    +                MVM_exception_throw_adhoc(tc, "const_iX NYI");
    +            OP(const_i64):
    +                GET_REG(cur_op, 0).i64 = MVM_BC_get_I64(cur_op, 2);
    +                cur_op += 10;
    +                goto NEXT;
    +
    + + + +
    + +
    + +

    MVM_interp_runメインループ

    + +
      +
    • DISPATCHはマクロになっている +
        +
      • ラベルgotoが使えるケースはDISPATCHは何も意味を持たない
      • +
      • 使えない場合はswitch文に展開される +
          +
        • op に実行する命令の数値が格納されているので、 これに応じてswitchで飛ぶ
        • +
        +
      • +
      +
    • +
    + +
    #if MVM_CGOTO
    +#define DISPATCH(op)
    +#define OP(name) OP_ ## name
    +#define NEXT *LABELS[NEXT_OP]
    +#else
    +#define DISPATCH(op) switch (op)
    +#define OP(name) case MVM_OP_ ## name
    +#define NEXT runloop
    +#endif
    +
    + + + +
    + +
    + +

    MVM_interp_runメインループ

    + +
      +
    • OP が命令のスコープを切るマクロとなっている +
        +
      • gotoが使える場合 +
          +
        • ##name に展開され、 それぞれの命令の名前を持つラベルになる
        • +
        +
      • +
      • gotoが使えない場合 +
          +
        • case文に展開される
        • +
        +
      • +
      +
    • +
    + +
    #if MVM_CGOTO
    +#define DISPATCH(op)
    +#define OP(name) OP_ ## name
    +#define NEXT *LABELS[NEXT_OP]
    +#else
    +#define DISPATCH(op) switch (op)
    +#define OP(name) case MVM_OP_ ## name
    +#define NEXT runloop
    +#endif
    +
    + + + +
    + +
    + +

    MVM_interp_runメインループ

    + +
      +
    • 次の命令には NEXT マクロで移動する +
        +
      • gotoが使える場合 +
          +
        • 次のラベルに対応したアドレスを配列LABELSから取り出しgotoする
        • +
        +
      • +
      • gotoが使えない場合 +
          +
        • runloopに対してgotoする(先頭へのループ)
        • +
        +
      • +
      +
    • +
    + +
    #if MVM_CGOTO
    +#define DISPATCH(op)
    +#define OP(name) OP_ ## name
    +#define NEXT *LABELS[NEXT_OP]
    +#else
    +#define DISPATCH(op) switch (op)
    +#define OP(name) case MVM_OP_ ## name
    +#define NEXT runloop
    +#endif
    +
    + + + +
    + +
    + +

    それぞれの命令の実装

    + +
      +
    • マクロ GET_REG でレジスタ情報を取得する +
        +
      • 末尾で型を指定する
      • +
      +
    • +
    • よしなに処理をした後に、バイトコード列 cur_op をインクリメントする +
        +
      • 計算機のプログラムカウンタを進めることに対応する
      • +
      +
    • +
    + +
        OP(add_i):
    +        GET_REG(cur_op, 0).i64 = GET_REG(cur_op, 2).i64 + GET_REG(cur_op, 4).i64;
    +        cur_op += 6;
    +        goto NEXT;
    +    OP(sub_i):
    +        GET_REG(cur_op, 0).i64 = GET_REG(cur_op, 2).i64 - GET_REG(cur_op, 4).i64;
    +        cur_op += 6;
    +        goto NEXT;
    +    OP(mul_i):
    +        GET_REG(cur_op, 0).i64 = GET_REG(cur_op, 2).i64 * GET_REG(cur_op, 4).i64;
    +        cur_op += 6;
    +        goto NEXT;
    +
    + + + +
    + +
    + +

    本日の展示について

    + +
      +
    • 現在当研究室で開発しているContinuationBasedC(CbC)で一部Perl6の処理系を書いています
    • +
    • また、 CbCを用いたGearsOSの展示も行っています
    • +
    + + + +
    + +
    + +

    まとめ

    +
      +
    • Perl6の内部構造に迫りました
    • +
    • スクリプト言語の中身も通常の計算機と同じような処理をしています
    • +
    • OSSなので皆さん読んで改造してみましょう!!
    • +
    +
    diff -r a176ea5c0264 -r 1fc9d0bd924f slide.md --- a/slide.md Fri Apr 19 23:22:02 2019 +0900 +++ b/slide.md Sat Apr 20 18:06:35 2019 +0900 @@ -26,6 +26,8 @@ - 現在は別の言語として開発がそれぞれ進んでいる - 仕様と実装が分離しており, 現在はテストが仕様となっている - 実装は歴史上複数存在しているが,主流な実装はRakudo + - Haskellで実装されたPugs + - Pythonとの共同基板を目指したParrot - 言語的にはスクリプト言語であり, 漸進的型付き言語 - 動作環境は、独自のVMのMoarVM, JVM、一部JavaScript上で動作する @@ -165,7 +167,7 @@ ## Rakudoの構成図 -![](fig/Rakudo_System_overview.png) +![](fig/Rakudo_overview.svg) (http://brrt-to-the-future.blogspot.com/2015/03/advancing-jit-compiler.html) @@ -413,10 +415,43 @@ - `smrt_numify` はレジスタ上のオブジェクトを、 num型に変換し、 別のレジスタに登録する命令 -- 今回の整数の比較では、 int型の強制がない為、 数値として比較するためにnum型にキャストしている +- 今回の整数の比較では、 int型の強制がない為、 数値として比較するためにn64型にキャストしている + - numはn64型であり、 64ビットの浮動小数点定数の意味 ![](fig/perl6_num_convert.svg) +## const_i64_16とcoerece_in + +``` + while ( $n > 1) { +``` + +``` +00009 const_i64_16 loc_2_int, 1 +00010 coerce_in loc_5_num, loc_2_int +``` +- 64ビット整数として1をレジスタ `loc_2` に設定する +- その後、 numでの比較のためにキャストし, `loc_5` レジスタに設定している + +## 比較とif文の判定 + +``` + while ( $n > 1) { +``` + +``` +00011 gt_n loc_2_int, loc_4_num, loc_5_num +00012 unless_i loc_2_int, label_2(00031) +``` + +- `gt_n` で `loc_4` (nが入っている) レジスタと `loc_5` (1が入っているレジスタ)を比較する + - 結果により `loc_2` レジスタに真偽値がint型で代入される +- その結果を `unless_i` で読み取り、 値が偽であったら `label_2(00031)` にジャンプする + +## C言語での実装へ +- 今まではスクリプトレベルでの実装を見てきました +− スクリプトが実行されるVMの実装をCレベルで見ていきましょう + ## MoarVMのバイトコードインタプリタ部分 MoarVMなどの言語処理系のバイトコードインタプリタは次のことを繰り返している 1. 入力されたバイトコード列から命令に対応する部分を読み取る @@ -424,7 +459,7 @@ 3. 命令部分を実行する 4. バイトコード列を次に進め、繰り返す -- この部分の実装は大体次のような処理をしている +- この部分の実装は大体次のようなパターンで記述されている ## 巨大なswitch文を使うケース @@ -432,16 +467,41 @@ - 実行のたびにループで先頭に戻り、次の命令を計算する必要があるので低速 ``` + while( pc != NULL) { + switch(pc){ + case ADD_INSTRUCTION: + // instruction.... + break; + case SUBD_INSTRUCTION: + // instruction.... + break; + } + } ``` ## Cコンパイラのラベルgotoを使うケース - 巨大なcase文とループではなく、 次の命令の実行場所に直接jmpで移動する +- GCC拡張でサポートしている場合「`&&ラベル名` 」でラベルのアドレスが取得できる + - 取得したアドレスに対して「goto アドレス」でgoto文でjmpする - 次の命令に対応するラベルを取得する必要があるが、 ループする必要がなく高速 - ラベルgotoであり、 Cコンパイラの拡張機能として搭載されている - gccおよびLLVM/clangには実装されている ``` + static const void *CODES[] = {&&ADD_INSTRUCTION, &&SUB_INSTRCUTION}; + + goto *CODES[pc]; + +ADD_INSTRUCTION: + // instruction... + pc++; + goto *CODES[pc]; + +SUB_INSTRUCTION: + // instruction... + pc++; + goto *CODES[pc]; ``` ## MoarVMでは @@ -449,3 +509,221 @@ - 使えないコンパイラの場合は、 switch文を利用する - この判断はマクロで処理をしている − 一般的にはラベルgotoの方が高速である為、他のスクリプト言語でもラベルgotoが使われている + +## MoarVMのC言語での実装 + +- [GitHub上にリポジトリ](https://github.com/MoarVM/MoarVM)がある +- `src/core/interp.c` がバイトコードインタプリタの実装コード +- この中でマクロを使いつつ、 バイトコードに対応した命令を処理している +- `MVM_interp_run`が実際にバイトコードを解釈している + + +``` +/* This is the interpreter run loop. We have one of these per thread. */ +void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContext *, void *), void *invoke_data) { +#if MVM_CGOTO +#include "oplabels.h" +#endif + + /* Points to the place in the bytecode right after the current opcode. */ + /* See the NEXT_OP macro for making sense of this */ + MVMuint8 *cur_op = NULL; + + /* The current frame's bytecode start. */ + MVMuint8 *bytecode_start = NULL; + + /* Points to the base of the current register set for the frame we + * are presently in. */ + MVMRegister *reg_base = NULL; + + /* Points to the current compilation unit. */ + MVMCompUnit *cu = NULL; + + /* The current call site we're constructing. */ + MVMCallsite *cur_callsite = NULL; + + /* Stash addresses of current op, register base and SC deref base + * in the TC; this will be used by anything that needs to switch + * the current place we're interpreting. */ + tc->interp_cur_op = &cur_op; + tc->interp_bytecode_start = &bytecode_start; + tc->interp_reg_base = ®_base; + tc->interp_cu = &cu; + + /* With everything set up, do the initial invocation (exactly what this does + * varies depending on if this is starting a new thread or is the top-level + * program entry point). */ + initial_invoke(tc, invoke_data); +``` + +## MoarVMのレジスタ構成 + +- レジスタはそれぞれの型を共用体のデータ構造で持っている +- 型名は各命令の接尾辞に対応している + +``` +/* Different views of a register. */ +union MVMRegister { + MVMObject *o; + MVMString *s; + MVMint8 i8; + MVMuint8 u8; + MVMint16 i16; + MVMuint16 u16; + MVMint32 i32; + MVMuint32 u32; + MVMint64 i64; + MVMuint64 u64; + MVMnum32 n32; + MVMnum64 n64; +}; +``` + + +## MVM_interp_runの登場人物 + +- `cur_op` が読み取るのバイトコード列の先頭ポインタを保存している +- `op` が現在実行する命令の数値が入っている +- `reg_base` がMoarVMのレジスタの集合配列 + +``` + /* Points to the place in the bytecode right after the current opcode. */ + /* See the NEXT_OP macro for making sense of this */ + MVMuint8 *cur_op = NULL; + + /* The current frame's bytecode start. */ + MVMuint8 *bytecode_start = NULL; + + /* Points to the base of the current register set for the frame we + * are presently in. */ + MVMRegister *reg_base = NULL; +``` + +## MVM_interp_runメインループ + +- `runloop`がメインのループで処理を行っている + - `DISPATCH`部分で命令をバイトコード列から取り出している + +``` + /* Enter runloop. */ + runloop: { + MVMuint16 op; + +#if MVM_TRACING + if (tracing_enabled) { + char *trace_line; + trace_line = MVM_exception_backtrace_line(tc, tc->cur_frame, 0, cur_op); + fprintf(stderr, "Op %d%s\n", (int)*((MVMuint16 *)cur_op), trace_line); + /* slow tracing is slow. Feel free to speed it. */ + MVM_free(trace_line); + } +#endif + + /* The ops should be in the same order here as in the oplist file, so + * the compiler can can optimise the switch properly. To check if they + * are in the same order as the oplist use the + * tools/compare-oplist-interp-order.sh helper script. */ + DISPATCH(NEXT_OP) { + OP(no_op): + goto NEXT; + OP(const_i8): + OP(const_i16): + OP(const_i32): + MVM_exception_throw_adhoc(tc, "const_iX NYI"); + OP(const_i64): + GET_REG(cur_op, 0).i64 = MVM_BC_get_I64(cur_op, 2); + cur_op += 10; + goto NEXT; +``` + +## MVM_interp_runメインループ + +- `DISPATCH`はマクロになっている + - ラベルgotoが使えるケースはDISPATCHは何も意味を持たない + - 使えない場合はswitch文に展開される + - `op` に実行する命令の数値が格納されているので、 これに応じてswitchで飛ぶ + + +``` +#if MVM_CGOTO +#define DISPATCH(op) +#define OP(name) OP_ ## name +#define NEXT *LABELS[NEXT_OP] +#else +#define DISPATCH(op) switch (op) +#define OP(name) case MVM_OP_ ## name +#define NEXT runloop +#endif +``` + +## MVM_interp_runメインループ + +- `OP` が命令のスコープを切るマクロとなっている + - gotoが使える場合 + - `##name` に展開され、 それぞれの命令の名前を持つラベルになる + - gotoが使えない場合 + - case文に展開される + +``` +#if MVM_CGOTO +#define DISPATCH(op) +#define OP(name) OP_ ## name +#define NEXT *LABELS[NEXT_OP] +#else +#define DISPATCH(op) switch (op) +#define OP(name) case MVM_OP_ ## name +#define NEXT runloop +#endif +``` + +## MVM_interp_runメインループ + +- 次の命令には `NEXT` マクロで移動する + - gotoが使える場合 + - 次のラベルに対応したアドレスを配列LABELSから取り出しgotoする + - gotoが使えない場合 + - `runloop`に対してgotoする(先頭へのループ) + +``` +#if MVM_CGOTO +#define DISPATCH(op) +#define OP(name) OP_ ## name +#define NEXT *LABELS[NEXT_OP] +#else +#define DISPATCH(op) switch (op) +#define OP(name) case MVM_OP_ ## name +#define NEXT runloop +#endif +``` + +## それぞれの命令の実装 + +- マクロ `GET_REG` でレジスタ情報を取得する + - 末尾で型を指定する +- よしなに処理をした後に、バイトコード列 `cur_op` をインクリメントする + - 計算機のプログラムカウンタを進めることに対応する + +``` + OP(add_i): + GET_REG(cur_op, 0).i64 = GET_REG(cur_op, 2).i64 + GET_REG(cur_op, 4).i64; + cur_op += 6; + goto NEXT; + OP(sub_i): + GET_REG(cur_op, 0).i64 = GET_REG(cur_op, 2).i64 - GET_REG(cur_op, 4).i64; + cur_op += 6; + goto NEXT; + OP(mul_i): + GET_REG(cur_op, 0).i64 = GET_REG(cur_op, 2).i64 * GET_REG(cur_op, 4).i64; + cur_op += 6; + goto NEXT; +``` + +## 本日の展示について + +- 現在当研究室で開発しているContinuationBasedC(CbC)で一部Perl6の処理系を書いています +- また、 CbCを用いたGearsOSの展示も行っています + +## まとめ +- Perl6の内部構造に迫りました +- スクリプト言語の中身も通常の計算機と同じような処理をしています +- OSSなので皆さん読んで改造してみましょう!! diff -r a176ea5c0264 -r 1fc9d0bd924f slide.pdf.html --- a/slide.pdf.html Fri Apr 19 23:22:02 2019 +0900 +++ b/slide.pdf.html Sat Apr 20 18:06:35 2019 +0900 @@ -123,7 +123,12 @@
  • 仕様と実装が分離しており, 現在はテストが仕様となっている
  • -
  • 実装は歴史上複数存在しているが,主流な実装はRakudo
  • +
  • 実装は歴史上複数存在しているが,主流な実装はRakudo +
      +
    • Haskellで実装されたPugs
    • +
    • Pythonとの共同基板を目指したParrot
    • +
    +
  • 言語的にはスクリプト言語であり, 漸進的型付き言語
  • 動作環境は、独自のVMのMoarVM, JVM、一部JavaScript上で動作する
  • @@ -368,7 +373,7 @@

    Rakudoの構成図

    -

    +

    (http://brrt-to-the-future.blogspot.com/2015/03/advancing-jit-compiler.html)

    @@ -733,7 +738,11 @@
    • smrt_numify はレジスタ上のオブジェクトを、 num型に変換し、 別のレジスタに登録する命令
    • -
    • 今回の整数の比較では、 int型の強制がない為、 数値として比較するためにnum型にキャストしている
    • +
    • 今回の整数の比較では、 int型の強制がない為、 数値として比較するためにn64型にキャストしている +
        +
      • numはn64型であり、 64ビットの浮動小数点定数の意味
      • +
      +

    @@ -744,6 +753,61 @@
    +

    const_i64_16とcoerece_in

    + +
        while ( $n > 1) {
    +
    + +
    00009      const_i64_16       loc_2_int, 1
    +00010      coerce_in          loc_5_num, loc_2_int
    +
    +
      +
    • 64ビット整数として1をレジスタ loc_2 に設定する
    • +
    • その後、 numでの比較のためにキャストし, loc_5 レジスタに設定している
    • +
    + + + +
    + +
    + +

    比較とif文の判定

    + +
        while ( $n > 1) {
    +
    + +
    00011      gt_n               loc_2_int, loc_4_num, loc_5_num
    +00012      unless_i           loc_2_int, label_2(00031)
    +
    + +
      +
    • gt_nloc_4 (nが入っている) レジスタと loc_5 (1が入っているレジスタ)を比較する +
        +
      • 結果により loc_2 レジスタに真偽値がint型で代入される
      • +
      +
    • +
    • その結果を unless_i で読み取り、 値が偽であったら label_2(00031) にジャンプする
    • +
    + + + +
    + +
    + +

    C言語での実装へ

    +
      +
    • 今まではスクリプトレベルでの実装を見てきました
    • +
    • スクリプトが実行されるVMの実装をCレベルで見ていきましょう
    • +
    + + + +
    + +
    +

    MoarVMのバイトコードインタプリタ部分

    MoarVMなどの言語処理系のバイトコードインタプリタは次のことを繰り返している

      @@ -754,7 +818,7 @@
      -
    • この部分の実装は大体次のような処理をしている
    • +
    • この部分の実装は大体次のようなパターンで記述されている
    @@ -770,7 +834,16 @@
  • 実行のたびにループで先頭に戻り、次の命令を計算する必要があるので低速
  • -
    
    +
        while( pc != NULL) {
    +        switch(pc){
    +            case ADD_INSTRUCTION:
    +                // instruction....
    +                break;
    +            case SUBD_INSTRUCTION:
    +                // instruction....
    +                break;
    +        }
    +    }
     
    @@ -783,6 +856,11 @@
    • 巨大なcase文とループではなく、 次の命令の実行場所に直接jmpで移動する
    • +
    • GCC拡張でサポートしている場合「&&ラベル名 」でラベルのアドレスが取得できる +
        +
      • 取得したアドレスに対して「goto アドレス」でgoto文でjmpする
      • +
      +
    • 次の命令に対応するラベルを取得する必要があるが、 ループする必要がなく高速
    • ラベルgotoであり、 Cコンパイラの拡張機能として搭載されている
        @@ -791,7 +869,19 @@
      -
      
      +
          static const void *CODES[] = {&&ADD_INSTRUCTION, &&SUB_INSTRCUTION};
      +
      +    goto *CODES[pc];
      +
      +ADD_INSTRUCTION:
      +    // instruction...
      +    pc++;
      +    goto *CODES[pc];
      +
      +SUB_INSTRUCTION:
      +    // instruction...
      +    pc++;
      +    goto *CODES[pc];
       
      @@ -811,6 +901,326 @@
    • 一般的にはラベルgotoの方が高速である為、他のスクリプト言語でもラベルgotoが使われている
    + + +
    + +
    + +

    MoarVMのC言語での実装

    + +
      +
    • GitHub上にリポジトリがある
    • +
    • src/core/interp.c がバイトコードインタプリタの実装コード
    • +
    • この中でマクロを使いつつ、 バイトコードに対応した命令を処理している
    • +
    • MVM_interp_runが実際にバイトコードを解釈している
    • +
    + +
    /* This is the interpreter run loop. We have one of these per thread. */
    +void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContext *, void *), void *invoke_data) {
    +#if MVM_CGOTO
    +#include "oplabels.h"
    +#endif
    +
    +    /* Points to the place in the bytecode right after the current opcode. */
    +    /* See the NEXT_OP macro for making sense of this */
    +    MVMuint8 *cur_op = NULL;
    +
    +    /* The current frame's bytecode start. */
    +    MVMuint8 *bytecode_start = NULL;
    +
    +    /* Points to the base of the current register set for the frame we
    +     * are presently in. */
    +    MVMRegister *reg_base = NULL;
    +
    +    /* Points to the current compilation unit. */
    +    MVMCompUnit *cu = NULL;
    +
    +    /* The current call site we're constructing. */
    +    MVMCallsite *cur_callsite = NULL;
    +
    +    /* Stash addresses of current op, register base and SC deref base
    +     * in the TC; this will be used by anything that needs to switch
    +     * the current place we're interpreting. */
    +    tc->interp_cur_op         = &cur_op;
    +    tc->interp_bytecode_start = &bytecode_start;
    +    tc->interp_reg_base       = &reg_base;
    +    tc->interp_cu             = &cu;
    +
    +    /* With everything set up, do the initial invocation (exactly what this does
    +     * varies depending on if this is starting a new thread or is the top-level
    +     * program entry point). */
    +    initial_invoke(tc, invoke_data);
    +
    + + + +
    + +
    + +

    MoarVMのレジスタ構成

    + +
      +
    • レジスタはそれぞれの型を共用体のデータ構造で持っている
    • +
    • 型名は各命令の接尾辞に対応している
    • +
    + +
    /* Different views of a register. */
    +union MVMRegister {
    +    MVMObject         *o;
    +    MVMString *s;
    +    MVMint8            i8;
    +    MVMuint8           u8;
    +    MVMint16           i16;
    +    MVMuint16          u16;
    +    MVMint32           i32;
    +    MVMuint32          u32;
    +    MVMint64           i64;
    +    MVMuint64          u64;
    +    MVMnum32           n32;
    +    MVMnum64           n64;
    +};
    +
    + + + +
    + +
    + +

    MVM_interp_runの登場人物

    + +
      +
    • cur_op が読み取るのバイトコード列の先頭ポインタを保存している
    • +
    • op が現在実行する命令の数値が入っている
    • +
    • reg_base がMoarVMのレジスタの集合配列
    • +
    + +
        /* Points to the place in the bytecode right after the current opcode. */
    +    /* See the NEXT_OP macro for making sense of this */
    +    MVMuint8 *cur_op = NULL;
    +
    +    /* The current frame's bytecode start. */
    +    MVMuint8 *bytecode_start = NULL;
    +
    +    /* Points to the base of the current register set for the frame we
    +     * are presently in. */
    +    MVMRegister *reg_base = NULL;
    +
    + + + +
    + +
    + +

    MVM_interp_runメインループ

    + +
      +
    • runloopがメインのループで処理を行っている +
        +
      • DISPATCH部分で命令をバイトコード列から取り出している
      • +
      +
    • +
    + +
        /* Enter runloop. */
    +    runloop: {
    +        MVMuint16 op;
    +
    +#if MVM_TRACING
    +        if (tracing_enabled) {
    +            char *trace_line;
    +            trace_line = MVM_exception_backtrace_line(tc, tc->cur_frame, 0, cur_op);
    +            fprintf(stderr, "Op %d%s\n", (int)*((MVMuint16 *)cur_op), trace_line);
    +            /* slow tracing is slow. Feel free to speed it. */
    +            MVM_free(trace_line);
    +        }
    +#endif
    +
    +        /* The ops should be in the same order here as in the oplist file, so
    +         * the compiler can can optimise the switch properly. To check if they
    +         * are in the same order as the oplist use the
    +         * tools/compare-oplist-interp-order.sh helper script. */
    +        DISPATCH(NEXT_OP) {
    +            OP(no_op):
    +                goto NEXT;
    +            OP(const_i8):
    +            OP(const_i16):
    +            OP(const_i32):
    +                MVM_exception_throw_adhoc(tc, "const_iX NYI");
    +            OP(const_i64):
    +                GET_REG(cur_op, 0).i64 = MVM_BC_get_I64(cur_op, 2);
    +                cur_op += 10;
    +                goto NEXT;
    +
    + + + +
    + +
    + +

    MVM_interp_runメインループ

    + +
      +
    • DISPATCHはマクロになっている +
        +
      • ラベルgotoが使えるケースはDISPATCHは何も意味を持たない
      • +
      • 使えない場合はswitch文に展開される +
          +
        • op に実行する命令の数値が格納されているので、 これに応じてswitchで飛ぶ
        • +
        +
      • +
      +
    • +
    + +
    #if MVM_CGOTO
    +#define DISPATCH(op)
    +#define OP(name) OP_ ## name
    +#define NEXT *LABELS[NEXT_OP]
    +#else
    +#define DISPATCH(op) switch (op)
    +#define OP(name) case MVM_OP_ ## name
    +#define NEXT runloop
    +#endif
    +
    + + + +
    + +
    + +

    MVM_interp_runメインループ

    + +
      +
    • OP が命令のスコープを切るマクロとなっている +
        +
      • gotoが使える場合 +
          +
        • ##name に展開され、 それぞれの命令の名前を持つラベルになる
        • +
        +
      • +
      • gotoが使えない場合 +
          +
        • case文に展開される
        • +
        +
      • +
      +
    • +
    + +
    #if MVM_CGOTO
    +#define DISPATCH(op)
    +#define OP(name) OP_ ## name
    +#define NEXT *LABELS[NEXT_OP]
    +#else
    +#define DISPATCH(op) switch (op)
    +#define OP(name) case MVM_OP_ ## name
    +#define NEXT runloop
    +#endif
    +
    + + + +
    + +
    + +

    MVM_interp_runメインループ

    + +
      +
    • 次の命令には NEXT マクロで移動する +
        +
      • gotoが使える場合 +
          +
        • 次のラベルに対応したアドレスを配列LABELSから取り出しgotoする
        • +
        +
      • +
      • gotoが使えない場合 +
          +
        • runloopに対してgotoする(先頭へのループ)
        • +
        +
      • +
      +
    • +
    + +
    #if MVM_CGOTO
    +#define DISPATCH(op)
    +#define OP(name) OP_ ## name
    +#define NEXT *LABELS[NEXT_OP]
    +#else
    +#define DISPATCH(op) switch (op)
    +#define OP(name) case MVM_OP_ ## name
    +#define NEXT runloop
    +#endif
    +
    + + + +
    + +
    + +

    それぞれの命令の実装

    + +
      +
    • マクロ GET_REG でレジスタ情報を取得する +
        +
      • 末尾で型を指定する
      • +
      +
    • +
    • よしなに処理をした後に、バイトコード列 cur_op をインクリメントする +
        +
      • 計算機のプログラムカウンタを進めることに対応する
      • +
      +
    • +
    + +
        OP(add_i):
    +        GET_REG(cur_op, 0).i64 = GET_REG(cur_op, 2).i64 + GET_REG(cur_op, 4).i64;
    +        cur_op += 6;
    +        goto NEXT;
    +    OP(sub_i):
    +        GET_REG(cur_op, 0).i64 = GET_REG(cur_op, 2).i64 - GET_REG(cur_op, 4).i64;
    +        cur_op += 6;
    +        goto NEXT;
    +    OP(mul_i):
    +        GET_REG(cur_op, 0).i64 = GET_REG(cur_op, 2).i64 * GET_REG(cur_op, 4).i64;
    +        cur_op += 6;
    +        goto NEXT;
    +
    + + + +
    + +
    + +

    本日の展示について

    + +
      +
    • 現在当研究室で開発しているContinuationBasedC(CbC)で一部Perl6の処理系を書いています
    • +
    • また、 CbCを用いたGearsOSの展示も行っています
    • +
    + + + +
    + +
    + +

    まとめ

    +
      +
    • Perl6の内部構造に迫りました
    • +
    • スクリプト言語の中身も通常の計算機と同じような処理をしています
    • +
    • OSSなので皆さん読んで改造してみましょう!!
    • +
    +