互換性
TruffleRubyは、C拡張機能を含め、Rubyの標準実装であるMRIのバージョン3.0.3との完全な互換性を確保することを目的としています。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はGraalVMバージョンであるか、または0.0-およびGitコミット・ハッシュです(ビルドがGraalVMリリースの一部でない場合)。
C APIでは、プリプロセッサ・マクロTRUFFLERUBYが定義されており、#ifdef TRUFFLERUBYで確認できます。
提供されない機能
継続およびcallcc
MRIでは継続が廃止され、かわりにファイバが推奨されています。継続およびcallccのセマンティクスは根本的にJVMアーキテクチャに適合しないため、これらがTruffleRubyに実装される可能性はほとんどありません。
フォーク
TruffleRubyインタプリタをforkすることはできません。JVMで実行する場合、この機能がサポートされる可能性はほとんどありませんが、ネイティブ構成では将来サポートされる可能性があります。forkを使用できるかどうかをテストするための移植可能かつ正しい方法は次のとおりです:
Process.respond_to?(:fork)
標準ライブラリ
次の標準ライブラリはサポートされていません:
continuation(MRIで廃止されました)dbmgdbmdebug(将来実装される可能性があります。かわりに--inspectを使用してください)io/console(部分的に実装されており、将来実装される可能性があります)io/wait(部分的に実装されており、将来実装される可能性があります)pty(将来実装される可能性があります)win32(Windowsの場合のみ関連します)win32ole(Windowsの場合のみ関連します)
TruffleRubyには、JRubyと同様に、ffi gemの独自のバックエンド実装が用意されています。これは完全に透過的であり、MRIの場合と同様に動作します。この実装はほぼ完了し、ほとんど使用されないまれなケースを除き、ffi gemの仕様すべてに準拠しています。
MRIの内部機能
RubyVMはユーザー向けではなく、実装されていません。
大きい違いのある機能
スレッドがパラレルに実行される
MRIでは、スレッドは、パラレルではなく同時にスケジュールされます。TruffleRubyでは、スレッドがパラレルにスケジュールされます。JRubyおよびRubiniusの場合と同様に、独自の共有可変データ構造へのアクセスを正しく同期することについてはユーザーが責任を負い、インタプリタの状態を正しく同期することについてはTruffleRubyが責任を負います。
スレッドが様々なポイントで中断を検出する
TruffleRubyスレッドは、プログラム内の中断されたポイントとして、MRIの場合とは異なる場所を検出する可能性があります。一般的に、TruffleRubyはMRIよりも早期に中断を検出する傾向があるようです。JRubyとRubiniusもMRIとは異なります。MRIでは動作が文書化されておらず、MRIのバージョン間で変更される可能性があるため、中断ポイントに依存することはお薦めしません。
ファイバのパフォーマンス特性がMRIと同じでない
ファイバのほとんどのユースケースは、起動が容易かつ低コストであることと、メモリーのオーバーヘッドが低いことを前提としています。TruffleRubyでは、ファイバは現在、オペレーティング・システム・スレッドを使用して実装されるため、パフォーマンス特性はRubyスレッドと同じです。Loomプロジェクトが安定し、JVMリリースで使用可能になったら、このことに対処する予定です。
内部としてマークされるクラスが異なる
MRIでは、MRI (CRuby)でのみ使用可能であるとしてドキュメントに記載されているクラスがいくつか提供されています。これらのクラスは、実装することが実用的であれば実装されますが、必ずしもそうではありません。たとえば、RubyVMは使用できません。
Regexp
Regexpインスタンスは、TruffleRubyでは常に不変です。CRuby 3.0では、リテラルのRegexpはすべて不変ですが、リテラルでないものは引き続き可変です。この制限は、Regexpインスタンスでシングルトン・メソッドを定義できず、TruffleRubyでRegexpのサブクラスのインスタンスを作成できないことを意味します。
わずかな違いがある機能
コマンドライン・スイッチ
-y、--yydebug、--dump=および--debug-frozen-string-literalスイッチはサポートされていない開発ツールであるため、無視されて警告が表示されます。
マジックコメントを伴う-e引数が渡されるプログラムには、UTF-8またはUTF-8のサブセットであるエンコーディングが必要です。これは、引数を取得するまでにJVMによってそれらがすでにデコードされているためです。
--jitオプションおよびjit機能はTruffleRubyでは機能せず、警告が表示されます。使用可能な場合は常にGraalVMコンパイラが使用されます。
時間がミリ秒の精度に制限される
Rubyは通常、マイクロ秒(100万分の1秒)のクロック精度を提供しますが、TruffleRubyは現在、ミリ秒(千分の1秒)の精度に制限されています。これは、Time.nowおよびProcess.clock_gettime(Process::CLOCK_REALTIME)に適用されます。
Stringの最大バイトサイズが231-1である
RubyのStringは、Javaのbyte[]として表されます。JVMでは、231-1という最大配列サイズが強制されるため(32ビットの符号付きintにサイズを格納することによる)、RubyのStringを231-1バイトより長くすることはできません。つまり、Stringは2GBより小さくする必要があります。これは、JRubyと同じ制限事項です。回避策として、ネイティブに割り当てられた文字列を使用することが考えられますが、ネイティブ文字列に対するすべてのRuby String操作をサポートする労力が大きくなります。
プロセス・タイトルが切り捨てられることがある
プロセス・タイトルの設定(Rubyで$0またはProcess.setproctitleを使用)は、ベストエフォートとして実行されます。これが機能しないこともあれば、設定しようとしたタイトルが切り捨てられることもあります。
ポリグロットの標準I/Oストリーム
ポリグロット・エンジンによって提供される標準I/Oストリームを、試験段階の--polyglot-stdioオプションを介して使用すると、ファイル記述子0、1および2に対する読取りおよび書込みがこれらのストリームにリダイレクトされます。つまり、これらのファイル記述子に対する他のI/O操作(isattyなど)は、これらのストリームが実際に終了する場所に関連しなくなる場合があり、dupなどの操作がポリグロット・ストリームへの接続を失う可能性があります。たとえば、一部のロギング・フレームワークで行われるように、$stdout.reopenを実行した場合、ポリグロット出力ではなく、ネイティブの標準出力が使用されます。
また、I/Oバッファ・ドレイン、syncが設定されたI/Oオブジェクトに対する書込み、およびwrite_nonblockでは、EAGAINおよびEWOULDBLOCKが発生しても、それを検出する方法がストリームによって提供されないため、書込みが再試行されません。
エラー・メッセージ
エラー・メッセージ文字列は、通常はRuby Spec Suiteまたはテストの対象にならないため、MRIとは異なる場合があります。
シグナル
TruffleRubyが処理できる一連のシグナルはMRIとは異なります。ネイティブ構成を使用する場合、TruffleRubyでは、MRIでトラップされるものと同じシグナルをすべてトラップできるとともに、MRIではトラップされないものもいくつかトラップできます。トラップできないシグナルは、KILL、STOPおよびVTALRMのみです。したがって、ネイティブ構成では変更を加えることなく、MRI上で実行されるすべてのシグナル処理コードをTruffleRuby上で実行できます。
ただし、JVMで実行する場合、TruffleRubyはUSR1またはQUITをトラップできません。これらのシグナルはJVMによって予約されているためです。このような場合、trap(:USR1) {}によってArgumentErrorが発生します。これらのシグナルをトラップできることに依存するコードは、使用可能な別のシグナルにフォールバックする必要があります。また、FPE、ILL、KILL、SEGV、STOPおよびVTALRMをトラップすることはできませんが、これらのシグナルはMRIでも使用できません。
TruffleRubyをポリグロット・アプリケーションの一部として実行する場合、別の言語によって処理されたシグナルはTruffleRubyでトラップできなくなります。
GC統計
TruffleRubyではMRIと同様のGC.stat統計が提供されますが、すべての統計を使用できるわけではなく、一部の統計は近似値である場合があります。実際値または近似値で提供される値を確認するには、GC.stat.keysを使用してください。値がない場合、0が返されます。
パフォーマンスが非常に低い機能
ObjectSpace
ObjectSpaceのほとんどのメソッドを使用すると、プログラムのパフォーマンスが一時的に低下します。それらをテスト・ケースや他の同様のオフライン操作で使用することは問題ありませんが、本番アプリケーションの内部ループでは使用しないことをお薦めします。
set_trace_func
set_trace_funcを使用すると、プログラムのパフォーマンスが一時的に低下します。ObjectSpaceと同様に、本番アプリケーションの内部ループではこれを使用しないことをお薦めします。
バックトレース
バックトレースを作成する必要がある例外や他の操作のスローには、MRIの場合よりも時間がかかります。これは、バックトレース・エントリを再作成するには、Rubyコードを高速に実行するために適用された最適化をTruffleRubyで元に戻す必要があるためです。いずれにしてもRubyの実装で制御フローに例外を使用することはお薦めしません。
この問題を軽減するために、バックトレースが使用されないことを検出できる場合、それらは自動的に無効になります。
C拡張機能との互換性
識別子がマクロである場合もあれば、関数である場合もある
通常はマクロである識別子が関数である場合や、関数がマクロである場合があり、また、グローバル変数がマクロである場合もあります。これにより、特定の実装に依存するコンテキストでそれらが使用された場合に、問題が発生することがあります(そのアドレスの取得、関数ポインタ変数への割当て、defined()を使用した、マクロが存在するかどうかの確認など)。これらの問題はすべてバグとみなされ、修正の対象となります。このようなケースがあれば報告してください。
rb_scan_args
rb_scan_argsでは、最大10個のポインタのみがサポートされます。
rb_funcall
rb_funcallでは、最大15個の引数のみがサポートされます。
RDATAおよびRTYPEDDATAのmark関数
RDATAおよびRTYPEDDATAのmark関数は、ガベージ・コレクション中にコールされるのではなく、定期的にコールされます。オブジェクトに関する情報は、それらが構造体に割り当てられるとキャッシュされ、キャッシュが一杯になると、TruffleRubyはすべてのmark関数を定期的に実行して、ガベージ・コレクタが理解する方法でそれらのオブジェクト関係を表します。このプロセスはMRIと同じように動作します。
JRubyとの互換性
RubyからJavaへの相互運用性
TruffleRubyは、JRubyがサポートするものと同じJavaへの相互運用性インタフェースをサポートしていません。TruffleRubyには、Javaを含む複数の言語と相互運用するための代替ポリグロットAPIがかわりに用意されています。
JavaからRubyへの相互運用性
JavaからのRubyコードのコールは、GraalVMポリグロットAPIによってサポートされています。
Java拡張機能
JRuby用に記述されたJava拡張機能の使用はサポートされていません。
ネイティブ構成でまだサポートされていない機能
ネイティブ構成でのTruffleRubyの実行は、JVMで実行する場合とほとんど同じです。両方のVMで使用されるガベージ・コレクタが異なるため、リソース管理には違いがありますが、機能的には、これらは基本的に互いに同等です。
ネイティブ構成とのJavaの相互運用性
Javaの相互運用性はネイティブ構成で機能しますが、より多くの設定が必要です。デフォルトでは、Javaの相互運用性のためにイメージで使用できるのは一部の配列クラスのみです。TruffleRubyを含むネイティブ・イメージをコンパイルすることにより、さらにクラスを追加できます。詳細は、ここを参照してください。
仕様の完全性
仕様はいくつあるかという問いに、簡単かつ正確に答えることはできません。仕様の数は、Ruby言語のバージョン、プラットフォーム、および仕様のバージョンによって異なります。また、標準ライブラリおよびC拡張機能APIの仕様は非常に不均一であり、誤解を招く結果が生じる可能性があります。
このブログ投稿には、TruffleRubyが準拠している仕様の数がまとめられています。