yjit icon indicating copy to clipboard operation
yjit copied to clipboard

Block invalidation doesn't always work (depends on invalidation order)

Open maximecb opened this issue 4 years ago • 1 comments

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.

maximecb avatar Aug 18 '21 20:08 maximecb

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.

maximecb avatar Aug 18 '21 20:08 maximecb