summaryrefslogtreecommitdiff
path: root/lib/ruby_vm/rjit/exit_compiler.rb
blob: 1ced2141a485e47a046e0c864095cc5ed2185d18 (plain)
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
156
157
158
159
160
161
162
163
164
module RubyVM::RJIT
  class ExitCompiler
    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
end