Experimental feature in GraalVM

互換性

TruffleRubyは、C拡張を含む、Rubyの標準実装であるMRIバージョン3.2.2との完全な互換性を目指しています。 TruffleRubyはまだ開発中であるため、100%の互換性はまだありません。

TruffleRubyはRailsを実行でき、C拡張を含む多くのgemと互換性があります。 TruffleRubyはruby/specの約97%をパスしており、他のどのRuby代替実装よりも優れています。

MRIとの非互換性は、以下に詳述するまれなケースを除いて、バグとみなされます。 MRIとの非互換性が見つかった場合は、報告してください。

TruffleRubyは、可能な限りMRIの動作に一致させようとします。 いくつかの限られたケースでは、TruffleRubyはより優れた機能を提供するために、意図的にMRIと互換性がありません。

識別 #

TruffleRubyは、識別のために以下の定数を定義します。

  • RUBY_ENGINE'truffleruby' です。
  • RUBY_VERSION は、互換性のあるMRIのバージョンです。
  • RUBY_REVISION は、TruffleRubyのビルドに使用された完全な git コミットハッシュです (MRI 2.7+と同様)。
  • RUBY_RELEASE_DATE は、git のコミット日付です。
  • RUBY_PATCHLEVEL は常にゼロです。
  • RUBY_ENGINE_VERSION は、TruffleRubyのバージョンです。ビルドがTruffleRubyリリースの一部ではない場合は、0.0- とGitコミットハッシュになります。

C APIでは、プリプロセッサマクロ TRUFFLERUBY が定義されており、#ifdef TRUFFLERUBY で確認できます。

Ruby 3.xの機能 #

Ruby 3.2以前のほとんどの機能は、TruffleRubyでサポートされています。 ただし、一部の機能はまだ実装されていません。 詳細については、以下の問題を参照してください。

完全に欠落している機能 #

継続と callcc #

継続はMRIでは廃止されており、代わりにFiberが推奨されています。 継続と callcc は、そのセマンティクスがJVMアーキテクチャと根本的に一致しないため、TruffleRubyに実装される可能性は低いでしょう。

Fork #

TruffleRubyインタープリターをforkすることはできません。 この機能は、JVM上で実行されている場合はサポートされる可能性は低いですが、将来的にはネイティブ構成でサポートされる可能性があります。 fork が利用可能かどうかをテストする、正しくポータブルな方法は次のとおりです。

Process.respond_to?(:fork)

標準ライブラリ #

以下の標準ライブラリはサポートされていません。

  • continuation: MRIで廃止されました
  • debug: RubyVM::InstructionSequence に依存しています。代わりにVSCode拡張機能または--inspectを使用してください
  • io/console: 部分的に実装されています
  • io/wait: 部分的に実装されています
  • pty: 将来的に実装される可能性があります

TruffleRubyは、JRubyと同様に、ffi gemの独自のバックエンド実装を提供します。 これは完全に透過的であり、MRIと同じように動作するはずです。実装はかなり完全であり、まれに使用されるコーナーケースを除いて、ffi gemのすべての仕様をパスします。

内部MRI機能 #

RubyVM はユーザー向けではなく、実装されていません。

大きな違いのある機能 #

スレッドは並列に実行される #

MRIでは、スレッドは並行してスケジュールされますが、並列にはスケジュールされません。 TruffleRubyでは、スレッドは並列にスケジュールされます。JRubyやRubiniusと同様に、共有の可変データ構造へのアクセスを正しく同期させる責任はユーザーにあり、TruffleRubyはインタープリターの状態を正しく同期させる責任を負います。

ファイバーはMRIと同じパフォーマンス特性を持たない #

ファイバーのユースケースのほとんどは、起動が簡単で安価であり、メモリオーバーヘッドが少ないことに依存しています。 TruffleRubyでは、ファイバーは現在オペレーティングシステムのスレッドを使用して実装されているため、Rubyスレッドと同じパフォーマンス特性を持ちます。これは、Loomプロジェクトが安定し、JVMリリースで利用可能になったら対処される予定です。

内部としてマークされている一部のクラスは異なる #

MRIは、ドキュメントにMRI (CRuby) でのみ利用可能と記載されているクラスをいくつか提供しています。 これらのクラスは、実装することが実際的であれば実装されますが、常にそうであるとは限りません。 たとえば、RubyVM は使用できません。

Regexp #

TruffleRubyでは、Regexp インスタンスは常に不変です。 CRuby 3.1では、すべてのリテラル Regexp は不変ですが、非リテラルは依然として可変です。 この制限により、TruffleRubyではRegexpインスタンスにシングルトンメソッドを定義したり、Regexpのサブクラスのインスタンスを作成したりすることはできません。

