- JDK 23対応GraalVM (最新)
- JDK 24対応GraalVM (早期アクセス)
- JDK 21対応GraalVM
- JDK 17対応GraalVM
- アーカイブ
- 開発ビルド
GraalVMのInstruments入門
GraalVMプラットフォームでは、ツールはInstrumentsと呼ばれることがあります。Instrument APIは、そのようなInstrumentsを実装するために使用されます。Instrumentsは、非常に細かい粒度のVMレベルのランタイムイベントを追跡して、GraalVM上で実行されているアプリケーションのランタイム動作をプロファイル、検査、および分析できます。
シンプルなツール #
ツール開発者にとってより簡単な出発点を提供するために、シンプルなツールのサンプルプロジェクトを作成しました。これは、シンプルなコードカバレッジツールを実装した、javadocが豊富なMavenプロジェクトです。
ツール開発の出発点として、リポジトリをクローンしてソースコードを調べてみることをお勧めします。以下のセクションでは、シンプルなツールのソースコードを実行例として使用し、GraalVMツールをビルドして実行するために必要な手順をガイドします。これらのセクションでは、Instrument APIのすべての機能を網羅しているわけではないため、詳細についてはjavadocを確認することをお勧めします。
要件 #
前述のように、シンプルなツールはコードカバレッジツールです。最終的には、ソースコードの何パーセントの行が実行されたか、そして正確にどの行が実行されたかについての情報を開発者に提供する必要があります。それを念頭に置いて、ツールからのいくつかの高レベルの要件を定義できます。
- ツールは、ロードされたソースコードを追跡します。
- ツールは、実行されたソースコードを追跡します。
- アプリケーション終了時に、ツールは行ごとのカバレッジ情報を計算して出力します。
Instrument API #
ツールの主な出発点は、TruffleInstrumentクラスをサブクラス化することです。当然のことながら、シンプルなツールのコードベースはまさにこれを行い、SimpleCoverageInstrumentクラスを作成します。
クラスのRegistrationアノテーションは、新しく作成されたInstrumentがInstrument APIに登録されることを保証します。つまり、フレームワークによって自動的に検出されます。また、Instrumentに関するメタデータ(ID、名前、バージョン、Instrumentが提供するサービス、Instrumentが内部か外部か)も提供します。このアノテーションを有効にするには、DSLプロセッサがこのクラスを処理する必要があります。これは、シンプルなツールの場合、Maven設定でDSLプロセッサを依存関係として持つことで自動的に行われます。
ここで、`SimpleCoverageInstrument`クラスの実装、つまり`TruffleInstrument`からオーバーライドするメソッドについて振り返ってみましょう。これらは、onCreate、onDispose、getOptionDescriptorsです。`onCreate`メソッドと`onDispose`メソッドは、その名前が示す通り、Instrumentの作成時と破棄時にフレームワークによって呼び出されます。これらの実装については後ほど説明しますが、まずは残りの1つである`getOptionDescriptors`について説明します。
Truffle言語実装フレームワークには、コマンドラインオプションを指定するための独自のシステムが備わっています。これらのオプションを使用すると、ツールユーザーはコマンドラインから、またはポリグロットコンテキストを作成するときにツールを制御できます。これはアノテーションベースであり、そのようなオプションの例としては、`SimpleCoverageInstrument`のENABLEDフィールドとPRINT_COVERAGEフィールドがあります。これらの両方は、OptionKey型の静的最終フィールドであり、Optionでアノテーションが付けられています。これは、`Registration`アノテーションと同様に、オプションのメタデータを提供します。繰り返しますが、`Registration`アノテーションと同様に、`Option`アノテーションを有効にするにはDSLプロセッサが必要であり、DSLプロセッサはOptionDescriptorsのサブクラスを生成します(この場合は`SimpleCoverageInstrumentOptionDescriptors`という名前)。フレームワークにInstrumentが提供するオプションを知らせるために、このクラスのインスタンスを`getOptionDescriptors`メソッドから返す必要があります。
`onCreate`メソッドに戻ると、引数としてEnvクラスのインスタンスを受け取ります。このオブジェクトは多くの有用な情報を提供しますが、`onCreate`メソッドでは、主にツールに渡されるオプションを読み取るために使用できるgetOptionsメソッドに興味があります。これを使用して、`ENABLED`オプションが設定されているかどうかを確認し、設定されている場合はenableメソッドを呼び出すことによってツールを有効にします。同様に、`onDispose`メソッドでは、`PRINT_COVERAGE`オプションの状態を確認し、有効になっている場合は結果を出力するprintResultsメソッドを呼び出します。
「ツールを有効にする」とはどういう意味でしょうか?一般に、これは、興味のあるイベントとそれらにどのように反応したいかをフレームワークに伝えることを意味します。`enable`メソッドを見ると、次のことが行われています。
- まず、SourceSectionFilterを定義します。このフィルターは、興味のあるソースコードの部分を宣言的に定義したものです。この例では、式と consideredれるすべてのノードに関心があり、内部言語の部分には関心がありません。
- 次に、システムのどの部分をインストゥルメント化したいかを指定できるオブジェクトであるInstrumenterクラスのインスタンスを取得します。
- 最後に、`Instrumenter`クラスを使用して、ソースセクションリスナーと実行イベントファクトリを指定します。これらの両方は、次の2つのセクションで説明します。
ソースセクションリスナー #
言語APIは、ソースコード単位であるSourceと、`Source`の1つの連続した部分(例:1つのメソッド、1つのステートメント、1つの式など)であるSourceSectionの概念を提供します。詳細は、それぞれのjavadocを参照してください。
シンプルなツールの最初の要件は、ロードされたソースコードを追跡することです。Instrument APIは、サブクラス化してインストゥルメンターに登録すると、ユーザーがソースセクションのランタイムロードに反応できるLoadSourceSectionListenerを提供します。これは、Instrumentのenableメソッドに登録されているGatherSourceSectionsListenerでまさに私たちが行っていることです。`GatherSourceSectionsListener`の実装は非常にシンプルです。onLoadメソッドをオーバーライドして、ロードされた各ソースセクションをInstrumentに通知します。Instrumentは、各`Source`からCoverageオブジェクトへのマッピングを保持します。このオブジェクトは、各ソースのロードされたソースセクションのセットを保持します。
実行イベントノード #
ゲスト言語は、抽象構文木(AST)インタープリターとして実装されます。言語実装者は、特定のノードにタグを付けます。これにより、前述の`SourceSectionFilter`を使用して、言語に依存しない方法で、どのノードに関心があるかを選択できます。
Instrument APIの主な強みは、ASTに特殊なノードを挿入して、対象のノードを「ラップ」できることです。これらのノードは、言語開発者が使用するのと同じインフラストラクチャを使用して構築されており、ランタイムの観点からは言語ノードと区別できません。これは、ゲスト言語をそのような高性能言語実装に最適化するために使用されるすべての技術が、ツール開発者にも利用できることを意味します。
これらの手法の詳細については、言語実装ドキュメントを参照してください。シンプルなツールが2番目の要件を満たすためには、すべての式を、その式が実行されたときに通知する独自のノードでインストゥルメント化する必要があると言うだけで十分です。
このタスクでは、CoverageNodeを使用します。これは、名前が示すように、実行中のイベントを計測するために使用されるExecutionEventNodeのサブクラスです。ExecutionEventNode
はオーバーライド可能なメソッドを多数提供しますが、ここではonReturnValueのみに注目します。このメソッドは、「ラップされた」ノードが値を返したとき、つまり正常に実行されたときに呼び出されます。実装は非常にシンプルです。この特定のSourceSection
を持つノードが実行されたことをインストゥルメントに通知するだけで、インストゥルメントはカバレッジマップ内のCoverage
オブジェクトを更新します。
ロジックはフラグによってガードされているため、インストゥルメントへの通知はノードごとに一度だけ行われます。このフラグがCompilationFinalでアノテーションされており、インストゥルメントの呼び出しの前にtransferToInterpreterAndInvalidate()の呼び出しが先行していることは、Truffleの標準的な手法です。これにより、この計測が不要になった場合(ノードが実行された場合)、計測はパフォーマンスのオーバーヘッドとともに、以降のコンパイルから削除されます。
フレームワークが必要なときにCoverageNode
をインスタンス化する方法を知るためには、そのためのファクトリを提供する必要があります。ファクトリは、ExecutionEventNodeFactoryのサブクラスであるCoverageEventFactoryです。このクラスは、提供されたEventContextで検索することにより、各CoverageNode
が計測しているSourceSection
を認識することを保証します。
最後に、インストゥルメントを有効にする際に、インストゥルメンターに、フィルターによって選択されたノードを「ラップする」ためにファクトリを使用するように指示します。
ユーザーとインストゥルメント間の相互作用 #
Simple Toolの3番目で最後の要件は、行カバレッジを標準出力に出力することで、実際にユーザーと対話することです。インストゥルメントは、インストゥルメントが破棄されるときに呼び出されるonDisposeメソッドをオーバーライドします。このメソッドでは、適切なオプションが設定されているかどうかを確認し、設定されている場合は、Coverage
オブジェクトのマップによって記録されたカバレッジを計算して出力します。
これはユーザーに役立つ情報を提供する簡単な方法ですが、決して唯一の方法ではありません。ツールはデータをファイルに直接ダンプしたり、情報を表示するWebエンドポイントを実行したりすることもできます。Instrument APIがユーザーに提供するメカニズムの1つは、他のインストゥルメントが検索できるように、インストゥルメントをサービスとして登録することです。インストゥルメントのRegistrationアノテーションを見ると、インストゥルメントが他のインストゥルメントに提供するサービスを指定できるservices
フィールドがあることがわかります。これらのサービスは明示的に登録する必要があります。これにより、インストゥルメント間の懸念事項をより適切に分離できます。たとえば、「リアルタイムカバレッジ」インストゥルメントと「カバレッジが低い場合は中止」インストゥルメントを用意できます。前者はSimpleCoverageInstrument
を使用してREST APIを介してユーザーにオンデマンドのカバレッジ情報を提供し、後者はカバレッジがしきい値を下回ると実行を停止します。どちらもSimpleCoverageInstrument
をサービスとして使用します。
注:分離の理由から、インストゥルメントサービスはアプリケーションコードでは使用できず、インストゥルメントサービスは他のインストゥルメントまたはゲスト言語からのみ使用できます。
GraalVMへのツールのインストール #
これまでのところ、Simple Toolはすべての要件を満たしているようですが、問題は残っています。どのように使用すればよいのでしょうか?前述のように、Simple ToolはMavenプロジェクトです。JAVA_HOME
をGraalVMインストールに設定し、mvn package
を実行すると、target/simpletool-<version>.jar
が生成されます。これがSimple Toolの配布形式です。
Truffleフレームワークは、言語/ツーリングコードとアプリケーションコードを明確に分離しています。このため、JARファイルをクラスパスに配置しても、フレームワークは新しいツールが必要であることを認識しません。これを達成するために、simple toolのランチャースクリプトに示されているように、--vm.Dtruffle.class.path.append=/path/to/simpletool-<version>.jar
を使用します。このスクリプトは、Simple Tool用に指定したCLIオプションを設定できることも示しています。つまり、./simpletool js example.js
を実行すると、GraalVMのjs
ランチャーが起動し、ツールがフレームワーククラスパスに追加され、付属のexample.jsファイルがSimple Toolを有効にして実行され、次の出力が得られます。
==
Coverage of /path/to/simpletool/example.js is 59.42%
+ var N = 2000;
+ var EXPECTED = 17393;
function Natural() {
+ x = 2;
+ return {
+ 'next' : function() { return x++; }
+ };
}
function Filter(number, filter) {
+ var self = this;
+ this.number = number;
+ this.filter = filter;
+ this.accept = function(n) {
+ var filter = self;
+ for (;;) {
+ if (n % filter.number === 0) {
+ return false;
+ }
+ filter = filter.filter;
+ if (filter === null) {
+ break;
+ }
+ }
+ return true;
+ };
+ return this;
}
function Primes(natural) {
+ var self = this;
+ this.natural = natural;
+ this.filter = null;
+ this.next = function() {
+ for (;;) {
+ var n = self.natural.next();
+ if (self.filter === null || self.filter.accept(n)) {
+ self.filter = new Filter(n, self.filter);
+ return n;
+ }
+ }
+ };
}
+ var holdsAFunctionThatIsNeverCalled = function(natural) {
- var self = this;
- this.natural = natural;
- this.filter = null;
- this.next = function() {
- for (;;) {
- var n = self.natural.next();
- if (self.filter === null || self.filter.accept(n)) {
- self.filter = new Filter(n, self.filter);
- return n;
- }
- }
- };
+ }
- var holdsAFunctionThatIsNeverCalledOneLine = function() {return null;}
function primesMain() {
+ var primes = new Primes(Natural());
+ var primArray = [];
+ for (var i=0;i<=N;i++) { primArray.push(primes.next()); }
- if (primArray[N] != EXPECTED) { throw new Error('wrong prime found: ' + primArray[N]); }
}
+ primesMain();
その他の例 #
以下の例は、Instrument APIで解決できる一般的なユースケースを示すことを目的としています。
- カバレッジインストゥルメント:Simple Toolの構築に使用されたカバレッジツールの例。必要に応じて、さらにテキストの実行例として使用されます。
- デバッガーインストゥルメント:デバッガーの実装方法のスケッチ。Instrument APIは、直接使用できるデバッガーインストゥルメントをすでに提供していることに注意してください。
- ステートメントプロファイラー:ステートメントの実行をプロファイルできるプロファイラー。
計測イベントリスナー #
Instrument APIは、com.oracle.truffle.api.instrumentation
パッケージで定義されています。計測エージェントは、TruffleInstrument
クラスを拡張することで開発でき、Instrumenter
クラスを使用して実行中のGraalVMインスタンスにアタッチできます。実行中の言語ランタイムにアタッチされると、言語ランタイムが破棄されない限り、計測エージェントは使用可能なままになります。GraalVMの計測エージェントは、次のようなさまざまなVMレベルのランタイムイベントを監視できます。
- ソースコード関連のイベント:監視対象の言語ランタイムによって新しいSourceまたはSourceSection要素がロードされるたびに、エージェントに通知されます。
- 割り当てイベント:監視対象の言語ランタイムのメモリ空間に新しいオブジェクトが割り当てられるたびに、エージェントに通知されます。
- 言語ランタイムとスレッド作成イベント:監視対象の言語ランタイムの新しい実行コンテキストまたは新しいスレッドが作成されるとすぐに、エージェントに通知されます。
- アプリケーション実行イベント:監視対象のアプリケーションが特定の言語操作セットを実行するたびに、エージェントに通知されます。このような操作の例には、言語ステートメントや式が含まれるため、計測エージェントは実行中のアプリケーションを非常に高い精度で検査できます。
実行イベントごとに、計測エージェントは、GraalVM計測ランタイムが関連する実行イベントのみを監視するために使用する_フィルタリング_条件を定義できます。現在、GraalVMインストゥルメントは、次の2つのフィルタータイプのいずれかを受け入れます。
AllocationEventFilter
:割り当てタイプ別に割り当てイベントをフィルタリングします。SourceSectionFilter
:アプリケーション内のソースコードの場所をフィルタリングします。
フィルターは、提供されているビルダーオブジェクトを使用して作成できます。たとえば、次のビルダーはSourceSectionFilter
を作成します。
SourceSectionFilter.newBuilder()
.tagIs(StandardTag.StatementTag)
.mimeTypeIs("x-application/js")
.build()
この例のフィルターを使用して、特定のアプリケーションのすべてのJavaScriptステートメントの実行を監視できます。行番号やファイル拡張子などの他のフィルタリングオプションも提供できます。
この例のソースセクションフィルターは、_タグ_を使用して、監視する実行イベントのセットを指定できます。ステートメントや式などの言語に依存しないタグは、com.oracle.truffle.api.instrumentation.Tag
クラスで定義されており、すべてのGraalVM言語でサポートされています。標準タグに加えて、GraalVM言語は、言語固有のイベントの詳細なプロファイリングを可能にするために、他の言語固有のタグを提供する場合があります。(例として、GraalVM JavaScriptエンジンは、Array
、Map
、Math
などのECMA組み込みオブジェクトの使用状況を追跡するためのJavaScript固有のタグを提供します。)
実行イベントの監視 #
アプリケーション実行イベントにより、非常に正確で詳細な監視が可能になります。GraalVMは、このようなイベントをプロファイルするために、2つの異なるタイプの計測エージェント、すなわち
- _実行リスナー_:特定のランタイムイベントが発生するたびに通知される計測エージェント。リスナーは、
ExecutionEventListener
インターフェースを実装し、ソースコードの場所に_状態_を関連付けることはできません。 - _実行イベントノード_:Truffle Framework ASTノードを使用して表現できる計測エージェント。このようなエージェントは、
ExecutionEventNode
クラスを拡張し、実行リスナーと同じ機能を備えていますが、状態をソースコードの場所に関連付けることができます。
単純な計測エージェント #
ランタイムコードカバレッジを実行するために使用されるカスタム計測エージェントの簡単な例は、CoverageExample
クラスにあります。以下は、エージェント、その設計、およびその機能の概要です。
すべてのインストゥルメントは、TruffleInstrument
抽象クラスを拡張し、@Registration
アノテーションを介してGraalVMランタイムに登録されます。
@Registration(id = CoverageExample.ID, services = Object.class)
public final class CoverageExample extends TruffleInstrument {
@Override
protected void onCreate(final Env env) {
}
/* Other methods omitted... */
}
インストゥルメントは、インストゥルメントのロード時にカスタム操作を実行するために、onCreate(Env env)
メソッドをオーバーライドします。通常、インストゥルメントはこのメソッドを使用して、既存のGraalVM実行環境に自身を登録します。例として、ASTノードを使用するインストゥルメントは、次の方法で登録できます。
@Override
protected void onCreate(final Env env) {
SourceSectionFilter.Builder builder = SourceSectionFilter.newBuilder();
SourceSectionFilter filter = builder.tagIs(EXPRESSION).build();
Instrumenter instrumenter = env.getInstrumenter();
instrumenter.attachExecutionEventFactory(filter, new CoverageEventFactory(env));
}
インストゥルメントは、attachExecutionEventFactory
メソッドを使用して、実行中のGraalVMに自身を接続し、次の2つの引数を指定します。
SourceSectionFilter
:追跡する特定のコードセクションについてGraalVMに通知するために使用されるソースセクションフィルター。ExecutionEventNodeFactory
:ランタイムイベント(ソースフィルターで指定)が実行されるたびに、エージェントによって実行される計測ASTノードを提供するTruffle ASTファクトリ。
アプリケーションのASTノードを計測する基本的なExecutionEventNodeFactory
は、次の方法で実装できます。
public ExecutionEventNode create(final EventContext ec) {
return new ExecutionEventNode() {
@Override
public void onReturnValue(VirtualFrame vFrame, Object result) {
/*
* Code to be executed every time a filtered source code
* element is evaluated by the guest language.
*/
}
};
}
実行イベントノードは、ランタイム実行イベントをインターセプトするために、特定のコールバックメソッドを実装できます。例としては、
onEnter
:フィルターされたソースコード要素(たとえば、言語ステートメントまたは式)に対応するASTノードが評価される_前_に実行されます。onReturnValue
:ソースコード要素が値を返した_後_に実行されます。onReturnExceptional
:フィルターされたソースコード要素が例外をスローした場合に実行されます。
実行イベントノードは、_コードの場所ごと_に作成されます。したがって、これらを使用して、計測対象アプリケーションの特定のソースコードの場所に固有のデータを格納できます。例として、計測ノードは、ノードローカルフラグを使用して、すでにアクセスしたすべてのコードの場所を追跡するだけです。このようなノードローカルのboolean
フラグを使用して、次の方法でASTノードの実行を追跡できます。
// To keep track of all source code locations executed
private final Set<SourceSection> coverage = new HashSet<>();
public ExecutionEventNode create(final EventContext ec) {
return new ExecutionEventNode() {
// Per-node flag to keep track of execution for this node
@CompilationFinal private boolean visited = false;
@Override
public void onReturnValue(VirtualFrame vFrame, Object result) {
if (!visited) {
CompilerDirectives.transferToInterpreterAndInvalidate();
visited = true;
SourceSection src = ec.getInstrumentedSourceSection();
coverage.add(src);
}
}
};
}
上記のコードが示すように、ExecutionEventNode
は有効な AST ノードです。これは、インストゥルメンテーションコードがインストゥルメントされたアプリケーションと共に GraalVM ランタイムによって最適化され、インストゥルメンテーションのオーバーヘッドが最小限に抑えられることを意味します。さらに、これにより、インストゥルメント開発者は Truffle フレームワークコンパイラディレクティブ をインストゥルメンテーションノードから直接使用できます。この例では、コンパイラディレクティブを使用して、visited
がコンパイル時に確定と見なすことができることを Graal コンパイラに通知しています。
各インストゥルメンテーションノードは、特定のコード位置にバインドされています。このような場所は、提供されている EventContext
オブジェクトを使用してエージェントによってアクセスできます。コンテキストオブジェクトは、インストゥルメンテーションノードに、実行中の現在の AST ノードに関するさまざまな情報へのアクセスを提供します。EventContext
を介してインストゥルメンテーションエージェントが使用できるクエリ API の例としては、以下が挙げられます。
hasTag
: インストゥルメントされたノードに特定のノードTag
があるかどうかをクエリします(たとえば、ステートメントノードが条件付きノードでもあるかどうかを確認するため)。getInstrumentedSourceSection
: 現在のノードに関連付けられたSourceSection
にアクセスします。getInstrumentedNode
: 現在のインストゥルメンテーションイベントに対応するNode
にアクセスします。
きめ細かい式プロファイリング #
インストゥルメンテーションエージェントは、言語式などの断片的なイベントでもプロファイリングできます。このため、エージェントは2つのソースセクションフィルタを提供して初期化する必要があります。
// What source sections are we interested in?
SourceSectionFilter sourceSectionFilter = SourceSectionFilter.newBuilder()
.tagIs(JSTags.BinaryOperation.class)
.build();
// What generates input data to track?
SourceSectionFilter inputGeneratingLocations = SourceSectionFilter.newBuilder()
.tagIs(StandardTags.ExpressionTag.class)
.build();
instrumenter.attachExecutionEventFactory(sourceSectionFilter, inputGeneratingLocations, factory);
最初のソースセクションフィルタ(例では sourceSectionFilter
)は、前に説明した他のフィルタと同等の通常のフィルタであり、監視するソースコードの場所を識別するために使用されます。2番目のセクションフィルタである inputGeneratingLocations
は、エージェントが特定のソースセクションで監視する必要がある*中間値*を指定するために使用されます。中間値は、監視対象のコード要素の実行に関与する*すべて*の観測可能な値に対応し、onInputValue
コールバックによってインストゥルメンテーションエージェントに報告されます。例として、エージェントが JavaScript の合計演算(+
)に提供されるすべての*オペランド*値をプロファイリングする必要があると仮定します。
var a = 3;
var b = 4;
// the '+' expression is profiled
var c = a + b;
JavaScript の二項式をフィルタリングすることにより、インストゥルメンテーションエージェントは、上記のコードスニペットに対して以下のランタイムイベントを検出できます。
onEnter()
: 3行目の二項式に対して。onInputValue()
: 3行目の二項演算の最初のオペランドに対して。コールバックによって報告される値は、a
ローカル変数の値である3
になります。onInputValue()
: 二項演算の2番目のオペランドに対して。コールバックによって報告される値は、b
ローカル変数の値である4
になります。onReturnValue()
: 二項式に対して。コールバックに提供される値は、式が評価を完了した後に返される値、つまり値7
になります。
ソースセクションフィルタを*すべて*の可能なイベントに拡張することにより、インストゥルメンテーションエージェントは、以下の実行トレース(擬似コード)と同等のものを観察します。
// First variable declaration
onEnter - VariableWrite
onEnter - NumericLiteral
onReturnValue - NumericLiteral
onInputValue - (3)
onReturnValue - VariableWrite
// Second variable declaration
onEnter - VariableWrite
onEnter - NumericLiteral
onReturnValue - NumericLiteral
onInputValue - (4)
onReturnValue - VariableWrite
// Third variable declaration
onEnter - VariableWrite
onEnter - BinaryOperation
onEnter - VariableRead
onReturnValue - VariableRead
onInputValue - (3)
onEnter - VariableRead
onReturnValue - VariableRead
onInputValue - (4)
onReturnValue - BinaryOperation
onInputValue - (7)
onReturnValue - VariableWrite
onInputValue
メソッドは、ソースセクションフィルタと組み合わせて使用することで、言語式で使用される中間値など、非常にきめ細かい実行イベントをインターセプトできます。Instrumentation フレームワークにアクセスできる中間値は、各言語によって提供されるインストゥルメンテーションサポートに大きく依存します。さらに、言語は、言語固有の Tag
クラスに関連付けられた追加のメタデータを提供する場合があります。
アプリケーションの実行フローの変更 #
これまでに紹介したインストゥルメンテーション機能により、ユーザーは実行中のアプリケーションの特定の側面を*観察*できます。アプリケーションの動作のパッシブモニタリングに加えて、Instrument API は、実行時にアプリケーションの動作を積極的に*変更*するためのサポートを提供しています。このような機能を使用して、実行中のアプリケーションの動作に影響を与えて特定のランタイムセマンティクスを実現する、複雑なインストゥルメンテーションエージェントを作成できます。たとえば、特定のメソッドまたは関数が実行されないように、実行中のアプリケーションのセマンティクスを変更できます(たとえば、呼び出されたときに例外をスローすることにより)。
このような機能を備えたインストゥルメンテーションエージェントは、実行イベントリスナーおよびファクトリの onUnwind
コールバックを活用することで実装できます。例として、次の JavaScript コードを考えてみましょう。
function inc(x) {
return x + 1
}
var a = 10
var b = a;
// Let's call inc() with normal semantics
while (a == b && a < 100000) {
a = inc(a);
b = b + 1;
}
c = a;
// Run inc() and alter it's return type using the instrument
return inc(c)
inc
の戻り値を常に 42
に変更するインストゥルメンテーションエージェントは、ExecutionEventListener
を使用して次のように実装できます。
ExecutionEventListener myListener = new ExecutionEventListener() {
@Override
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
String callSrc = context.getInstrumentedSourceSection().getCharacters();
// is this the function call that we want to modify?
if ("inc(c)".equals(callSrc)) {
CompilerDirectives.transferToInterpreter();
// notify the runtime that we will change the current execution flow
throw context.createUnwind(null);
}
}
@Override
public Object onUnwind(EventContext context, VirtualFrame frame, Object info) {
// just return 42 as the return value for this node
return 42;
}
}
イベントリスナーは、たとえば、次のインストゥルメントを使用して、すべての関数呼び出しをインターセプトして実行できます。
@TruffleInstrument.Registration(id = "UniversalAnswer", services = UniversalAnswerInstrument.class)
public static class UniversalAnswerInstrument extends TruffleInstrument {
@Override
protected void onCreate(Env env) {
env.registerService(this);
env.getInstrumenter().attachListener(SourceSectionFilter.newBuilder().tagIs(CallTag.class).build(), myListener);
}
}
有効にすると、インストゥルメントは関数呼び出しが戻るたびに onReturnValue
コールバックを実行します。コールバックは、関連付けられたソースセクションを読み取り(getInstrumentedSourceSection
を使用)、特定のソースコードパターン(この場合は関数呼び出し inc(c)
)を探します。このようなコードパターンが見つかるとすぐに、インストゥルメントは UnwindException
と呼ばれる特別なランタイム例外をスローします。これは、現在のアプリケーションの実行フローの変更について Instrumentation フレームワークに指示します。例外は、インストゥルメンテーションエージェントの onUnwind
コールバックによってインターセプトされます。これは、*任意*の値を元のインストゥルメントされたアプリケーションに返すために使用できます。
この例では、inc(c)
へのすべての呼び出しは、アプリケーション固有のデータに関係なく 42
を返します。より現実的なインストゥルメントは、アプリケーションの複数の側面にアクセスして監視する可能性があり、ソースコードの場所ではなく、オブジェクトインスタンスまたは他のアプリケーション固有のデータに依存する可能性があります。