class RubyVM::RJIT::ExitCompiler - ruby_vm: Ruby Standard Library Documentation

# class RubyVM::RJIT::ExitCompiler

### Public Class Methods

new(= freeze) click to toggle source

    # File ruby_vm/rjit/exit_compiler.rb, line 3
      def initialize = freeze

      # Used for invalidating a block on entry.
      # @param pc [Integer]
      # @param asm [RubyVM::RJIT::Assembler]
      def compile_entry_exit(pc, ctx, asm, cause:)
        # Fix pc/sp offsets for the interpreter
        save_pc_and_sp(pc, ctx, asm, reset_sp_offset: false)

        # Increment per-insn exit counter
        count_insn_exit(pc, asm)

        # Restore callee-saved registers
        asm.comment("#{cause}: entry exit")
        asm.pop(SP)
        asm.pop(EC)
        asm.pop(CFP)

        asm.mov(C_RET, Qundef)
        asm.ret
      end

      # Set to cfp->jit_return by default for leave insn
      # @param asm [RubyVM::RJIT::Assembler]
      def compile_leave_exit(asm)
        asm.comment('default cfp->jit_return')

        # Restore callee-saved registers
        asm.pop(SP)
        asm.pop(EC)
        asm.pop(CFP)

        # :rax is written by #leave
        asm.ret
      end

      # Fire cfunc events on invalidation by TracePoint
      # @param asm [RubyVM::RJIT::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_ARGS[0], EC)
        asm.mov(C_ARGS[1], :rax)
        asm.call(C.rjit_full_cfunc_return)

        # TODO: count the exit

        # Restore callee-saved registers
        asm.pop(SP)
        asm.pop(EC)
        asm.pop(CFP)

        asm.mov(C_RET, Qundef)
        asm.ret
      end

      # @param jit [RubyVM::RJIT::JITState]
      # @param ctx [RubyVM::RJIT::Context]
      # @param asm [RubyVM::RJIT::Assembler]
      def compile_side_exit(pc, ctx, asm)
        # Fix pc/sp offsets for the interpreter
        save_pc_and_sp(pc, ctx.dup, asm) # dup to avoid sp_offset update

        # Increment per-insn exit counter
        count_insn_exit(pc, asm)

        # Restore callee-saved registers
        asm.comment("exit to interpreter on #{pc_to_insn(pc).name}")
        asm.pop(SP)
        asm.pop(EC)
        asm.pop(CFP)

        asm.mov(C_RET, Qundef)
        asm.ret
      end

      # @param asm [RubyVM::RJIT::Assembler]
      # @param entry_stub [RubyVM::RJIT::EntryStub]
      def compile_entry_stub(asm, entry_stub)
        # Call rb_rjit_entry_stub_hit
        asm.comment('entry stub hit')
        asm.mov(C_ARGS[0], to_value(entry_stub))
        asm.call(C.rb_rjit_entry_stub_hit)

        # Jump to the address returned by rb_rjit_entry_stub_hit
        asm.jmp(:rax)
      end

      # @param ctx [RubyVM::RJIT::Context]
      # @param asm [RubyVM::RJIT::Assembler]
      # @param branch_stub [RubyVM::RJIT::BranchStub]
      # @param target0_p [TrueClass,FalseClass]
      def compile_branch_stub(ctx, asm, branch_stub, target0_p)
        # Call rb_rjit_branch_stub_hit
        iseq = branch_stub.iseq
        if C.rjit_opts.dump_disasm && C.imemo_type_p(iseq, C.imemo_iseq) # Guard against ISEQ GC at random moments
          asm.comment("branch stub hit: #{iseq.body.location.label}@#{C.rb_iseq_path(iseq)}:#{iseq_lineno(iseq, target0_p ? branch_stub.target0.pc : branch_stub.target1.pc)}")
        end
        asm.mov(:rdi, to_value(branch_stub))
        asm.mov(:esi, ctx.sp_offset)
        asm.mov(:edx, target0_p ? 1 : 0)
        asm.call(C.rb_rjit_branch_stub_hit)

        # Jump to the address returned by rb_rjit_branch_stub_hit
        asm.jmp(:rax)
      end

      private

      def pc_to_insn(pc)
        Compiler.decode_insn(C.VALUE.new(pc).*)
      end

      # @param pc [Integer]
      # @param asm [RubyVM::RJIT::Assembler]
      def count_insn_exit(pc, asm)
        if C.rjit_opts.stats
          insn = Compiler.decode_insn(C.VALUE.new(pc).*)
          asm.comment("increment insn exit: #{insn.name}")
          asm.mov(:rax, (C.rjit_insn_exits + insn.bin).to_i)
          asm.add([:rax], 1) # TODO: lock
        end
        if C.rjit_opts.trace_exits
          asm.comment('rjit_record_exit_stack')
          asm.mov(C_ARGS[0], pc)
          asm.call(C.rjit_record_exit_stack)
        end
      end

      # @param jit [RubyVM::RJIT::JITState]
      # @param ctx [RubyVM::RJIT::Context]
      # @param asm [RubyVM::RJIT::Assembler]
      def save_pc_and_sp(pc, ctx, asm, reset_sp_offset: true)
        # Update pc (TODO: manage PC offset?)
        asm.comment("save PC#{' and SP' if ctx.sp_offset != 0} to CFP")
        asm.mov(:rax, pc) # rax = jit.pc
        asm.mov([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax) # cfp->pc = rax

        # Update sp
        if ctx.sp_offset != 0
          asm.add(SP, C.VALUE.size * ctx.sp_offset) # sp += stack_size
          asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], SP) # cfp->sp = sp
          if reset_sp_offset
            ctx.sp_offset = 0
          end
        end
      end

      def to_value(obj)
        GC_REFS << obj
        C.to_value(obj)
      end

      def iseq_lineno(iseq, pc)
        C.rb_iseq_line_no(iseq, (pc - iseq.body.iseq_encoded.to_i) / C.VALUE.size)
      rescue RangeError # bignum too big to convert into `unsigned long long' (RangeError)
        -1
      end
    end

