mirror of
https://github.com/ruby/ruby.git
synced 2026-10-29 08:55:35 +08:00
[PATCH] ZJIT: Drop has_blockiseq guard on local variable access (#16113) So `has_blockiseq` was masking two other bugs. First, that we had only a static EP escape check for an iseq. We need to check both that an iseq is not known to statically escape them *and that it has not escaped them before*. Only then can we use SSA locals. This popped up in a weird PP unit test that was reducible to the test cases added in the PR. So I fixed that. Second, that we didn't VM lock when code patching for EP escape. That showed up in the Ractor btests in my previous PRs #14600 and #15254 #15316, and I had no idea what was going on at the time, so I dropped it. But if we add this lock, like all the other invariant patching cases have, the problem goes away. Seems reasonable. Finally, we can use normal full SSA locals even in the presence of a blockiseq. We still reload all of them after each send-with-block, but we can in a later PR reduce that to just the locals that are (recursively) visibly written to. [PATCH] [Bug #22224] Ractors, YJIT: Skip re-reporting recorded EP escapes to the JITs (#18176) * [22224] Ractors, YJIT: Skip re-reporting recorded EP escapes to the JITs vm_make_env_each reports every EP escape, and both rb_yjit_invalidate_ep_is_bp and rb_zjit_invalidate_no_ep_escape take the VM lock with a stop-all-Ractors barrier on every call. For a given iseq only the first report does anything: afterwards YJIT's no_ep_escape_iseqs entry is permanently an empty set and ZJIT's ep_escape_iseqs insert is a no-op, so the barrier is paid for nothing. Under multiple Ractors this serializes the whole process on every Proc created from an already-escaped iseq: a workload creating one lambda per iteration runs 150x slower on 8 Ractors than on 1 with YJIT enabled. Record the report in a jit_ep_escape_recorded flag on the iseq body, set by the active JIT while it holds the VM lock, and skip the calls in vm_make_env_each once it is set. The flag sits in existing padding and the body is ZALLOC'd, so it starts false and dies with the iseq. Setting it in the JIT rather than in the caller keeps mid-process RubyVM::YJIT.enable correct: escapes reported while no JIT is initialized are recorded by nobody and must not set the flag. The unlocked read is benign: a stale false causes one redundant, idempotent report. On the micro-benchmark from the bug, --yjit at 8 Ractors goes from 0.04M to 11.07M iterations/s (277x). * [22224] Deduplicate the EP escape record onto the iseq body The jit_ep_escape_recorded flag introduced in the previous commit is now the JITs' only record of an EP escape: YJIT's no_ep_escape_iseqs keeps only iseqs with live assumptions (the empty-set escape markers are gone), and ZJIT's ep_escape_iseqs set is removed together with its update_ep_escape_iseqs compaction pass, since the flag lives in the iseq body, which the GC does not move. Both JITs read the flag back at compile time through a new rb_jit_iseq_ep_escape_recorded_p helper. Co-authored-by: Alan Wu <XrXr@users.noreply.github.com> Only the two correctness changes of9efe557da5are backported: the runtime EP-escape check in iseq_to_hir() and the VM lock in rb_zjit_invalidate_no_ep_escape(). Without the former, suppressing repeat escape reports would leave NoEPEscape patch points registered after an escape never invalidated, because ZJIT here registers them unconditionally. Its has_blockiseq removal is an optimization and is left out; keeping has_blockiseq is strictly more conservative. ZJIT here keeps the name invariants::iseq_escapes_ep() rather than renaming it to iseq_seen_ep_escape(), sincee0184fff1ais not backported.