はじめに

スクリプト解析処理まで進んだのでいよいよJavaバイトコードへの変換部分を読んでいきたいと思います。

org.jruby.Ruby

runNormallyメソッド

スクリプトの解析が終わると解析結果のNodeを引数にrunNormallyメソッドが呼び出されます。JRubyでのスクリプト実行にはコンパイル式とインタプリタ式がありますがデフォルトではJavaバイトコードにコンパイルされてから実行されるのでコンパイル式のみ見ることにします。

tryCompileメソッド

tryCompileメソッドはいくつかありますが順に追っていくとコンパイル処理の主要クラスはASTCompilerクラスであることがわかります。また、ASTCompiler.compileRootメソッドにはStandardASMCompilerクラスのインスタンスが渡されており、このクラスも重要な役割をしそうなことがわかります。

JRubyのRuby互換バージョンが1.9の場合はASTCompiler19クラスのインスタンスが生成され一部の処理がオーバーライドされているようです。

org.jruby.compiler.ASTCompiler

compileRootメソッド

compileRootメソッドに移ります。まずStandardASMCompilerインスタンスはcompileRootメソッドにはScriptCompilerインターフェースとして渡されていることがわかります。

compileRootメソッドでは以下の処理が行われています。

  1. 開始処理(ScriptCompiler.startScriptメソッド)
  2. トップレベルのコードをメソッドとしてコンパイル
    1. BodyCompilerインターフェースの取得(実際に返されるのはorg.jruby.compiler.impl.MethodBodyCompiler)
    2. RootNodeの子要素に対してcompileメソッドを適用
  3. 終了処理(ScriptCompiler.endScriptメソッド)

compileメソッド

compileメソッドは巨大なswitch文です。Nodeの種類に応じてcompileXxxメソッドが呼ばれコンパイル処理が進行していくようです。

org.ruby.compiler.impl.StandardASMCompiler

startScriptメソッド

クラスの生成にはASMが利用されています。startScriptメソッドを見ると生成されるクラスのスーパークラスとしてorg.jruby.ast.executable.AbstractScriptクラスが使われていることがわかります。

余談:staticインポート

ところでstartScriptメソッドを見ているとpとかsigというメソッドを見かけますがStandardASMCompilerクラスには該当メソッドは定義されていません。Eclipseの場合、メソッドにカーソルを合わせるとorg.jruby.util.Codegenのメソッドであることがわかります。どういうカラクリかというとJava 5.0から導入されたstaticインポート機能を使用しているようです。

startFileMethodメソッド

このメソッドではスクリプトのトップレベルに書かれているコードに対応するメソッドとして__file__メソッドの作成を開始しています(__file__メソッドを構築するためのMethodBodyCompilerが返されます)。

endScriptメソッド

このメソッドでは引数により生成するクラスにloadメソッドとmainメソッドを追加しています。loadメソッドでは__file__メソッドの呼び出しを行っています。Ruby.runScriptメソッドを見ると生成されたクラス(Scriptインターフェース)のloadメソッドを呼び出しています。これによりコンパイルしたトップレベルのコードが実行されるというカラクリのようです。

コンパイルしてみる

それではスクリプト解析を読むで構築したNodeをコンパイルしてみることにします。ちなみにjrubyに--bytecodeオプションを指定すると生成されたクラスのバイトコードが出力されます。montecarlo.rbをコンパイルした結果はこちらになります。filemontecarlo.bytecode.txt

MethodBodyCompiler.beginMethodメソッド

先ほど説明したstartFileMethodメソッド中でMethodBodyCompiler.beginMethodメソッドが呼び出されます。beginMethodメソッドは以下のようになっています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
-
-
|
|
|
|
-
!
!
!
public class MethodBodyCompiler extends RootScopedBodyCompiler {
    public void beginMethod(CompilerCallback args, StaticScope scope) {
        method.start();
 
        variableCompiler.beginMethod(args, scope);
 
        // visit a label to start scoping for local vars in this method
        method.label(scopeStart);
    }
}

