Experimental feature in GraalVM

兼容性

TruffleRuby 旨在与标准 Ruby 实现(MRI)版本 3.2.2 完全兼容,包括 C 扩展。 TruffleRuby 仍在开发中,因此它尚未完全兼容。

TruffleRuby 可以运行 Rails,并且与许多 gems 兼容,包括 C 扩展。 TruffleRuby 通过了大约 97% 的 ruby/spec,比任何其他替代 Ruby 实现都要多。

除了下面详细说明的罕见情况外,任何与 MRI 的不兼容性都被视为错误。 如果你发现与 MRI 的不兼容性,请报告它。

TruffleRuby 尝试尽可能地匹配 MRI 的行为。 在少数有限的情况下,TruffleRuby 故意与 MRI 不兼容,以提供更大的功能。

标识 #

TruffleRuby 定义了以下常量用于标识

  • RUBY_ENGINE'truffleruby'
  • RUBY_VERSION 是兼容的 MRI 版本。
  • RUBY_REVISION 是用于构建 TruffleRuby 的完整 git 提交哈希值(类似于 MRI 2.7+)。
  • RUBY_RELEASE_DATEgit 提交日期。
  • RUBY_PATCHLEVEL 始终为零。
  • RUBY_ENGINE_VERSION 是 TruffleRuby 版本,或者如果你的构建不是 TruffleRuby 版本的一部分,则为 0.0- 和 Git 提交哈希值。

在 C API 中,定义了预处理器宏 TRUFFLERUBY,可以使用 #ifdef TRUFFLERUBY 检查它。

Ruby 3.x 功能 #

TruffleRuby 支持 Ruby 3.2 及更早版本的大多数功能。 但是,某些功能尚未实现。 有关详细信息,请参阅以下问题

完全缺少的功能 #

延续和 callcc #

延续在 MRI 中已过时,建议使用 Fibers 替代。 延续和 callcc 不太可能在 TruffleRuby 中实现,因为它们的语义从根本上不匹配 JVM 架构。

Fork #

你无法 fork TruffleRuby 解释器。 在 JVM 上运行时,该功能不太可能得到支持,但将来可能会在原生配置中得到支持。 检查 fork 是否可用的正确且可移植的方式是

Process.respond_to?(:fork)

标准库 #

以下标准库不支持

  • continuation:在 MRI 中已过时
  • debug:它依赖于 RubyVM::InstructionSequence,请改用 VSCode 扩展--inspect
  • io/console:部分实现
  • io/wait:部分实现
  • pty:将来可能会实现

TruffleRuby 为 ffi gem 提供了自己的后端实现,类似于 JRuby。 这应该是完全透明的,并且行为与 MRI 上的行为相同。 该实现应该相当完整,并且通过了 ffi gem 的所有规范,除了少数很少使用的特殊情况。

内部 MRI 功能 #

RubyVM 不适合用户,并且未实现。

具有重大差异的功能 #

线程并行运行 #

在 MRI 中,线程是并发调度,而不是并行调度。 在 TruffleRuby 中,线程是并行调度的。 与 JRuby 和 Rubinius 一样,你需要负责正确同步对自己的共享可变数据结构的访问,而 TruffleRuby 将负责正确同步解释器的状态。

Fibers 的性能特征与 MRI 中的性能特征不同 #

Fibers 的大多数用例依赖于它们易于启动且启动成本低廉,并且内存开销很小。 在 TruffleRuby 中,Fibers 目前使用操作系统线程实现,因此它们具有与 Ruby 线程相同的性能特征。 这 将得到解决,一旦 Loom 项目变得稳定并在 JVM 版本中可用。

一些标记为内部的类将不同 #

MRI 提供了一些类,文档中描述这些类仅在 MRI(CRuby)上可用。 如果在实践中可行,则会实现这些类,但这并不总是这样。 例如,RubyVM 不可用。

Regexp #

