Files
ruby/jit_undef.rb
Nozomi Hijikata 81c5830a66 ZJIT: Inline BasicObject#!= through a JIT hook (#19162)
Closes: https://github.com/Shopify/ruby/issues/884

Inline `BasicObject#!=` by using a ZJIT hook. This expands specialization coverage to additional cases beyond the existing String and Integer specializations.

Although this might add some overhead in the interpreter when ZJIT is enabled, as the `opt_neq` fast path requires the C definition, the benchmarks below show speedups in several cases (e.g. lobsters, railsbench). I ran the benchmarks several times. The speedups varied between runs, but I consistently observed improvements over the previous implementation.

```
zjit-master: ruby 4.1.0dev (2026-10-10T08:21:06Z master 05917fd1e6) +ZJIT stats +PRISM [arm64-darwin25]
zjit-basicobject-hook: ruby 4.1.0dev (2026-10-10T13:30:56Z zjit-basicobject-n.. a6f95cadaa) +ZJIT stats +PRISM [arm64-darwin25]
ruby-bench: 3cc371202311fe2c821a6adb5adfced364f029de

-------------  ----------------  --------------------------  -----------------------------  ---------------------------------
bench          zjit-master (ms)  zjit-basicobject-hook (ms)  zjit-basicobject-hook 1st itr  zjit-master/zjit-basicobject-hook
liquid-render      44.6 ± 10.2%                 43.9 ± 6.3%                          0.933                              1.014
lobsters           415.1 ± 6.7%                384.1 ± 4.7%                          1.070                              1.081
railsbench         592.1 ± 5.8%                545.8 ± 2.0%                          1.117                              1.085
-------------  ----------------  --------------------------  -----------------------------  ---------------------------------
```
---

<details>
<summary>Resolved: specialization with statically known receiver types</summary>

Note: Currently, the `==` call inside `BasicObject#!=` uses a profile shared across callers. When that profile is polymorphic, `==` can fall back to a dynamic send even when the receiver's exact type is known in the caller.
As a follow-up, we could improve specialization of `==` inside the inlined `!=` by using receiver types known at the call site, even when the shared profile for `==` is polymorphic.

```rb
2.times do
  "x" != nil
  1 != nil
end

NULL = Object.new
def test(value) = NULL != value

4.times { test(nil) }
```
```sh
#  ./miniruby --zjit-dump-hir --zjit-call-threshold=4 --zjit-num-profiles=4 ../test.rb
```
On master:
```
v30:ObjectExact[VALUE(...)] = Const Value(VALUE(...))
PatchPoint MethodRedefined(Object@..., !=@..., cme:...)
PatchPoint MethodRedefined(Object@..., ==@..., cme:...)
v51:CBool = IsBitNotEqual v30, v11
v52:BoolExact = BoxBool v51
```

On current branch:
```
v30:ObjectExact[VALUE(...)] = Const Value(VALUE(...))
PatchPoint NoSingletonClass(Object@...)
PatchPoint MethodRedefined(Object@..., !=@..., cme:...)
PushInlineFrame :!=, v30 (...), num_args=1
v68:BasicObject = Send v30, :==, v11 # SendFallbackReason: Send: polymorphic fallback
v71:CBool = Test v68
CondBranch v71, bb14(), bb8()

// Removing the initial 2.times block lets the current branch specialize the comparison to IsBitEqual.
```

</details>
2026-10-14 18:00:32 +00:00

7 lines
116 B
Ruby

# Remove the helper defined in jit_hook.rb
class Module
undef :with_jit
undef :with_yjit
undef :with_zjit
end