variableCompilerはスーパークラスであるBaseBodyCompilerのコンストラクタで設定されます。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
!
!
public abstract class BaseBodyCompiler implements BodyCompiler {
    public BaseBodyCompiler(StandardASMCompiler scriptCompiler, String methodName, String rubyName, ASTInspector inspector, StaticScope scope, int scopeIndex) {
        this.script = scriptCompiler;
        this.scope = scope;
        this.inspector = inspector;
        this.methodName = methodName;
        this.rubyName = rubyName;
        this.argParamCount = getActualArgsCount(scope);
 
        method = new SkinnyMethodAdapter(script.getClassVisitor(), ACC_PUBLIC | ACC_STATIC, methodName, getSignature(), null, null);
 
        createVariableCompiler();
        invocationCompiler = OptoFactory.newInvocationCompiler(this, method);
 
        this.scopeIndex = scopeIndex;
    }
}

委譲されてMethodBodyCompilerに戻ってきます。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
-
-
-
|
-
|
-
|
!
!
!
 
-
-
-
!
!
!
public class MethodBodyCompiler extends RootScopedBodyCompiler {
    protected void createVariableCompiler() {
        if (inspector == null) {
            variableCompiler = new HeapBasedVariableCompiler(this, method, scope, specificArity, StandardASMCompiler.ARGS_INDEX, getFirstTempIndex());
        } else if (inspector.hasClosure() || inspector.hasScopeAwareMethods()) {
            variableCompiler = new HeapBasedVariableCompiler(this, method, scope, specificArity, StandardASMCompiler.ARGS_INDEX, getFirstTempIndex());
        } else {
            variableCompiler = new StackBasedVariableCompiler(this, method, scope, specificArity, StandardASMCompiler.ARGS_INDEX, getFirstTempIndex());
        }
    }
}
 
public abstract class BaseBodyCompiler implements BodyCompiler {
    protected int getFirstTempIndex() {
        // さわだ追記:ARGS_INDEX = 3, argParamCount = 1, FIRST_TEMP_OFFSET = 5
        return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.FIRST_TEMP_OFFSET;
    }
}

トップレベルではクロージャ(ブロック)もscope awareなメソッドも使っていないためStackBasedVariableCompilerが生成されるはずです。

で、StackBasedVariableCompiler.beginMethodメソッド。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
-
|
|
|
|
|
|
-
|
|
!
|
-
-
-
-
|
|
|
-
|
|
-
|
|
!
!
|
-
!
!
|
-
|
!
!
|
-
-
|
!
|
|
!
!
public class StackBasedVariableCompiler extends AbstractVariableCompiler {
    public StackBasedVariableCompiler(
            BaseBodyCompiler methodCompiler,
            SkinnyMethodAdapter method,
            StaticScope scope,
            boolean specificArity,
            int argsIndex,
            int firstTempIndex) {
        super(methodCompiler, method, scope, specificArity, argsIndex, firstTempIndex);
        this.baseVariableIndex = firstTempIndex;
    }
 
    public void beginMethod(CompilerCallback argsCallback, StaticScope scope) {
        // fill in all vars with nil so compiler is happy about future accesses
        if (scope.getNumberOfVariables() > 0) {
            // if we don't have opt args, start after args (they will be assigned later)
            // this is for crap like def foo(a = (b = true; 1)) which numbers b before a
            // FIXME: only starting after required args, since opt args may access others
            // and rest args conflicts with compileRoot using "0" to indicate [] signature.
            if (scope.getRequiredArgs() < scope.getNumberOfVariables()) {
                int start = scope.getRequiredArgs();
                methodCompiler.loadNil();
                for (int i = start; i < scope.getNumberOfVariables(); i++) {
                    if (i + 1 < scope.getNumberOfVariables()) methodCompiler.method.dup();
                    assignLocalVariable(i, false);
                }
            }
 
            // temp locals must start after last real local
            tempVariableIndex += scope.getNumberOfVariables();
        }
 
        if (argsCallback != null) {
            argsCallback.call(methodCompiler);
        }
    }
 
    public void assignLocalVariable(int index, boolean expr) {
        if (expr) {
            method.dup();
        }
 
        method.astore(baseVariableIndex + index);
    }
}

