зеркало из https://github.com/github/ruby.git
Implement invalidation after cfunc
This commit is contained in:
Родитель
494989e87e
Коммит
aba530e23b
|
@ -36,6 +36,7 @@ module RubyVM::MJIT
|
||||||
@blocks = Hash.new { |h, k| h[k] = [] }
|
@blocks = Hash.new { |h, k| h[k] = [] }
|
||||||
@stub_starts = Hash.new { |h, k| h[k] = [] }
|
@stub_starts = Hash.new { |h, k| h[k] = [] }
|
||||||
@stub_ends = Hash.new { |h, k| h[k] = [] }
|
@stub_ends = Hash.new { |h, k| h[k] = [] }
|
||||||
|
@pos_markers = Hash.new { |h, k| h[k] = [] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def assemble(addr)
|
def assemble(addr)
|
||||||
|
@ -45,6 +46,9 @@ module RubyVM::MJIT
|
||||||
|
|
||||||
write_bytes(addr)
|
write_bytes(addr)
|
||||||
|
|
||||||
|
@pos_markers.each do |write_pos, markers|
|
||||||
|
markers.each { |marker| marker.call(addr + write_pos) }
|
||||||
|
end
|
||||||
@bytes.size
|
@bytes.size
|
||||||
ensure
|
ensure
|
||||||
@bytes.clear
|
@bytes.clear
|
||||||
|
@ -617,6 +621,10 @@ module RubyVM::MJIT
|
||||||
@stub_ends[@bytes.size] << stub
|
@stub_ends[@bytes.size] << stub
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pos_marker(&block)
|
||||||
|
@pos_markers[@bytes.size] << block
|
||||||
|
end
|
||||||
|
|
||||||
def new_label(name)
|
def new_label(name)
|
||||||
Label.new(id: @label_id += 1, name:)
|
Label.new(id: @label_id += 1, name:)
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,6 +45,7 @@ module RubyVM::MJIT
|
||||||
@ocb = CodeBlock.new(mem_block: mem_block + mem_size / 2, mem_size: mem_size / 2, outlined: true)
|
@ocb = CodeBlock.new(mem_block: mem_block + mem_size / 2, mem_size: mem_size / 2, outlined: true)
|
||||||
@exit_compiler = ExitCompiler.new
|
@exit_compiler = ExitCompiler.new
|
||||||
@insn_compiler = InsnCompiler.new(@cb, @ocb, @exit_compiler)
|
@insn_compiler = InsnCompiler.new(@cb, @ocb, @exit_compiler)
|
||||||
|
Invariants.initialize(@cb, @ocb, @exit_compiler)
|
||||||
|
|
||||||
@leave_exit = Assembler.new.then do |asm|
|
@leave_exit = Assembler.new.then do |asm|
|
||||||
@exit_compiler.compile_leave_exit(asm)
|
@exit_compiler.compile_leave_exit(asm)
|
||||||
|
|
|
@ -24,7 +24,8 @@ module RubyVM::MJIT
|
||||||
asm.ret
|
asm.ret
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param ocb [CodeBlock]
|
# Set to cfp->jit_return by default for leave insn
|
||||||
|
# @param asm [RubyVM::MJIT::Assembler]
|
||||||
def compile_leave_exit(asm)
|
def compile_leave_exit(asm)
|
||||||
asm.comment('default cfp->jit_return')
|
asm.comment('default cfp->jit_return')
|
||||||
|
|
||||||
|
@ -37,6 +38,28 @@ module RubyVM::MJIT
|
||||||
asm.ret
|
asm.ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Fire cfunc events on invalidation by TracePoint
|
||||||
|
# @param asm [RubyVM::MJIT::Assembler]
|
||||||
|
def compile_full_cfunc_return(asm)
|
||||||
|
# This chunk of code expects REG_EC to be filled properly and
|
||||||
|
# RAX to contain the return value of the C method.
|
||||||
|
|
||||||
|
asm.comment('full cfunc return')
|
||||||
|
asm.mov(C_ARG_OPNDS[0], EC)
|
||||||
|
asm.mov(C_ARG_OPNDS[1], :rax)
|
||||||
|
asm.call(C.rb_full_cfunc_return)
|
||||||
|
|
||||||
|
# TODO: count the exit
|
||||||
|
|
||||||
|
# Restore callee-saved registers
|
||||||
|
asm.pop(SP)
|
||||||
|
asm.pop(EC)
|
||||||
|
asm.pop(CFP)
|
||||||
|
|
||||||
|
asm.mov(:rax, Qundef)
|
||||||
|
asm.ret
|
||||||
|
end
|
||||||
|
|
||||||
# @param jit [RubyVM::MJIT::JITState]
|
# @param jit [RubyVM::MJIT::JITState]
|
||||||
# @param ctx [RubyVM::MJIT::Context]
|
# @param ctx [RubyVM::MJIT::Context]
|
||||||
# @param asm [RubyVM::MJIT::Assembler]
|
# @param asm [RubyVM::MJIT::Assembler]
|
||||||
|
|
|
@ -1,32 +1,27 @@
|
||||||
module RubyVM::MJIT::Hooks # :nodoc: all
|
module RubyVM::MJIT
|
||||||
C = RubyVM::MJIT.const_get(:C, false)
|
module Hooks # :nodoc: all
|
||||||
|
def self.on_bop_redefined(_redefined_flag, _bop)
|
||||||
|
# C.mjit_cancel_all("BOP is redefined")
|
||||||
|
end
|
||||||
|
|
||||||
def self.on_bop_redefined(_redefined_flag, _bop)
|
def self.on_cme_invalidate(cme)
|
||||||
# C.mjit_cancel_all("BOP is redefined")
|
Invariants.on_cme_invalidate(cme)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.on_cme_invalidate(_cme)
|
def self.on_ractor_spawn
|
||||||
# to be used later
|
# C.mjit_cancel_all("Ractor is spawned")
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.on_ractor_spawn
|
def self.on_constant_state_changed(_id)
|
||||||
# C.mjit_cancel_all("Ractor is spawned")
|
# to be used later
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.on_constant_state_changed(_id)
|
def self.on_constant_ic_update(_iseq, _ic, _insn_idx)
|
||||||
# to be used later
|
# to be used later
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.on_constant_ic_update(_iseq, _ic, _insn_idx)
|
def self.on_tracing_invalidate_all(_new_iseq_events)
|
||||||
# to be used later
|
Invariants.on_tracing_invalidate_all
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.on_tracing_invalidate_all(new_iseq_events)
|
|
||||||
# # Stop calling all JIT-ed code. We can't rewrite existing JIT-ed code to trace_ insns for now.
|
|
||||||
# # :class events are triggered only in ISEQ_TYPE_CLASS, but mjit_target_iseq_p ignores such iseqs.
|
|
||||||
# # Thus we don't need to cancel JIT-ed code for :class events.
|
|
||||||
# if new_iseq_events != C.RUBY_EVENT_CLASS
|
|
||||||
# C.mjit_cancel_all("TracePoint is enabled")
|
|
||||||
# end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,8 +5,13 @@ module RubyVM::MJIT
|
||||||
def initialize(cb, ocb, exit_compiler)
|
def initialize(cb, ocb, exit_compiler)
|
||||||
@ocb = ocb
|
@ocb = ocb
|
||||||
@exit_compiler = exit_compiler
|
@exit_compiler = exit_compiler
|
||||||
@invariants = Invariants.new(cb, ocb, exit_compiler)
|
|
||||||
@gc_refs = [] # TODO: GC offsets?
|
@gc_refs = [] # TODO: GC offsets?
|
||||||
|
|
||||||
|
@full_cfunc_return = Assembler.new.then do |asm|
|
||||||
|
@exit_compiler.compile_full_cfunc_return(asm)
|
||||||
|
@ocb.write(asm)
|
||||||
|
end
|
||||||
|
|
||||||
# freeze # workaround a binding.irb issue. TODO: resurrect this
|
# freeze # workaround a binding.irb issue. TODO: resurrect this
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -421,7 +426,7 @@ module RubyVM::MJIT
|
||||||
# Generate a side exit before popping operands
|
# Generate a side exit before popping operands
|
||||||
side_exit = side_exit(jit, ctx)
|
side_exit = side_exit(jit, ctx)
|
||||||
|
|
||||||
unless @invariants.assume_bop_not_redefined(jit, C.INTEGER_REDEFINED_OP_FLAG, C.BOP_PLUS)
|
unless Invariants.assume_bop_not_redefined(jit, C.INTEGER_REDEFINED_OP_FLAG, C.BOP_PLUS)
|
||||||
return CantCompile
|
return CantCompile
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -467,7 +472,7 @@ module RubyVM::MJIT
|
||||||
# Generate a side exit before popping operands
|
# Generate a side exit before popping operands
|
||||||
side_exit = side_exit(jit, ctx)
|
side_exit = side_exit(jit, ctx)
|
||||||
|
|
||||||
unless @invariants.assume_bop_not_redefined(jit, C.INTEGER_REDEFINED_OP_FLAG, C.BOP_MINUS)
|
unless Invariants.assume_bop_not_redefined(jit, C.INTEGER_REDEFINED_OP_FLAG, C.BOP_MINUS)
|
||||||
return CantCompile
|
return CantCompile
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -531,7 +536,7 @@ module RubyVM::MJIT
|
||||||
# Generate a side exit before popping operands
|
# Generate a side exit before popping operands
|
||||||
side_exit = side_exit(jit, ctx)
|
side_exit = side_exit(jit, ctx)
|
||||||
|
|
||||||
unless @invariants.assume_bop_not_redefined(jit, C.INTEGER_REDEFINED_OP_FLAG, C.BOP_LT)
|
unless Invariants.assume_bop_not_redefined(jit, C.INTEGER_REDEFINED_OP_FLAG, C.BOP_LT)
|
||||||
return CantCompile
|
return CantCompile
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -601,7 +606,7 @@ module RubyVM::MJIT
|
||||||
asm.incr_counter(:optaref_array)
|
asm.incr_counter(:optaref_array)
|
||||||
CantCompile
|
CantCompile
|
||||||
elsif comptime_recv.class == Hash
|
elsif comptime_recv.class == Hash
|
||||||
unless @invariants.assume_bop_not_redefined(jit, C.HASH_REDEFINED_OP_FLAG, C.BOP_AREF)
|
unless Invariants.assume_bop_not_redefined(jit, C.HASH_REDEFINED_OP_FLAG, C.BOP_AREF)
|
||||||
return CantCompile
|
return CantCompile
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1051,7 +1056,7 @@ module RubyVM::MJIT
|
||||||
end
|
end
|
||||||
|
|
||||||
# Invalidate on redefinition (part of vm_search_method_fastpath)
|
# Invalidate on redefinition (part of vm_search_method_fastpath)
|
||||||
@invariants.assume_method_lookup_stable(jit, cme)
|
Invariants.assume_method_lookup_stable(jit, cme)
|
||||||
|
|
||||||
jit_call_method_each_type(jit, ctx, asm, ci, argc, flags, cme, comptime_recv, recv_opnd)
|
jit_call_method_each_type(jit, ctx, asm, ci, argc, flags, cme, comptime_recv, recv_opnd)
|
||||||
end
|
end
|
||||||
|
@ -1155,7 +1160,7 @@ module RubyVM::MJIT
|
||||||
return CantCompile
|
return CantCompile
|
||||||
end
|
end
|
||||||
|
|
||||||
# Disabled until we implement TracePoint invalidation
|
# Disabled until we figure out why $' gets broken on test-all
|
||||||
disabled = true
|
disabled = true
|
||||||
if disabled
|
if disabled
|
||||||
return CantCompile
|
return CantCompile
|
||||||
|
@ -1214,6 +1219,8 @@ module RubyVM::MJIT
|
||||||
asm.call(:rax) # TODO: use rel32 if close enough
|
asm.call(:rax) # TODO: use rel32 if close enough
|
||||||
ctx.stack_pop(1 + argc)
|
ctx.stack_pop(1 + argc)
|
||||||
|
|
||||||
|
Invariants.record_global_inval_patch(asm, @full_cfunc_return)
|
||||||
|
|
||||||
asm.comment('push the return value')
|
asm.comment('push the return value')
|
||||||
stack_ret = ctx.stack_push
|
stack_ret = ctx.stack_push
|
||||||
asm.mov(stack_ret, :rax)
|
asm.mov(stack_ret, :rax)
|
||||||
|
|
|
@ -2,61 +2,82 @@ require 'set'
|
||||||
|
|
||||||
module RubyVM::MJIT
|
module RubyVM::MJIT
|
||||||
class Invariants
|
class Invariants
|
||||||
# @param cb [CodeBlock]
|
class << self
|
||||||
# @param ocb [CodeBlock]
|
# Called by RubyVM::MJIT::Compiler to lazily initialize this
|
||||||
# @param exit_compiler [RubyVM::MJIT::ExitCompiler]
|
# @param cb [CodeBlock]
|
||||||
def initialize(cb, ocb, exit_compiler)
|
# @param ocb [CodeBlock]
|
||||||
@cb = cb
|
# @param exit_compiler [RubyVM::MJIT::ExitCompiler]
|
||||||
@ocb = ocb
|
def initialize(cb, ocb, exit_compiler)
|
||||||
@exit_compiler = exit_compiler
|
@cb = cb
|
||||||
@bop_blocks = Set.new # TODO: actually invalidate this
|
@ocb = ocb
|
||||||
@cme_blocks = Hash.new { |h, k| h[k] = Set.new }
|
@exit_compiler = exit_compiler
|
||||||
|
@bop_blocks = Set.new # TODO: actually invalidate this
|
||||||
|
@cme_blocks = Hash.new { |h, k| h[k] = Set.new }
|
||||||
|
@patches = {}
|
||||||
|
|
||||||
invariants = self
|
# freeze # workaround a binding.irb issue. TODO: resurrect this
|
||||||
hooks = Module.new
|
|
||||||
hooks.define_method(:on_cme_invalidate) do |cme|
|
|
||||||
invariants.on_cme_invalidate(cme)
|
|
||||||
end
|
end
|
||||||
Hooks.singleton_class.prepend(hooks)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param jit [RubyVM::MJIT::JITState]
|
# @param jit [RubyVM::MJIT::JITState]
|
||||||
# @param klass [Integer]
|
# @param klass [Integer]
|
||||||
# @param op [Integer]
|
# @param op [Integer]
|
||||||
def assume_bop_not_redefined(jit, klass, op)
|
def assume_bop_not_redefined(jit, klass, op)
|
||||||
return false unless C.BASIC_OP_UNREDEFINED_P(klass, op)
|
return false unless C.BASIC_OP_UNREDEFINED_P(klass, op)
|
||||||
|
|
||||||
ensure_block_entry_exit(jit.block, cause: 'assume_bop_not_redefined')
|
ensure_block_entry_exit(jit.block, cause: 'assume_bop_not_redefined')
|
||||||
@bop_blocks << jit.block
|
@bop_blocks << jit.block
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param jit [RubyVM::MJIT::JITState]
|
# @param jit [RubyVM::MJIT::JITState]
|
||||||
def assume_method_lookup_stable(jit, cme)
|
def assume_method_lookup_stable(jit, cme)
|
||||||
ensure_block_entry_exit(jit.block, cause: 'assume_method_lookup_stable')
|
ensure_block_entry_exit(jit.block, cause: 'assume_method_lookup_stable')
|
||||||
@cme_blocks[cme.to_i] << jit.block
|
@cme_blocks[cme.to_i] << jit.block
|
||||||
end
|
end
|
||||||
|
|
||||||
def on_cme_invalidate(cme)
|
# @param asm [RubyVM::MJIT::Assembler]
|
||||||
@cme_blocks.fetch(cme.to_i, []).each do |block|
|
def record_global_inval_patch(asm, target)
|
||||||
@cb.with_write_addr(block.start_addr) do
|
asm.pos_marker do |address|
|
||||||
asm = Assembler.new
|
if @patches.key?(address)
|
||||||
asm.comment('on_cme_invalidate')
|
raise 'multiple patches in the same address'
|
||||||
asm.jmp(block.entry_exit)
|
end
|
||||||
@cb.write(asm)
|
@patches[address] = target
|
||||||
end
|
end
|
||||||
# TODO: re-generate branches that refer to this block
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
private
|
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
|
||||||
|
end
|
||||||
|
|
||||||
# @param block [RubyVM::MJIT::Block]
|
def on_tracing_invalidate_all
|
||||||
def ensure_block_entry_exit(block, cause:)
|
# TODO: assert patches don't overlap each other
|
||||||
if block.entry_exit.nil?
|
@patches.each do |address, target|
|
||||||
block.entry_exit = Assembler.new.then do |asm|
|
@cb.with_write_addr(address) do
|
||||||
@exit_compiler.compile_entry_exit(block.pc, block.ctx, asm, cause:)
|
asm = Assembler.new
|
||||||
@ocb.write(asm)
|
asm.comment('on_tracing_invalidate_all')
|
||||||
|
asm.jmp(target)
|
||||||
|
@cb.write(asm)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# @param block [RubyVM::MJIT::Block]
|
||||||
|
def ensure_block_entry_exit(block, cause:)
|
||||||
|
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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
3
mjit.c
3
mjit.c
|
@ -353,6 +353,9 @@ void
|
||||||
rb_mjit_tracing_invalidate_all(rb_event_flag_t new_iseq_events)
|
rb_mjit_tracing_invalidate_all(rb_event_flag_t new_iseq_events)
|
||||||
{
|
{
|
||||||
if (!mjit_call_p) return;
|
if (!mjit_call_p) return;
|
||||||
|
WITH_MJIT_DISABLED({
|
||||||
|
rb_funcall(rb_mMJITHooks, rb_intern("on_tracing_invalidate_all"), 1, UINT2NUM(new_iseq_events));
|
||||||
|
});
|
||||||
mjit_call_p = false;
|
mjit_call_p = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -138,6 +138,13 @@ module RubyVM::MJIT # :nodoc: all
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def rb_full_cfunc_return
|
||||||
|
Primitive.cstmt! %{
|
||||||
|
extern void rb_full_cfunc_return(rb_execution_context_t *ec, VALUE return_value);
|
||||||
|
return SIZET2NUM((size_t)rb_full_cfunc_return);
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
#========================================================================================
|
#========================================================================================
|
||||||
#
|
#
|
||||||
# Old stuff
|
# Old stuff
|
||||||
|
|
Загрузка…
Ссылка в новой задаче