summaryrefslogtreecommitdiff
path: root/yjit
AgeCommit message (Collapse)Author
2025-12-16merge revision(s) 9168cad4d63a5d281d443bde4edea6be213b0b25: [Backport #21266]Takashi Kokubun
[PATCH] YJIT: Bail out if proc would be stored above stack top Fixes [Bug #21266].
2025-12-16YJIT: Print `Rc` strong and weak count on assert failureAlan Wu
For <https://bugs.ruby-lang.org/issues/21716>, the panic is looking like some sort of third party memory corruption, with YJIT taking the fall. At the point of this assert, the assembler has dropped, so there's nothing in YJIT's code other than JITState that could be holding on to these transient `PendingBranchRef`. The strong count being more than a handful or the weak count is non-zero shows that someone in the process (likely some native extension) corrupted the Rc's counts.
2025-12-12YJIT: Fix panic from overly loose filtering in identity method inliningAlan Wu
Credits to @rwstauner for noticing this issue in GH-15533.
2025-12-12YJIT: Add missing local variable type update for fallback setlocal blocksAlan Wu
Previously, the chain_depth>0 version of setlocal blocks did not update the type of the local variable in the context. This can leave the context with stale type information and trigger or lead to miscompilation. To trigger the issue, YJIT needs to see the same ISEQ before and after environment escape and have tracked type info before the escape. To trigger in ISEQs that do not send with a block, it probably requires Kernel#binding or the use of include/ruby/debug.h APIs. [Backport #21772]
2025-12-01YJIT: Abort expandarray optimization if method_missing is definedRandy Stauner
Fixes: [Bug #21707] [AW: rewrote comments] Co-authored-by: Alan Wu <alanwu@ruby-lang.org>
2025-11-14YJIT: Fix stack handling in rb_str_dupJohn Hawthorn
Previously because we did a stack_push before ccall, in some cases we could end up pushing an uninitialized value to the VM stack when spilling regs as part of the ccall. Co-authored-by: Luke Gruber <luke.gru@gmail.com>
2025-10-29YJIT: Prevent making a branch from a dead block to a live blockAlan Wu
I'm seeing some memory corruption in the wild on blocks in `IseqPayload::dead_blocks`. While I unfortunately can't recreate the issue, (For all I know, it could be some external code corrupting YJIT's memory.) establishing a link between dead blocks and live blocks seems fishy enough that we ought to prevent it. When it did happen, it might've had bad interacts with Code GC and the optimization to immediately free empty blocks.
2025-10-22YJIT: Fix `defined?(yield)` and `block_given?` at top levelAlan Wu
Previously, YJIT returned truthy for the block given query at the top level. That's incorrect because the top level script never receives a block, and `yield` is a syntax error there. Inside methods, the number of hops to get from `iseq` to `iseq->body->local_iseq` is the same as the number of `VM_ENV_PREV_EP(ep)` hops to get to an environment with `VM_ENV_FLAG_LOCAL`. YJIT and the interpreter both rely on this as can be seen in get_lvar_level(). However, this identity does not hold for the top level frame because of `vm_set_eval_stack()`, which sets up `TOPLEVEL_BINDING`. Since only methods can take a block that `yield` goes to, have ISEQs that are the child of a non-method ISEQ return falsy for the block given query. This fixes the issue for the top level script and is an optimization for non-method contexts such as inside `ISEQ_TYPE_CLASS`.
2025-09-10YJIT: Print more disassembly in release buildsAlan Wu
These `#[cfg(feature = "disasm")]` were unnecessary and we can provide the information like ruby source location regardless of the availability of capstone.
2025-09-10YJIT: Remove dead code: `asm_comment!` checks `--yjit-dump-disasm`Alan Wu
2025-09-08YJIT: Add more information to an assert message (#14481)Takashi Kokubun
YJIT: Add more information to an assert message in jit_guard_known_class (#14480) --------- Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
2025-08-11YJIT: Fix `mismatched_lifetime_syntaxes`, new in Rust 1.89.0Alan Wu
2025-07-17YJIT: Explicitly specify C ABI to fix Rust warningAlan Wu
Backport of 7e733ca55168e3b1f10b685f6e9a52cf1deb5aff to fix [Bug #21514].
2025-05-13YJIT: Split the block on optimized getlocal/setlocal (#13331)Takashi Kokubun
2025-05-06YJIT: End the block after OPTIMIZE_METHOD_TYPE_CALLTakashi Kokubun
2025-04-28YJIT: Fix potential infinite loop when OOM (GH-13186)Rian McGuire
Avoid generating an infinite loop in the case where: 1. Block `first` is adjacent to block `second`, and the branch from `first` to `second` is a fallthrough, and 2. Block `second` immediately exits to the interpreter, and 3. Block `second` is invalidated and YJIT is OOM While pondering how to fix this, I think I've stumbled on another related edge case: 1. Block `incoming_one` and `incoming_two` both branch to block `second`. Block `incoming_one` has a fallthrough 2. Block `second` immediately exits to the interpreter (so it starts with its exit) 3. When Block `second` is invalidated, the incoming fallthrough branch from `incoming_one` might be rewritten first, which overwrites the start of block `second` with a jump to a new branch stub. 4. YJIT runs of out memory 5. The incoming branch from `incoming_two` is then rewritten, but because we're OOM we can't generate a new stub, so we use `second`'s exit as the branch target. However `second`'s exit was already overwritten with a jump to the branch stub for `incoming_one`, so `incoming_two` will end up jumping to `incoming_one`'s branch stub. Backport [Bug #21257]
2025-02-13YJIT: Filter `&` calls from specialized C method codegenAlan Wu
Evident with the crash reported in [Bug #20997], the C replacement codegen functions aren't authored to handle block arguments (nor should they because the extra code from the complexity defeats optimization). Filter sites with VM_CALL_ARGS_BLOCKARG.
2025-02-13YJIT: Initialize locals in ISeqs defined with `...`Alan Wu
Backport of GH-12660: Previously, callers of forwardable ISeqs moved the stack pointer up without writing to the stack. If there happens to be a stale value in the area skipped over, it could crash due to "try to mark T_NONE". Also, the uninitialized local variables were observable through `binding`. Initialize the locals to nil. [Bug #21021]
2025-02-13YJIT: Fix crash when yielding keyword argumentsAlan Wu
Previously, the code for dropping surplus arguments when yielding into blocks erroneously attempted to drop keyword arguments when there is in fact no surplus arguments. Fix the condition and test that supplying the exact number of keyword arguments as require compiles without fallback.
2024-12-23YJIT: Return None if entry block compilation fails (#12445)Takashi Kokubun
Notes: Merged-By: k0kubun <takashikkbn@gmail.com>
2024-12-17YJIT: Load registers on JIT entry to reuse blocks (#12355)Takashi Kokubun
Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-12-13YJIT: Speculate block arg for `c_func_method(&nil)` calls (#12326)Alan Wu
A good amount of call sites always pass nil as block argument, but the nil doesn't show up in the context. Put a runtime guard for those cases to handle it. Particular relevant for the `ruby-lsp` benchmark in `yjit-bench`. Up to a 2% speedup across headline benchmarks. Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com> Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com> Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org> Co-authored-by: Kevin Menard <kevin@nirvdrum.com> Co-authored-by: Randy Stauner <randy.stauner@shopify.com> Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-12-12YJIT: Allow then-unknown `static_mut_refs` on older Rusts [ci skip]Alan Wu
2024-12-11YJIT: Fix unread field lint in release buildsAlan Wu
``` warning: fields `blue_begin` and `blue_end` are never read ``` Notes: Merged: https://github.com/ruby/ruby/pull/12310
2024-12-11YJIT: Disable static_mut_refs for nowAlan Wu
Notes: Merged: https://github.com/ruby/ruby/pull/12310
2024-12-11YJIT: Use the correct size constantAlan Wu
Notes: Merged: https://github.com/ruby/ruby/pull/12310
2024-12-09YJIT: Add a comment about a lazy frame callTakashi Kokubun
jit_prepare_lazy_frame_call is a complicated trick and comes with memory overhead. Every use of the function should come with justification.
2024-12-09YJIT: Spill/load argument registers to reuse blocks (#12287)Takashi Kokubun
* YJIT: Spill/load argument registers to reuse blocks * Mention the immediate function name * Explain the context behind spill/load operations Notes: Merged-By: k0kubun <takashikkbn@gmail.com>
2024-12-09YJIT: Only enable disassembly colors for tty (#12283)Max Bernstein
* YJIT: Use fully-qualified name for OPTIONS in get_options! * YJIT: Only enable disassembly colors for tty Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-12-04YJIT: Generate specialized code for Symbol for objtostring (#12247)Maximillian Polhill
* YJIT: Generate specialized code for Symbol for objtostring Co-authored-by: John Hawthorn <john@hawthorn.email> * Update yjit/src/codegen.rs --------- Co-authored-by: John Hawthorn <john@hawthorn.email> Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-12-04YJIT: track time since initialization (#12263)Maxime Chevalier-Boisvert
Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-11-29YJIT: Avoid std::ffi::CString with rb_intern2() during bootAlan Wu
Fewer allocations on boot, too. Suggested-by: https://github.com/ruby/ruby/pull/12217 Notes: Merged: https://github.com/ruby/ruby/pull/12220
2024-11-28YJIT: Add missing prepare before calling str_dupJohn Hawthorn
Notes: Merged: https://github.com/ruby/ruby/pull/12202
2024-11-26YJIT: Implement opt_reverse insn (#12175)Randy Stauner
Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-11-26Optimize instructions when creating an array just to call `include?` (#12123)Randy Stauner
* Add opt_duparray_send insn to skip the allocation on `#include?` If the method isn't going to modify the array we don't need to copy it. This avoids the allocation / array copy for things like `[:a, :b].include?(x)`. This adds a BOP for include? and tracks redefinition for it on Array. Co-authored-by: Andrew Novoselac <andrew.novoselac@shopify.com> * YJIT: Implement opt_duparray_send include_p Co-authored-by: Andrew Novoselac <andrew.novoselac@shopify.com> * Update opt_newarray_send to support simple forms of include?(arg) Similar to opt_duparray_send but for non-static arrays. * YJIT: Implement opt_newarray_send include_p --------- Co-authored-by: Andrew Novoselac <andrew.novoselac@shopify.com> Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-11-25YJIT: fix small typo in command line options help (#12167)Maxime Chevalier-Boisvert
Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-11-20YJIT: Make compilation_failure a default stat (#12128)Alan Wu
It's good to monitor compilation failures. Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-11-20YJIT: Refactor to forward jump_to_next_insn() return valueAlan Wu
It's more concise this way and since `return Some(EndBlock)` is the only correct answer, no point repeating it everywhere. Notes: Merged: https://github.com/ruby/ruby/pull/12124
2024-11-20YJIT: Abandon block when gen_outlined_exit() failsAlan Wu
When CodeBlock::set_page fails (part of next_page(), see their docs for exact conditions), it can cause gen_outlined_exit() to fail while there is still plenty of memory available. Previously, this can have YJIT running incomplete code due to taking the early return in end_block_with_jump() that manifested as crashes with SIGILL. Add and use a wrapper with error handling. Notes: Merged: https://github.com/ruby/ruby/pull/12124
2024-11-14YJIT: Specialize String#dup (#12090)Takashi Kokubun
Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-11-14YJIT: Specialize Integer#pred (#12082)Takashi Kokubun
Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-11-13YJIT: Add inline_block_count stat (#12081)Takashi Kokubun
Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-11-13YJIT: Specialize `String#[]` (`String#slice`) with fixnum arguments (#12069)Randy Stauner
* YJIT: Specialize `String#[]` (`String#slice`) with fixnum arguments String#[] is in the top few C calls of several YJIT benchmarks: liquid-compile rubocop mail sudoku This speeds up these benchmarks by 1-2%. * YJIT: Try harder to get type info for `String#[]` In the large generated code of the mail gem the context doesn't have the type info. In that case if we peek at the stack and add a guard we can still apply the specialization and it speeds up the mail benchmark by 5%. Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com> Co-authored-by: Takashi Kokubun (k0kubun) <takashikkbn@gmail.com> --------- Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com> Co-authored-by: Takashi Kokubun (k0kubun) <takashikkbn@gmail.com> Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-11-13Mark strings returned by Symbol#to_s as chilled (#12065)Jean byroot Boussier
* Use FL_USER0 for ELTS_SHARED This makes space in RString for two bits for chilled strings. * Mark strings returned by `Symbol#to_s` as chilled [Feature #20350] `STR_CHILLED` now spans on two user flags. If one bit is set it marks a chilled string literal, if it's the other it marks a `Symbol#to_s` chilled string. Since it's not possible, and doesn't make much sense to include debug info when `--debug-frozen-string-literal` is set, we can't include allocation source, but we can safely include the symbol name in the warning message, making it much easier to find the source of the issue. Co-Authored-By: Étienne Barrié <etienne.barrie@gmail.com> --------- Co-authored-by: Étienne Barrié <etienne.barrie@gmail.com> Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2024-11-11Fix false-positive memory leak using Valgrind in YJIT (#12057)Peter Zhu
When we run with RUBY_FREE_AT_EXIT, there's a false-positive memory leak reported in YJIT because the METHOD_CODEGEN_TABLE is never freed. This commit adds rb_yjit_free_at_exit that is called at shutdown when RUBY_FREE_AT_EXIT is set. Reported memory leak: ==699816== 1,104 bytes in 1 blocks are possibly lost in loss record 1 of 1 ==699816== at 0x484680F: malloc (vg_replace_malloc.c:446) ==699816== by 0x155B3E: UnknownInlinedFun (unix.rs:14) ==699816== by 0x155B3E: UnknownInlinedFun (stats.rs:36) ==699816== by 0x155B3E: UnknownInlinedFun (stats.rs:27) ==699816== by 0x155B3E: alloc (alloc.rs:98) ==699816== by 0x155B3E: alloc_impl (alloc.rs:181) ==699816== by 0x155B3E: allocate (alloc.rs:241) ==699816== by 0x155B3E: do_alloc<alloc::alloc::Global> (alloc.rs:15) ==699816== by 0x155B3E: new_uninitialized<alloc::alloc::Global> (mod.rs:1750) ==699816== by 0x155B3E: fallible_with_capacity<alloc::alloc::Global> (mod.rs:1788) ==699816== by 0x155B3E: prepare_resize<alloc::alloc::Global> (mod.rs:2864) ==699816== by 0x155B3E: resize_inner<alloc::alloc::Global> (mod.rs:3060) ==699816== by 0x155B3E: reserve_rehash_inner<alloc::alloc::Global> (mod.rs:2950) ==699816== by 0x155B3E: hashbrown::raw::RawTable<T,A>::reserve_rehash (mod.rs:1231) ==699816== by 0x5BC39F: UnknownInlinedFun (mod.rs:1179) ==699816== by 0x5BC39F: find_or_find_insert_slot<(usize, fn(&mut yjit::codegen::JITState, &mut yjit::backend::ir::Assembler, *const yjit::cruby::autogened::rb_callinfo, *const yjit::cruby::autogened::rb_callable_method_entry_struct, core::option::Option<yjit::codegen::BlockHandler>, i32, core::option::Option<yjit::cruby::VALUE>) -> bool), alloc::alloc::Global, hashbrown::map::equivalent_key::{closure_env#0}<usize, usize, fn(&mut yjit::codegen::JITState, &mut yjit::backend::ir::Assembler, *const yjit::cruby::autogened::rb_callinfo, *const yjit::cruby::autogened::rb_callable_method_entry_struct, core::option::Option<yjit::codegen::BlockHandler>, i32, core::option::Option<yjit::cruby::VALUE>) -> bool>, hashbrown::map::make_hasher::{closure_env#0}<usize, fn(&mut yjit::codegen::JITState, &mut yjit::backend::ir::Assembler, *const yjit::cruby::autogened::rb_callinfo, *const yjit::cruby::autogened::rb_callable_method_entry_struct, core::option::Option<yjit::codegen::BlockHandler>, i32, core::option::Option<yjit::cruby::VALUE>) -> bool, std::hash::random::RandomState>> (mod.rs:1413) ==699816== by 0x5BC39F: hashbrown::map::HashMap<K,V,S,A>::insert (map.rs:1754) ==699816== by 0x57C5C6: insert<usize, fn(&mut yjit::codegen::JITState, &mut yjit::backend::ir::Assembler, *const yjit::cruby::autogened::rb_callinfo, *const yjit::cruby::autogened::rb_callable_method_entry_struct, core::option::Option<yjit::codegen::BlockHandler>, i32, core::option::Option<yjit::cruby::VALUE>) -> bool, std::hash::random::RandomState> (map.rs:1104) ==699816== by 0x57C5C6: yjit::codegen::reg_method_codegen (codegen.rs:10521) ==699816== by 0x57C295: yjit::codegen::yjit_reg_method_codegen_fns (codegen.rs:10464) ==699816== by 0x5C6B07: rb_yjit_init (yjit.rs:40) ==699816== by 0x393723: ruby_opt_init (ruby.c:1820) ==699816== by 0x393723: ruby_opt_init (ruby.c:1767) ==699816== by 0x3957D4: prism_script (ruby.c:2215) ==699816== by 0x3957D4: process_options (ruby.c:2538) ==699816== by 0x396065: ruby_process_options (ruby.c:3166) ==699816== by 0x236E56: ruby_options (eval.c:117) ==699816== by 0x15BAED: rb_main (main.c:43) ==699816== by 0x15BAED: main (main.c:62) After this patch, there are no more memory leaks reported when running RUBY_FREE_AT_EXIT with Valgrind on an empty Ruby script: $ RUBY_FREE_AT_EXIT=1 valgrind --leak-check=full ruby -e "" ... ==700357== HEAP SUMMARY: ==700357== in use at exit: 0 bytes in 0 blocks ==700357== total heap usage: 36,559 allocs, 36,559 frees, 6,064,783 bytes allocated ==700357== ==700357== All heap blocks were freed -- no leaks are possible Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-11-08YJIT: Always abandon the block when gen_branch() or defer_compilation() failsAlan Wu
In [1], we started checking for gen_branch failures, but I made two crucial mistakes. One, defer_compilation() had the same issue as gen_branch() but wasn't checked. Two, returning None from a codegen function does not throw away the block. Checking how gen_single_block() handles codegen functions, you can see that None terminates the block with an exit, but does not overall return an Err. This handling is fine for unimplemented instructions, for example, but incorrect in case gen_branch() fails. The missing branch essentially corrupts the block; adding more code after a missing branch doesn't correct the code. Always abandon the block when defer_compilation() or gen_branch() fails. [1]: cb661d7d82984cdb54485ea3f4af01ac21960882 Fixup: [1] Notes: Merged: https://github.com/ruby/ruby/pull/12035 Merged-By: XrXr
2024-11-08YJIT: Pass panic message to rb_bug()Alan Wu
So that the Rust panic message is forwarded to the RUBY_CRASH_REPORT system, instead of only the static "YJIT panicked" message done so previously. This helps with triaging crashes since it's easier than trying to parse stderr output. Sample: <internal:yjit_hook>:2: [BUG] YJIT: panicked at src/codegen.rs:1197:5: explicit panic ... Notes: Merged: https://github.com/ruby/ruby/pull/12027
2024-11-06Ignore return value of `into_raw_fd`Nobuyoshi Nakada
Fix as the compiler orders: ``` warning: unused return value of `into_raw_fd` that must be used --> ../src/yjit/src/disasm.rs:123:21 | 123 | file.into_raw_fd(); // keep the fd open | ^^^^^^^^^^^^^^^^^^ | = note: losing the raw file descriptor may leak resources = note: `#[warn(unused_must_use)]` on by default help: use `let _ = ...` to ignore the resulting value | 123 | let _ = file.into_raw_fd(); // keep the fd open | +++++++ warning: unused return value of `into_raw_fd` that must be used --> ../src/yjit/src/log.rs:84:21 | 84 | file.into_raw_fd(); // keep the fd open | ^^^^^^^^^^^^^^^^^^ | = note: losing the raw file descriptor may leak resources help: use `let _ = ...` to ignore the resulting value | 84 | let _ = file.into_raw_fd(); // keep the fd open | +++++++ ``` Notes: Merged: https://github.com/ruby/ruby/pull/12009
2024-11-04YJIT: Replace Array#each only when YJIT is enabled (#11955)Takashi Kokubun
* YJIT: Replace Array#each only when YJIT is enabled * Add comments about BUILTIN_ATTR_C_TRACE * Make Ruby Array#each available with --yjit as well * Fix all paths that expect a C location * Use method_basic_definition_p to detect patches * Copy a comment about C_TRACE flag to compilers * Rephrase a comment about add_yjit_hook * Give METHOD_ENTRY_BASIC flag to Array#each * Add --yjit-c-builtin option * Allow inconsistent source_location in test-spec * Refactor a check of BUILTIN_ATTR_C_TRACE * Set METHOD_ENTRY_BASIC without touching vm->running Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-10-23YJIT: Make PendingBranch::set_target `must_use` [ci skip]Alan Wu