Skip to content

Instantly share code, notes, and snippets.

@nirvdrum
Last active June 30, 2026 15:56
Show Gist options
  • Select an option

  • Save nirvdrum/da1fc00d656c0d03281e553a843a6d87 to your computer and use it in GitHub Desktop.

Select an option

Save nirvdrum/da1fc00d656c0d03281e553a843a6d87 to your computer and use it in GitHub Desktop.
Reproduction for a ZJIT stale value bug
# Minimal ZJIT miscompile repro -- no pub_grub, no RubyGems.
#
# Recipe (mirrors VersionSolver#add_incompatibility):
# 1. `noop` is an empty method that takes a block. The trivial inliner folds
# such a call to `nil` and DROPS the block, deleting the SendDirect and its
# gen_spill_locals -- but the post-send local reload survives, orphaned.
# 2. `callee(arg)` calls `noop { arg }` (block-passing) and THEN reads `arg`.
# The orphaned reload overwrites `arg` with LoadField SP, :arg@-slot.
# 3. `callee` is reached via a JIT-to-JIT SendDirect (from JIT-compiled
# `caller`), so `arg` arrives in a register and its frame slot is never
# written. The reload therefore reads a stale stack slot.
#
# Expected: with a correct JIT, callee returns its argument unchanged.
class Empty
def noop(&blk)
end
end
class Driver
def initialize
@e = Empty.new
end
def callee(arg)
@e.noop { arg } # trivially-inlined block-passing call -> orphans reload of arg
arg # read arg again -> stale slot on direct entry
end
def caller(arg)
callee(arg)
end
end
d = Driver.new
fails = 0
2_000_000.times do |i|
obj = Object.new
got = d.caller(obj)
unless got.equal?(obj)
fails += 1
if fails <= 3
STDERR.puts "MISCOMPILE i=#{i}: got #{got.inspect}, expected fresh Object"
end
end
end
if fails > 0
STDERR.puts "FAIL: #{fails} miscompiled iterations"
exit 1
else
puts "OK"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment