# This ZJIT bug repro requires winning a race condition.
# Run in a shell loop as following:
#
#
# $ while ./miniruby --zjit-call-threshold=1 ../test.rb; do ; done
# Seems to be working fine, exiting
# Seems to be working fine, exiting
# Seems to be working fine, exiting
# Seems to be working fine, exiting
# ../test.rb:25:in 'Object#test': undefined method 'empty?' for nil (NoMethodError)
# from ../test.rb:39:in '<main>'
#
# As of https://github.com/ruby/ruby/commit/f6886fc1ccde667845bd691cad18f8f32f00d312
# HIR for the `test` is:
#
# bb4(v41:BasicObject, v42:Truthy, v43:NilClass|ArrayExact):
# v47:ArrayExact = NewArray
# v51:ArrayExact = NewArray
# v55:ArrayExact = NewArray
# v59:ArrayExact = NewArray
# v63:ArrayExact = NewArray
# v67:ArrayExact = NewArray
# v71:ArrayExact = NewArray
# v75:ArrayExact = NewArray
# v79:ArrayExact = NewArray
# v83:ArrayExact = NewArray
# v87:ArrayExact = NewArray
# v91:ArrayExact = NewArray
# v95:ArrayExact = NewArray
# PatchPoint NoSingletonClass(Array@0x105d52598)
# PatchPoint MethodRedefined(Array@0x105d52598, class@0x8bb1, cme:0x105ef61c0)
#>> PatchPoint NoEPEscape(test)
# PatchPoint MethodRedefined(Array@0x105d52598, empty?@0x9b, cme:0x105dbbfd0)
# Jump bb5(v41, v42, v95)
#
# Since the NoEpEscape patchpoint uses without_locals(), the new array is not written
# to memory before exiting to the interpreter. What happens to be in memory for `b`
# is nil and the interpreter raises NoMethodError.
#
# To make this happen, we race in a different ractor to get the follow sequence of
# events:
# 1. NewArray enters GC in the main thread
# 2. main thread GC calls rb_gc_vm_barrier() and sleeps for rendezvous synchronization
# 3. sub thread calls `binding`, patching NoEPEscape(test)
# 4. sub thread joins the GC barrier and sleeps
# 5. main thread wakes up and exits through NoEPEscape(test), not writing `b` to memory
def test(a)
while a
b = []
b = []
b = []
b = []
b = []
b = []
b = []
b = []
b = []
b = []
b = []
b = []
b = []
b.class
b.class
b.empty?
end
binding
end
$VERBOSE = nil # Silence ractor creation warning
Ractor.new do
GC.stress = true
deadline = Time.now + 1
test(false) until Time.now > deadline
puts("Seems to be working fine, exiting")
Process.exit!(true)
end
test(true)
A similar situation to ruby#16558. This proves that NoEPEscape sometimes have to write locals and sometimes should not.
A similar situation to ruby#16558. This proves that
NoEPEscapesometimes have to write locals and sometimes should not.