微妙な違いのある機能 #

コマンドラインスイッチ #

-y--yydebug--dump=--debug-frozen-string-literal スイッチは、サポートされていない開発ツールであるため、警告付きで無視されます。

マジックコメント付きの -e 引数で渡されるプログラムは、JVMが引数を取得するまでにすでにデコードされているため、UTF-8またはUTF-8のサブセットであるエンコーディングである必要があります。

--jit オプションと jit 機能はTruffleRubyには影響せず、警告が表示されます。 GraalVMコンパイラは、利用可能な場合は常に使用されます。

文字列の最大バイトサイズは231-1です #

Rubyの文字列は、Javaの byte[] として表されます。 JVMは、配列の最大サイズを231-1に制限しています (サイズを32ビット符号付き int に格納することにより)。そのため、Rubyの文字列は231-1バイトより長くすることはできません。 つまり、文字列は2GB未満である必要があります。 これはJRubyと同じ制限です。回避策として、ネイティブに割り当てられた文字列を使用することが考えられますが、すべてのRuby文字列操作をネイティブ文字列でサポートするには、多大な労力が必要になります。

UTF-16およびUTF-32エンコーディングの文字列 #

TruffleRubyは、奇数バイト数のUTF-16文字列(ネイティブエンディアン)をサポートしていません。 同様に、UTF-32では、4の倍数である必要があります。 これは、最適化、圧縮、不変条件などに必要です。

スレッドは異なるポイントで割り込みを検出する #

TruffleRubyスレッドは、MRIの場合と比較して、プログラムの異なるポイントで割り込みが発生したことを検出する場合があります。 一般に、TruffleRubyはMRIよりも早く割り込みを検出するようです。 JRubyとRubiniusもMRIとは異なり、この動作はMRIでは文書化されておらず、MRIのバージョン間で変更される可能性があるため、割り込みポイントに依存することはお勧めしません。

ポリグロット標準I/Oストリーム #

実験的な --polyglot-stdio オプションを介して、Polyglotエンジンによって提供される標準I/Oストリームを使用する場合、ファイル記述子0、1、および2への読み取りと書き込みは、これらのストリームにリダイレクトされます。 つまり、これらのファイル記述子に対する isatty などの他のI/O操作は、これらのストリームが実際に出力される場所とは関係がない場合があり、dup などの操作ではポリグロットストリームへの接続が失われる可能性があります。 たとえば、一部のロギングフレームワークが行うように、$stdout.reopen を実行すると、ポリグロット出力ではなく、ネイティブの標準出力が得られます。

また、I/Oバッファのドレイン、sync が設定されたI/Oオブジェクトへの書き込み、および write_nonblock は、ストリームがこれを検出する方法を提供しないため、EAGAIN および EWOULDBLOCK で書き込みを再試行しません。

エラーメッセージ #

エラーメッセージの文字列は、一般にRuby Spec Suiteまたはテストの対象ではないため、MRIとは異なる場合があります。

シグナル #

まず、POSIX(man 2 signal)に従って、KILLSTOP はトラップできません。 一部のシグナルはCRubyで予約されており、それらをトラップするとあらゆる種類の問題が発生するため、TruffleRubyでも予約されています。SEGVBUSILLFPEVTALRM です。

ネイティブ構成を使用する場合、TruffleRubyはMRIと同じシグナルをすべてトラップできます。そのため、MRIで実行されるシグナル処理コードは、ネイティブ構成では変更なしでTruffleRubyで実行できます。

ただし、JVMで実行する場合、TruffleRubyはQUITシグナルをトラップできません。これは、このシグナルがJVMによって予約されているためです。このような場合、trap(:QUIT) {}ArgumentErrorを発生させます。このシグナルをトラップできることに依存するコードは、別の使用可能なシグナルにフォールバックする必要があります。

TruffleRubyが多言語アプリケーションの一部として実行される場合、他の言語によって処理されるシグナルは、TruffleRubyがトラップできなくなります。

GC統計 #

TruffleRubyはMRIと同様のGC.stat統計を提供しますが、すべての統計が利用できるわけではなく、一部の統計は概算値である場合があります。実際の値または概算値が提供されているものについては、GC.stat.keysを使用してください。値がない場合は0が返されます。

呼び出し元の位置 #

Kernel#caller_locationsまたはThread.each_caller_locationを使用すると、エンジン固有の場所オブジェクトやパスが含まれる場合があります。これは想定されており、必要に応じてアプリケーションコードでフィルタリングする必要があります。