あちこち飛びますがloadNilメソッドは以下の通りです。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
-
-
|
-
|
-
|
!
!
|
-
|
!
!
public class MethodBodyCompiler extends RootScopedBodyCompiler {
    public void loadNil() {
        loadThreadContext();
        if (Options.COMPILE_INVOKEDYNAMIC.load()) {
            method.invokedynamic("nil", sig(IRubyObject.class, ThreadContext.class), InvokeDynamicSupport.getContextFieldHandle());
        } else {
            method.getfield(p(ThreadContext.class), "nil", ci(IRubyObject.class));
        }
    }
 
    public void loadThreadContext() {
        method.aload(StandardASMCompiler.THREADCONTEXT_INDEX);
    }
}

何をしているかというと、ローカル変数にnilを代入するという処理を行っています。結果として、__file__メソッドの先頭に以下のコードが埋め込まれます。

ALOAD 1     // ThreadContextロード
GETFIELD org/jruby/runtime/ThreadContext.nil : Lorg/jruby/runtime/builtin/IRubyObject;  // nilを取得
DUP        // 取得したnilを複製
ASTORE 9   // ローカル変数nにnilを設定
ASTORE 10  // ローカル変数piにnilを設定

ASTCompiler.compileClassメソッド

さてローカル変数の設定ができたのでNodeのコンパイルに取りかかります。まず始めにあるのはClassNodeです。対応するASTCompiler.compileClassメソッドではクラスパス、スーパークラス、クラス定義本体のコールバックを作成しBodyCompiler.defineClassメソッドを呼んでいます。defineClassメソッドはBaseBodyCompilerクラスで定義されています。

defineClassメソッドはけっこう長いですがやっていることは以下になります。

  1. JRuby的にRubyクラスを定義(RubyModule.defineOrGetClassUnderメソッドの呼び出しまで)
  2. クラス定義文に対応するメソッドの呼び出し(先頭で定義しているclassMethodNameの呼び出しまで)
  3. クラス定義文に対応するメソッドのコンパイル(ClassBodyCompilerインスタンスを生成しているところ以降)

ASTCompiler.compileDefnメソッド

クラス定義本体のNodeをたどっていくとDefnNodeに行き当たります。引数、メソッド本体に対するコールバックを作成した上でBodyCompiler.defineNewMethodメソッドを呼んでいます。

defineNewMethodメソッドでは以下のことが行われています。

  1. JRuby的にメソッドの登録(RuntimeHelpers.defの呼び出しまで)
  2. メソッド定義のコンパイル(BodyCompiler取得しているところ以降)

ASTCompiler19.compileArgsメソッド

メソッド定義用のBodyCompilerを生成する際にVariableCompiler.beginMethodメソッドが呼ばれます。トップレベルと違い今回は引数のargsCallbackがnullではないのでcallメソッドが呼び出されます。その結果、ASTCompiler.compileArgsメソッドが呼ばれ引数設定処理のコンパイルが行われます。ちなみに、compileArgsメソッドはASTCompiler19クラスでオーバーロードされているので注意が必要です。

ASTCompiler19.compileArgsメソッドはcompileMethodArgsメソッドに処理を委譲します。compileMethodArgsメソッドでは各種引数に対するコールバックを作成しVariableCompiler.assignMethodArguments19メソッドを呼び出しています。

Rubyには引数の種類が複数あるので引数設定処理は単純ではありません。それがassignMethodArguments19メソッドに現れています。特にオプション引数は実際に渡された引数の数によって処理を変える必要があります。実際に生成されるコードを使って解説した方がいいので一瞬別のスクリプトを使います。

def bar
  2
end

def foo(a, b = 1, c = bar)  # 実はデフォルト値としてメソッド呼ぶことで来ます
end

