yjit
yjit copied to clipboard
Block invalidation doesn't always work (depends on invalidation order)
This is a bug found by @XrXr with repro shown below:
# Repro for buggy invalidation. Run with --yjit-call-threshold=1 and compare with interpreter only mode
# The gist of it is that we can invalidate blocks in an order that doesn't patch the right code
# It's a bit hard to repro with 2 calls to the same method, because what happens is that the lazy stub for the second call because a zero-size block that proceeds the actual call.
# To repro this you need two blocks, say A and B, where A is the **sole** predecessor of B. Invalidating A and then B means we don't patch B when we potentially need to,
# as demoed by this script
class A
def initialize
@doit = false
end
def inval
if @doit
Array.remove_method :min
Integer.define_method(:+) { |_| puts "custom plus" }
@doit = false
end
self
end
def foo(array)
array.min{ inval; 0 } + 3
end
attr_accessor :doit
end
A.new.foo([]) rescue nil
puts YJIT.disasm(A.instance_method(:foo))
A.new.tap{_1.doit = true}.foo([3, 3])
We should find a way to fix it and add the repro to our tests.
I'm wondering if maybe just not removing blocks from the incoming list of successors when invalidating them would be enough to fix the problem?
I think we should try that and if the tests pass, could be good enough for now. We can then later fix dangling pointers with code GC.