Regexp 实例在 TruffleRuby 中始终是不可变的。 在 CRuby 3.1 中,所有文字 Regexp 都是不可变的,但非文字仍然是可变的。 此限制意味着无法在 Regexp 实例上定义单例方法,也无法在 TruffleRuby 上创建 Regexp 子类的实例。

具有细微差异的功能 #

命令行开关 #

-y--yydebug--dump=--debug-frozen-string-literal 开关将被忽略并发出警告,因为它们不支持开发工具。

-e 参数中传递的程序,其中包含魔术注释,必须具有 UTF-8 或 UTF-8 的子集编码,因为 JVM 在我们获取参数之前已经对它们进行了解码。

--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 的倍数。 这对于优化、压缩、不变式等是必要的。

线程在不同的点检测到中断 #

与在 MRI 上相比,TruffleRuby 线程可能在程序中的不同点检测到它们已被中断。 通常,TruffleRuby 似乎比 MRI 更早检测到中断。 JRuby 和 Rubinius 也与 MRI 不同;行为在 MRI 中没有记录,并且可能在不同 MRI 版本之间发生变化,因此不建议依赖中断点。

多语言标准 I/O 流 #

如果你使用通过实验性的 --polyglot-stdio 选项提供的多语言引擎提供的标准 I/O 流,则对文件描述符 0、1 和 2 的读写将被重定向到这些流。 这意味着,对这些文件描述符的其他 I/O 操作(如 isatty)可能与这些流的实际结束位置不相关,并且 dup 等操作可能会失去与多语言流的连接。 例如,如果你使用 $stdout.reopen(正如某些日志记录框架所做的那样),你将获得原生标准输出,而不是多语言输出。

此外,I/O 缓冲区排水、对 sync 设置为 true 的 I/O 对象的写入以及 write_nonblock 不会在 EAGAINEWOULDBLOCK 上重试写入,因为流没有提供检测此问题的方法。

错误消息 #

错误消息字符串有时会与 MRI 不同,因为这些通常不受 Ruby Spec 套件 或测试的覆盖。

信号 #

首先,KILLSTOP 永远无法被捕获,这是 POSIX 的规定(man 2 signal)。 一些信号在 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_locationsThread.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 个参数。

RDATARTYPEDDATAmark 函数 #

RDATARTYPEDDATAmark 函数在垃圾回收期间不会被调用,而是在定期调用。有关对象的信息在它们被分配到结构体时被缓存,并且 TruffleRuby 在缓存已满时定期运行所有 mark 函数,以以垃圾收集器可以理解的方式表示这些对象关系。该过程应该与 MRI 相同。

与 JRuby 的兼容性 #

Ruby 到 Java 的互操作性 #

TruffleRuby 不支持与 JRuby 相同的 Java 互操作性接口。TruffleRuby 提供了一个替代的多语言 API,用于与多种语言(包括 Java)进行互操作。

Java 到 Ruby 的互操作性 #

从 Java 调用 Ruby 代码由 GraalVM 多语言 API 支持。

Java 扩展 #

不支持使用为 JRuby 编写的 Java 扩展。

原生配置中尚未支持的功能 #

在原生配置中运行 TruffleRuby 与在 JVM 上运行基本相同。在资源管理方面存在差异,因为这两个虚拟机使用不同的垃圾收集器,但在功能方面,它们基本相当。

原生配置中的 Java 互操作性 #

Java 互操作性在原生配置中有效,但需要更多设置。默认情况下,只有某些数组类在 Java 互操作性的镜像中可用。可以通过编译包括 TruffleRuby 的原生镜像来添加更多类。有关更多详细信息,请参阅此处

规范完整性 #

“有多少规范?”这个问题没有一个简单而精确的答案。规范的数量因 Ruby 语言的不同版本、不同平台以及规范的不同版本而异。标准库和 C 扩展 API 的规范也非常不均匀,可能会给出误导性的结果。

这篇博文总结了 TruffleRuby 通过了多少规范。

联系我们