summaryrefslogtreecommitdiff
path: root/yjit/src/core.rs
AgeCommit message (Collapse)Author
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-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-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]
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-11YJIT: Use the correct size constantAlan Wu
Notes: Merged: https://github.com/ruby/ruby/pull/12310
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-11-13YJIT: Add inline_block_count stat (#12081)Takashi Kokubun
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-10-23YJIT: Make PendingBranch::set_target `must_use` [ci skip]Alan Wu
2024-10-23YJIT: Check when gen_branch() failsAlan Wu
We got some core dumps in the wild where a PendingBranch had everything as None, leading to a panic unwrapping in PendingBranch::into_branch(). This happened while compiling a `branchif`. It seems that the only way this can happen is when core::gen_branch() fails, but not due to OOM. We wouldn't have reach into_branch() when OOM, and the only way to not leave markers that would've set the branch's start_addr to some value in gen_branch() is for set_target() to fail, causing an early return. Unfortunately, it's hard to tell the exact sequence of events that led to this situation, but regardless, the dumps show us that we should check for errors in gen_branch(). Because gen_branch() is used deep in the stack during compilation (e.g. guard_known_class() -> jit_chain_guard() -> gen_branch()), it'd be bad for compile speed to propagate the error everywhere, not to mention the massive patch required. Opt for a flag checked near the end of compilation. Notes: Merged: https://github.com/ruby/ruby/pull/11938 Merged-By: XrXr
2024-10-23YJIT: Count compiled_branch_count when branch is finalized [ci skip]Alan Wu
2024-10-17YJIT: Add compilation log (#11818)Kevin Menard
* YJIT: Add `--yjit-compilation-log` flag to print out the compilation log at exit. * YJIT: Add an option to enable the compilation log at runtime. * YJIT: Fix a typo in the `IseqPayload` docs. * YJIT: Add stubs for getting the YJIT compilation log in memory. * YJIT: Add a compilation log based on a circular buffer to cap the log size. * YJIT: Allow specifying either a file or directory name for the YJIT compilation log. The compilation log will be populated as compilation events occur. If a directory is supplied, then a filename based on the PID will be used as the write target. If a file name is supplied instead, the log will be written to that file. * YJIT: Add JIT compilation of C function substitutions to the compilation log. * YJIT: Add compilation events to the circular buffer even if output is sent to a file. Previously, the two modes were treated as being exclusive of one another. However, it could be beneficial to log all events to a file while also allowing for direct access of the last N events via `RubyVM::YJIT.compilation_log`. * YJIT: Make timestamps the first element in the YJIT compilation log tuple. * YJIT: Stream log to stderr if `--yjit-compilation-log` is supplied without an argument. * YJIT: Eagerly compute compilation log messages to avoid hanging on to references that may GC. * YJIT: Log all compiled blocks, not just the method entry points. * YJIT: Remove all compilation events other than block compilation to slim down the log. * YJIT: Replace circular buffer iterator with a consuming loop. * YJIT: Support `--yjit-compilation-log=quiet` as a way to activate the in-memory log without printing it. Co-authored-by: Randy Stauner <randy.stauner@shopify.com> * YJIT: Promote the compilation log to being the one YJIT log. Co-authored-by: Randy Stauner <randy.stauner@shopify.com> * Update doc/yjit/yjit.md * Update doc/yjit/yjit.md --------- Co-authored-by: Randy Stauner <randy.stauner@shopify.com> Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-10-11Fix spellingJohn Bampton
Notes: Merged: https://github.com/ruby/ruby/pull/11882 Merged-By: XrXr
2024-09-25YJIT: Cache Context decoding (#11680)Takashi Kokubun
Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-09-23Fix a typoTakashi Kokubun
2024-08-27YJIT: Pass method arguments using registers (#11280)Takashi Kokubun
* YJIT: Pass method arguments using registers * s/at_current_insn/at_compile_target/ * Implement register shuffle Notes: Merged-By: k0kubun <takashikkbn@gmail.com>
2024-07-31YJIT: Decouple Context from encoding details (#11283)Takashi Kokubun
Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-07-15YJIT: split chain_depth and flag booleans in context (#11169)Maxime Chevalier-Boisvert
Split these values to avoid using a bit mask in the context Use variable length encoding to save a few bits on chain depth
2024-07-15YJIT: Local variable register allocation (#11157)Takashi Kokubun
* YJIT: Local variable register allocation * locals are not stack temps * Rename RegTemps to RegMappings * Rename RegMapping to RegOpnd * Rename local_size to num_locals * s/stack value/operand/ * Rename spill_temps() to spill_regs() * Clarify when num_locals becomes None * Mention that InsnOut uses different registers * Rename get_reg_mapping to get_reg_opnd * Resurrect --yjit-temp-regs capability * Use MAX_CTX_TEMPS and MAX_CTX_LOCALS
2024-07-11YJIT: increase context cache size to 1024 redux (#11140)Maxime Chevalier-Boisvert
* YJIT: increase context cache size to 1024 redux * Move context hashing code outside of unsafe block * Avoid allocating large table on the stack, which would cause a stack overflow Co-authored by Alan Wu @XrXr
2024-07-10YJIT: increase context cache size to 1024 (#10983)Maxime Chevalier-Boisvert
* YJIT: increase context cache size to 1024 The other day I ran into a mysterious bug while increasing the cache size to 1024. I was not able to reproduce this locally. Opening this PR for testing/debugging. * Add extra debug assertions * Add more comments to context code * Update yjit/src/core.rs Co-authored-by: Alan Wu <XrXr@users.noreply.github.com> * Update yjit/src/core.rs * Comment out potentially problematic assertion * Revert cache size to 512 so we can merge other changes --------- Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
2024-06-28YJIT: Fix `cargo doc --document-private-items` warnings [ci skip]Alan Wu
Mostly putting angle brackets around links to follow markdown syntax.
2024-06-28YJIT: Move `ocb` parameters into `JITState`Alan Wu
Many functions take an outlined code block but do nothing more than passing it along; only a couple of functions actually make use of it. So, in most cases the `ocb` parameter is just boilerplate. Most functions that take `ocb` already also take a `JITState` and this commit moves `ocb` into `JITState` to remove the visual noise of the `ocb` parameter.
2024-06-13YJIT: Delete otherwise-empty defer_compilation() blocksAlan Wu
Calls to defer_compilation() leave behind a stub and a `struct Block` that we retain. If the block is empty, it only exits to hold the `struct Branch` that the stub needs. This patch transplants the branch out of the empty block into the newly generated block when the defer_compilation() stub is hit, and deletes the empty block to save memory. To assist the transplantation, `Block::outgoing` is now a `MutableBranchList`, and `Branch::Block` now in a `Cell`. These types don't incur a size cost. On the `lobsters` benchmark, `yjit_alloc_size` is roughly 98% of what it was before the change. Co-authored-by: Kevin Menard <kevin.menard@shopify.com> Co-authored-by: Randy Stauner <randy@r4s6.net> Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com>
2024-06-12YJIT: add context cache hits stat (#10979)Maxime Chevalier-Boisvert
* YJIT: add context cache hits stat This stat should make more sense when it comes to interpreting the effectiveness of the cache on large deployed apps.
2024-06-11YJIT: add context cache size stat, lazily allocate cacheMaxime Chevalier-Boisvert
* YJIT: add context cache size stat * Allocate the context cache in a box so CRuby doesn't pay overhead * Add an extra debug assertion
2024-06-07YJIT: implement cache for recently encoded/decoded contexts (#10938)Maxime Chevalier-Boisvert
* YJIT: implement cache for recently encoded/decoded contexts * Increase cache size to 512
2024-06-07YJIT: implement variable-length context encoding scheme (#10888)Maxime Chevalier-Boisvert
* Implement BitVector data structure for variable-length context encoding * Rename method to make intent clearer * Rename write_uint => push_uint to make intent clearer * Implement debug trait for BitVector * Fix bug in BitVector::read_uint_at(), enable more tests * Add one more test for good measure * Start sketching Context::encode() * Progress on variable length context encoding * Add tests. Fix bug. * Encode stack state * Add comments. Try to estimate context encoding size. * More compact encoding for stack size * Commit before rebase * Change Context::encode() to take a BitVector as input * Refactor BitVector::read_uint(), add helper read functions * Implement Context::decode() function. Add test. * Fix bug, add tests * Rename methods * Add Context::encode() and decode() methods using global data * Make encode and decode methods use u32 indices * Refactor YJIT to use variable-length context encoding * Tag functions as allow unused * Add a simple caching mechanism and stats for bytes per context etc * Add comments, fix formatting * Grow vector of bytes by 1.2x instead of 2x * Add debug assert to check round-trip encoding-decoding * Take some rustfmt formatting * Add decoded_from field to Context to reuse previous encodings * Remove olde context stats * Re-add stack_size assert * Disable decoded_from optimization for now
2024-05-29YJIT: Fix a warning from nightly rustAlan Wu
No plan about migrating to the 2024 edition yet (it's not even available yet), but this is a simple enough suggestion so we can just take it. ``` warning: this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<Box<[T]> as IntoIterator>::into_iter` in Rust 2024 --> ../yjit/src/core.rs:1003:49 | 1003 | formatter.debug_list().entries(branches.into_iter()).finish() | ^^^^^^^^^ | = warning: this changes meaning in Rust 2024 = note: `#[warn(boxed_slice_into_iter)]` on by default help: use `.iter()` instead of `.into_iter()` to avoid ambiguity | 1003 | formatter.debug_list().entries(branches.iter()).finish() | ~~~~ help: or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value | 1003 | formatter.debug_list().entries(IntoIterator::into_iter(branches)).finish() | ++++++++++++++++++++++++ ~ ```
2024-04-26YJIT: Fix reference update for `Invariants::no_ep_escape_iseqs`Alan Wu
Previously, the update was done in the ISEQ callback. That effectively never updated anything because the callback itself is given an intact reference, so it could update its content, and `rb_gc_location(iseq)` never returned a new address. Update the whole table once in the YJIT root instead.
2024-04-25YJIT: Relax `--yjit-verify-ctx` after singleton class creationAlan Wu
Types like `Type::CString` really only assert that at one point the object had its class field equal to `String`. Once a singleton class is created for any strings, the type makes no assertion about any class field anymore, and becomes the same as `Type::TString`. Previously, the `--yjit-verify-ctx` option wasn't allowing objects of these kind that have have singleton classes to pass verification even though the code generators handle it just fine. Found through `ruby/spec`.
2024-04-25YJIT: Optimize local variables when EP == BP (take 2) (#10607)Takashi Kokubun
* Revert "Revert "YJIT: Optimize local variables when EP == BP" (#10584)" This reverts commit c8783441952217c18e523749c821f82cd7e5d222. * YJIT: Take care of GC references in ISEQ invariants Co-authored-by: Alan Wu <alansi.xingwu@shopify.com> --------- Co-authored-by: Alan Wu <alansi.xingwu@shopify.com>
2024-04-22YJIT: Fix shrinking block with assumption too much (#10585)Alan Wu
* YJIT: Fix shrinking block with assumption too much Under the very specific circumstances, discovered by a test case in `ruby/spec`, an `expandarray` block can contain just a branch and carry a method lookup assumption. Previously, when we regenerated the branch, we allowed it to shrink to empty, since we put the code at the jump target immediately after it. That was incorrect and caused a crash while the block is invalidated, since that left no room to patch in an exit. When regenerating a branch that makes up a block entirely, and the block could be invalidated, we need to ensure there is room for invalidation. When there is code before the branch, they should act as padding, so we don't need to worry about those cases. * skip on RJIT
2024-04-19Revert "YJIT: Optimize local variables when EP == BP" (#10584)Alan Wu
This reverts commit 4cc58ea0b865f2fd20f1e881ddbd4c4fab0b072c. Since the change landed call-threshold=1 CI runs have been timing out. There has also been `verify-ctx` violations. Revert for now while we debug.
2024-04-17YJIT: Optimize local variables when EP == BP (#10487)Takashi Kokubun
2024-04-03YJIT: Let sp_opnd take the number of slots (#10442)Takashi Kokubun
2024-03-25YJIT: Propagate Array, Hash, and String classes (#10323)Takashi Kokubun
2024-03-20YJIT: Get rid of Type::TProc (#10287)Takashi Kokubun
2024-02-23Assert running_iseq before using itTakashi Kokubun
When running_iseq happens to be 0, it's better to fail on the assertion rather than referencing the null pointer.
2024-02-14YJIT: Use i32 over isize for ctx.sp_opnd() (#9968)Alan Wu
It eventually casts it to i32 anyways, and a lot of callers already have an i32, so using isize was just adding unnecessary casts.
2024-02-08YJIT: Allow tracing a counted exit (#9890)Takashi Kokubun
* YJIT: Allow tracing a counted exit * Avoid clobbering caller-saved registers
2024-02-08YJIT: Maintain MapToLocal that is just upgraded (#9876)Takashi Kokubun
2024-01-30YJIT: Specialize splatkw on T_HASH (#9764)Takashi Kokubun
* YJIT: Specialize splatkw on T_HASH * Fix a typo Co-authored-by: Alan Wu <XrXr@users.noreply.github.com> * Fix a few more comments --------- Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
2024-01-23YJIT: Allow inlining ISEQ calls with a block (#9622)Takashi Kokubun
* YJIT: Allow inlining ISEQ calls with a block * Leave a TODO comment about u16 inline_block
2024-01-18YJIT: Stop incrementing chain_depth on defer_compilation (#9597)Takashi Kokubun
2023-12-12YJIT: Fix off-by-one in Kernel#send type handling (#9212)Alan Wu
Previously, if the method ID argument happens to be on one below the top of the stack, we didn't overwrite the type of the stack slot, which leaves an incorrect type for the stack slot. The included script tripped asserts both with and without --yjit-verify-ctx.
2023-12-07YJIT: Fix on-stack ISEQ comparison for auto_compact (#9164)Takashi Kokubun
2023-12-06YJIT: Avoid register allocation conflict with a higher stack_idx (#9143)Takashi Kokubun
YJIT: Avoid register allocation conflict with a higher stack_idx
2023-12-04YJIT: Mark and update stubs in invalidated blocks (#9104)Alan Wu
Like in the example given in delayed_deallocation(), stubs can be hit even if the block housing it is invalidated. Mark them so we don't work with invalidate ISeqs when hitting these stubs.