Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable.
## JIT

* ZJIT
* Introduce an experimental method-based JIT compiler.
* Introduce an [experimental method-based JIT compiler](https://docs.ruby-lang.org/en/master/jit/zjit_md.html).
To enable `--zjit` support, build Ruby with Rust 1.85.0 or later.
* As of Ruby 4.0.0, ZJIT is faster than the interpreter, but not yet as fast as YJIT.
We encourage experimentation with ZJIT, but advise against deploying it in production for now.
Expand Down
71 changes: 71 additions & 0 deletions doc/jit/zjit.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,49 @@
# ZJIT: ADVANCED RUBY JIT PROTOTYPE

ZJIT is a method-based just-in-time (JIT) compiler for Ruby. It uses profile
information from the interpreter to guide optimization in the compiler.

ZJIT is currently supported for macOS, Linux and BSD on x86-64 and arm64/aarch64 CPUs.
This project is open source and falls under the same license as CRuby.

## Current Limitations

ZJIT may not be suitable for certain applications. It currently only supports macOS, Linux and BSD on x86-64 and arm64/aarch64 CPUs. ZJIT will use more memory than the Ruby interpreter because the JIT compiler needs to generate machine code in memory and maintain additional state information.
You can change how much executable memory is allocated using [ZJIT's command-line options](rdoc-ref:@Command-Line+Options).

## Build Instructions

### For normal use

To build ZJIT on macOS:

```bash
./autogen.sh

./configure \
--enable-zjit \
--prefix="$HOME"/.rubies/ruby-zjit \
--disable-install-doc \
--with-opt-dir="$(brew --prefix openssl):$(brew --prefix readline):$(brew --prefix libyaml)"

make -j miniruby
```

To build ZJIT on Linux:

```bash
./autogen.sh

./configure \
--enable-zjit \
--prefix="$HOME"/.rubies/ruby-zjit \
--disable-install-doc

make -j miniruby
```

### For development

To build ZJIT on macOS:

```bash
Expand Down Expand Up @@ -47,6 +89,35 @@ make zjit-bindgen

## Documentation

### Command-Line Options

See `ruby --help` for ZJIT-specific command-line options:

```
$ ruby --help
...
ZJIT options:
--zjit-mem-size=num
Max amount of memory that ZJIT can use in MiB (default: 128).
--zjit-call-threshold=num
Number of calls to trigger JIT (default: 30).
--zjit-num-profiles=num
Number of profiled calls before JIT (default: 5).
--zjit-stats[=quiet]
Enable collecting ZJIT statistics (=quiet to suppress output).
--zjit-disable Disable ZJIT for lazily enabling it with RubyVM::ZJIT.enable.
--zjit-perf Dump ISEQ symbols into /tmp/perf-{}.map for Linux perf.
--zjit-log-compiled-iseqs=path
Log compiled ISEQs to the file. The file will be truncated.
--zjit-trace-exits[=counter]
Record source on side-exit. `Counter` picks specific counter.
--zjit-trace-exits-sample-rate=num
Frequency at which to record side exits. Must be `usize`.
$
```

### Source level documentation

You can generate and open the source level documentation in your browser using:

```bash
Expand Down
2 changes: 1 addition & 1 deletion include/ruby/internal/fl_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ ruby_fl_type {
RUBY_FL_EXIVAR

#if defined(RBIMPL_HAVE_ENUM_ATTRIBUTE)
RBIMPL_ATTR_DEPRECATED(("FL_EXIVAR is an outdated implementation detail, it shoudl be used."))
RBIMPL_ATTR_DEPRECATED(("FL_EXIVAR is an outdated implementation detail, it should not be used."))
#elif defined(_MSC_VER)
# pragma deprecated(RUBY_FL_EXIVAR)
#endif
Expand Down
3 changes: 1 addition & 2 deletions test/test_timeout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,5 @@ def test_ractor

assert_equal :ok, r
end;
end if defined?(::Ractor) && RUBY_VERSION >= '4.0' && !RUBY_PLATFORM.include?('x86_64-darwin')
# Exclude on x86_64-darwin as it failed 4 times out of 4 tries in the CI of ruby/ruby-dev-builder
end if defined?(::Ractor) && RUBY_VERSION >= '4.0'
end
28 changes: 17 additions & 11 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@ fn gen_ccall_with_frame(

// Can't use gen_prepare_non_leaf_call() because we need to adjust the SP
// to account for the receiver and arguments (and block arguments if any)
gen_prepare_call_with_gc(asm, state, false);
gen_save_pc_for_gc(asm, state);
gen_save_sp(asm, caller_stack_size);
gen_spill_stack(jit, asm, state);
gen_spill_locals(jit, asm, state);
Expand Down Expand Up @@ -875,7 +875,7 @@ fn gen_ccall_variadic(

// Can't use gen_prepare_non_leaf_call() because we need to adjust the SP
// to account for the receiver and arguments (like gen_ccall_with_frame does)
gen_prepare_call_with_gc(asm, state, false);
gen_save_pc_for_gc(asm, state);
gen_save_sp(asm, caller_stack_size);
gen_spill_stack(jit, asm, state);
gen_spill_locals(jit, asm, state);
Expand Down Expand Up @@ -1304,8 +1304,8 @@ fn gen_send_without_block_direct(
gen_stack_overflow_check(jit, asm, state, stack_growth);

// Save cfp->pc and cfp->sp for the caller frame
gen_prepare_call_with_gc(asm, state, false);
// Special SP math. Can't use gen_prepare_non_leaf_call
// Can't use gen_prepare_non_leaf_call because we need special SP math.
gen_save_pc_for_gc(asm, state);
gen_save_sp(asm, state.stack().len() - args.len() - 1); // -1 for receiver

gen_spill_locals(jit, asm, state);
Expand Down Expand Up @@ -2008,6 +2008,18 @@ fn gen_incr_send_fallback_counter(asm: &mut Assembler, reason: SendFallbackReaso
}
}

/// Save only the PC to CFP. Use this when you need to call gen_save_sp()
/// immediately after with a custom stack size (e.g., gen_ccall_with_frame
/// adjusts SP to exclude receiver and arguments).
fn gen_save_pc_for_gc(asm: &mut Assembler, state: &FrameState) {
let opcode: usize = state.get_opcode().try_into().unwrap();
let next_pc: *const VALUE = unsafe { state.pc.offset(insn_len(opcode) as isize) };

gen_incr_counter(asm, Counter::vm_write_pc_count);
asm_comment!(asm, "save PC to CFP");
asm.mov(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::const_ptr(next_pc));
}

/// Save the current PC on the CFP as a preparation for calling a C function
/// that may allocate objects and trigger GC. Use gen_prepare_non_leaf_call()
/// if it may raise exceptions or call arbitrary methods.
Expand All @@ -2017,13 +2029,7 @@ fn gen_incr_send_fallback_counter(asm: &mut Assembler, reason: SendFallbackReaso
/// However, to avoid marking uninitialized stack slots, this also updates SP,
/// which may have cfp->sp for a past frame or a past non-leaf call.
fn gen_prepare_call_with_gc(asm: &mut Assembler, state: &FrameState, leaf: bool) {
let opcode: usize = state.get_opcode().try_into().unwrap();
let next_pc: *const VALUE = unsafe { state.pc.offset(insn_len(opcode) as isize) };

gen_incr_counter(asm, Counter::vm_write_pc_count);
asm_comment!(asm, "save PC to CFP");
asm.mov(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::const_ptr(next_pc));

gen_save_pc_for_gc(asm, state);
gen_save_sp(asm, state.stack_size());
if leaf {
asm.expect_leaf_ccall(state.stack_size());
Expand Down