このfooメソッドの引数設定処理は以下のようになります。

 // 引数aの設定
 ALOAD 3
 ICONST_0
 ALOAD 1
 GETFIELD org/jruby/runtime/ThreadContext.nil : Lorg/jruby/runtime/builtin/IRubyObject;
 INVOKESTATIC org/jruby/javasupport/util/RuntimeHelpers.elementOrNil ([Lorg/jruby/runtime/builtin/IRubyObject;ILorg/jruby/runtime/builtin/IRubyObject;)Lorg/jruby/runtime/builtin/IRubyObject;
 ASTORE 9
 // 引数bの設定
   // 2つ目の引数が渡されているかのチェック
 ALOAD 3
 ICONST_1
 ICONST_0
 INVOKESTATIC org/jruby/javasupport/util/RuntimeHelpers.optElementOrNull ([Lorg/jruby/runtime/builtin/IRubyObject;II)Lorg/jruby/runtime/builtin/IRubyObject;
 DUP
   // 2つ目の引数が渡されてない場合はデフォルト値設定処理へ
 IFNULL L1
   // 渡されている場合はそれを設定
 ASTORE 10
 // 引数cの設定
   // 3つ目の引数が渡されているかのチェック
 ALOAD 3
 ICONST_2
 ICONST_0
 INVOKESTATIC org/jruby/javasupport/util/RuntimeHelpers.optElementOrNull ([Lorg/jruby/runtime/builtin/IRubyObject;II)Lorg/jruby/runtime/builtin/IRubyObject;
 DUP
   // 2つ目の引数が渡されてない場合はデフォルト値設定処理へ
 IFNULL L2
   // 渡されている場合はそれを設定
 ASTORE 11
   // 引数が3つ渡されているのでデフォルト値設定処理をスキップ
 GOTO L3
// 引数bのデフォルト値設定処理
L1
FRAME FULL [opt org/jruby/runtime/ThreadContext org/jruby/runtime/builtin/IRubyObject [Lorg/jruby/runtime/builtin/IRubyObject; org/jruby/runtime/Block T T T T org/jruby/runtime/builtin/IRubyObject org/jruby/runtime/builtin/IRubyObject org/jruby/runtime/builtin/IRubyObject] [org/jruby/runtime/builtin/IRubyObject]
 ALOAD 1
 GETFIELD org/jruby/runtime/ThreadContext.runtime : Lorg/jruby/Ruby;
 INVOKESTATIC org/jruby/RubyFixnum.one (Lorg/jruby/Ruby;)Lorg/jruby/RubyFixnum;
 ASTORE 10
// 引数cのデフォルト値設定処理
L2
FRAME SAME1 org/jruby/runtime/builtin/IRubyObject
 ALOAD 0
 INVOKEVIRTUAL opt.getCallSite0 ()Lorg/jruby/runtime/CallSite;
 ALOAD 1
 ALOAD 2
 ALOAD 2
 INVOKEVIRTUAL org/jruby/runtime/CallSite.call (Lorg/jruby/runtime/ThreadContext;Lorg/jruby/runtime/builtin/IRubyObject;Lorg/jruby/runtime/builtin/IRubyObject;)Lorg/jruby/runtime/builtin/IRubyObject;
 ASTORE 11

今回対象としているスクリプトではメソッドの引数はシンプルなので以下のように渡された引数をそのまま格納するというコードが生成されます。ちなみに、piメソッドはクロージャを持っているためVariableCompilerの実体はHeapBaseVariableCompilerになっています。(というわけでASTOREではなく、メソッド呼び出しになっています)

ALOAD 3  // 1つ目の引数ロード
ALOAD 5  // DynamicScopeロード
SWAP
INVOKEVIRTUAL org/jruby/runtime/DynamicScope.setValueZeroDepthZero (Lorg/jruby/runtime/builtin/IRubyObject;)Lorg/jruby/runtime/builtin/IRubyObject;  // 引数をローカル変数nに設定

ASTCompiler.compileCallメソッド

メソッド内に入って1つ目にあるのはLocalAsgnNodeですがまあこれはあまり面白くないので飛ばしてメソッド呼び出しをしているCallNoArgNodeを見てみましょう。処理はInvocationCompilerのinvokeDynamicメソッドに委譲されます。InvocationCompilerの実体は通常StandardInvocationCompilerのようです。

invokeDynamicメソッドではレシーバのコンパイル、引数のコンパイル、ブロックのコンパイルをした後、メソッド呼び出しを行っています。メソッドの呼び出しはCallSiteというクラスが鍵になっているようですがそれは後から見ることにしてブロックのコンパイルに移りましょう。

ASTCompiler19.compileIterメソッド

IterNodeに対応するcompileIterメソッドもASTCompiler19クラスでオーバーロードされています。そのため、ブロックのコンパイルはBodyCompiler.createNewClosure19メソッドに委譲されます。

createNewClosure19メソッドではBlockBodyクラスとブロックを関連づけた後、ブロック本体のコンパイルを行っています。

ASTCompiler.compileIfメソッド

ブロックに入るとDAsgnNodeがあります。もっともそのブロックで定義されたブロック変数の場合はLocalAsgnNodeの処理と変わりがないので飛ばします。

IfNodeを処理するcompileIfではいくつか最適化が行われています。常にtrueやfalseなif、条件部がグローバル変数や定数時とすると主にデバッグ用途ですかね。

通常はBodyCompiler.performBooleanBranch2メソッドに処理が委譲されています。分岐の処理自体は単純ですね。

条件部で生成されるコードがちょっとわかりにくいので解説します。まず、条件部のNodeはこんな感じです。

condition=CallOneArgNode
  receiverNode=CallOneArgNode
    receiverNode=CallOneArgNode
      receiverNode=DVarNode
        name='x'
        location=0
      name='*'
      arg1=DVarNode
        name='x'
        location=0
    name='+'
    arg1=CallOneArgNode
      receiverNode=CallOneArgNode
        receiverNode=DVarNode
          name='y'
          location=1
        name='*'
        arg1=DVarNode
          name='y'
          location=1
  name='<='
  arg1=FixNumNode
    value=1

で、生成されるコードはこんな感じです。

ALOAD 0
INVOKEVIRTUAL montecarlo.getCallSite3
ALOAD 1
ALOAD 2
ALOAD 0
INVOKEVIRTUAL montecarlo.getCallSite4
ALOAD 1
ALOAD 2
ALOAD 0
INVOKEVIRTUAL montecarlo.getCallSite5
ALOAD 1
ALOAD 2
ALOAD 9
ALOAD 9
INVOKEVIRTUAL org/jruby/runtime/CallSite.call
ALOAD 0
INVOKEVIRTUAL montecarlo.getCallSite6
ALOAD 1
ALOAD 2
ALOAD 10
ALOAD 10
INVOKEVIRTUAL org/jruby/runtime/CallSite.call
INVOKEVIRTUAL org/jruby/runtime/CallSite.call
LDC 1
INVOKEVIRTUAL org/jruby/runtime/CallSite.call

何回もALOADされててわかりにくいですがStandardInvocationCompiler.invokeDynamicメソッドを手がかりに読み解いていくと、メソッド呼び出しは

  1. CallSiteのキャッシュ(ALOAD 0とINVOKEVIRTUAL montecarlo.getCallSiteX)
  2. ThreadContextのロード(ALOAD 1)
  3. selfのロード(ALOAD 2)
  4. 引数の処理
  5. Rubyメソッドの呼び出し(INVOKEVIRTUAL CallSite.call

となっています。それを考えると、

ALOAD 0                                       ----------+
INVOKEVIRTUAL montecarlo.getCallSite3                   |
ALOAD 1                                                 |
ALOAD 2                                                 |
ALOAD 0                                       --------+ |
INVOKEVIRTUAL montecarlo.getCallSite4                 | |
ALOAD 1                                               | |
ALOAD 2                                               | |
ALOAD 0                                       -+      | |
INVOKEVIRTUAL montecarlo.getCallSite5          |      | |
ALOAD 1                                        |      | |
ALOAD 2                                        |x * x | |
ALOAD 9                                        |      | |
ALOAD 9                                        |      | |
INVOKEVIRTUAL org/jruby/runtime/CallSite.call -+      |x * x + y * y
ALOAD 0                                       -+      | |
INVOKEVIRTUAL montecarlo.getCallSite6          |      | |x * x + y * y <= 1
ALOAD 1                                        |      | |
ALOAD 2                                        |y * y | |
ALOAD 10                                       |      | |
ALOAD 10                                       |      | |
INVOKEVIRTUAL org/jruby/runtime/CallSite.call -+      | |
INVOKEVIRTUAL org/jruby/runtime/CallSite.call --------+ |
LDC 1                                                   |
INVOKEVIRTUAL org/jruby/runtime/CallSite.call ----------+

という入れ子関係になっていることがわかります。ちなみに、一番外側の「x * x + y * y <= 1」はinvokeDynamicではなくinvokeBinaryBooleanFixnumRHSメソッドが使われています。最適化の一環でしょう。

ASTCompiler.compileLocalAsgnメソッド、compileLocalVarメソッド

さて次にthen節のコンパイル処理を見ていきましょう。「count += 1」は以下のようなNodeになります。

thenBody=LocalAsgnNode
  name='count'
  location=1
  depth=1
  valueNode=CallOneArgNode
    receiverNode=LocalVarNode
      name='count'
      location=1
      depth=1
    name='+'
    arg1=FixNumNode
      value=1

先ほどはLocalAsgnNodeは面白くないと飛ばしましたがそれは通常時のローカル変数代入の話です。ブロック内でブロックの外のローカル変数(depthでどれだけさかのぼるかを管理)にアクセスするとなると話は面白くなります。

StackBasedVariableCompilerのassignLocalVariableメソッドは次のようになっています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
-
-
|
-
|
!
!
 public void assignLocalVariable(int index, int depth, CompilerCallback value, boolean expr) {
     if (depth == 0) {
         assignLocalVariable(index, value, expr);
     } else {
         assignHeapLocal(value, depth, index, expr);
     }
 }

assignHeapLocalメソッドはAbstractVariableCompilerクラスに書かれています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
-
-
|
|
|
|
|
|
!
|
!
 
-
-
!
-
|
|
!
!
protected void assignHeapLocal(CompilerCallback value, int depth, int index, boolean expr) {
    switch (index) {
    case 0:
        unwrapParentScopes(depth);
        value.call(methodCompiler);
        method.invokevirtual(p(DynamicScope.class), "setValueZeroDepthZero", sig(IRubyObject.class, params(IRubyObject.class)));
        break;
    ...
    }
    ...
}
 
protected void unwrapParentScopes(int depth) {
    // unwrap scopes to appropriate depth
    method.aload(methodCompiler.getDynamicScopeIndex());
    while (depth > 0) {
        method.invokevirtual(p(DynamicScope.class), "getNextCapturedScope", sig(DynamicScope.class));
        depth--;
    }
}

というわけでdepth分スコープをさかのぼる処理をした後、そのスコープに値を設定している雰囲気です。以上を考えて生成されるコードを読むと理解が容易になります。(LocalVarNodeも話は同じです)

ALOAD 5                                                                --------------+
INVOKEVIRTUAL org/jruby/runtime/DynamicScope.getNextCapturedScope                    |
ALOAD 0                                                                ------------+ |
INVOKEVIRTUAL montecarlo.getCallSite7                                              | |
ALOAD 1                                                                            | |
ALOAD 2                                                                            | |
ALOAD 5                                                                -+          | |
INVOKEVIRTUAL org/jruby/runtime/DynamicScope.getNextCapturedScope       |          | |
ALOAD 1                                                                 |count参照 | |
GETFIELD org/jruby/runtime/ThreadContext.nil                            |          |count+1
INVOKEVIRTUAL org/jruby/runtime/DynamicScope.getValueOneDepthZeroOrNil -+          | |
LDC 1                                                                              | |count代入
INVOKEVIRTUAL org/jruby/runtime/CallSite.call                          ------------+ |
INVOKEVIRTUAL org/jruby/runtime/DynamicScope.setValueOneDepthZero      --------------+

ここまで読めば後は大体わかるでしょう。

再度org.jruby.Ruby

runScriptメソッド

これでNodeからJavaバイトコードへの変換が終了しました。Rubyクラスまで戻った後、runScriptメソッドで生成されたクラスのloadメソッドを呼び出すことで実行が開始されます。(ちなみに、バイトコードをダンプした場合はloadメソッドは作られません)

おわりに

今回はスクリプト解析で作成したNodeをJavaバイトコードに変換する処理を見てきました。Java VMのバイトコードを生成するため、CRubyやmrubyのようなRuby実行に特化した命令コードよりもやっていることがわかりにくいと感じました。それでも一つ一つ読み解いていくことで次第に「このまとまりがメソッド呼び出しで、それが入れ子になってるのか ということがわかるようになりました。

さて、ここまででバイトコードに変換され後は実行するだけなのですが、メソッド呼び出しやらブロック呼び出しやらがどう動いてるのか全く解決していませんね。引き続きそこら辺を読んでいきたいと思います。


添付ファイル: filemontecarlo.bytecode.txt 939件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2013-09-08 (日) 21:59:47 (3881d)