Thread.to_enum(:each_caller_location)によって返される列挙子は、.nextによる反復をサポートしていません。CRubyでは、これはStopIterationを発生させますが、TruffleRubyでは、未確定(.nextが呼び出される場所と方法に関連する)コールスタックで反復処理を行います。どのような状況でも(CRubyとTruffleRubyのどちらでも)これを使用することはお勧めしません。

パフォーマンスが非常に低い機能 #

ObjectSpace #

ObjectSpace#each_objectは実装されていますが、ヒープ全体を反復処理し、本質的にGCマーキングフェーズと同等の処理を行う必要があるため、かなり低速です。ObjectSpace#trace_object_allocations_startは、CRubyの動作と同様に、すべての割り当てを遅くします。ObjectSpaceのほとんどのメソッドを使用すると、プログラムのパフォーマンスが一時的に低下します。テストケースやその他の同様の「オフライン」操作で使用するのは問題ありませんが、本番アプリケーションの内部ループで使用することはおそらく望ましくありません。

set_trace_func #

set_trace_funcを使用すると、プログラムのパフォーマンスが一時的に低下します。ObjectSpaceと同様に、本番アプリケーションの内部ループでこれを使用することはお勧めしません。

バックトレース #

例外のスローやバックトレースの作成が必要なその他の操作は、一般的にMRIよりも低速です。これは、TruffleRubyがRubyコードを高速に実行するために適用された最適化を元に戻して、バックトレースエントリを再作成する必要があるためです。Rubyのどの実装でも、制御フローに例外を使用することはお勧めしません。

この問題を軽減するために、バックトレースが使用されないことが検出できる場合は、バックトレースが自動的に無効になります。

C拡張機能の互換性 #

識別子はマクロまたは関数である可能性があります #

通常はマクロである識別子は関数である可能性があり、関数はマクロである可能性があり、グローバル変数はマクロである可能性があります。これは、特定の実装に依存するコンテキストで使用される場合に問題を引き起こす可能性があります(たとえば、そのアドレスを取得し、関数ポインタ変数に割り当て、defined()を使用してマクロが存在するかどうかを確認する場合)。これらの問題はすべてバグと見なされ、修正される必要があります。これらのケースを報告してください。

rb_scan_args #

rb_scan_argsは最大10個のポインタまでしかサポートしていません。

rb_funcall #

rb_funcallは最大15個の引数までしかサポートしていません。

RDATAおよびRTYPEDDATAmark関数 #

RDATAおよびRTYPEDDATAmark関数は、ガベージコレクション中には呼び出されず、定期的に呼び出されます。オブジェクトに関する情報は、構造体に割り当てられるとキャッシュされ、TruffleRubyは、ガベージコレクターが理解できる方法でこれらのオブジェクト関係を表すために、キャッシュがいっぱいになったときにすべてのmark関数を定期的に実行します。このプロセスはMRIと同様に動作するはずです。

JRubyとの互換性 #

RubyとJavaの相互運用性 #

TruffleRubyは、JRubyと同じJavaとの相互運用性インターフェースをサポートしていません。TruffleRubyは、代わりに、Javaを含む複数の言語と相互運用するための代替ポリグロットAPIを提供します。

JavaからRubyへの相互運用 #

JavaからRubyコードを呼び出すことは、GraalVM Polyglot APIによってサポートされています。

Java拡張機能 #

JRuby用に記述されたJava拡張機能の使用はサポートされていません。

ネイティブ構成でまだサポートされていない機能 #

ネイティブ構成でTruffleRubyを実行することは、JVMで実行することとほぼ同じです。両方のVMは異なるガベージコレクターを使用するため、リソース管理に違いがありますが、機能的には本質的に同等です。

ネイティブ構成でのJavaの相互運用性 #

Javaの相互運用性はネイティブ構成で機能しますが、より多くのセットアップが必要です。デフォルトでは、Javaの相互運用性のためにイメージ内で使用できる配列クラスは一部のみです。TruffleRubyを含むネイティブイメージをコンパイルすることで、より多くのクラスを追加できます。詳細については、こちらをご覧ください。

仕様の網羅性 #

「いくつの仕様がありますか?」という質問は、簡単で正確な答えがあるわけではありません。仕様の数は、Ruby言語のバージョン、プラットフォーム、仕様のバージョンによって異なります。標準ライブラリとC拡張APIの仕様も非常に不均一であり、誤解を招く結果が生じる可能性があります。

このブログ投稿では、TruffleRubyがいくつの仕様に合格しているかをまとめています。

お問い合わせ