diff --git a/NEWS.md b/NEWS.md index 95c02be9513306..033fc2ea76da1b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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. diff --git a/doc/jit/zjit.md b/doc/jit/zjit.md index 7434d44b9d7d60..1e5a36fd5e6f04 100644 --- a/doc/jit/zjit.md +++ b/doc/jit/zjit.md @@ -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 @@ -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 diff --git a/include/ruby/internal/fl_type.h b/include/ruby/internal/fl_type.h index da8670a8086abf..9bbcb9d2b82433 100644 --- a/include/ruby/internal/fl_type.h +++ b/include/ruby/internal/fl_type.h @@ -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 diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 3be9013f7abbc4..51666b73d8e52a 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -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 diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 8c5fdc816d6450..73c092f72091c5 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -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); @@ -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); @@ -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); @@ -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. @@ -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());