### Public Instance Methods

compile_branch_stub(ctx, asm, branch_stub, target0_p) click to toggle source

@param ctx [RubyVM::RJIT::Context] @param asm [RubyVM::RJIT::Assembler] @param branch_stub [RubyVM::RJIT::BranchStub] @param target0_p [TrueClass,FalseClass]

    # File ruby_vm/rjit/exit_compiler.rb, line 97
    def compile_branch_stub(ctx, asm, branch_stub, target0_p)
      # Call rb_rjit_branch_stub_hit
      iseq = branch_stub.iseq
      if C.rjit_opts.dump_disasm && C.imemo_type_p(iseq, C.imemo_iseq) # Guard against ISEQ GC at random moments
        asm.comment("branch stub hit: #{iseq.body.location.label}@#{C.rb_iseq_path(iseq)}:#{iseq_lineno(iseq, target0_p ? branch_stub.target0.pc : branch_stub.target1.pc)}")
      end
      asm.mov(:rdi, to_value(branch_stub))
      asm.mov(:esi, ctx.sp_offset)
      asm.mov(:edx, target0_p ? 1 : 0)
      asm.call(C.rb_rjit_branch_stub_hit)

      # Jump to the address returned by rb_rjit_branch_stub_hit
      asm.jmp(:rax)
    end

compile_entry_exit(pc, ctx, asm, cause:) click to toggle source

Used for invalidating a block on entry. @param pc [Integer] @param asm [RubyVM::RJIT::Assembler]

    # File ruby_vm/rjit/exit_compiler.rb, line 8
    def compile_entry_exit(pc, ctx, asm, cause:)
      # Fix pc/sp offsets for the interpreter
      save_pc_and_sp(pc, ctx, asm, reset_sp_offset: false)

      # Increment per-insn exit counter
      count_insn_exit(pc, asm)

      # Restore callee-saved registers
      asm.comment("#{cause}: entry exit")
      asm.pop(SP)
      asm.pop(EC)
      asm.pop(CFP)

      asm.mov(C_RET, Qundef)
      asm.ret
    end

compile_entry_stub(asm, entry_stub) click to toggle source

@param asm [RubyVM::RJIT::Assembler] @param entry_stub [RubyVM::RJIT::EntryStub]

    # File ruby_vm/rjit/exit_compiler.rb, line 83
    def compile_entry_stub(asm, entry_stub)
      # Call rb_rjit_entry_stub_hit
      asm.comment('entry stub hit')
      asm.mov(C_ARGS[0], to_value(entry_stub))
      asm.call(C.rb_rjit_entry_stub_hit)

      # Jump to the address returned by rb_rjit_entry_stub_hit
      asm.jmp(:rax)
    end

