Bug #22427
closed`--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).
Updated by eightbitraptor (Matt V-H) 4 days ago
Thanks, Proposed fix in https://github.com/ruby/ruby/pull/19307
Updated by eightbitraptor (Matt V-H) 2 days ago
- Status changed from Open to Closed
Applied in changeset git|d71692d49f30e60296bd57c932eb8877e83c3b4d.
ZJIT: prepare options whenever the zjit feature is enabled
--enable=zjit sets the zjit feature bit without parsing a --zjit option,
so OPTIONS is never initialized and rb_zjit_option_enable() returns
false.
ZJIT then stays disabled at boot. The same happens with --enable-zjit
and RUBYOPT=--enable=zjit, and with --jit, --enable=jit and --enable=all
on ZJIT-only builds.
In this commit we call rb_zjit_prepare_options() whenever the zjit
feature is set, regardless of where the bit came from. It's a no-op
when the --zjit option parser has already initialized OPTIONS, so
--zjit-disable and the other ZJIT options still apply.
[Bug #22427]