1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
require 'set'
module RubyVM::RJIT
class Invariants
class << self
# Called by RubyVM::RJIT::Compiler to lazily initialize this
# @param cb [CodeBlock]
# @param ocb [CodeBlock]
# @param compiler [RubyVM::RJIT::Compiler]
# @param exit_compiler [RubyVM::RJIT::ExitCompiler]
def initialize(cb, ocb, compiler, exit_compiler)
@cb = cb
@ocb = ocb
@compiler = compiler
@exit_compiler = exit_compiler
@bop_blocks = Set.new # TODO: actually invalidate this
@cme_blocks = Hash.new { |h, k| h[k] = Set.new }
@const_blocks = Hash.new { |h, k| h[k] = Set.new }
@patches = {}
# freeze # workaround a binding.irb issue. TODO: resurrect this
end
# @param jit [RubyVM::RJIT::JITState]
# @param klass [Integer]
# @param op [Integer]
def assume_bop_not_redefined(jit, klass, op)
return false unless C.BASIC_OP_UNREDEFINED_P(klass, op)
ensure_block_entry_exit(jit, cause: 'assume_bop_not_redefined')
@bop_blocks << jit.block
true
end
# @param jit [RubyVM::RJIT::JITState]
def assume_method_lookup_stable(jit, cme)
ensure_block_entry_exit(jit, cause: 'assume_method_lookup_stable')
@cme_blocks[cme.to_i] << jit.block
end
# @param jit [RubyVM::RJIT::JITState]
def assume_method_basic_definition(jit, klass, mid)
if C.rb_method_basic_definition_p(klass, mid)
cme = C.rb_callable_method_entry(klass, mid)
assume_method_lookup_stable(jit, cme)
true
else
false
end
end
def assume_stable_constant_names(jit, idlist)
(0..).each do |i|
break if (id = idlist[i]) == 0
@const_blocks[id] << jit.block
end
end
# @param asm [RubyVM::RJIT::Assembler]
def record_global_inval_patch(asm, target)
asm.pos_marker do |address|
if @patches.key?(address)
raise 'multiple patches in the same address'
end
@patches[address] = target
end
end
def on_cme_invalidate(cme)
@cme_blocks.fetch(cme.to_i, []).each do |block|
@cb.with_write_addr(block.start_addr) do
asm = Assembler.new
asm.comment('on_cme_invalidate')
asm.jmp(block.entry_exit)
@cb.write(asm)
end
# TODO: re-generate branches that refer to this block
end
@cme_blocks.delete(cme.to_i)
end
def on_constant_ic_update(iseq, ic, insn_idx)
# TODO: check multi ractor as well
if ic.entry.ic_cref
# No need to recompile the slowpath
return
end
pc = iseq.body.iseq_encoded + insn_idx
insn_name = Compiler.decode_insn(pc.*).name
if insn_name != :opt_getconstant_path && insn_name != :trace_opt_getconstant_path
raise 'insn_idx was not at opt_getconstant_path'
end
if ic.to_i != pc[1]
raise 'insn_idx + 1 was not at the updated IC'
end
@compiler.invalidate_blocks(iseq, pc.to_i)
end
def on_constant_state_changed(id)
@const_blocks.fetch(id, []).each do |block|
@compiler.invalidate_block(block)
end
end
def on_tracing_invalidate_all
invalidate_all
end
def on_update_references
# Give up. In order to support GC.compact, you'd have to update ISEQ
# addresses in BranchStub, etc. Ideally, we'd need to update moved
# pointers in JITed code here, but we just invalidate all for now.
invalidate_all
end
# @param jit [RubyVM::RJIT::JITState]
# @param block [RubyVM::RJIT::Block]
def ensure_block_entry_exit(jit, cause:)
block = jit.block
if block.entry_exit.nil?
block.entry_exit = Assembler.new.then do |asm|
@exit_compiler.compile_entry_exit(block.pc, block.ctx, asm, cause:)
@ocb.write(asm)
end
end
end
private
def invalidate_all
# On-Stack Replacement
@patches.each do |address, target|
# TODO: assert patches don't overlap each other
@cb.with_write_addr(address) do
asm = Assembler.new
asm.comment('on_tracing_invalidate_all')
asm.jmp(target)
@cb.write(asm)
end
end
@patches.clear
C.rjit_for_each_iseq do |iseq|
# Avoid entering past code
iseq.body.jit_entry = 0
# Avoid reusing past code
iseq.body.rjit_blocks.clear if iseq.body.rjit_blocks
# Compile this again if not converted to trace_* insns
iseq.body.jit_entry_calls = 0
end
end
end
end
end
|