compile_full_cfunc_return(asm) click to toggle source

Fire cfunc events on invalidation by TracePoint @param asm [RubyVM::RJIT::Assembler]

    # File ruby_vm/rjit/exit_compiler.rb, line 41
    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_ARGS[0], EC)
      asm.mov(C_ARGS[1], :rax)
      asm.call(C.rjit_full_cfunc_return)

      # TODO: count the exit

      # Restore callee-saved registers
      asm.pop(SP)
      asm.pop(EC)
      asm.pop(CFP)

      asm.mov(C_RET, Qundef)
      asm.ret
    end

compile_leave_exit(asm) click to toggle source

Set to cfp->jit_return by default for leave insn @param asm [RubyVM::RJIT::Assembler]

    # File ruby_vm/rjit/exit_compiler.rb, line 27
    def compile_leave_exit(asm)
      asm.comment('default cfp->jit_return')

      # Restore callee-saved registers
      asm.pop(SP)
      asm.pop(EC)
      asm.pop(CFP)

      # :rax is written by #leave
      asm.ret
    end

compile_side_exit(pc, ctx, asm) click to toggle source

@param jit [RubyVM::RJIT::JITState] @param ctx [RubyVM::RJIT::Context] @param asm [RubyVM::RJIT::Assembler]

    # File ruby_vm/rjit/exit_compiler.rb, line 64
    def compile_side_exit(pc, ctx, asm)
      # Fix pc/sp offsets for the interpreter
      save_pc_and_sp(pc, ctx.dup, asm) # dup to avoid sp_offset update

      # Increment per-insn exit counter
      count_insn_exit(pc, asm)

      # Restore callee-saved registers
      asm.comment("exit to interpreter on #{pc_to_insn(pc).name}")
      asm.pop(SP)
      asm.pop(EC)
      asm.pop(CFP)

      asm.mov(C_RET, Qundef)
      asm.ret
    end

count_insn_exit(pc, asm) click to toggle source

@param pc [Integer] @param asm [RubyVM::RJIT::Assembler]

    # File ruby_vm/rjit/exit_compiler.rb, line 120
    def count_insn_exit(pc, asm)
      if C.rjit_opts.stats
        insn = Compiler.decode_insn(C.VALUE.new(pc).*)
        asm.comment("increment insn exit: #{insn.name}")
        asm.mov(:rax, (C.rjit_insn_exits + insn.bin).to_i)
        asm.add([:rax], 1) # TODO: lock
      end
      if C.rjit_opts.trace_exits
        asm.comment('rjit_record_exit_stack')
        asm.mov(C_ARGS[0], pc)
        asm.call(C.rjit_record_exit_stack)
      end
    end

iseq_lineno(iseq, pc) click to toggle source

    # File ruby_vm/rjit/exit_compiler.rb, line 158
    def iseq_lineno(iseq, pc)
      C.rb_iseq_line_no(iseq, (pc - iseq.body.iseq_encoded.to_i) / C.VALUE.size)
    rescue RangeError # bignum too big to convert into `unsigned long long' (RangeError)
      -1
    end

pc_to_insn(pc) click to toggle source

    # File ruby_vm/rjit/exit_compiler.rb, line 114
    def pc_to_insn(pc)
      Compiler.decode_insn(C.VALUE.new(pc).*)
    end

save_pc_and_sp(pc, ctx, asm, reset_sp_offset: true) click to toggle source

@param jit [RubyVM::RJIT::JITState] @param ctx [RubyVM::RJIT::Context] @param asm [RubyVM::RJIT::Assembler]

    # File ruby_vm/rjit/exit_compiler.rb, line 137
    def save_pc_and_sp(pc, ctx, asm, reset_sp_offset: true)
      # Update pc (TODO: manage PC offset?)
      asm.comment("save PC#{' and SP' if ctx.sp_offset != 0} to CFP")
      asm.mov(:rax, pc) # rax = jit.pc
      asm.mov([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax) # cfp->pc = rax

      # Update sp
      if ctx.sp_offset != 0
        asm.add(SP, C.VALUE.size * ctx.sp_offset) # sp += stack_size
        asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], SP) # cfp->sp = sp
        if reset_sp_offset
          ctx.sp_offset = 0
        end
      end
    end

to_value(obj) click to toggle source

    # File ruby_vm/rjit/exit_compiler.rb, line 153
    def to_value(obj)
      GC_REFS << obj
      C.to_value(obj)
    end