Bug #22427
open`--enable=zjit` does not enable ZJIT
Description
ruby --help lists zjit under "Features" (zjit Method-based JIT compiler (default: disabled).), but --enable=zjit alone does not enable ZJIT, while --enable=yjit does enable YJIT.
$ ruby -v
ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [x86_64-linux]
$ ruby --enable=yjit -v
ruby 4.0.7 (2026-09-15 revision 229531a6cf) +YJIT +PRISM [x86_64-linux]
$ ruby --enable=yjit -e 'p RubyVM::YJIT.enabled?'
true
$ ruby --enable=zjit -v
ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [x86_64-linux]
$ ruby --enable=zjit -e 'p RubyVM::ZJIT.enabled?'
false
$ ruby --zjit -v
ruby 4.0.7 (2026-09-15 revision 229531a6cf) +ZJIT +PRISM [x86_64-linux]
$ ruby --zjit -e 'p RubyVM::ZJIT.enabled?'
true
The same happens with --enable-zjit and with RUBYOPT=--enable=zjit. ZJIT is enabled as soon as any --zjit-* option is added (for example --enable=zjit --zjit-call-threshold=30), and also with RUBY_ZJIT_ENABLE=1.
The cause is in ruby.c. --enable=zjit only sets the feature bit. Later, opt->zjit is computed as
#if USE_ZJIT
if (FEATURE_SET_P(opt->features, zjit)) {
bool rb_zjit_option_enable(void);
opt->zjit = rb_zjit_option_enable(); // set opt->zjit for Init_ruby_description() and calling rb_zjit_init()
}
#endif
and rb_zjit_option_enable() (zjit/src/options.rs) returns true only when the Rust-side OPTIONS has been initialized (OPTIONS.as_ref().is_some_and(|opts| !opts.disable)). OPTIONS is initialized by rb_zjit_prepare_options(), which is called for --zjit* (through setup_zjit_options()) and for RUBY_ZJIT_ENABLE, but not for --enable=zjit. So opt->zjit stays false and rb_zjit_init(false) is called. The YJIT counterpart, rb_yjit_option_disable(), has no such dependency, which is why --enable=yjit works.
Judging from the diff, this was introduced by f84bbb4238 ("ZJIT: add support for lazy RubyVM::ZJIT.enable", 2025-11-17, included in 4.0.0). Before it, the code was
if (FEATURE_SET_P(opt->features, zjit) && !opt->zjit) {
extern void rb_zjit_prepare_options(void);
rb_zjit_prepare_options();
opt->zjit = true;
}
A possible fix is to prepare the options when the feature bit is set, before asking whether ZJIT should be enabled:
#if USE_ZJIT
if (FEATURE_SET_P(opt->features, zjit)) {
// --enable=zjit sets the feature bit without going through setup_zjit_options()
extern void rb_zjit_prepare_options(void);
rb_zjit_prepare_options();
bool rb_zjit_option_enable(void);
opt->zjit = rb_zjit_option_enable();
}
#endif
rb_zjit_prepare_options() already handles the already-initialized case, so --zjit-* options and --zjit-disable keep working. test/ruby/test_rubyoptions.rb does not exercise --enable=yjit or --enable=zjit, so a test for the --enable= form would also help.
Reproduced in the ghcr.io/ruby/ruby:4.0 (4.0.7) and ghcr.io/ruby/ruby:master (4.1.0dev 2d08b5f42f) Docker images (x86_64-linux).
No data to display